use ndarray::prelude::*;
pub fn calc_curvature(v_ego: f64, angle_steers: f64, vp: &VP, angle_offset: f64) -> f64 {
let deg_to_rad = std::f64::consts::PI / 180.0;
let angle_steers_rad = (angle_steers - angle_offset) * deg_to_rad;
angle_steers_rad / (vp.steer_ratio * vp.wheelbase * (1. + vp.slip_factor * v_ego.powi(2)))
}
pub fn calc_d_lookahead(v_ego: f64) -> f64 {
let offset_lookahead = 1.0;
let coeff_lookahead = 4.4;
offset_lookahead + (v_ego.max(0.0)).sqrt() * coeff_lookahead
}
pub fn calc_lookahead_offset(
v_ego: f64,
angle_steers: f64,
d_lookahead: f64,
vp: &VP,
angle_offset: f64,
) -> (f64, f64) {
let curvature = calc_curvature(v_ego, angle_steers, vp, angle_offset);
let y_actual = d_lookahead * (d_lookahead * curvature).asin().tan() / 2.0;
(y_actual, curvature)
}
#[allow(clippy::too_many_arguments)]
pub fn pid_lateral_control(
v_ego: f64,
y_actual: f64,
y_des: f64,
ui_steer: f64,
steer_max: f64,
steer_override: bool,
sat_count: f64,
enabled: bool,
half_pid: bool,
rate: f64,
) -> (f64, f64, f64, bool, f64, bool) {
let sat_count_rate = 1.0 / rate;
let sat_count_limit = 0.8;
let error_steer = y_des - y_actual;
let ui_unwind_speed = 0.3 / rate;
let (kp, ki) = if !half_pid { (12.0, 1.0) } else { (6.0, 0.5) };
let up_steer = error_steer * kp;
let ui_steer_new = ui_steer + error_steer * ki * 1.0 / rate;
let output_steer_new = ui_steer_new + up_steer;
let updated_ui_steer = if (error_steer >= 0. && (output_steer_new < steer_max || ui_steer < 0.))
|| (error_steer <= 0. && (output_steer_new > -steer_max || ui_steer > 0.))
&& !steer_override
{
ui_steer_new
} else {
if steer_override {
ui_steer - ui_unwind_speed * ui_steer.signum()
} else {
ui_steer
}
};
let clamped_ui_steer = updated_ui_steer.max(-steer_max).min(steer_max);
let output_steer = up_steer + clamped_ui_steer;
let final_ui_steer = if v_ego < 0.3 || !enabled {
0.0
} else {
clamped_ui_steer
};
let lateral_control_sat = output_steer > steer_max;
let final_output_steer = output_steer.max(-steer_max).min(steer_max);
let updated_sat_count =
if lateral_control_sat && !steer_override && v_ego > 10.0 && error_steer.abs() > 0.1 {
sat_count + sat_count_rate
} else {
sat_count - sat_count_rate
};
let clamped_sat_count = updated_sat_count.max(0.0).min(1.0);
let sat_flag = clamped_sat_count >= sat_count_limit;
(
final_output_steer,
up_steer,
final_ui_steer,
lateral_control_sat,
clamped_sat_count,
sat_flag,
)
}
#[derive(Debug, Clone)]
pub struct VP {
pub steer_ratio: f64,
pub wheelbase: f64,
pub slip_factor: f64,
}
#[derive(Debug, Clone)]
pub struct CS {
pub v_ego: f64,
pub angle_steers: f64,
pub torque_mod: bool,
pub steer_override: bool,
pub vp: VP,
}
pub struct LatControl {
pub ui_steer: f64,
pub sat_count: f64,
pub y_des: f64,
pub lateral_control_sat: bool,
}
impl LatControl {
pub fn new() -> Self {
LatControl {
ui_steer: 0.0,
sat_count: 0.0,
y_des: 0.0,
lateral_control_sat: false,
}
}
pub fn reset(&mut self) {
self.ui_steer = 0.0;
}
pub fn update(
&mut self,
enabled: bool,
cs: &CS,
d_poly: &Array1<f64>,
angle_offset: f64,
) -> (f64, bool) {
let rate = 100.0;
let steer_max = 1.0;
let d_lookahead = calc_d_lookahead(cs.v_ego);
let (y_actual, _) =
calc_lookahead_offset(cs.v_ego, cs.angle_steers, d_lookahead, &cs.vp, angle_offset);
self.y_des = d_poly.iter().enumerate().fold(0.0, |acc, (i, &coeff)| {
acc + coeff * d_lookahead.powi(i as i32)
});
let (output_steer, _, ui_steer, lateral_control_sat, sat_count, sat_flag) =
pid_lateral_control(
cs.v_ego,
y_actual,
self.y_des,
self.ui_steer,
steer_max,
cs.steer_override,
self.sat_count,
enabled,
cs.torque_mod,
rate,
);
self.ui_steer = ui_steer;
self.sat_count = sat_count;
self.lateral_control_sat = lateral_control_sat;
(output_steer, sat_flag)
}
}
impl Default for LatControl {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
#[test]
fn test_calc_curvature() {
let v_ego = 10.0;
let angle_steers = 5.0;
let vp = VP {
steer_ratio: 12.0,
wheelbase: 2.7,
slip_factor: 0.01,
};
let curvature = calc_curvature(v_ego, angle_steers, &vp, 0.0);
assert_abs_diff_eq!(curvature, 0.0013, epsilon = 1e-4);
}
#[test]
fn test_calc_d_lookahead() {
let v_ego = 20.0;
let d_lookahead = calc_d_lookahead(v_ego);
assert_abs_diff_eq!(d_lookahead, 20.6773, epsilon = 1e-4);
}
#[test]
fn test_calc_lookahead_offset() {
let v_ego = 20.0;
let angle_steers = 5.0;
let d_lookahead = 2.1;
let vp = VP {
steer_ratio: 12.0,
wheelbase: 2.7,
slip_factor: 0.01,
};
let (y_actual, curvature) =
calc_lookahead_offset(v_ego, angle_steers, d_lookahead, &vp, 0.0);
assert_abs_diff_eq!(y_actual, 0.0011, epsilon = 1e-4);
assert_abs_diff_eq!(curvature, -0.0005, epsilon = 1e-2);
}
#[test]
fn test_pid_lateral_control() {
let v_ego = 20.0;
let y_actual = 0.1;
let y_des = 0.0;
let ui_steer = 0.2;
let steer_max = 1.0;
let steer_override = false;
let sat_count = 0.0;
let enabled = true;
let half_pid = false;
let rate = 100.0;
let (output_steer, up_steer, new_ui_steer, lateral_control_sat, new_sat_count, sat_flag) =
pid_lateral_control(
v_ego,
y_actual,
y_des,
ui_steer,
steer_max,
steer_override,
sat_count,
enabled,
half_pid,
rate,
);
assert_eq!(output_steer, -1.0);
assert_abs_diff_eq!(up_steer, -1.20, epsilon = 1e-4);
assert_eq!(new_ui_steer, 0.199);
assert_eq!(lateral_control_sat, false);
assert_eq!(new_sat_count, 0.0);
assert_eq!(sat_flag, false);
}
#[test]
fn test_lat_control_new() {
let lat_control = LatControl::new();
assert_eq!(lat_control.ui_steer, 0.0);
}
#[test]
fn test_lat_control_reset() {
let mut lat_control = LatControl::new();
lat_control.ui_steer = 0.5;
lat_control.reset();
assert_eq!(lat_control.ui_steer, 0.0);
}
#[test]
fn test_lat_control_update() {
let mut lat_control = LatControl::new();
let enabled = true;
let cs = CS {
v_ego: 20.0,
angle_steers: 5.0,
steer_override: false,
torque_mod: false,
vp: VP {
steer_ratio: 12.0,
wheelbase: 2.7,
slip_factor: 0.01,
},
};
let d_poly = Array1::from(vec![0.1, 0.05, -0.02]);
let angle_offset = 0.0;
let (output_steer, sat_flag) = lat_control.update(enabled, &cs, &d_poly, angle_offset);
assert_eq!(output_steer, -1.0);
assert_eq!(sat_flag, false);
}
}