• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

cbruiz / printhor / 13775596467

10 Mar 2025 09:59PM UTC coverage: 90.713% (-0.02%) from 90.734%
13775596467

push

github

web-flow
Review/motion optimizations (#36)

* Module refactoring and light doc improvement
* Bump embassy upstream

184 of 218 new or added lines in 16 files covered. (84.4%)

11 existing lines in 1 file now uncovered.

14837 of 16356 relevant lines covered (90.71%)

733512.36 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.29
/printhor/src/bin/motion/profile.rs
1
//! The Motion profile trait and current implementation: Trajectory with Double S Velocity Profile \[1\]
2
//!
3
//! \[1\] Biagiotti, L., Melchiorri, C.: Trajectory Planning for Automatic Machines and Robots. Springer, Heidelberg (2008). [DOI:10.1007/978-3-540-85629-0](https://doi.org/10.1007/978-3-540-85629-0)
4

5
use crate::hwa;
6
use crate::motion;
7
use crate::processing;
8
use hwa::math;
9
use hwa::math::*;
10

11
/// Struct representing the motion profile configuration.
12
///
13
/// # Fields
14
///
15
/// * `constraints` - The motion profile constraints.
16
/// * `times` - The time parameters for different phases of the motion profile.
17
/// * `initial_velocity` - Initial velocity for the motion profile (in [hwa::HwiContract::SPACE_UNIT_MAGNITUDE]/s).
18
/// * `final_velocity` - Final velocity for the motion profile (in [hwa::HwiContract::SPACE_UNIT_MAGNITUDE]/s).
19
/// * `error_correction` - An optional error correction factor.
20
pub struct ProfileConfig {
21
    /// The motion profile constraints.
22
    ///
23
    /// # Units
24
    ///
25
    /// - `v_max`: [hwa::HwiContract::SPACE_UNIT_MAGNITUDE]/s
26
    /// - `a_max`: [hwa::HwiContract::SPACE_UNIT_MAGNITUDE]/s²
27
    /// - `j_max`: [hwa::HwiContract::SPACE_UNIT_MAGNITUDE]/s³
28
    pub constraints: Constraints,
29

30
    /// The time parameters of the motion profile.
31
    ///
32
    /// # Units
33
    ///
34
    /// - `t_j1`: s
35
    /// - `t_a`: s
36
    /// - `t_v`: s
37
    /// - `t_d`: s
38
    /// - `t_j2`: s
39
    pub times: Times,
40

41
    /// Initial velocity for the motion profile.
42
    ///
43
    /// # Units
44
    ///
45
    /// - Initial velocity: [hwa::HwiContract::SPACE_UNIT_MAGNITUDE]/s
46
    pub initial_velocity: Real,
47

48
    /// Final velocity for the motion profile.
49
    ///
50
    /// # Units
51
    ///
52
    /// - Final velocity: [hwa::HwiContract::SPACE_UNIT_MAGNITUDE]/s
53
    pub final_velocity: Real,
54

55
    /// An optional error correction factor.
56
    ///
57
    /// # Units
58
    ///
59
    /// - Error correction factor: Implementation-specific units
60
    pub error_correction: Option<Real>,
61
}
62

63
/// The `Constraints` struct represents the limits of the motion profile in terms of velocity, acceleration, and jerk.
64
///
65
/// # Fields
66
///
67
/// * `v_max` - The maximum velocity (in [hwa::HwiContract::SPACE_UNIT_MAGNITUDE]/s). This defines the upper limit of speed that can be achieved.
68
/// * `a_max` - The maximum acceleration (in [hwa::HwiContract::SPACE_UNIT_MAGNITUDE]/s²). This represents the highest rate of change of velocity that is permissible.
69
/// * `j_max` - The maximum jerk (in [hwa::HwiContract::SPACE_UNIT_MAGNITUDE]/s³). This is the maximum rate of change of acceleration.
70
#[derive(Clone, Copy)]
71
pub struct Constraints {
72
    /// The maximum velocity (v_max) (in [hwa::HwiContract::SPACE_UNIT_MAGNITUDE]/s).
73
    /// This defines the upper limit of speed that can be achieved.
74
    pub v_max: Real,
75

76
    /// The maximum acceleration (a_max) (in [hwa::HwiContract::SPACE_UNIT_MAGNITUDE]/s²).
77
    /// This represents the highest rate of change of velocity that is permissible.
78
    pub a_max: Real,
79

80
    /// The maximum jerk (j_max) (in [hwa::HwiContract::SPACE_UNIT_MAGNITUDE]/s³).
81
    /// This is the maximum rate of change of acceleration.
82
    pub j_max: Real,
83
}
84

85
impl core::fmt::Debug for Constraints {
86
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
6✔
87
        core::write!(
6✔
88
            f,
6✔
89
            "v_{{max}}={:?} a_{{max}}={:?} j_{{max}}={:?}",
6✔
90
            self.v_max,
6✔
91
            self.a_max,
6✔
92
            self.j_max
6✔
93
        )
6✔
94
    }
6✔
95
}
96

97
/// Struct representing the time parameters for different phases of the S-curve motion profile.
98
///
99
/// This struct is used to define the specific time segments allocated to different phases of the S-curve.
100
///
101
/// # Fields
102
///
103
/// * `t_j1` - Time spent in the first jerk phase (in seconds).
104
/// * `t_a` - Time spent in the acceleration phase (in seconds).
105
/// * `t_v` - Time spent at constant velocity (in seconds).
106
/// * `t_d` - Time spent in the deceleration phase (in seconds).
107
/// * `t_j2` - Time spent in the second jerk phase (in seconds).
108
pub struct Times {
109
    /// Time spent in the first jerk phase (in seconds).
110
    pub t_j1: Real,
111

112
    /// Time spent in the acceleration phase (in seconds).
113
    pub t_a: Real,
114

115
    /// Time spent at constant velocity (in seconds).
116
    pub t_v: Real,
117

118
    /// Time spent in the deceleration phase (in seconds).
119
    pub t_d: Real,
120

121
    /// Time spent in the second jerk phase (in seconds).
122
    pub t_j2: Real,
123
}
124

125
/// Struct representing the S-curve motion profile with various parameters.
126
///
127
/// The S-curve motion profile is used to define the motion of an object
128
/// where the transition of velocity includes a jerk phase, an acceleration
129
/// phase, a constant velocity phase, a deceleration phase, and a final jerk
130
/// phase to achieve smooth motion.
131
///
132
/// This profile is characterized by the following parameters:
133
///
134
/// # Fields
135
///
136
/// * `t_j1` - Time spent in the first jerk phase (in seconds).
137
/// * `t_a` - Time spent in the acceleration phase (in seconds).
138
/// * `t_v` - Time spent at constant velocity (in seconds).
139
/// * `t_d` - Time spent in the deceleration phase (in seconds).
140
/// * `t_j2` - Time spent in the second jerk phase (in seconds).
141
/// * `v_0` - Initial velocity (in [hwa::HwiContract::SPACE_UNIT_MAGNITUDE]/s).
142
/// * `v_1` - Final velocity (in [hwa::HwiContract::SPACE_UNIT_MAGNITUDE]/s).
143
/// * `j_max` - Maximum jerk (in [hwa::HwiContract::SPACE_UNIT_MAGNITUDE]/s³).
144
/// * `a_lim_a` - Acceleration limit during the acceleration phase (in [hwa::HwiContract::SPACE_UNIT_MAGNITUDE]/s²).
145
/// * `a_lim_d` - Deceleration limit during the deceleration phase (in [hwa::HwiContract::SPACE_UNIT_MAGNITUDE]/s²).
146
/// * `v_lim` - Velocity limit (in [hwa::HwiContract::SPACE_UNIT_MAGNITUDE]/s).
147
/// * `q1` - Displacement or position to be achieved (in [hwa::HwiContract::SPACE_UNIT_MAGNITUDE]).
148
/// * `constraints` - Constraints applied to the motion profile.
149
/// * `cache` - Cached values for optimization and repeated calculations.
150
pub struct SCurveMotionProfile {
151
    /// Time spent in the first jerk phase.
152
    pub t_j1: Real,
153

154
    /// Time spent in the acceleration phase.
155
    pub t_a: Real,
156

157
    /// Time spent at constant velocity.
158
    pub t_v: Real,
159

160
    /// Time spent in the deceleration phase.
161
    pub t_d: Real,
162

163
    /// Time spent in the second jerk phase.
164
    pub t_j2: Real,
165

166
    /// Initial velocity.
167
    pub v_0: Real,
168

169
    /// Final velocity.
170
    pub v_1: Real,
171

172
    /// Maximum jerk.
173
    pub j_max: Real,
174

175
    /// Acceleration limit during the acceleration phase.
176
    pub a_lim_a: Real,
177

178
    /// Deceleration limit during the deceleration phase.
179
    pub a_lim_d: Real,
180

181
    /// Velocity limit.
182
    pub v_lim: Real,
183

184
    /// Displacement or position to be achieved.
185
    pub q1: Real,
186

187
    /// Constraints applied to the motion profile.
188
    pub constraints: Constraints,
189

190
    /// Cached values for optimization and repeated calculations.
191
    pub cache: Cache,
192
}
193

