use crate::domains::matrix::Matrix;
use crate::poly::multipoly::{GrevLex, MultiPoly};
use crate::poly::polysys;
use crate::prelude::*;
use crate::units::si::{Angle, Length};
use num_bigint::BigInt;
use num_rational::Ratio;
use num_traits::ToPrimitive;
pub fn dh_matrix(theta: &Ex, d: &Ex, a: &Ex, alpha: &Ex) -> Matrix {
let cos_theta = theta.cos();
let sin_theta = theta.sin();
let cos_alpha = alpha.cos();
let sin_alpha = alpha.sin();
let zero = theta.context().int(0);
let one = theta.context().int(1);
let r00 = cos_theta.clone();
let r01 = -(&sin_theta * &cos_alpha);
let r02 = &sin_theta * &sin_alpha;
let r03 = a * &cos_theta;
let r10 = sin_theta.clone();
let r11 = &cos_theta * &cos_alpha;
let r12 = -(&cos_theta * &sin_alpha);
let r13 = a * &sin_theta;
let r20 = zero.clone();
let r21 = sin_alpha;
let r22 = cos_alpha;
let r23 = d.clone();
let r30 = zero.clone();
let r31 = zero.clone();
let r32 = zero;
let r33 = one;
Matrix::from_rows_unchecked(vec![
vec![r00, r01, r02, r03],
vec![r10, r11, r12, r13],
vec![r20, r21, r22, r23],
vec![r30, r31, r32, r33],
])
}
fn mul_square(a: &Matrix, b: &Matrix) -> Matrix {
let n = a.nrows();
Matrix::from_fn(n, n, |i, j| {
let mut acc = a.get(i, 0) * b.get(0, j);
for k in 1..n {
acc += a.get(i, k) * b.get(k, j);
}
acc
})
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct DhLink<'a> {
pub theta: &'a Ex,
pub d: &'a Ex,
pub a: &'a Ex,
pub alpha: &'a Ex,
}
pub fn fk_chain(links: &[DhLink<'_>]) -> Matrix {
let ctx = if let Some(first) = links.first() {
first.theta.context()
} else {
crate::api::context::Context::new()
};
let mut result = Matrix::identity(&ctx, 4);
for link in links {
result = mul_square(&result, &dh_matrix(link.theta, link.d, link.a, link.alpha));
}
result
}
pub fn fk_position(links: &[DhLink<'_>]) -> (Ex, Ex, Ex) {
let t = fk_chain(links);
let px = t.get(0, 3).clone().eval();
let py = t.get(1, 3).clone().eval();
let pz = t.get(2, 3).clone().eval();
(px, py, pz)
}
pub fn fk_rotation(links: &[DhLink<'_>]) -> Matrix {
let t = fk_chain(links);
Matrix::from_fn(3, 3, |i, j| t.get(i, j).clone())
}
pub fn rot_x(theta: &Ex) -> Matrix {
let zero = theta.context().int(0);
let one = theta.context().int(1);
let c = theta.cos();
let s = theta.sin();
Matrix::from_rows_unchecked(vec![
vec![one, zero.clone(), zero.clone()],
vec![zero.clone(), c.clone(), -&s],
vec![zero, s, c],
])
}
pub fn rot_y(theta: &Ex) -> Matrix {
let zero = theta.context().int(0);
let one = theta.context().int(1);
let c = theta.cos();
let s = theta.sin();
Matrix::from_rows_unchecked(vec![
vec![c.clone(), zero.clone(), s.clone()],
vec![zero.clone(), one, zero.clone()],
vec![-&s, zero, c],
])
}
pub fn rot_z(theta: &Ex) -> Matrix {
let zero = theta.context().int(0);
let one = theta.context().int(1);
let c = theta.cos();
let s = theta.sin();
Matrix::from_rows_unchecked(vec![
vec![c.clone(), -&s, zero.clone()],
vec![s, c, zero.clone()],
vec![zero.clone(), zero, one],
])
}
pub fn skew3(a: &Ex, b: &Ex, c: &Ex) -> Matrix {
let zero = a.context().int(0);
Matrix::from_rows_unchecked(vec![
vec![zero.clone(), -c, b.clone()],
vec![c.clone(), zero.clone(), -a],
vec![-b, a.clone(), zero],
])
}
pub fn homogeneous(rotation: &Matrix, position: &[Ex; 3]) -> Result<Matrix, SymplexError> {
if rotation.shape() != (3, 3) {
return Err(SymplexError::InvalidArgument {
operation: "robotics::homogeneous",
reason: format!(
"rotation must be 3×3, got {}×{}",
rotation.nrows(),
rotation.ncols()
),
});
}
let zero = position[0].context().int(0);
let one = position[0].context().int(1);
Ok(Matrix::from_rows_unchecked(vec![
vec![
rotation.get(0, 0).clone(),
rotation.get(0, 1).clone(),
rotation.get(0, 2).clone(),
position[0].clone(),
],
vec![
rotation.get(1, 0).clone(),
rotation.get(1, 1).clone(),
rotation.get(1, 2).clone(),
position[1].clone(),
],
vec![
rotation.get(2, 0).clone(),
rotation.get(2, 1).clone(),
rotation.get(2, 2).clone(),
position[2].clone(),
],
vec![zero.clone(), zero.clone(), zero, one],
]))
}
pub fn translation(x: &Ex, y: &Ex, z: &Ex) -> Matrix {
let zero = x.context().int(0);
let one = x.context().int(1);
Matrix::from_rows_unchecked(vec![
vec![one.clone(), zero.clone(), zero.clone(), x.clone()],
vec![zero.clone(), one.clone(), zero.clone(), y.clone()],
vec![zero.clone(), zero.clone(), one, z.clone()],
vec![zero.clone(), zero.clone(), zero, x.context().int(1)],
])
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EulerConvention {
ZYX,
ZXZ,
XYZ,
}
pub fn rot_euler(phi: &Ex, theta: &Ex, psi: &Ex, convention: EulerConvention) -> Matrix {
match convention {
EulerConvention::ZYX => mul_square(&mul_square(&rot_z(phi), &rot_y(theta)), &rot_x(psi)),
EulerConvention::ZXZ => mul_square(&mul_square(&rot_z(phi), &rot_x(theta)), &rot_z(psi)),
EulerConvention::XYZ => mul_square(&mul_square(&rot_x(phi), &rot_y(theta)), &rot_z(psi)),
}
}
fn f64_to_ratio(v: f64) -> Option<Ratio<BigInt>> {
crate::base::numeric::f64_to_ratio_approx(v, 1_000_000)
}
pub fn inverse_kinematics_2dof(l1: f64, l2: f64, target_x: f64, target_y: f64) -> Vec<(f64, f64)> {
let nv = 4;
let (Some(rl1), Some(rl2), Some(rtx), Some(rty)) = (
f64_to_ratio(l1),
f64_to_ratio(l2),
f64_to_ratio(target_x),
f64_to_ratio(target_y),
) else {
return vec![];
};
let s1 = MultiPoly::<GrevLex>::var(nv, 0);
let c1 = MultiPoly::<GrevLex>::var(nv, 1);
let s2 = MultiPoly::<GrevLex>::var(nv, 2);
let c2 = MultiPoly::<GrevLex>::var(nv, 3);
let one = MultiPoly::<GrevLex>::from_int(nv, 1);
let pyth1 = &(&s1 * &s1) + &(&c1 * &c1) - one.clone();
let pyth2 = &(&s2 * &s2) + &(&c2 * &c2) - one;
let fk_x = {
let term1 = c1.scale(&rl1);
let cos12 = &(&c1 * &c2) - &(&s1 * &s2); let term2 = cos12.scale(&rl2);
let target_poly = MultiPoly::<GrevLex>::constant(nv, rtx.clone());
&(&term1 + &term2) - &target_poly
};
let fk_y = {
let term1 = s1.scale(&rl1);
let sin12 = &(&s1 * &c2) + &(&c1 * &s2); let term2 = sin12.scale(&rl2);
let target_poly = MultiPoly::<GrevLex>::constant(nv, rty.clone());
&(&term1 + &term2) - &target_poly
};
let system = vec![fk_x, fk_y, pyth1, pyth2];
let solutions = match polysys::solve_polynomial_system(&system) {
Ok(sols) => sols,
Err(_) => return vec![],
};
let mut angles = Vec::new();
for sol in &solutions {
if sol.len() != 4 {
continue;
}
let s1_val = sol[0].to_f64().unwrap_or(0.0);
let c1_val = sol[1].to_f64().unwrap_or(0.0);
let s2_val = sol[2].to_f64().unwrap_or(0.0);
let c2_val = sol[3].to_f64().unwrap_or(0.0);
let theta1 = s1_val.atan2(c1_val);
let theta2 = s2_val.atan2(c2_val);
angles.push((theta1, theta2));
}
angles.sort_by(|a, b| {
a.0.partial_cmp(&b.0)
.unwrap_or(std::cmp::Ordering::Equal)
.then(a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal))
});
angles.dedup_by(|a, b| (a.0 - b.0).abs() < 1e-6 && (a.1 - b.1).abs() < 1e-6);
angles
}
#[derive(Clone, Copy, Debug)]
pub struct DhParams<'a> {
pub theta: &'a Angle,
pub d: &'a Length,
pub a: &'a Length,
pub alpha: &'a Angle,
}
impl<'a> From<DhParams<'a>> for DhLink<'a> {
fn from(params: DhParams<'a>) -> Self {
DhLink {
theta: params.theta.inner(),
d: params.d.inner(),
a: params.a.inner(),
alpha: params.alpha.inner(),
}
}
}
fn untyped_links<'a>(dh_params: &[DhParams<'a>]) -> Vec<DhLink<'a>> {
dh_params.iter().map(|&p| DhLink::from(p)).collect()
}
pub fn fk_position_typed(dh_params: &[DhParams<'_>]) -> (Length, Length, Length) {
let (px, py, pz) = fk_position(&untyped_links(dh_params));
(
Length::from_ex(px),
Length::from_ex(py),
Length::from_ex(pz),
)
}
pub fn fk_chain_typed(dh_params: &[DhParams<'_>]) -> Matrix {
fk_chain(&untyped_links(dh_params))
}
pub fn fk_rotation_typed(dh_params: &[DhParams<'_>]) -> Matrix {
fk_rotation(&untyped_links(dh_params))
}