Skip to main content

franka/robot/target_control/
position.rs

1//! The joint position limits of the torque backend: the box a cycle's joint goal step is kept
2//! in, under the arm's velocity limit and the braking profile `E` of [`super::envelope`].
3
4pub(super) use super::envelope::VelocityLimit;
5use super::velocity::fade_start;
6use crate::rate_limiting::DELTA_T;
7
8/// Default of the distance, rad, the joint goal keeps from the joint position limits.
9pub(super) const DEFAULT_JOINT_POSITION_MARGIN: f64 = 0.05;
10
11/// The joint position limits the goal is kept inside, and how it may approach them.
12#[derive(Debug, Clone, Copy, PartialEq)]
13pub(super) struct JointLimits {
14    /// (lower, upper), rad.
15    pub position: ([f64; 7], [f64; 7]),
16    /// How far, rad, inside `position` the goal stops.
17    pub margin: f64,
18    /// `f`: the fraction of the velocity limit and of `E` the goal may move at.
19    pub fraction: f64,
20    pub velocity: VelocityLimit,
21}
22
23/// A cycle's bounds on the step `δ = q − from`, `lower ≤ 0 ≤ upper`.
24#[derive(Debug, Clone, Copy, PartialEq)]
25pub(super) struct CycleBox {
26    pub lower: [f64; 7],
27    pub upper: [f64; 7],
28    /// Per side, whether the bound is the braking envelope's rather than the velocity limit's.
29    pub position_lower: [bool; 7],
30    pub position_upper: [bool; 7],
31    /// The velocity limit's bounds alone, `−f·v·Δt` and `f·v·Δt`: the backstop's.
32    pub velocity_lower: [f64; 7],
33    pub velocity_upper: [f64; 7],
34}
35
36impl CycleBox {
37    /// The pin of joint `i` on `side` (−1 lower, 0 free, +1 upper): 0 free, ∓1 on a position
38    /// bound, ∓2 on a velocity bound.
39    pub(super) fn pin(&self, i: usize, side: i8) -> i8 {
40        let position = if side < 0 {
41            self.position_lower[i]
42        } else {
43            self.position_upper[i]
44        };
45        side.signum() * if position { 1 } else { 2 }
46    }
47}
48
49/// rad: the ramp over which a goal comes to rest on its margin. The braking box's step there is
50/// 0.9 % (joint 4) to 3.4 % (wrists) of the distance per cycle, so the rest rises by at most 0.034
51/// a cycle whatever the width; in a replayed stall 1e-5 chattered least of 1e-5 to 3e-4, and
52/// 1e-3 parks the goal short of its margin.
53const REST_BAND: f64 = 1e-5;
54
55/// rad: the band inside the margin over which the law's torque toward a joint limit fades out,
56/// from full at the margin to none at the position barrier's onset, `margin − POSITION_FADE_BAND`
57/// from the limit. Inside that onset a spring pushes the joint out.
58pub const POSITION_FADE_BAND: f64 = 0.02;
59
60/// 1/rad: the position barrier's stiffness as a multiple of the joint's torque clamp, `K_i =
61/// POSITION_BARRIER_STIFFNESS × torque_limits_i`: 1075 Nm/rad at the 86 Nm clamp, 144 at 11.5.
62pub const POSITION_BARRIER_STIFFNESS: f64 = 12.5;
63
64/// Cycles from the loop's start over which the spring ramps in: an arm started inside the
65/// barrier eases out instead of being kicked.
66const SPRING_RAMP_CYCLES: u32 = 500;
67
68/// The spring's share, 0 to 1, `cycles` after the loop's start.
69pub(super) fn spring_ramp(cycles: u32) -> f64 {
70    f64::from(cycles.min(SPRING_RAMP_CYCLES)) / f64::from(SPRING_RAMP_CYCLES)
71}
72
73impl JointLimits {
74    /// The box of a step `u` from `from` over `dt`: toward each side at most
75    /// `min(f·v·dt, max(0, min(x, f·E(x)·dt)))`, `x` the distance to that limit less the margin.
76    /// Both limits are taken where the step ends, `u ≤ f·v(from ± u)·dt` and `u ≤ f·E(x − u)·dt`:
77    /// each falls as `u` grows, so one more evaluation at the end of the step to the start's
78    /// limit gives a step under the limit at its own end, short of the largest by 0.12 % at `f`
79    /// 0.7, under 0.25 % at any `f`. A velocity limit that does not depend on the position keeps
80    /// its term at `from`.
81    pub(super) fn cycle_box(&self, from: &[f64; 7], dt: f64) -> CycleBox {
82        let flat = self.velocity.flat();
83        let (lower, upper) = self.position;
84        let f = self.fraction;
85        let start = self.velocity.at(from);
86        // The velocity limits where the start's velocity step ends, per side.
87        let end = self.velocity.position_dependent().then(|| {
88            let toward = |sign: f64, speed: &[f64; 7]| {
89                std::array::from_fn(|i| from[i] + sign * f * speed[i] * dt)
90            };
91            (
92                self.velocity.at(&toward(-1.0, &start.0)).0,
93                self.velocity.at(&toward(1.0, &start.1)).1,
94            )
95        });
96        let (toward_lower, toward_upper) = end.unwrap_or(start);
97        let side = |i: usize, distance: f64, speed: f64| {
98            let x = distance - self.margin;
99            let velocity = f * speed * dt;
100            let braking = f * self.velocity.braking(i, x, flat[i]) * dt;
101            let braking = f * self.velocity.braking(i, x - braking, flat[i]) * dt;
102            let bound = velocity.min(x.min(braking).max(0.0));
103            (bound, velocity, braking < velocity - 1e-12)
104        };
105        let mut b = CycleBox {
106            lower: [0.0; 7],
107            upper: [0.0; 7],
108            position_lower: [false; 7],
109            position_upper: [false; 7],
110            velocity_lower: [0.0; 7],
111            velocity_upper: [0.0; 7],
112        };
113        for i in 0..7 {
114            let (bound, velocity, position) = side(i, from[i] - lower[i], toward_lower[i]);
115            (b.lower[i], b.velocity_lower[i], b.position_lower[i]) = (-bound, -velocity, position);
116            let (bound, velocity, position) = side(i, upper[i] - from[i], toward_upper[i]);
117            (b.upper[i], b.velocity_upper[i], b.position_upper[i]) = (bound, velocity, position);
118        }
119        b
120    }
121
122    /// How far the goal `q` of the `joints` pinned on a position bound (∓1) rests on that side's
123    /// margin, the largest of them: 1 on it, falling linearly to 0 [`REST_BAND`] before it.
124    pub(super) fn rest(&self, q: &[f64; 7], joints: &[i8; 7]) -> f64 {
125        let (lower, upper) = self.position;
126        (0..7)
127            .map(|i| match joints[i] {
128                -1 => q[i] - lower[i] - self.margin,
129                1 => upper[i] - q[i] - self.margin,
130                _ => f64::INFINITY,
131            })
132            .map(|x| (1.0 - x / REST_BAND).clamp(0.0, 1.0))
133            .fold(0.0, f64::max)
134    }
135
136    /// The distances, rad, of `q` to its lower and its upper limit.
137    fn distances(&self, i: usize, q: f64) -> (f64, f64) {
138        (q - self.position.0[i], self.position.1[i] - q)
139    }
140
141    /// Where the barrier's onset lies, `b = margin − POSITION_FADE_BAND` from a limit.
142    fn onset_distance(&self) -> f64 {
143        self.margin - POSITION_FADE_BAND
144    }
145
146    /// The velocity envelope at measured `q` toward the side `dq` points to, rad/s per joint: the
147    /// fade's start and the barrier's onset. The onset is `barrier_fraction` of the arm's limit
148    /// there, or of the braking envelope to the barrier's onset if lower; the fade starts at
149    /// [`fade_start`] of `fraction` of the limit, or of the envelope to the margin if lower.
150    /// Each is the lower of its value at `q` and at `q + dq·Δt`: the next state's velocity is
151    /// checked against the limit where that state is, and the fade's start is not monotone in
152    /// the limit.
153    pub(super) fn envelope(
154        &self,
155        q: &[f64; 7],
156        dq: &[f64; 7],
157        barrier_fraction: f64,
158    ) -> ([f64; 7], [f64; 7]) {
159        let now = self.envelope_at(q, dq, barrier_fraction);
160        let next = std::array::from_fn(|i| q[i] + dq[i] * DELTA_T);
161        let (start, onset) = self.envelope_at(&next, dq, barrier_fraction);
162        (
163            std::array::from_fn(|i| now.0[i].min(start[i])),
164            std::array::from_fn(|i| now.1[i].min(onset[i])),
165        )
166    }
167
168    fn envelope_at(
169        &self,
170        q: &[f64; 7],
171        dq: &[f64; 7],
172        barrier_fraction: f64,
173    ) -> ([f64; 7], [f64; 7]) {
174        let flat = self.velocity.flat();
175        let (toward_lower, toward_upper) = self.velocity.at(q);
176        let (mut start, mut onset) = ([0.0; 7], [0.0; 7]);
177        for i in 0..7 {
178            let (lower, upper) = self.distances(i, q[i]);
179            let (s, v) = if dq[i] < 0.0 {
180                (lower, toward_lower[i])
181            } else {
182                (upper, toward_upper[i])
183            };
184            let braking = |x: f64| v.min(self.velocity.braking(i, x, flat[i]));
185            onset[i] = barrier_fraction * braking(s - self.onset_distance());
186            start[i] = fade_start(v, self.fraction * braking(s - self.margin), onset[i]);
187        }
188        (start, onset)
189    }
190
191    /// `torque` with every joint's torque toward a limit faded linearly from full at the margin
192    /// to none at the barrier's onset; torque away from it is never scaled.
193    pub(super) fn fade_toward(&self, torque: &[f64; 7], q: &[f64; 7]) -> [f64; 7] {
194        let b = self.onset_distance();
195        std::array::from_fn(|i| {
196            let (lower, upper) = self.distances(i, q[i]);
197            let s = if torque[i] < 0.0 { lower } else { upper };
198            torque[i] * ((s - b) / (self.margin - b)).clamp(0.0, 1.0)
199        })
200    }
201
202    /// The spring inside the barrier's onset, Nm per joint: `ramp × K_i × depth` out of the limit,
203    /// at most the joint's torque clamp, `K_i` [`POSITION_BARRIER_STIFFNESS`] × that clamp.
204    pub(super) fn spring(&self, q: &[f64; 7], torque_limits: &[f64; 7], ramp: f64) -> [f64; 7] {
205        let b = self.onset_distance();
206        std::array::from_fn(|i| {
207            let push = |s: f64| {
208                let depth = (b - s).max(0.0);
209                (ramp * POSITION_BARRIER_STIFFNESS * torque_limits[i] * depth).min(torque_limits[i])
210            };
211            let (lower, upper) = self.distances(i, q[i]);
212            push(lower) - push(upper)
213        })
214    }
215}