194
#[allow(unused)]
195
impl SCurveMotionProfile {
196
    /// Compute the S-curve motion profile.
197
    ///
198
    /// # Arguments
199
    ///
200
    /// * `q_1` - Displacement or position to be achieved (in [hwa::HwiContract::SPACE_UNIT_MAGNITUDE]).
201
    /// * `v_0` - Initial velocity (in [hwa::HwiContract::SPACE_UNIT_MAGNITUDE]/s).
202
    /// * `v_1` - Final velocity (in [hwa::HwiContract::SPACE_UNIT_MAGNITUDE]/s).
203
    /// * `constraints` - [Constraints] applied to the motion profile.
204
    /// * `error_correction` - Flag to enable error correction.
205
    ///
206
    /// # Returns
207
    ///
208
    /// `Result<SCurveMotionProfile,  processing::CodeExecutionFailure>` - The computed S-curve motion profile or an error.
209
    ///
210
    /// # Description
211
    ///
212
    /// This method computes the S-curve motion profile considering the initial and final velocities,
213
    /// the displacement to be achieved, and the provided constraints. The method ensures that the
214
    /// computed trajectory respects the given constraints and returns a result containing the motion
215
    /// profile or an error if the motion cannot be achieved with the specified constraints.
216
    ///
217
    /// # Examples
218
    ///
219
    /// ```
220
    /// let constraints = Constraints {
221
    ///     v_max: Real::from_f32(10.0),
222
    ///     a_max: Real::from_f32(2.0),
223
    ///     j_max: Real::from_f32(1.0),
224
    /// };
225
    /// let profile = SCurveMotionProfile::compute(Real::from_f32(20.0), Real::from_f32(0.0), Real::from_f32(5.0), &constraints, false)?;
226
    /// println!("{}", profile);
227
    /// ```
228
    ///
229
    pub fn compute(
414,889✔
230
        q_1: Real,
414,889✔
231
        v_0: Real,
414,889✔
232
        v_1: Real,
414,889✔
233
        constraints: &Constraints,
414,889✔
234
        error_correction: bool,
414,889✔
235
    ) -> Result<SCurveMotionProfile, processing::CodeExecutionFailure> {
414,889✔
236
        if constraints.v_max.is_negligible()
414,889✔
237
            || constraints.a_max.is_negligible()
414,889✔
238
            || constraints.j_max.is_negligible()
414,889✔
239
        {
240
            hwa::warn!("[SCurveMotionProfile] Unable to perform movement: constraints are unset");
×
NEW
241
            return Err(processing::CodeExecutionFailure::NumericalError);
×
242
        }
414,889✔
243
        // Set v_max to arg_max { v_0, v_1, v_max}
414,889✔
244
        let mut v_max = v_0.max(v_1).max(constraints.v_max);
414,889✔
245

414,889✔
246
        #[cfg(feature = "debug-motion-planning")]
414,889✔
247
        hwa::info!("[SCurveMotionProfile] v_{{0}} = {:?}", v_0);
414,889✔
248
        #[cfg(feature = "debug-motion-planning")]
414,889✔
249
        hwa::info!("[SCurveMotionProfile] v_{{1}} = {:?}", v_1);
414,889✔
250
        #[cfg(feature = "debug-motion-planning")]
414,889✔
251
        hwa::info!("[SCurveMotionProfile] q_{{1}} = {:?}", q_1);
414,889✔
252
        #[cfg(feature = "debug-motion-planning")]
414,889✔
253
        hwa::info!("[SCurveMotionProfile] v_{{max}} = {:?}", constraints.v_max);
414,889✔
254
        #[cfg(feature = "debug-motion-planning")]
414,889✔
255
        hwa::info!("[SCurveMotionProfile] a_{{max}} = {:?}", constraints.a_max);
414,889✔
256
        #[cfg(feature = "debug-motion-planning")]
414,889✔
257
        hwa::info!("[SCurveMotionProfile] j_{{max}} = {:?}", constraints.j_max);
414,889✔
258

414,889✔
259
        // First, compute the displacement
414,889✔
260

414,889✔
261
        // [[1]] First necessary to verify whether a trajectory can be actually performed or not.
414,889✔
262
        // As a matter of fact, there are several cases in which a trajectory cannot be computed
414,889✔
263
        // with the given constraints. For example, if the desired displacement h is small with
414,889✔
264
        // respect to the difference between the initial and final velocities v0 and v1, it might be not possible
414,889✔
265
        // to change the velocity (with the given limits on jerk and acceleration), while accomplishing the displacement h := q_1 - q_0
414,889✔
266
        // In printhor, always h = q_1
414,889✔
267

414,889✔
268
        let j_max_inv = constraints.j_max.recip();
414,889✔
269
        let t_jmax = constraints.a_max * j_max_inv;
414,889✔
270

414,889✔
271
        #[cfg(feature = "debug-motion-planning")]
414,889✔
272
        hwa::info!("[SCurveMotionProfile] T_{{jmax}} = {:?}", t_jmax);
414,889✔
273

414,889✔
274
        // [eq 3.17] T_{j}^{*} = min\Bigg\{\sqrt{\frac{|v_{1} - v_{0}|}{j_{max}}}, \frac{a_{max}}{j_{max}}\Bigg\}
414,889✔
275
        let t_jstar =
414,889✔
276
            Real::vmin((((v_1 - v_0).abs()) * j_max_inv).sqrt(), Some(t_jmax)).unwrap_or(ZERO);
414,889✔
277

278
        // [[1]] If t_jstar = amax/jmax, the acceleration reaches its maximum value and a segment with zero jerk may exist.
279
        let q_lim = if (t_jstar - t_jmax).is_negligible() {
414,889✔
280
            let lim = (HALF * (v_0 + v_1)) * (t_jstar + ((v_1 - v_0).abs() * j_max_inv));
24✔
281
            #[cfg(feature = "debug-motion-planning")]
24✔
282
            hwa::info!(
24✔
283
                "[SCurveMotionProfile] T_{{j}}^{{*}} = \\sqrt{{\\frac{{|v_{{1}} - v_{{0}}|}}{{j_{{max}}}}}} = {:?}",
24✔
284
                lim
24✔
285
            );
24✔
286
            #[cfg(feature = "debug-motion-planning")]
24✔
287
            hwa::info!("[SCurveMotionProfile] The acceleration does not reach its maximum value");
24✔
288
            lim
24✔
289
        } else {
290
            let lim = t_jstar * (v_0 + v_1);
414,865✔
291
            // the acceleration reaches its maximum value and a segment with zero jerk may exist
414,865✔
292
            #[cfg(feature = "debug-motion-planning")]
414,865✔
293
            hwa::info!(
414,865✔
294
                "[SCurveMotionProfile] T_{{j}}^{{*}} = \\frac{{a_{{max}}}}{{j_{{max}}}} = {:?}",
414,865✔
295
                lim
414,865✔
296
            );
414,865✔
297
            #[cfg(feature = "debug-motion-planning")]
414,865✔
298
            hwa::info!("[SCurveMotionProfile] The acceleration reaches its maximum value");
414,865✔
299
            lim
414,865✔
300
        };
301

302
        let not_feasible = q_1 <= q_lim;
414,889✔
303

414,889✔
304
        if not_feasible {
414,889✔
305
            #[cfg(feature = "verbose-timings")]
306
            hwa::warn!(
307
                "[SCurveMotionProfile] Movement NOT FEASIBLE. Performing unconstrained parabolic blends"
308
            );
309

310
            // y := j_max
311
            // e1: q1 = (y * t^3) / 6 + v0 * t
312
            // e2: v1 = v0 + y * t^2 / 2 => 2*(v1 - v0) = y * t^2 => y = 2*(v1-v0) / t^2
313
            // e1|e2: q1 = ((2*(v1-v0) / t^2) * t^3) / 6 + v0 * t
314
            // t = 3*q1 / 2*v0 + v1 | v0+v1 >0 and q1 > 0
315

316
            // v1(t) = v0 + j_max * t^2 / 2
317
            // s1(t) = (y * t^3) / 6 + v0*t
318
            // v2(t) = 2*v1(t_j1) - v0 - j_max * (t - 2t_j1)^2 / 2
319
            //  2*v0 + j_max * t_j1^2 / 2
320
            // v2(t) = 2*(v0 + j_max * t^2 / 2) - v0 - j_max * (t - 2t_j1)^2 / 2
321

322
            // e1: q1 = (y * t^3) / 6 + v0*t
323
            // e2: q1 = (- y * t^3) / 6 + a*t^2*T_j1 - a * t * T_j1^2 + t*v_0
324
            // e3: T_j1 = t/2
325
            // y = q1/x
326

327
            // solving -1/6 a x^3 + d/x *x^3/2 - d/x* x^3/4 + v x = d for x
328
            // x = (2 (sqrt(d^2 + v^2) - v))/d y d!=0 y a = 0
329

330
            // v1(t) = v0 + a * (t^2 / 2)
331
            // s1(t) = (a * t^3) / 6 + v0 * t
332

333
            // v2(t) = v_1 - a * (((T - t)^2)/2)
334
            // s2(t) = v_{1} * t - (1/6) * a * t (t^2 - 3*t*T + 3*T^2)
335

336
            // q_b = Int{T/2, T} ( v_1 - a * (((T - t)^2)/2) ) = s2(T) - s2(T/2)
337
            // q_b = v_1 * T - (1/6) * a * T (T^2 - 3*T*T + 3*T^2) - ( v_1 * T/2 - (1/6) * a * T/2 ((T/2)^2 - 3*(T/2)*T + 3*T^2) )
338
            //   = 1/2 T v_1 - (a T^3)/48
339
            //
340
            // q_a = Int{0, T/2} (v_0 + a * (t^2 / 2)) = s1(T/2) - s1(0)
341
            // q_a = (a * (T/2)^3) / 6 + v_0 * T/2 - ( (a * 0^3) / 6 + v_0 * 0 )
342
            // = 1/48 T (a T^2 + 24 * v_0)
343

344
            // s = q_a + q_b = 1/2 * T * v_1 - (a * T^3) / 48 + 1/48 * T * (-a T^2 + 24 * v_0) =
345
            //   = 1/2 T (v_0 + v_1)
346
            // T = (2 * s) / (v_0 + v_1)
347

348
            // q_a = 1/48 T (a T^2 + 24 * v_0)
349
            // q_b = 1/2 T v_1 - (a T^3)/48
350
            // simplifiying q_a = 1/48 ((2 * s) / (v_0 + v_1)) (a ((2 * s) / (v_0 + v_1))^2 + 24 * v_0)
351
            // q_a = (s (a s^2 + 6 v_0 (v_0 + v_1)^2))/(6 (v_0 + v_1)^3)
352
            // simplifiying q_b = 1/2 ((2 * s) / (v_0 + v_1)) v_1 - (a ((2 * s) / (v_0 + v_1))^3)/48
353
            // q_b = (s v_1)/(v_0 + v_1) - (a s^3)/(6 (v_0 + v_1)^3)
354

355
            // (q_1 (q_1 q_1^2 + 6 v_0 (v_0 + v_1)^2))/(6 (v_0 + v_1)^3)
356
            // (q_1 v_1)/(v_0 + v_1) - (a q_1^3)/(6 (v_0 + v_1)^3)
357

358
            // Compute the time need to accel
359
            let t_a = TWO * (q_1 / (v_0 + v_1));
178,563✔
360

178,563✔
361
            // Compute the accel
178,563✔
362
            let a = (v_1 - v_0) / t_a;
178,563✔
363
            let t_j1 = HALF * t_a;
178,563✔
364

178,563✔
365
            // Constructively define the two parabolas:
178,563✔
366
            // The first one (smooth accel from v_0):
178,563✔
367
            // v_{1}(t) := a_{v1} * (t - 0)^2 + v_{0}
178,563✔
368
            // with a_{v_1} as:
178,563✔
369
            //  a_{1} = \frac{a \frac{T}{2} - 0}{(0 - \frac{T}{2})^2}
178,563✔
370
            // The second one (smooth decel to v_1); TBD
178,563✔
371
            //  a_{2} = \frac{-a \frac{T}{2} - 0}{(0 - \frac{T}{2})^2}
178,563✔
372

178,563✔
373
            let a_1 = (a * t_j1) / (t_j1 * t_j1);
178,563✔
374

178,563✔
375
            // Finally, reformulating to meet the paper convention it is trivial to determine that:
178,563✔
376
            // In \dot{q}(t) = v_{0} + j_{max} \frac{t^2}{2}:
178,563✔
377
            // a_{v1} = j_max / 2, hence j_max = 2 * a_{v1}
178,563✔
378

178,563✔
379
            let j_max = TWO * a_1;
178,563✔
380

178,563✔
381
            let mut profile = SCurveMotionProfile {
178,563✔
382
                t_j1,
178,563✔
383
                t_a,
178,563✔
384
                t_v: math::ZERO,
178,563✔
385
                t_d: math::ZERO,
178,563✔
386
                t_j2: math::ZERO,
178,563✔
387
                v_0: v_0,
178,563✔
388
                v_1: v_1,
178,563✔
389
                j_max,
178,563✔
390
                a_lim_a: math::ZERO,
178,563✔
391
                a_lim_d: math::ZERO,
178,563✔
392
                v_lim: v_1.max(v_0),
178,563✔
393
                q1: q_1,
178,563✔
394
                cache: Cache::default(),
178,563✔
395
                constraints: *constraints,
178,563✔
396
            };
178,563✔
397
            profile.compute_cache();
178,563✔
398
            if error_correction {
178,563✔
399
                let final_pos = profile.s_i7(&profile.i7_end());
×
400
                let delta_e = profile.q1 - final_pos;
×
401
                profile
×
402
                    .extend(delta_e)
×
NEW
403
                    .map_err(|_| processing::CodeExecutionFailure::ERR)?;
×
404
            }
178,563✔
405
            return Ok(profile);
178,563✔
406
            /*
407

408
            if t_jstar < t_jmax {
409
                hwa::error!(" => cannot accel to travel {} mm from {} to {} ({}). Will take {} mm ({} * (v0+v1))", q_1, v_0, v_1, (v_1 - v_0).abs(), q_lim, t_jstar);
410
            }
411
            else {
412
                hwa::error!("=> cannot accel to travel {} mm from {} to {}. Will take {} mm (((v_0 + v_1) / TWO) * ({} + ((v_1 - v_0).abs() / {})))", q_1, v_0, v_1, q_lim, t_jstar, constraints.a_max);
413
            }
414
            return Err(CodeExecutionFailure::ERR)
415

416
             */
417
        } else {
418
            #[cfg(feature = "debug-motion-planning")]
419
            hwa::info!(
420
                "[SCurveMotionProfile] q_{{1}} > q_{{lim}} [ {:?} > {:?} ] ",
421
                q_1,
422
                q_lim
423
            );
424

425
            let mut a_max = constraints.a_max;
236,326✔
426
            let mut a_max_inv = a_max.recip();
236,326✔
427
            let mut a_max_squared = a_max * a_max;
236,326✔
428
            // aj_ratio = a_max / j_max
236,326✔
429
            let mut aj_ratio = a_max * j_max_inv;
236,326✔
430

236,326✔
431
            let mut prev_vmax = v_max;
236,326✔
432
            let gamma = Real::from_f32(0.9);
236,326✔
433
            loop {
434
                match Self::compute_case_1(
8,064,708✔
435
                    q_1,
8,064,708✔
436
                    v_0,
8,064,708✔
437
                    v_1,
8,064,708✔
438
                    v_max,
8,064,708✔
439
                    a_max,
8,064,708✔
440
                    constraints.j_max,
8,064,708✔
441
                    aj_ratio,
8,064,708✔
442
                    a_max_squared,
8,064,708✔
443
                ) {
8,064,708✔
444
                    Ok(times) => {
8,064,708✔
445
                        if times.t_v <= ZERO {
8,064,708✔
446
                            // TODO: lagrange multipliers or something better to find a solution with less iterations
447

448
                            let t_j = aj_ratio;
8,064,696✔
449

8,064,696✔
450
                            let sqrt_delta =
8,064,696✔
451
                                ((a_max * a_max * a_max * a_max * j_max_inv * j_max_inv)
8,064,696✔
452
                                    + (TWO * ((v_0 * v_0) + (v_1 * v_1))
8,064,696✔
453
                                        + (a_max * ((FOUR * q_1) - (TWO * (t_j) * (v_0 + v_1))))))
8,064,696✔
454
                                    .sqrt()
8,064,696✔
455
                                    .unwrap();
8,064,696✔
456
                            let aj = (a_max * a_max) * j_max_inv;
8,064,696✔
457
                            let mut t_a_2 = ((aj - (v_0 + v_0) + sqrt_delta) * HALF * a_max_inv);
8,064,696✔
458
                            let mut t_d_2 = ((aj - (v_1 + v_1) + sqrt_delta) * HALF * a_max_inv);
8,064,696✔
459
                            let mut t_j1 = t_j;
8,064,696✔
460
                            let mut t_j2 = t_j;
8,064,696✔
461

8,064,696✔
462
                            if t_a_2 < math::ZERO || t_d_2 < math::ZERO {
8,064,696✔
463
                                // it may happen that Ta or Td becomes negative. In this case, only one of the acceleration or deceleration phase is necessary
464
                                let qd = (q_1 / (v_0 + v_1));
80,467✔
465
                                let vsum_sq = (v_0 + v_1) * (v_0 + v_1);
80,467✔
466
                                if t_a_2 < math::ZERO {
80,467✔
467
                                    t_a_2 = math::ZERO;
37,340✔
468
                                    t_j1 = math::ZERO;
37,340✔
469

37,340✔
470
                                    t_d_2 = qd * TWO;
37,340✔
471
                                    t_j2 = ((constraints.j_max * q_1)
37,340✔
472
                                        - (constraints.j_max
37,340✔
473
                                            * (constraints.j_max * q_1 * q_1
37,340✔
474
                                                + (v_1 + v_0) * (v_1 + v_0) * (v_1 - v_0)))
37,340✔
475
                                            .sqrt()
37,340✔
476
                                            .unwrap())
37,340✔
477
                                        / (constraints.j_max * (v_1 + v_0));
37,340✔
478
                                } else if t_d_2 < math::ZERO {
43,127✔
479
                                    t_d_2 = math::ZERO;
43,127✔
480
                                    t_j2 = math::ZERO;
43,127✔
481
                                    t_a_2 = qd * TWO;
43,127✔
482
                                    t_j1 = ((constraints.j_max * q_1)
43,127✔
483
                                        - (constraints.j_max
43,127✔
484
                                            * (constraints.j_max * q_1 * q_1
43,127✔
485
                                                + (v_1 + v_0) * (v_1 + v_0) * (v_0 - v_1)))
43,127✔
486
                                            .sqrt()
43,127✔
487
                                            .unwrap())
43,127✔
488
                                        / (constraints.j_max * (v_1 + v_0));
43,127✔
489
                                }
43,127✔
490
                            } else {
7,984,229✔
491
                                t_j1 = t_j;
7,984,229✔
492
                                t_j2 = t_j;
7,984,229✔
493
                            }
7,984,229✔
494

495
                            /*
496
                            #[cfg(feature = "native")]
497
                            std::println!("gamma={} -> a={} -> t_j1={}, t_a_2={} t_j2={} t_d_2={}",
498
                                          gamma.rdp(4),
499
                                a_max, t_j1, t_a_2, t_j2, t_d_2
500
                            );
501
                            */
502

503
                            if t_a_2 >= (t_j1 + t_j1) && t_d_2 >= (t_j2 + t_j2) {
8,064,696✔
504
                                let a_lim_a = constraints.j_max * t_j1;
236,314✔
505
                                let a_lim_d = -constraints.j_max * t_j2;
236,314✔
506

236,314✔
507
                                let v_lim = v_0 + (t_a_2 - t_j1) * a_lim_a;
236,314✔
508
                                let t = t_a_2 + math::ZERO + t_d_2;
236,314✔
509
                                let mut profile = SCurveMotionProfile {
236,314✔
510
                                    t_j1,
236,314✔
511
                                    t_a: t_a_2,
236,314✔
512
                                    t_v: math::ZERO,
236,314✔
513
                                    t_d: t_d_2,
236,314✔
514
                                    t_j2,
236,314✔
515
                                    v_0: v_0,
236,314✔
516
                                    v_1: v_1,
236,314✔
517
                                    j_max: constraints.j_max,
236,314✔
518
                                    a_lim_a,
236,314✔
519
                                    a_lim_d,
236,314✔
520
                                    v_lim,
236,314✔
521
                                    q1: q_1,
236,314✔
522
                                    cache: Cache::default(),
236,314✔
523
                                    constraints: *constraints,
236,314✔
524
                                };
236,314✔
525
                                profile.compute_cache();
236,314✔
526
                                if error_correction {
236,314✔
527
                                    let final_pos =
12✔
528
                                        profile.s_i7(&profile.i7_end()) + Real::epsilon();
12✔
529
                                    let delta_e = profile.q1 - final_pos;
12✔
530
                                    profile
12✔
531
                                        .extend(delta_e)
12✔
532
                                        .map_err(|_| processing::CodeExecutionFailure::ERR)?;
12✔
533
                                }
236,302✔
534
                                return Ok(profile);
236,314✔
535
                            }
7,828,382✔
536
                            a_max *= gamma;
7,828,382✔
537
                            a_max_inv = a_max.recip();
7,828,382✔
538
                            a_max_squared = a_max * a_max;
7,828,382✔
539
                            aj_ratio = a_max * j_max_inv;
7,828,382✔
540
                            if a_max < Real::from_f32(0.1) {
7,828,382✔
541
                                return if (v_1 - v_0).abs().is_negligible() {
×
542
                                    let t_v = TWO * (q_1 / (v_0 + v_1));
×
543
                                    let mut profile = SCurveMotionProfile {
×
544
                                        t_j1: math::ZERO,
×
545
                                        t_a: math::ZERO,
×
546
                                        t_v,
×
547
                                        t_d: math::ZERO,
×
548
                                        t_j2: math::ZERO,
×
549
                                        v_0: v_0,
×
550
                                        v_1: v_1,
×
551
                                        j_max: constraints.j_max,
×
552
                                        a_lim_a: math::ZERO,
×
553
                                        a_lim_d: math::ZERO,
×
554
                                        v_lim: v_1,
×
555
                                        q1: q_1,
×
556
                                        cache: Cache::default(),
×
557
                                        constraints: *constraints,
×
558
                                    };
×
559
                                    profile.compute_cache()?;
×
560
                                    if error_correction {
×
561
                                        let final_pos = profile.s_i7(&profile.i7_end());
×
562
                                        let delta_e = profile.q1 - final_pos;
×
563
                                        profile
×
564
                                            .extend(delta_e)
×
NEW
565
                                            .map_err(|_| processing::CodeExecutionFailure::ERR)?;
×
566
                                    }
×
567
                                    Ok(profile)
×
568
                                } else {
569
                                    // Compute the time need to accel
570
                                    let t_a = TWO * (q_1 / (v_0 + v_1));
×
571

×
572
                                    // Compute the accel
×
573
                                    let a = (v_1 - v_0) / t_a;
×
574
                                    let t_j1 = HALF * t_a;
×
575

×
576
                                    // Constructively define the two parabolas:
×
577
                                    // The first one (smooth accel from v_0):
×
578
                                    // v_{1}(t) := a_{v1} * (t - 0)^2 + v_{0}
×
579
                                    // with a_{v_1} as:
×
580
                                    //  a_{1} = \frac{a \frac{T}{2} - 0}{(0 - \frac{T}{2})^2}
×
581
                                    // The second one (smooth decel to v_1); TBD
×
582
                                    //  a_{2} = \frac{-a \frac{T}{2} - 0}{(0 - \frac{T}{2})^2}
×
583

×
584
                                    let a_1 = (a * t_j1) / (t_j1 * t_j1);
×
585

×
586
                                    // Finally, reformulating to meet the paper convention it is trivial to determine that:
×
587
                                    // In \dot{q}(t) = v_{0} + j_{max} \frac{t^2}{2}:
×
588
                                    // a_{v1} = j_max / 2, hence j_max = 2 * a_{v1}
×
589

×
590
                                    let j_max = TWO * a_1;
×
591

×
592
                                    let mut profile = SCurveMotionProfile {
×
593
                                        t_j1,
×
594
                                        t_a,
×
595
                                        t_v: math::ZERO,
×
596
                                        t_d: math::ZERO,
×
597
                                        t_j2: math::ZERO,
×
598
                                        v_0: v_0,
×
599
                                        v_1: v_1,
×
600
                                        j_max,
×
601
                                        a_lim_a: math::ZERO,
×
602
                                        a_lim_d: math::ZERO,
×
603
                                        v_lim: v_1.max(v_0),
×
604
                                        q1: q_1,
×
605
                                        cache: Cache::default(),
×
606
                                        constraints: *constraints,
×
607
                                    };
×
608
                                    profile.compute_cache();
×
609
                                    if error_correction {
×
610
                                        let final_pos = profile.s_i7(&profile.i7_end());
×
611
                                        let delta_e = profile.q1 - final_pos;
×
612
                                        profile
×
613
                                            .extend(delta_e)
×
NEW
614
                                            .map_err(|_| processing::CodeExecutionFailure::ERR)?;
×
615
                                    }
×
616
                                    Ok(profile)
×
617
                                };
618
                            }
7,828,382✔
619
                            /*
620
                            let tv_excess = times.t_v.abs();
621
                            if tv_excess > times.t_a + times.t_d {
622
                                panic!("Unable to handle this")
623
                            }
624
                            let ta = times.t_a * Real::from_f32(0.1);
625
                            let td = times.t_d * Real::from_f32(0.1);
626
                            let tj1 = times.t_j1 * Real::from_f32(0.1);
627
                            let tj2 = times.t_j2 * Real::from_f32(0.1);
628
                            let a_lim_a = constraints.j_max * tj1;
629
                            let a_lim_d = constraints.j_max * tj2;
630
                            let v_lim0 = v_0 + (ta - tj1) * a_lim_a;
631
                            let v_lim1 = v_1 + (td - tj2) * a_lim_d;
632
                            v_max = v_lim0.max(v_lim1);
633
                            let vred = prev_vmax - v_max;
634
                            if vred < Real::from_lit(1,1) {
635

636
                                let mut profile = SCurveMotionProfile {
637
                                    t_j1: times.t_j1,
638
                                    t_a: times.t_a,
639
                                    t_v: times.t_v,
640
                                    t_d: times.t_d,
641
                                    t_j2: times.t_j2,
642
                                    v_0: v_0,
643
                                    v_1: v_1,
644
                                    j_max: constraints.j_max,
645
                                    a_lim_a,
646
                                    a_lim_d,
647
                                    v_lim: v_max,
648
                                    q1: q_1,
649
                                    cache: Cache::default(),
650
                                    constraints: *constraints,
651
                                };
652
                                cfg_if::cfg_if! {
653
                                    if #[cfg(feature="verbose-timings")] {
654
                                        hwa::debug!("Motion plan computed in {} us", _t0.elapsed().as_micros());
655
                                    }
656
                                }
657
                                profile.compute_cache();
658
                                if error_correction {
659
                                    let final_pos = profile.s_i7(&profile.i7_end());
660
                                    let delta_e = profile.q1 - final_pos;
661
                                    profile.extend(delta_e).map_err(|_|  processing::CodeExecutionFailure::ERR)?;
662

663
                                }
664
                                return Ok(profile);
665
                            }
666
                            else {
667
                                prev_vmax = v_max;
668
                            }
669
                            */
670

671
                            /*
672
                            let t_a = TWO * (q_1 / (v_0 + v_1));
673

674
                            // Compute the accel
675
                            let a = (v_1 - v_0) / t_a;
676
                            let t_j1 = HALF * t_a;
677

678

679
                            // Constructively define the two parabolas:
680
                            // The first one (smooth accel from v_0):
681
                            // v_{1}(t) := a_{v1} * (t - 0)^2 + v_{0}
682
                            // with a_{v_1} as:
683
                            //  a_{1} = \frac{a \frac{T}{2} - 0}{(0 - \frac{T}{2})^2}
684
                            // The second one (smooth decel to v_1); TBD
685
                            //  a_{2} = \frac{-a \frac{T}{2} - 0}{(0 - \frac{T}{2})^2}
686

687
                            let a_1 = (a * t_j1) / (t_j1 * t_j1);
688

689
                            // Finally, reformulating to meet the paper convention it is trivial to determine that:
690
                            // In \dot{q}(t) = v_{0} + j_{max} \frac{t^2}{2}:
691
                            // a_{v1} = j_max / 2, hence j_max = 2 * a_{v1}
692

693
                            let j_max = TWO * a_1;
694

695
                            let mut profile = SCurveMotionProfile {
696
                                t_j1,
697
                                t_a,
698
                                t_v: math::ZERO,
699
                                t_d: math::ZERO,
700
                                t_j2: math::ZERO,
701
                                v_0: v_0,
702
                                v_1: v_1,
703
                                j_max,
704
                                a_lim_a: math::ZERO,
705
                                a_lim_d: math::ZERO,
706
                                v_lim: v_1,
707
                                q1: q_1,
708
                                cache: Cache::default(),
709
                                constraints: *constraints,
710
                            };
711
                            cfg_if::cfg_if! {
712
                                if #[cfg(feature="verbose-timings")] {
713
                                    hwa::debug!("Motion plan computed in {} us", _t0.elapsed().as_micros());
714
                                }
715
                            }
716
                            profile.compute_cache();
717
                            if error_correction {
718
                                let final_pos = profile.s_i7(&profile.i7_end());
719
                                let delta_e = profile.q1 - final_pos;
720
                                profile.extend(delta_e).map_err(|_|  processing::CodeExecutionFailure::ERR)?;
721

722
                            }
723
                            return Ok(profile);
724

725
                             */
726
                        } else {
727
                            // H
728
                            hwa::debug!(
12✔
729
                                "OK at {:?} -> ta = {:?} td = {:?} tv = {:?}",
×
730
                                v_max,
731
                                times.t_a,
732
                                times.t_d,
733
                                times.t_v
734
                            );
735
                            let a_lim_a = constraints.j_max * times.t_j1;
12✔
736
                            let a_lim_d = constraints.j_max * times.t_j2;
12✔
737
                            let v_lim = v_0 + (times.t_a - times.t_j1) * a_lim_a;
12✔
738
                            let t = times.t_a + times.t_v + times.t_d;
12✔
739
                            let mut profile = SCurveMotionProfile {
12✔
740
                                t_j1: times.t_j1,
12✔
741
                                t_a: times.t_a,
12✔
742
                                t_v: times.t_v,
12✔
743
                                t_d: times.t_d,
12✔
744
                                t_j2: times.t_j2,
12✔
745
                                v_0,
12✔
746
                                v_1,
12✔
747
                                j_max: constraints.j_max,
12✔
748
                                a_lim_a,
12✔
749
                                a_lim_d,
12✔
750
                                v_lim,
12✔
751
                                q1: q_1,
12✔
752
                                cache: Cache::default(),
12✔
753
                                constraints: *constraints,
12✔
754
                            };
12✔
755
                            profile.compute_cache();
12✔
756
                            if error_correction {
12✔
757
                                let final_pos = profile.s_i7(&profile.i7_end());
6✔
758
                                let delta_e = profile.q1 - final_pos;
6✔
759
                                profile
6✔
760
                                    .extend(delta_e)
6✔
761
                                    .map_err(|_| processing::CodeExecutionFailure::ERR)?;
6✔
762
                            }
6✔
763
                            return Ok(profile);
12✔
764
                        }
765
                    }
766
                    Err(_e) => return Err(_e),
×
767
                }
768
            }
769
        }
770
    }
414,889✔
771

772
    /// Procedure:
773
    ///
774
    /// Assuming that `v_vmax` and `a_max` are reached (*case_1*) compute the time intervals:
775
    ///
776
    /// For T_j1, T_a:
777
    /// <p>
778
    /// \[
779
    /// \begin{align}
780
    ///   (v_{max} - v_{0}) j_{max} < a_{max}^2 & \implies a_{max} \text{ is not reached} \implies
781
    /// T_{j1} & = \sqrt{ \frac{v_{max} - v_{0}}{j_{max}} }\\
782
    /// & & T_{a} & = 2 T_{j1} \\
783
    /// \text{ otherwise} & \implies a_{max} \text{ is reached} \implies T_{j1} & = \frac{a_{max}}{j_{max}} \\
784
    /// & & T_{a} = T_{j1} + \frac{v_{max} - v_{0}}{a_{max}}
785
    /// \end{align}
786
    /// \]
787
    /// </p>
788
    ///
789
    /// For T_j2, T_d:
790
    ///
791
    /// $$
792
    ///   (v_{max} - v_{1}) j_{max} < a_{max}^2 \implies a_{min} \text{ is not reached} \implies T_{j2} = \sqrt{ \frac{v_{max} - v_{1}}{j_{max}} }, T_{d} = 2 T_{j2}
793
    /// $$
794
    ///
795
    /// $$
796
    ///   \text{ otherwise} \implies a_{min} \text{ is reached} \implies T_{j2} = \frac{a_{max}}{j_{max}}, T_{d} = T_{j2} + \frac{v_{max} - v_{1}}{a_{max}}
797
    /// $$
798
    fn compute_case_1(
8,064,708✔
799
        q_1: Real,
8,064,708✔
800
        v_0: Real,
8,064,708✔
801
        v_1: Real,
8,064,708✔
802
        v_max: Real,
8,064,708✔
803
        a_max: Real,
8,064,708✔
804
        j_max: Real,
8,064,708✔
805
        aj_ratio: Real,
8,064,708✔
806
        amax_squared: Real,
8,064,708✔
807
    ) -> Result<Times, processing::CodeExecutionFailure> {
8,064,708✔
808
        #[cfg(feature = "debug-motion-planning")]
8,064,708✔
809
        hwa::info!(
8,064,708✔
810
            "[SCurveMotionProfile] [compute_case_1] with aj_ratio {:?}",
8,064,708✔
811
            aj_ratio
8,064,708✔
812
        );
8,064,708✔
813
        let a_max_not_reached = (v_max - v_0) * j_max < amax_squared;
8,064,708✔
814
        #[cfg(feature = "debug-motion-planning")]
8,064,708✔
815
        hwa::info!(
8,064,708✔
816
            "[SCurveMotionProfile] [compute_case_1] a_max is {}",
8,064,708✔
817
            if a_max_not_reached {
8,064,708✔
818
                "not reached"
8,064,708✔
819
            } else {
8,064,708✔
820
                "reached"
8,064,708✔
821
            }
8,064,708✔
822
        );
8,064,708✔
823
        let a_min_not_reached = (v_max - v_1) * j_max < amax_squared;
8,064,708✔
824
        #[cfg(feature = "debug-motion-planning")]
825
        hwa::info!(
826
            "[SCurveMotionProfile] [compute_case_1] a_min is {}",
827
            if a_min_not_reached {
828
                "not reached"
829
            } else {
830
                "reached"
831
            }
832
        );
833

834
        let j_max_inv = if a_max_not_reached || a_min_not_reached {
8,064,708✔
835
            j_max.recip()
1,441,599✔
836
        } else {
837
            math::ZERO
6,623,109✔
838
        };
839

840
        let a_max_inv = if !a_max_not_reached || !a_min_not_reached {
8,064,708✔
841
            a_max.recip()
6,623,762✔
842
        } else {
843
            math::ZERO
1,440,946✔
844
        };
845

846
        let (t_j1, t_a) = if a_max_not_reached {
8,064,708✔
847
            let t_j = ((v_max - v_0).max(math::ZERO) * j_max_inv)
1,441,267✔
848
                .sqrt()
1,441,267✔
849
                .ok_or(processing::CodeExecutionFailure::NumericalError)?;
1,441,267✔
850
            (t_j, t_j + t_j)
1,441,267✔
851
        } else {
852
            let t_j = aj_ratio;
6,623,441✔
853
            (t_j, t_j + (v_max - v_0).max(math::ZERO) * a_max_inv)
6,623,441✔
854
        };
855
        let (t_j2, t_d) = if a_min_not_reached {
8,064,708✔
856
            let t_j = ((v_max - v_1).max(math::ZERO) * j_max_inv)
1,441,278✔
857
                .sqrt()
1,441,278✔
858
                .ok_or(processing::CodeExecutionFailure::NumericalError)?;
1,441,278✔
859
            (t_j, t_j + t_j)
1,441,278✔
860
        } else {
861
            let t_j = aj_ratio;
6,623,430✔
862
            (t_j, t_j + (v_max - v_1).max(math::ZERO) * a_max_inv)
6,623,430✔
863
        };
864

865
        // Efficiently compute: (q_1 / v_max) - (t_a / 2) * (1 + (v_0 / v_max)) - (t_d / 2) * (1 + (v_1 / v_max))
866
        // ... with maximum possible numerical precision
867

868
        let v_max_inv = v_max.recip();
8,064,708✔
869
        let half_ta = t_a * HALF;
8,064,708✔
870
        let term1 = half_ta + half_ta * (v_0 * v_max_inv);
8,064,708✔
871
        let half_td = t_d * HALF;
8,064,708✔
872
        let term2 = half_td + half_td * (v_1 * v_max_inv);
8,064,708✔
873

8,064,708✔
874
        let t_v = (q_1 * v_max_inv) - term1 - term2;
8,064,708✔
875
        Ok(Times {
8,064,708✔
876
            t_j1,
8,064,708✔
877
            t_a,
8,064,708✔
878
            t_v,
8,064,708✔
879
            t_j2,
8,064,708✔
880
            t_d,
8,064,708✔
881
        })
8,064,708✔
882
    }
8,064,708✔
883

884
    pub fn params_dump(&self) {
6✔
885
        hwa::debug!(
6✔
886
            "Params:\nq_{{1}} = {:?}\nv_{{0}} = {:?}\nv_{{1}} = {:?}\nv_{{max}} = {:?}\na_{{max}} = {:?}\nj_{{max}} = {:?}\nT_{{j1}} = {:?}\nT_{{a}} = {:?}\nT_{{v}} = {:?}\nT_{{j2}} = {:?}\nT_{{d}} = {:?}\na_{{lima}} = {:?}\na_{{limd}} = {:?}\nv_{{lim}} = {:?}",
×
887
            self.q1,
888
            self.v_0,
889
            self.v_1,
890
            self.constraints.v_max,
891
            self.constraints.a_max,
892
            self.constraints.j_max,
893
            self.t_j1,
894
            self.t_a,
895
            self.t_v,
896
            self.t_j2,
897
            self.t_d,
898
            self.a_lim_a,
899
            self.a_lim_d,
900
            self.v_lim,
901
        );
902

903
        hwa::debug!("V_{{1}}(t) = v_{{0}} + j_{{max}} * \\frac{{t^2}}{{2}}");
6✔
904
        hwa::debug!("V_{{2}}(t) = v_{{0}} + a_{{lima}} * (t - \\frac{{T_{{j1}}}}{{2}})");
6✔
905
        hwa::debug!("V_{{3}}(t) = v_{{lim}} - j_{{max}} * \\frac{{(T_{{a}} - t)^2}}{{2}}");
6✔
906
        hwa::debug!("V_{{4}}(t) = v_{{lim}}");
6✔
907
        hwa::debug!(
6✔
908
            "V_{{5}}(t) = v_{{lim}} - j_{{max}} * \\frac{{(t - T_{{a}} - T_{{v}})^2}}{{2}}"
×
909
        );
910
        hwa::debug!(
6✔
911
            "V_{{6}}(t) = v_{{lim}} - a_{{limd}} * (t - T_{{a}} - T_{{v}} - \\frac{{T_{{j2}}}}{{2}})"
×
912
        );
913
        hwa::debug!(
6✔
914
            "V_{{7}}(t) = v_{{1}} + j_{{max}} * \\frac{{(t - T_{{a}} - T_{{v}} - T_{{d}})^2}}{{2}}"
×
915
        );
916
        hwa::debug!(
6✔
917
            "V(t) = if( t < 0, v_{{0}}, if( 0 <= t < T_{{j1}}, V_{{1}}(t), if( T_{{j1}} <= t < T_{{a}} - T_{{j1}}, V_{{2}}(t), if( T_{{a}} - T_{{j1}} <= t < T_{{a}}, V_{{3}}(t), if( T_{{a}} <= t < T_{{a}} + T_{{v}}, V_{{4}}(t), if( T_{{a}} + T_{{v}} <= t < T_{{a}} + T_{{v}} + T_{{j2}}, V_{{5}}(t), if( T_{{a}} + T_{{v}} + T_{{j2}} <= t < T_{{a}} + T_{{v}} + T_{{d}} - T_{{j2}}, V_{{6}}(t), if( T_{{a}} + T_{{v}} + T_{{d}} - T_{{j2}} <= t < T_{{a}} + T_{{v}} + T_{{d}}, V_{{7}}(t), v_{{1}} ) ) ) ) ) ) ) )"
×
918
        );
919
        hwa::debug!(
6✔
920
            "Points:\nP_{{j1a}}=(T_{{j1}}, 0)\nP_{{j1d}}=(T_{{a}} - T_{{j1}}, 0)\nP_{{a}}=(T_{{a}}, 0)\nP_{{v}}=(T_{{a}} + T_{{v}}, 0)\nP_{{v}}=(T_{{a}} + T_{{v}}, 0)\nP_{{j2a}}=(T_{{a}} + T_{{v}} + T_{{j2}}, 0)\nP_{{j2d}}=(T_{{a}} + T_{{v}} + T_{{d}} - T_{{j2}}, 0)\nP_{{j2a}}=(T_{{a}} + T_{{v}} + T_{{d}}, 0)"
×
921
        );
922
        hwa::debug!("s_i7 = {:?}", self.s_i7(&self.i7_end()));
6✔
923
        hwa::debug!("--");
6✔
924
    }
6✔
925

926
    /// The time at the start of 1st interval:
927
    /// \[ 0, T_{j1}\]
928
    #[inline]
929
    pub fn i1_start(&self) -> Real {
4,265,497✔
930
        Real::zero()
4,265,497✔
931
    }
4,265,497✔
932

933
    /// The time at the end of 1st interval:
934
    /// \[ 0, T_{j1}\]
935
    #[inline]
936
    pub fn i1_end(&self) -> Real {
3,771,305✔
937
        self.i2_start()
3,771,305✔
938
    }
3,771,305✔
939

940
    /// The time at the start of 2nd interval:
941
    /// \[ T_{j1}, T_{a} - T_{j1}\]
942
    #[inline]
943
    pub fn i2_start(&self) -> Real {
8,586,689✔
944
        self.t_j1
8,586,689✔
945
    }
8,586,689✔
946

947
    /// The time at the end of 2nd interval:
948
    /// \[ T_{j1}, T_{a} - T_{j1}\]
949
    #[inline]
950
    pub fn i2_end(&self) -> Real {
3,277,113✔
951
        self.i3_start()
3,277,113✔
952
    }
3,277,113✔
953

954
    /// The time at the start of 3rd interval:
955
    /// \[ T_{a} - T_{j1}, T_{a}\]
956
    #[inline]
957
    pub fn i3_start(&self) -> Real {
6,795,494✔
958
        self.t_a - self.t_j1
6,795,494✔
959
    }
6,795,494✔
960

961
    /// The time at the end of 3rd interval:
962
    /// \[ T_{a} - T_{j1}, T_{a}\]
963
    #[inline]
964
    pub fn i3_end(&self) -> Real {
2,836,056✔
965
        self.i4_start()
2,836,056✔
966
    }
2,836,056✔
967

968
    /// The time at the start of 4th interval:
969
    /// \[T_a, T_a + T_v\]
970
    #[inline]
971
    pub fn i4_start(&self) -> Real {
5,856,671✔
972
        self.t_a
5,856,671✔
973
    }
5,856,671✔
974

975
    /// The time at the end of 4th interval:
976
    /// \[T_a, T_a + T_v\]
977
    #[inline]
978
    pub fn i4_end(&self) -> Real {
2,153,731✔
979
        self.i5_start()
2,153,731✔
980
    }
2,153,731✔
981

982
    /// The time at the start of 5th interval:
983
    /// \[T_a + T_v, T - T_d + T_j2\]
984
    ///
985
    /// \[T_a + T_v, T_a + T_v + T_j2\]
986
    #[inline]
987
    pub fn i5_start(&self) -> Real {
4,759,457✔
988
        self.t_a + self.t_v
4,759,457✔
989
    }
4,759,457✔
990

991
    /// The time at the end of 5th interval:
992
    /// \[T_a + T_v, T - T_d + T_j2\]
993
    ///
994
    /// \[T_a + T_v, T_a + T_v + T_j2\]
995
    #[inline]
996
    pub fn i5_end(&self) -> Real {
2,153,731✔
997
        self.i6_start()
2,153,731✔
998
    }
2,153,731✔
999

1000
    /// The time at the start of 6th interval:
1001
    /// \[T - T_{d} + T_{j2}, T - T_{j2}\]
1002
    ///
1003
    /// \[T_{a} + T_{v} + T_{j2}, T_{a} + T_{v} + T_{d} - T_{j2}\]
1004
    #[inline]
1005
    pub fn i6_start(&self) -> Real {
5,557,215✔
1006
        self.t_a + self.t_v + self.t_j2
5,557,215✔
1007
    }
5,557,215✔
1008

1009
    /// The time at the end of 7th interval:
1010
    /// \[T - T_d + T_j2, T - T_j2\]
1011
    ///
1012
    /// \[T_a + T_v + T_j2, T_a + T_v + T_d - T_j2\]
1013
    #[inline]
1014
    pub fn i6_end(&self) -> Real {
1,701,736✔
1015
        self.i7_start()
1,701,736✔
1016
    }
1,701,736✔
1017

1018
    /// The time at the start of 7th interval:
1019
    /// \[T - T_j2, T\]
1020
    ///
1021
    /// \[T_a + T_v + T_d - T_j2, T_a + T_v + T_d\]
1022
    #[inline]
1023
    pub fn i7_start(&self) -> Real {
3,584,199✔
1024
        self.t_a + self.t_v + self.t_d - self.t_j2
3,584,199✔
1025
    }
3,584,199✔
1026

1027
    /// The time at the end of 7th interval:
1028
    /// \[T - T_j2, T\]
1029
    ///
1030
    /// \[T_a + T_v + T_d - T_j2, T_a + T_v + T_d\]
1031
    #[inline]
1032
    pub fn i7_end(&self) -> Real {
5,148,848✔
1033
        self.t_a + self.t_v + self.t_d
5,148,848✔
1034
    }
5,148,848✔
1035

1036
    /// Compute intermediate piecewise function points to speedup equations
1037
    /// A max between previous segment max pos and next one is applied to guarantee consistency
1038
    /// when the move is (pseudo)triangular, given that position can never decrease
1039
    fn compute_cache(&mut self) -> Result<(), processing::CodeExecutionFailure> {
414,889✔
1040
        if self.v_lim.is_negligible() {
414,889✔
NEW
1041
            return Err(processing::CodeExecutionFailure::NumericalError);
×
1042
        }
414,889✔
1043
        self.cache.s1_pt = self.s_i1(&self.i1_end());
414,889✔
1044
        self.cache.s2_pt = self.cache.s1_pt.max(self.s_i2(&self.i2_end()));
414,889✔
1045
        self.cache.s3_pt = self.cache.s2_pt.max(self.s_i3(&self.i3_end()));
414,889✔
1046
        self.cache.s4_pt = self.cache.s3_pt.max(self.s_i4(&self.i4_end()));
414,889✔
1047
        self.cache.s5_pt = self.cache.s4_pt.max(self.s_i5(&self.i5_end()));
414,889✔
1048
        self.cache.s6_pt = self.cache.s5_pt.max(self.s_i6(&self.i6_end()));
414,889✔
1049
        self.cache.s7_pt = self.cache.s6_pt.max(self.s_i7(&self.i7_end()));
414,889✔
1050
        Ok(())
414,889✔
1051
    }
414,889✔
1052

1053
    pub fn extend(&mut self, delta_e: Real) -> Result<(), ()> {
18✔
1054
        if self.v_lim > Real::epsilon() {
18✔
1055
            let extra_time = delta_e / self.v_lim;
18✔
1056
            if self.t_v + extra_time > math::ZERO {
18✔
1057
                self.t_v += extra_time;
18✔
1058
                self.cache.s4_pt += delta_e;
18✔
1059
                self.cache.s5_pt += delta_e;
18✔
1060
                self.cache.s6_pt += delta_e;
18✔
1061
                self.cache.s7_pt += delta_e;
18✔
1062
            }
18✔
1063
        }
×
1064

1065
        Ok(())
18✔
1066
    }
18✔
1067

1068
    /// Acceleration phase, jerk limited acceleration
1069
    /// <p>
1070
    /// \[
1071
    /// \begin{align}{l}
1072
    /// v_{i1}(t) &= \frac{j_{max}t^2}{2}+v_{0} \\
1073
    /// s_{i1}(t) &= \int{v_{i1}(t)dt} \\
1074
    /// s_{i1}(t)_{|t>\delta} &= \frac{j_{max} (t-\delta)^3}{6} + v_{0} (t-\delta)
1075
    /// \end{align}
1076
    /// \]
1077
    /// </p>
1078
    ///
1079
    pub fn s_i1(&self, t: &Real) -> Real {
909,081✔
1080
        let dt = (*t) - self.i1_start();
909,081✔
1081
        (self.j_max * SIXTH * dt * dt * dt) + (self.v_0 * dt)
909,081✔
1082
    }
909,081✔
1083

1084
    /// Acceleration phase, constant acceleration
1085
    ///
1086
    /// $$ v_{i2}(t)_{|t>\delta} = j_{max} T_{j1} t + v_{i1}(\delta) $$
1087
    ///
1088
    /// $$ s_{i2}(t) = \int{v_{i2}(t)dt} $$
1089
    ///
1090
    /// $$ s_{i2}(t)_{|t>\delta} = \frac{j_{max} T_{j1} (t - \delta)^2}{2} + v_{i1}(\delta)(t-\delta) + s_{i1}(\delta) $$
1091
    ///
1092
    pub fn s_i2(&self, t: &Real) -> Real {
1,953,160✔
1093
        let dt = (*t) - self.i2_start();
1,953,160✔
1094
        (self.j_max * HALF * self.t_j1 * dt) * (dt + self.t_j1) + self.v_0 * dt + self.cache.s1_pt
1,953,160✔
1095
    }
1,953,160✔
1096

1097
    /// Acceleration phase, jerk limited deceleration
1098
    ///
1099
    /// $$ v_{i3}(t)(t)_{|t>\delta} = v_{i2}(t) + v_{0} - j_{max} T_{j1} (t-\delta) $$
1100
    ///
1101
    /// $$ s_{i3}(t) = \int{v_{i2}(t) + v_0 dt} - \int{v_{i3}(t)dt} $$
1102
    pub fn s_i3(&self, t: &Real) -> Real {
1,097,214✔
1103
        let dt = (*t) - self.i3_start();
1,097,214✔
1104
        self.s_i2(t) - ((self.j_max * SIXTH * dt) * (dt * dt))
1,097,214✔
1105
    }
1,097,214✔
1106

1107
    /// Constant velocity
1108
    ///
1109
    /// $$ v_{i4}(t)(t)_{|t>\delta} = v_{lim} $$
1110
    ///
1111
    /// $$ s_{i4}(t) = s_{i3}(\delta) + \int{v_{i3}(t)dt} $$
1112
    pub fn s_i4(&self, t: &Real) -> Real {
1,281,773✔
1113
        let dt = (*t) - self.i4_start();
1,281,773✔
1114
        self.cache.s3_pt + (self.v_lim * dt)
1,281,773✔
1115
    }
1,281,773✔
1116

1117
    /// Deceleration phase, jerk limited deceleration
1118
    ///
1119
    /// Same as s_{í1}(t)
1120
    ///
1121
    /// $$ v_{i5}(t) = \frac{j_{max}t^2}{2}+v_{0} $$
1122
    ///
1123
    /// $$ s_{i5}(t) = s_i4(t) - \int{v_{i5}(t)dt} $$
1124
    ///
1125
    /// $$ s_{i5}(t)_{|t>\delta} = s_{i4}(t) - \frac{j_{max} (t-\delta)^3}{6} + v_{0} (t-\delta) $$
1126
    pub fn s_i5(&self, t: &Real) -> Real {
866,884✔
1127
        let dt = (*t) - self.i5_start();
866,884✔
1128
        let r = (self.j_max * SIXTH * dt * dt * dt);
866,884✔
1129
        self.s_i4(t) - r
866,884✔
1130
    }
866,884✔
1131

1132
    /// Deceleration phase, constant deceleration
1133
    ///
1134
    /// Same as s_{í2}(t)
1135
    ///
1136
    /// $$ v_{i6}(t)_{|t>\delta} = j_{max} T_{j1} t + v_{i5}(\delta) $$
1137
    ///
1138
    /// $$ s_{i2}(t) = \int{v_{i2}(t)dt} + s_{i5}(\delta) $$
1139
    ///
1140
    /// $$ s_{i5}(t)_{|t>\delta} = -\frac{j_{max} T_{j1} (t - \delta)^2}{2} + v_{i5}(\delta)(t-\delta) + s_{i5}(\delta) $$
1141
    ///
1142
    pub fn s_i6(&self, t: &Real) -> Real {
2,116,637✔
1143
        let dt = (*t) - self.i6_start();
2,116,637✔
1144
        let mhj2 = -self.j_max * HALF * self.t_j2;
2,116,637✔
1145
        (dt * (mhj2 * dt)) + (((mhj2 * self.t_j2) + self.v_lim) * dt) + self.cache.s5_pt
2,116,637✔
1146
    }
2,116,637✔
1147

1148
    /// Deceleration phase, jerk limited acceleration
1149
    ///
1150
    /// $$ v_{i3}(t)(t)_{|t>\delta} = v_{i2}(t) + v_{0} - j_{max} T_{j1} (t-\delta) $$
1151
    ///
1152
    /// $$ s_{i3}(t) = \int{v_{i2}(t) + v_0 dt} - \int{v_{i3}(t)dt} $$
1153
    pub fn s_i7(&self, t: &Real) -> Real {
1,148,682✔
1154
        let dt = (*t) - self.i7_start();
1,148,682✔
1155
        self.s_i6(t) + ((self.j_max * SIXTH * dt) * dt * dt)
1,148,682✔
1156
    }
1,148,682✔
1157

1158
    /// Constant (exit) speed at the end
1159
    pub fn s_i8(&self, t: &Real) -> Real {
×
1160
        let dt = (*t) - self.i7_end();
×
1161
        self.cache.s7_pt + (self.v_1 * dt)
×
1162
    }
×
1163
}
1164

