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