use symplex::matrix::jacobian;
use symplex::prelude::*;
use symplex::robotics::fk_position;
#[test]
fn codegen_2dof_no_trivial_temps() {
let ctx = Context::new();
let theta1 = ctx.symbol("theta1");
let theta2 = ctx.symbol("theta2");
let l1 = ctx.symbol("L1");
let l2 = ctx.symbol("L2");
let zero = ctx.int(0);
let dh: [(&Ex, &Ex, &Ex, &Ex); 2] =
[(&theta1, &zero, &l1, &zero), (&theta2, &zero, &l2, &zero)];
let (px, py, _pz) = fk_position(&dh);
let jac = jacobian(&[&px, &py], &[&theta1, &theta2]);
let code = jac
.to_rust_fn("jacobian_2dof", &["theta1", "theta2", "L1", "L2"])
.expect("codegen should succeed for 2-DOF Jacobian");
assert!(
!code.contains("= 1.0_f64;"),
"should not emit `let tN = 1.0_f64;` binding in:\n{code}"
);
assert!(
!code.contains("0.0_f64.powi"),
"should not emit `0.0_f64.powi(...)` in:\n{code}"
);
assert!(
!code.contains("= 0.0_f64;"),
"should not emit `let tN = 0.0_f64;` binding in:\n{code}"
);
}
#[test]
fn codegen_3dof_no_trivial_temps() {
let ctx = Context::new();
let theta1 = ctx.symbol("theta1");
let theta2 = ctx.symbol("theta2");
let theta3 = ctx.symbol("theta3");
let l1 = ctx.rational(3, 10);
let l2 = ctx.rational(1, 4);
let l3 = ctx.rational(1, 5);
let zero = ctx.int(0);
let dh: [(&Ex, &Ex, &Ex, &Ex); 3] = [
(&theta1, &zero, &l1, &zero),
(&theta2, &zero, &l2, &zero),
(&theta3, &zero, &l3, &zero),
];
let (px, py, _pz) = fk_position(&dh);
let jac = jacobian(&[&px, &py], &[&theta1, &theta2, &theta3]);
let code = jac
.to_rust_fn("jacobian_3dof", &["theta1", "theta2", "theta3"])
.expect("codegen should succeed for 3-DOF Jacobian");
assert!(
!code.contains("= 1.0_f64;"),
"should not emit `let tN = 1.0_f64;` binding in:\n{code}"
);
assert!(
!code.contains("0.0_f64.powi"),
"should not emit `0.0_f64.powi(...)` in:\n{code}"
);
assert!(
!code.contains("= 0.0_f64;"),
"should not emit `let tN = 0.0_f64;` binding in:\n{code}"
);
}
#[test]
fn codegen_subtraction_style() {
let ctx = Context::new();
let x = ctx.symbol("x");
let y = ctx.symbol("y");
let neg_y = -&y;
let expr = &x + &neg_y;
let code = expr
.to_rust_fn("sub_test", &["x", "y"])
.expect("codegen should succeed");
assert!(
code.contains("- "),
"expected subtraction operator `- ` in:\n{code}"
);
assert!(
!code.contains("+ (-"),
"should not contain `+ (-` pattern in:\n{code}"
);
}
#[test]
fn codegen_fraction_as_decimal() {
let ctx = Context::new();
let x = ctx.symbol("x");
let quarter = ctx.rational(1, 4);
let expr = &quarter * &x;
let code = expr
.to_rust_fn("frac_test", &["x"])
.expect("codegen should succeed");
assert!(code.contains("0.25"), "expected decimal `0.25` in:\n{code}");
assert!(
!code.contains("1_f64 / 4_f64"),
"should not contain fraction syntax `1_f64 / 4_f64` in:\n{code}"
);
}
#[test]
fn codegen_zero_elimination() {
let ctx = Context::new();
let x = ctx.symbol("x");
let zero = ctx.int(0);
let expr = &zero * &x.sin();
let code = expr
.to_rust_fn("zero_test", &["x"])
.expect("codegen should succeed");
assert!(
code.contains("0.0") || code.contains("0_f64"),
"expected zero result in:\n{code}"
);
assert!(
!code.contains(".sin()"),
"should not contain sin() call for zero product in:\n{code}"
);
}
#[test]
fn codegen_3dof_compiles() {
let ctx = Context::new();
let theta1 = ctx.symbol("theta1");
let theta2 = ctx.symbol("theta2");
let theta3 = ctx.symbol("theta3");
let l1 = ctx.rational(3, 10);
let l2 = ctx.rational(1, 4);
let l3 = ctx.rational(1, 5);
let zero = ctx.int(0);
let dh: [(&Ex, &Ex, &Ex, &Ex); 3] = [
(&theta1, &zero, &l1, &zero),
(&theta2, &zero, &l2, &zero),
(&theta3, &zero, &l3, &zero),
];
let (px, py, _pz) = fk_position(&dh);
let jac = jacobian(&[&px, &py], &[&theta1, &theta2, &theta3]);
let code = jac
.to_rust_fn("robot_jac", &["theta1", "theta2", "theta3"])
.expect("codegen should succeed for 3-DOF Jacobian");
let open_braces = code.chars().filter(|&c| c == '{').count();
let close_braces = code.chars().filter(|&c| c == '}').count();
assert_eq!(
open_braces, close_braces,
"unbalanced braces ({open_braces} open vs {close_braces} close) in:\n{code}"
);
let open_parens = code.chars().filter(|&c| c == '(').count();
let close_parens = code.chars().filter(|&c| c == ')').count();
assert_eq!(
open_parens, close_parens,
"unbalanced parens ({open_parens} open vs {close_parens} close) in:\n{code}"
);
let open_brackets = code.chars().filter(|&c| c == '[').count();
let close_brackets = code.chars().filter(|&c| c == ']').count();
assert_eq!(
open_brackets, close_brackets,
"unbalanced brackets ({open_brackets} open vs {close_brackets} close) in:\n{code}"
);
assert!(
code.contains("pub fn robot_jac("),
"missing function signature in:\n{code}"
);
assert!(
code.contains("[f64;"),
"missing array return type in:\n{code}"
);
}