1165
impl motion::MotionProfile for SCurveMotionProfile {
1166
    #[inline(always)]
1167
    fn end_time(&self) -> Real {
4,000,160✔
1168
        self.i7_end()
4,000,160✔
1169
    }
4,000,160✔
1170
    #[inline(always)]
1171
    fn end_pos(&self) -> Real {
3,356,416✔
1172
        self.q1
3,356,416✔
1173
    }
3,356,416✔
1174

1175
    /// Computes the position in [hwa::HwiContract::SPACE_UNIT_MAGNITUDE] at given relative instant (uSecs)
1176
    /// * p_{1}(t) = if( 0 <= t < T_{j1} , s_{i1}(t) ) | \[0, T_{j1} \]
1177
    /// * p_{2}(t) = if( T_{j1} <= t < T_{a} - T_{j1}, s_{i2}(t) ) | \[ T_{j1}, T_{a} - T_{j1} \]
1178
    /// * p_{3}(t) = if( T_{a} - T_{j1} <= t < T_{a}, s_{i3}(t) ) | \[ T_{a} - T_{j1}, T_{a} \]
1179
    /// * p_{4}(t) = if( T_{a} <= t < T_{a} + T_{v}, s_{i4}(t) ) | \[ T_{a}, T_{a} + T_{v} \]
1180
    /// * p_{5}(t) = if( T_{a} + T_{v} <= t < T - T_{d} + T_{j2}, s_{i5}(t) ) | \[ T_{a} + T_{v}, T - T_{d} + T_{j2} \]
1181
    /// * p_{6}(t) = if( T_{a} + T_{v} + T_{j2} <= t < T_{a} + T_{v} + T_{d} - T_{j2}, s_{i6}(t) ) | \[ T_{a} + T_{v} + T_{j2}, T_{a} + T_{v} + T_{d} - T_{j2} \]
1182
    /// * p_{7}(t) = if( T_{a} + T_{v} <= t < T - T_{d} + T_{j2}, s_{i5}(t) ) | \[ T_{a} + T_{v}, T - T_{d} + T_{j2} \]
1183
    fn eval_position(&self, t: Real) -> Option<Real> {
3,356,422✔
1184
        if t < math::ZERO {
3,356,422✔
1185
            None
6✔
1186
        } else if t >= self.i1_start() && t < self.i1_end() {
3,356,416✔
1187
            Some(self.s_i1(&t))
494,192✔
1188
        } else if t >= self.i2_start() && t < self.i2_end() {
2,862,224✔
1189
            Some(self.s_i2(&t))
441,057✔
1190
        } else if t >= self.i3_start() && t < self.i3_end() {
2,421,167✔
1191
            Some(self.s_i3(&t))
682,325✔
1192
        } else if t >= self.i4_start() && t < self.i4_end() {
1,738,842✔
1193
            Some(self.s_i4(&t))
×
1194
        } else if t >= self.i5_start() && t < self.i5_end() {
1,738,842✔
1195
            Some(self.s_i5(&t))
451,995✔
1196
        } else if t >= self.i6_start() && t < self.i6_end() {
1,286,847✔
1197
            Some(self.s_i6(&t))
553,066✔
1198
        } else if t >= self.i7_start() && t <= self.i7_end() {
733,781✔
1199
            Some(self.s_i7(&t))
733,775✔
1200
        } else {
1201
            Some(self.q1)
6✔
1202
        }
1203
    }
3,356,422✔
1204
}
1205

