use deke_types::{ContinuousFKChain, IkOutcome, IkSolver, SRobotPath, SRobotQ, Validator};
use crate::constraints::PlannerOptions;
use crate::diagnostic::LinearPlannerDiagnostic;
use crate::error::LinearError;
use crate::path::CartesianRun;
use crate::util::ladder_dp;
#[derive(Clone, Debug)]
pub struct CartesianLinearPlanner<'a, const N: usize, FK> {
fk: &'a FK,
}
impl<'a, const N: usize, FK> CartesianLinearPlanner<'a, N, FK>
where
FK: ContinuousFKChain<N, f64> + IkSolver<N, f64>,
{
pub fn new(fk: &'a FK) -> Self {
Self { fk }
}
pub fn plan_run<V: Validator<N, (), f64>>(
&self,
run: &CartesianRun,
opts: &PlannerOptions<N>,
validator: &V,
ctx: &V::Context<'_>,
seed: Option<&SRobotQ<N, f64>>,
run_idx: usize,
) -> Result<(SRobotPath<N, f64>, LinearPlannerDiagnostic), LinearError> {
let length = run.length();
let n = ((length / opts.sample_ds).ceil() as usize).max(1) + 1;
let mut layers: Vec<Vec<(SRobotQ<N, f64>, f64, f64)>> = Vec::with_capacity(n);
for i in 0..n {
let s = (i as f64 * opts.sample_ds).min(length);
let pose = run.eval(s);
let sols = match self.fk.ik(pose)? {
IkOutcome::Solved(sols) if !sols.is_empty() => sols,
_ => return Err(LinearError::Unreachable { run: run_idx, s }),
};
let mut nodes = Vec::with_capacity(sols.len());
for q in sols {
if validator.validate(q, ctx).is_err() {
continue;
}
let w = self.fk.manipulability(&q).map_err(LinearError::Deke)?;
nodes.push((q, opts.manip_weight / (w + 1e-9), w));
}
if nodes.is_empty() {
return Err(LinearError::Obstructed { run: run_idx, s });
}
layers.push(nodes);
}
let min_manip = layers
.iter()
.flat_map(|l| l.iter().map(|&(_, _, w)| w))
.fold(f64::INFINITY, f64::min);
let ds_at =
|k: usize| (k as f64 * opts.sample_ds).min(length) - ((k - 1) as f64 * opts.sample_ds);
let layer_sizes: Vec<usize> = layers.iter().map(Vec::len).collect();
let (chosen, total) = ladder_dp(
&layer_sizes,
|k, i| {
let (q0, nc, _) = layers[k][i];
match seed {
Some(s) if k == 0 => {
if is_reconfiguration(s, &q0, f64::INFINITY, opts) {
f64::INFINITY
} else {
nc + s.distance(&q0)
}
}
_ => nc,
}
},
|k, pi, ci| {
let qp = layers[k - 1][pi].0;
let qc = layers[k][ci].0;
if is_reconfiguration(&qp, &qc, ds_at(k), opts) {
None
} else {
Some(qp.distance(&qc))
}
},
)
.ok_or(LinearError::NoContinuousTrack { run: run_idx })?;
let track: Vec<SRobotQ<N, f64>> = chosen
.iter()
.enumerate()
.map(|(k, &i)| layers[k][i].0)
.collect();
let path = SRobotPath::try_new(track).map_err(LinearError::from)?;
Ok((
path,
LinearPlannerDiagnostic {
samples: n,
min_manipulability: min_manip,
total_cost: total,
},
))
}
}
pub(crate) fn is_reconfiguration<const N: usize>(
a: &SRobotQ<N, f64>,
b: &SRobotQ<N, f64>,
ds: f64,
opts: &PlannerOptions<N>,
) -> bool {
if (*a - *b).linf_norm() > opts.max_branch_jump {
return true;
}
if opts.max_velocity > 0.0 && ds > 1e-12 {
for j in 0..N {
let vmax = opts.joint_v_max.0[j];
if vmax.is_finite() {
let req = (b.0[j] - a.0[j]).abs() * opts.max_velocity / ds;
if req > opts.reconfig_vel_fraction * vmax {
return true;
}
}
}
}
false
}
#[cfg(test)]
mod tests {
use super::is_reconfiguration;
use crate::constraints::PlannerOptions;
use deke_types::SRobotQ;
fn opts(max_velocity: f64, v_max: f64) -> PlannerOptions<2> {
PlannerOptions {
sample_ds: 2e-3,
manip_weight: 1.0,
max_branch_jump: 100.0, max_velocity,
joint_v_max: SRobotQ::splat(v_max),
reconfig_vel_fraction: 0.9,
}
}
#[test]
fn velocity_criterion_flags_fast_joint() {
let a = SRobotQ::<2, f64>::from_array([0.0, 0.0]);
let b = SRobotQ::<2, f64>::from_array([0.5, 0.0]);
assert!(is_reconfiguration(&a, &b, 2e-3, &opts(0.01, 2.0)));
let c = SRobotQ::<2, f64>::from_array([0.01, 0.0]);
assert!(!is_reconfiguration(&a, &c, 2e-3, &opts(0.01, 2.0)));
}
#[test]
fn velocity_criterion_disabled_when_speed_zero() {
let a = SRobotQ::<2, f64>::from_array([0.0, 0.0]);
let b = SRobotQ::<2, f64>::from_array([0.5, 0.0]);
assert!(!is_reconfiguration(&a, &b, 2e-3, &opts(0.0, 2.0)));
}
}