1use std::time::Duration;
17
18use deke_types::glam::DVec3;
19use deke_types::{
20 ContinuousFKChain, DekeError, DekeResult, Retimer, SRobotPath, SRobotQ, SRobotTraj, Validator,
21};
22
23use crate::constraints::LinearConstraints;
24use crate::diagnostic::LinearRetimerDiagnostic;
25use crate::error::LinearError;
26
27const BIG: f64 = 1e9;
28
29#[derive(Clone, Debug)]
31pub struct ConstantSpeedRetimer<'a, const N: usize, FK> {
32 fk: &'a FK,
33}
34
35impl<'a, const N: usize, FK> ConstantSpeedRetimer<'a, N, FK>
36where
37 FK: ContinuousFKChain<N, f64>,
38{
39 pub fn new(fk: &'a FK) -> Self {
40 Self { fk }
41 }
42
43 pub(crate) fn retime_path(
44 &self,
45 c: &LinearConstraints<N>,
46 path: &SRobotPath<N, f64>,
47 run_idx: usize,
48 ) -> Result<(SRobotTraj<N, f64>, LinearRetimerDiagnostic), LinearError> {
49 let q: Vec<SRobotQ<N, f64>> = path.iter().copied().collect();
50 let m = q.len();
51 let dt = c.output_dt.as_secs_f64().max(1e-6);
52
53 let pos: Vec<DVec3> = q
55 .iter()
56 .map(|qi| self.fk.fk_end(qi).map(|t| t.translation))
57 .collect::<Result<_, DekeError>>()?;
58 let mut s = vec![0.0f64; m];
59 for i in 1..m {
60 s[i] = s[i - 1] + pos[i].distance(pos[i - 1]);
61 }
62 let total = s[m - 1];
63 if m < 2 || total < 1e-9 {
64 let traj = SRobotTraj::new(c.output_dt, path.clone());
65 return Ok((
66 traj,
67 LinearRetimerDiagnostic {
68 output_samples: m,
69 duration: Duration::from_secs_f64((m.saturating_sub(1)) as f64 * dt),
70 arc_length: total,
71 commanded_speed: c.tcp_speed,
72 peak_speed: 0.0,
73 },
74 ));
75 }
76
77 let mut qp = vec![SRobotQ::<N, f64>::zeros(); m];
79 for (i, qpi) in qp.iter_mut().enumerate() {
80 let (lo, hi) = if i == 0 {
81 (0, 1)
82 } else if i == m - 1 {
83 (m - 2, m - 1)
84 } else {
85 (i - 1, i + 1)
86 };
87 let ds = (s[hi] - s[lo]).max(1e-12);
88 *qpi = (q[hi] - q[lo]) * (1.0 / ds);
89 }
90
91 if c.forbid_interior_dips {
96 let mut worst: Option<(usize, f64)> = None;
97 #[allow(clippy::needless_range_loop)]
98 for i in 1..m - 1 {
99 let g = project_min(&qp[i], &c.joint.v_max);
100 if g < c.tcp_speed * (1.0 - 1e-3) && worst.is_none_or(|(_, gw)| g < gw) {
101 worst = Some((i, g));
102 }
103 }
104 if let Some((i, g)) = worst {
105 return Err(LinearError::SpeedDipRequired {
106 run: run_idx,
107 s: s[i],
108 feasible_speed: g,
109 commanded: c.tcp_speed,
110 });
111 }
112 }
113
114 let v_ceiling = |i: usize| project_min(&qp[i], &c.joint.v_max).min(c.tcp_speed);
115 let a_path: Vec<f64> = (0..m)
116 .map(|i| project_min(&qp[i], &c.joint.a_max))
117 .collect();
118 let j_path: Vec<f64> = (0..m)
119 .map(|i| project_min(&qp[i], &c.joint.j_max))
120 .collect();
121
122 let mut vc: Vec<f64> = (0..m).map(v_ceiling).collect();
126 vc[m - 1] = 0.0;
127 for i in (0..m - 1).rev() {
128 let ds = s[i + 1] - s[i];
129 vc[i] = vc[i].min((vc[i + 1] * vc[i + 1] + 2.0 * a_path[i] * ds).sqrt());
130 }
131 for i in 1..m {
132 let ds = s[i] - s[i - 1];
133 vc[i] = vc[i].min((vc[i - 1] * vc[i - 1] + 2.0 * a_path[i - 1] * ds).sqrt());
134 }
135
136 let seg_n = m - 1;
141 let mut inv_ds = vec![0.0f64; seg_n];
142 let mut vc_d = vec![0.0f64; seg_n];
143 let mut a_d = vec![0.0f64; seg_n];
144 let mut j_d = vec![0.0f64; seg_n];
145 let mut dq = vec![SRobotQ::<N, f64>::zeros(); seg_n];
146 for i in 0..seg_n {
147 let ds = s[i + 1] - s[i];
148 inv_ds[i] = if ds > 0.0 { 1.0 / ds } else { 0.0 };
149 vc_d[i] = vc[i + 1] - vc[i];
150 a_d[i] = a_path[i + 1] - a_path[i];
151 j_d[i] = j_path[i + 1] - j_path[i];
152 dq[i] = q[i + 1] - q[i];
153 }
154
155 let est = (total / (c.tcp_speed.max(1e-6) * dt)) as usize;
160 let mut samples: Vec<SRobotQ<N, f64>> = Vec::with_capacity(est * 2 + 16);
161 samples.push(q[0]);
162 let mut sx = 0.0f64;
163 let mut v = 0.0f64;
164 let mut a = 0.0f64;
165 let mut peak = 0.0f64;
166 let max_iters = (est + m) * 8 + 100_000;
167
168 let mut cur = 0usize;
174 let mut i = 0usize;
175 let mut f = 0.0f64;
176 let mut iters = 0usize;
177 while sx < total - 1e-9 {
178 iters += 1;
179 if iters > max_iters {
180 return Err(LinearError::Stalled {
181 run: run_idx,
182 s: sx,
183 });
184 }
185 let vlim = (vc[i] + vc_d[i] * f).max(0.0);
186 let alim = a_path[i] + a_d[i] * f;
187 let jlim = j_path[i] + j_d[i] * f;
188
189 let a_des = ((vlim - v) / dt).clamp(-alim, alim);
190 a = a_des.clamp(a - jlim * dt, a + jlim * dt).clamp(-alim, alim);
191 v = (v + a * dt).clamp(0.0, vlim);
192 peak = peak.max(v);
193 sx += v * dt;
194 (i, f) = seg(&s, &inv_ds, &mut cur, sx.min(total));
195 samples.push(q[i] + dq[i] * f);
196
197 if v < 1e-9 && vlim < 1e-9 && sx < total - 1e-6 {
199 return Err(LinearError::Stalled {
200 run: run_idx,
201 s: sx,
202 });
203 }
204 }
205 if samples.last().map(|l| l.distance(&q[m - 1])).unwrap_or(1.0) > 1e-9 {
206 samples.push(q[m - 1]);
207 }
208
209 let out_samples = samples.len();
210 let path_out = SRobotPath::try_new(samples).map_err(LinearError::from)?;
211 let traj = SRobotTraj::new(c.output_dt, path_out);
212 Ok((
213 traj,
214 LinearRetimerDiagnostic {
215 output_samples: out_samples,
216 duration: Duration::from_secs_f64((out_samples.saturating_sub(1)) as f64 * dt),
217 arc_length: total,
218 commanded_speed: c.tcp_speed,
219 peak_speed: peak,
220 },
221 ))
222 }
223}
224
225impl<'a, const N: usize, FK> Retimer<N, f64> for ConstantSpeedRetimer<'a, N, FK>
226where
227 FK: ContinuousFKChain<N, f64>,
228{
229 type Diagnostic = LinearRetimerDiagnostic;
230 type Constraints = LinearConstraints<N>;
231
232 fn retime<V: Validator<N, (), f64>>(
233 &self,
234 constraints: &Self::Constraints,
235 path: &SRobotPath<N, f64>,
236 validator: &V,
237 ctx: &V::Context<'_>,
238 ) -> (DekeResult<SRobotTraj<N, f64>>, Self::Diagnostic) {
239 match self.retime_path(constraints, path, 0) {
240 Ok((traj, diag)) => {
241 let samples: Vec<SRobotQ<N, f64>> = traj.path().iter().copied().collect();
242 if let Err(e) = validator.validate_motion(&samples, ctx) {
243 return (Err(e), diag);
244 }
245 (Ok(traj), diag)
246 }
247 Err(e) => (
248 Err(e.into()),
249 LinearRetimerDiagnostic {
250 output_samples: 0,
251 duration: Duration::ZERO,
252 arc_length: 0.0,
253 commanded_speed: constraints.tcp_speed,
254 peak_speed: 0.0,
255 },
256 ),
257 }
258 }
259}
260
261fn project_min<const N: usize>(qp: &SRobotQ<N, f64>, limit: &SRobotQ<N, f64>) -> f64 {
263 let mut m = BIG;
264 let mut any = false;
265 for j in 0..N {
266 let g = qp.0[j].abs();
267 if g > 1e-9 {
268 any = true;
269 m = m.min(limit.0[j] / g);
270 }
271 }
272 if any { m } else { BIG }
273}
274
275#[inline]
281fn seg(s: &[f64], inv_ds: &[f64], cur: &mut usize, x: f64) -> (usize, f64) {
282 let last = s.len() - 1;
283 let x = x.clamp(s[0], s[last]);
284 while *cur < last - 1 && s[*cur + 1] <= x {
285 *cur += 1;
286 }
287 let f = (x - s[*cur]) * inv_ds[*cur];
288 (*cur, f)
289}