1206
#[derive(Copy, Clone, Default)]
1207
pub struct Cache {
1208
    /// Position at self.s_i1(&self.i1_end())
1209
    pub s1_pt: Real,
1210

1211
    /// Position at self.s_i2(&self.i2_end())
1212
    pub s2_pt: Real,
1213

1214
    /// Position at self.s_i3(&self.i3_end())
1215
    pub s3_pt: Real,
1216

1217
    /// Position at self.s_i4(&self.i4_end())
1218
    pub s4_pt: Real,
1219

1220
    /// Position at self.s_i5(&self.i5_end())
1221
    pub s5_pt: Real,
1222

1223
    /// Position at self.s_i6(&self.i6_end())
1224
    pub s6_pt: Real,
1225

1226
    /// Position at self.s_i7(&self.i7_end())
1227
    pub s7_pt: Real,
1228
}
1229

1230
#[cfg(feature = "native")]
1231
impl core::fmt::Display for SCurveMotionProfile {
1232
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
6✔
1233
        write!(
6✔
1234
            f,
6✔
1235
            "\n\tt_j1={:?}, t_a={:?}, t_v={:?}, t_d={:?}, t_j2={:?}",
6✔
1236
            self.t_j1.rdp(4),
6✔
1237
            self.t_a.rdp(4),
6✔
1238
            self.t_v.rdp(4),
6✔
1239
            self.t_d.rdp(4),
6✔
1240
            self.t_j1.rdp(4),
6✔
1241
        )?;
6✔
1242
        write!(
6✔
1243
            f,
6✔
1244
            "\n\ta_lim_a={:?}, v_lim={:?}, a_lim_d={:?}",
6✔
1245
            self.a_lim_a, self.v_lim, self.a_lim_d,
6✔
1246
        )
6✔
1247
    }
