1use deke_types::{ContinuousFKChain, IkOutcome, IkSolver, SRobotPath, SRobotQ, Validator};
12
13use crate::constraints::PlannerOptions;
14use crate::diagnostic::LinearPlannerDiagnostic;
15use crate::error::LinearError;
16use crate::path::CartesianRun;
17use crate::util::ladder_dp;
18
19#[derive(Clone, Debug)]
21pub struct CartesianLinearPlanner<'a, const N: usize, FK> {
22 fk: &'a FK,
23}
24
25impl<'a, const N: usize, FK> CartesianLinearPlanner<'a, N, FK>
26where
27 FK: ContinuousFKChain<N, f64> + IkSolver<N, f64>,
28{
29 pub fn new(fk: &'a FK) -> Self {
30 Self { fk }
31 }
32
33 pub fn plan_run<V: Validator<N, (), f64>>(
42 &self,
43 run: &CartesianRun,
44 opts: &PlannerOptions<N>,
45 validator: &V,
46 ctx: &V::Context<'_>,
47 seed: Option<&SRobotQ<N, f64>>,
48 run_idx: usize,
49 ) -> Result<(SRobotPath<N, f64>, LinearPlannerDiagnostic), LinearError> {
50 let length = run.length();
51 let n = ((length / opts.sample_ds).ceil() as usize).max(1) + 1;
52
53 let mut layers: Vec<Vec<(SRobotQ<N, f64>, f64, f64)>> = Vec::with_capacity(n);
56 for i in 0..n {
57 let s = (i as f64 * opts.sample_ds).min(length);
58 let pose = run.eval(s);
59 let sols = match self.fk.ik(pose)? {
60 IkOutcome::Solved(sols) if !sols.is_empty() => sols,
61 _ => return Err(LinearError::Unreachable { run: run_idx, s }),
62 };
63 let mut nodes = Vec::with_capacity(sols.len());
64 for q in sols {
65 if validator.validate(q, ctx).is_err() {
66 continue;
67 }
68 let w = self.fk.manipulability(&q).map_err(LinearError::Deke)?;
69 nodes.push((q, opts.manip_weight / (w + 1e-9), w));
70 }
71 if nodes.is_empty() {
72 return Err(LinearError::Obstructed { run: run_idx, s });
73 }
74 layers.push(nodes);
75 }
76
77 let min_manip = layers
78 .iter()
79 .flat_map(|l| l.iter().map(|&(_, _, w)| w))
80 .fold(f64::INFINITY, f64::min);
81
82 let ds_at =
83 |k: usize| (k as f64 * opts.sample_ds).min(length) - ((k - 1) as f64 * opts.sample_ds);
84 let layer_sizes: Vec<usize> = layers.iter().map(Vec::len).collect();
85 let (chosen, total) = ladder_dp(
86 &layer_sizes,
87 |k, i| {
88 let (q0, nc, _) = layers[k][i];
89 match seed {
90 Some(s) if k == 0 => {
91 if is_reconfiguration(s, &q0, f64::INFINITY, opts) {
92 f64::INFINITY
93 } else {
94 nc + s.distance(&q0)
95 }
96 }
97 _ => nc,
98 }
99 },
100 |k, pi, ci| {
101 let qp = layers[k - 1][pi].0;
102 let qc = layers[k][ci].0;
103 if is_reconfiguration(&qp, &qc, ds_at(k), opts) {
104 None
105 } else {
106 Some(qp.distance(&qc))
107 }
108 },
109 )
110 .ok_or(LinearError::NoContinuousTrack { run: run_idx })?;
111
112 let track: Vec<SRobotQ<N, f64>> = chosen
113 .iter()
114 .enumerate()
115 .map(|(k, &i)| layers[k][i].0)
116 .collect();
117 let path = SRobotPath::try_new(track).map_err(LinearError::from)?;
118 Ok((
119 path,
120 LinearPlannerDiagnostic {
121 samples: n,
122 min_manipulability: min_manip,
123 total_cost: total,
124 },
125 ))
126 }
127}
128
129pub(crate) fn is_reconfiguration<const N: usize>(
136 a: &SRobotQ<N, f64>,
137 b: &SRobotQ<N, f64>,
138 ds: f64,
139 opts: &PlannerOptions<N>,
140) -> bool {
141 if (*a - *b).linf_norm() > opts.max_branch_jump {
142 return true;
143 }
144 if opts.max_velocity > 0.0 && ds > 1e-12 {
145 for j in 0..N {
146 let vmax = opts.joint_v_max.0[j];
147 if vmax.is_finite() {
148 let req = (b.0[j] - a.0[j]).abs() * opts.max_velocity / ds;
149 if req > opts.reconfig_vel_fraction * vmax {
150 return true;
151 }
152 }
153 }
154 }
155 false
156}
157
158#[cfg(test)]
159mod tests {
160 use super::is_reconfiguration;
161 use crate::constraints::PlannerOptions;
162 use deke_types::SRobotQ;
163
164 fn opts(max_velocity: f64, v_max: f64) -> PlannerOptions<2> {
165 PlannerOptions {
166 sample_ds: 2e-3,
167 manip_weight: 1.0,
168 max_branch_jump: 100.0, max_velocity,
170 joint_v_max: SRobotQ::splat(v_max),
171 reconfig_vel_fraction: 0.9,
172 }
173 }
174
175 #[test]
176 fn velocity_criterion_flags_fast_joint() {
177 let a = SRobotQ::<2, f64>::from_array([0.0, 0.0]);
178 let b = SRobotQ::<2, f64>::from_array([0.5, 0.0]);
180 assert!(is_reconfiguration(&a, &b, 2e-3, &opts(0.01, 2.0)));
182 let c = SRobotQ::<2, f64>::from_array([0.01, 0.0]);
184 assert!(!is_reconfiguration(&a, &c, 2e-3, &opts(0.01, 2.0)));
185 }
186
187 #[test]
188 fn velocity_criterion_disabled_when_speed_zero() {
189 let a = SRobotQ::<2, f64>::from_array([0.0, 0.0]);
190 let b = SRobotQ::<2, f64>::from_array([0.5, 0.0]);
191 assert!(!is_reconfiguration(&a, &b, 2e-3, &opts(0.0, 2.0)));
192 }
193}