Skip to main content

deke_linear/
constraints.rs

1use std::time::Duration;
2
3use deke_types::SRobotQ;
4
5/// Per-axis joint velocity/acceleration/jerk ceilings.
6#[derive(Clone, Debug)]
7pub struct JointLimits<const N: usize> {
8    pub v_max: SRobotQ<N, f64>,
9    pub a_max: SRobotQ<N, f64>,
10    pub j_max: SRobotQ<N, f64>,
11}
12
13impl<const N: usize> JointLimits<N> {
14    /// The same v/a/j ceiling on every axis.
15    pub fn symmetric(v: f64, a: f64, j: f64) -> Self {
16        Self {
17            v_max: SRobotQ::splat(v),
18            a_max: SRobotQ::splat(a),
19            j_max: SRobotQ::splat(j),
20        }
21    }
22}
23
24/// Cartesian TCP motion limits along the path tangent. The commanded `speed` is
25/// always set; `accel` and `jerk` are optional caps on the tangential `s̈`/`s⃛`
26/// of the tool centre point. `None` leaves a quantity bounded only by what the
27/// per-joint limits permit through the path curvature.
28#[derive(Clone, Copy, Debug)]
29pub struct TcpLimits {
30    /// Commanded constant TCP linear speed (m/s), held wherever feasible.
31    pub speed: f64,
32    /// Optional cap on tangential TCP acceleration (m/s²).
33    pub accel: Option<f64>,
34    /// Optional cap on tangential TCP jerk (m/s³).
35    pub jerk: Option<f64>,
36}
37
38impl TcpLimits {
39    /// Speed-only limits — acceleration and jerk are left to the joint ceilings.
40    pub fn speed(speed: f64) -> Self {
41        Self {
42            speed,
43            accel: None,
44            jerk: None,
45        }
46    }
47
48    /// Speed plus explicit tangential acceleration and jerk caps.
49    pub fn new(speed: f64, accel: f64, jerk: f64) -> Self {
50        Self {
51            speed,
52            accel: Some(accel),
53            jerk: Some(jerk),
54        }
55    }
56}
57
58/// How the raw Cartesian polyline is conditioned into smooth, arc-length runs.
59#[derive(Clone, Debug)]
60pub struct PathConditioning {
61    /// Turn angle (radians) above which a vertex is treated as a *sharp* corner —
62    /// the path is split there into separate runs that start/stop at rest.
63    pub sharp_corner_angle: f64,
64}
65
66impl Default for PathConditioning {
67    fn default() -> Self {
68        Self {
69            sharp_corner_angle: 30.0_f64.to_radians(),
70        }
71    }
72}
73
74/// Knobs for the branch-tracking planner (Stage B).
75#[derive(Clone, Debug)]
76pub struct PlannerOptions<const N: usize> {
77    /// Arc-length spacing (metres) at which the run is sampled and IK'd.
78    pub sample_ds: f64,
79    /// Weight on the manipulability (singularity-avoidance) node cost.
80    pub manip_weight: f64,
81    /// Absolute per-sample joint continuity guard (radians): an edge whose worst
82    /// per-axis joint jump exceeds this is a reconfiguration. Always active.
83    pub max_branch_jump: f64,
84    /// TCP speed (m/s) used by the velocity-based reconfiguration test. When
85    /// `> 0` and [`Self::joint_v_max`] is finite, an edge that would drive **any**
86    /// joint past [`Self::reconfig_vel_fraction`] of its velocity limit at this
87    /// speed is treated as a reconfiguration/discontinuity. At weld speeds this is
88    /// the signature of a singularity or wrist flip. Set `0.0` to disable.
89    pub max_velocity: f64,
90    /// Per-joint velocity ceilings for the velocity-based reconfiguration test.
91    /// `INFINITY` (the default) disables the test on that axis.
92    pub joint_v_max: SRobotQ<N, f64>,
93    /// Fraction of `joint_v_max` an edge may demand before it counts as a
94    /// reconfiguration (e.g. `0.9` = 90%).
95    pub reconfig_vel_fraction: f64,
96}
97
98impl<const N: usize> Default for PlannerOptions<N> {
99    fn default() -> Self {
100        Self {
101            sample_ds: 2e-3,
102            manip_weight: 1.0,
103            max_branch_jump: 0.6,
104            max_velocity: 0.0,
105            joint_v_max: SRobotQ::splat(f64::INFINITY),
106            reconfig_vel_fraction: 0.9,
107        }
108    }
109}
110
111/// Kinematic ceilings + the commanded constant TCP speed (Stage C).
112#[derive(Clone, Debug)]
113pub struct LinearConstraints<const N: usize> {
114    pub joint: JointLimits<N>,
115    /// Cartesian TCP limits: commanded `speed` plus optional tangential
116    /// acceleration and jerk caps (see [`TcpLimits`]).
117    pub tcp: TcpLimits,
118    /// Output trajectory sample period.
119    pub output_dt: Duration,
120    /// When `true`, the speed may only fall below the commanded `tcp.speed` during the rest
121    /// ramp at the start and end of each run. If the joint v/a/j geometry would
122    /// force a dip anywhere in a run's interior (a shallow corner or a
123    /// near-singular patch), the retime fails with [`crate::LinearError::SpeedDipRequired`]
124    /// instead of slowing down. Sharp corners are unaffected — they are already
125    /// split into separate runs whose endpoints are legitimate stops.
126    pub forbid_interior_dips: bool,
127    /// Optional joint-space chord-length spacing for natural-cubic-spline corner
128    /// smoothing of each run before retiming. `None` keeps the raw piecewise-
129    /// linear path (sharp knot corners → unbounded joint jerk on coarse inputs).
130    /// `Some(res)` interpolates the waypoints with a C² spline (zero deviation
131    /// at the waypoints) and resamples it at `res`, so the executed path has
132    /// continuous curvature and bounded joint jerk. Sharp corners are still
133    /// split into separate runs upstream, so this only rounds the coarse-
134    /// sampling artifacts of an otherwise smooth run.
135    pub corner_smoothing: Option<f64>,
136}