6✔
1248
}
1249

1250
#[cfg(test)]
1251
pub mod test {
1252
    //Example 3.9
1253

1254
    use crate::{hwa, motion, processing};
1255
    use hwa::math;
1256
    use motion::{Constraints, SCurveMotionProfile};
1257
    use num_traits::ToPrimitive;
1258

1259
    pub fn do_compute(
42✔
1260
        q1: f32,
42✔
1261
        v_0: f32,
42✔
1262
        v_1: f32,
42✔
1263
        v_max: f32,
42✔
1264
        a_max: f32,
42✔
1265
        j_max: f32,
42✔
1266
        error_correction: bool,
42✔
1267
    ) -> Result<SCurveMotionProfile, processing::CodeExecutionFailure> {
42✔
1268
        let constraints = Constraints {
42✔
1269
            v_max: math::Real::from_f32(v_max),
42✔
1270
            a_max: math::Real::from_f32(a_max),
42✔
1271
            j_max: math::Real::from_f32(j_max),
42✔
1272
        };
42✔
1273
        SCurveMotionProfile::compute(
42✔
1274
            math::Real::from_f32(q1),
42✔
1275
            math::Real::from_f32(v_0),
42✔
1276
            math::Real::from_f32(v_1),
42✔
1277
            &constraints,
42✔
1278
            error_correction,
42✔
1279
        )
42✔
1280
    }
42✔
1281

1282
    fn approx_equal(what: &str, v: math::Real, expected: f32, tolerance: f32) {
210✔
1283
        let v1 = v.to_f64().to_f32().unwrap();
210✔
1284
        let ok = (v1 - expected).abs() < tolerance;
210✔
1285
        assert!(ok, "{} = {} but should be = {}", what, v1, expected);
210✔
1286
    }
210✔
1287

1288
    fn init_logger() {
6✔
1289
        cfg_if::cfg_if! {
1290
            if #[cfg(feature = "with-log")] {
1291
                use std::io::Write;
1292
                let env = env_logger::Env::new().default_filter_or("info");
6✔
1293
                let _ = env_logger::builder()
6✔
1294
                    .parse_env(env)
6✔
1295
                    .format(|buf, record| {
1,174✔
1296
                        writeln!(buf, "{}: {}", record.level(), record.args())
1,174✔
1297
                    })
1,174✔
1298
                    .try_init();
6✔
1299
            }
6✔
1300
        }
6✔
1301
    }
6✔
1302

1303
    #[test]
1304
    fn ex_3_9() {
6✔
1305
        // With: q_1 = 10, v_0 = 1, v_1 = 0
6✔
1306
        // Given: v_max = 5, a_max = 10, j_max = 30
6✔
1307
        // Exp: T_a = 0.7333, T_v = 1.1433, T_d = 0.8333, T_j1 = 0.3333, T_j2 = 0.3333
6✔
1308
        let r = do_compute(10., 1., 0., 5., 10., 30., false).unwrap();
6✔
1309

6✔
1310
        approx_equal("T_a", r.t_a, 0.7333, 0.001);
6✔
1311
        approx_equal("T_d", r.t_d, 0.8333, 0.001);
6✔
1312
        approx_equal("T_j1", r.t_j1, 0.3333, 0.001);
6✔
1313
        approx_equal("T_j2", r.t_j2, 0.3333, 0.001);
6✔
1314
        approx_equal("T_v", r.t_v, 1.1433, 0.001);
6✔
1315

6✔
1316
        let r = do_compute(10., 1., 0., 5., 10., 30., true).unwrap();
6✔
1317

6✔
1318
        approx_equal("T_a", r.t_a, 0.7333, 0.001);
6✔
1319
        approx_equal("T_d", r.t_d, 0.8333, 0.001);
6✔
1320
        approx_equal("T_j1", r.t_j1, 0.3333, 0.001);
6✔
1321
        approx_equal("T_j2", r.t_j2, 0.3333, 0.001);
6✔
1322
        approx_equal("T_v", r.t_v, 1.1433, 0.001);
6✔
1323
    }
6✔
1324

1325
    #[test]
1326
    fn ex_3_10() {
6✔
1327
        // With: q_1 = 10, v_0 = 1, v_1 = 0
6✔
1328
        // Given: v_max = 10, a_max = 10, j_max = 30
6✔
1329
        // Exp: Ta = 1.0747, T_v = 0.0, T_d = 1.1747, T_j1 = 0.3333, T_j2 = 0.3333, vlim = 8.4136
6✔
1330
        let r = do_compute(10., 1., 0., 10., 10., 30., false).unwrap();
6✔
1331

6✔
1332
        approx_equal("T_a", r.t_a, 1.0747, 0.001);
6✔
1333
        approx_equal("T_v", r.t_v, 0.0, 0.001);
6✔
1334
        approx_equal("T_d", r.t_d, 1.1747, 0.001);
6✔
1335
        approx_equal("T_j1", r.t_j1, 0.3333, 0.001);
6✔
1336
        approx_equal("T_j2", r.t_j2, 0.3333, 0.001);
6✔
1337
        approx_equal("v_lim", r.v_lim, 8.4136, 0.001);
6✔
1338
    }
6✔
1339

1340
    #[test]
1341
    fn ex_3_11() {
6✔
1342
        // With: q_1 = 10, v_0 = 7, v_1 = 0
6✔
1343
        // Given: v_max = 10, a_max = 10, j_max = 30
6✔
1344
        // According to the paper is:
6✔
1345
        // Exp: Ta = 0.4666, T_v = 0.0, T_d = 1.4718, T_j1 = 0.2321, T_j2 = 0.2321, vlim = 8.6329
6✔
1346
        // But with the less costly descend:
6✔
1347
        // Exp: Ta = 0.4526, T_v = 0.0, T_d = 1.5195, T_j1 = 0.2186, T_j2 = 0.2186, vlim = 8.5347
6✔
1348
        let r = do_compute(10., 7., 0., 10., 10., 30., false).unwrap();
6✔
1349

6✔
1350
        approx_equal("T_a", r.t_a, 0.4526, 0.001);
6✔
1351
        approx_equal("T_v", r.t_v, 0.0, 0.001);
6✔
1352
        approx_equal("T_d", r.t_d, 1.5195, 0.001);
6✔
1353
        approx_equal("T_j1", r.t_j1, 0.2186, 0.001);
6✔
1354
        approx_equal("T_j2", r.t_j2, 0.2186, 0.001);
6✔
1355
        approx_equal("vlim", r.v_lim, 8.5347, 0.001);
6✔
1356
    }
6✔
1357

1358
    #[test]
1359
    fn ex_3_12() {
6✔
1360
        // With: q_1 = 10, v_0 = 7.5, v_1 = 0
6✔
1361
        // Given: v_max = 10, a_max = 10, j_max = 30
6✔
1362
        // Exp: Ta = 0.0, T_v = 0.0, T_d = 2.6667, T_j1 = 0.0, T_j2 = 0.0973, vlim = 7.5
6✔
1363
        let r = do_compute(10., 7.5, 0., 10., 10., 30., false).unwrap();
6✔
1364

6✔
1365
        approx_equal("T_a", r.t_a, 0.0, 0.01);
6✔
1366
        approx_equal("T_v", r.t_v, 0.0, 0.001);
6✔
1367
        approx_equal("T_d", r.t_d, 2.6667, 0.01);
6✔
1368
        approx_equal("T_j1", r.t_j1, 0.0, 0.01);
6✔
1369
        approx_equal("T_j2", r.t_j2, 0.0973, 0.01);
6✔
1370
        approx_equal("v_lim", r.v_lim, 7.5, 0.01);
6✔
1371
    }
6✔
1372

1373
    #[test]
1374
    fn ex_3_13() {
6✔
1375
        init_logger();
6✔
1376
        // With: q_1 = 10, v_0 = 0, v_1 = 0
6✔
1377
        // Given: v_max = 10, a_max = 20, j_max = 30
6✔
1378
        // Exp: Ta = 1.1006, T_v = 0.0, T_d = 1.1006, T_j1 = 0.5503, T_j2 = 0.5503, vlim = 9.0826
6✔
1379
        let r = do_compute(10., 0.0, 0., 10., 20., 30., false).unwrap();
6✔
1380

6✔
1381
        r.params_dump();
6✔
1382
        hwa::info!("profile: {}", r);
6✔
1383
        hwa::info!("constraints: {:?}", r.constraints);
6✔
1384

1385
        approx_equal("T_a", r.t_a, 1.1006, 0.01);
6✔
1386
        approx_equal("T_v", r.t_v, 0.0, 0.001);
6✔
1387
        approx_equal("T_d", r.t_d, 1.1006, 0.01);
6✔
1388
        approx_equal("T_j1", r.t_j1, 0.5333, 0.01);
6✔
1389
        approx_equal("T_j2", r.t_j2, 0.5333, 0.01);
6✔
1390
        approx_equal("v_lim", r.v_lim, 9.0826, 0.01);
6✔
1391
    }
6✔
1392

1393
    #[test]
1394
    fn test_limits() {
6✔
1395
        use crate::motion::MotionProfile;
1396
        let r = do_compute(10., 1., 0., 10., 10., 30., false).unwrap();
6✔
1397

6✔
1398
        assert_eq!(
6✔
1399
            MotionProfile::eval_position(&r, math::ZERO - math::ONE),
6✔
1400
            None
6✔
1401
        );
6✔
1402

1403
        let pos = MotionProfile::eval_position(&r, math::ONE_HUNDRED);
6✔
1404
        approx_equal("pos", pos.unwrap(), 10.0, 0.0001);
6✔
1405
    }
6✔
1406

1407
    #[test]
1408
    fn test_bad_args() {
6✔
1409
        //TODO if constraints.v_max.is_negligible()
6✔
1410
        //             || constraints.a_max.is_negligible()
6✔
1411
        //             || constraints.j_max.is_negligible()
6✔
1412

6✔
1413
        // TODO: s_i8()
6✔
1414

6✔
1415
        // fn compute_cache(&mut self) -> Result<(),  processing::CodeExecutionFailure> {
6✔
1416
        //         if self.v_lim.is_negligible() {
6✔
1417
        //             return Err(CodeExecutionFailure::NumericalError);
6✔
1418
        //         }
6✔
1419
    }
6✔
1420
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc