nabled_sim/
control_loop.rs1use nabled_control::lqr::{LqrResult, discrete_lqr};
4use nabled_control::observer::luenberger_gain;
5use nabled_core::scalar::NabledReal;
6use nabled_linalg::lu::LuProviderScalar;
7use ndarray::{Array1, Array2};
8
9use crate::SimError;
10
11#[derive(Debug, Clone, PartialEq)]
13pub struct ClosedLoopPlant<T> {
14 pub a: Array2<T>,
15 pub b: Array2<T>,
16 pub c: Array2<T>,
17}
18
19#[derive(Debug, Clone, PartialEq)]
21pub struct ClosedLoopGains<T> {
22 pub k: Array2<T>,
23 pub l: Array2<T>,
24}
25
26#[derive(Debug, Clone, PartialEq)]
28pub struct ClosedLoopState<T> {
29 pub x: Array1<T>,
30 pub x_hat: Array1<T>,
31}
32
33#[derive(Debug, Clone, PartialEq)]
35pub struct ClosedLoopStep<T> {
36 pub plant: ClosedLoopPlant<T>,
37 pub gains: ClosedLoopGains<T>,
38}
39
40impl<T: NabledReal + LuProviderScalar> ClosedLoopStep<T> {
41 pub fn design(
43 plant: ClosedLoopPlant<T>,
44 q_cost: &Array2<T>,
45 r_cost: &Array2<T>,
46 observer_poles: &[T],
47 ) -> Result<Self, SimError> {
48 let LqrResult { gain: k, .. } = discrete_lqr(&plant.a, &plant.b, q_cost, r_cost)?;
49 let l = luenberger_gain(&plant.a, &plant.c, observer_poles)?;
50 Ok(Self { plant, gains: ClosedLoopGains { k, l } })
51 }
52
53 pub fn step(&self, state: &mut ClosedLoopState<T>) -> Result<T, SimError> {
55 let y = self.plant.c.dot(&state.x);
56 let u = -self.gains.k.dot(&state.x_hat)[0];
57 let innovation = &y - &self.plant.c.dot(&state.x_hat);
58 state.x = self.plant.a.dot(&state.x) + &(self.plant.b.column(0).to_owned() * u);
59 state.x_hat = self.plant.a.dot(&state.x_hat)
60 + &(self.plant.b.column(0).to_owned() * u)
61 + &(self.gains.l.column(0).to_owned() * innovation[[0]]);
62 Ok(u)
63 }
64}
65
66#[cfg(test)]
67mod tests {
68 use approx::assert_relative_eq;
69 use ndarray::{arr1, arr2};
70
71 use super::*;
72
73 #[test]
74 fn closed_loop_reduces_observer_error() {
75 let dt = 0.05_f64;
76 let plant = ClosedLoopPlant {
77 a: arr2(&[[1.0, dt], [0.0, 1.0]]),
78 b: arr2(&[[0.0], [dt]]),
79 c: arr2(&[[1.0, 0.0]]),
80 };
81 let controller =
82 ClosedLoopStep::design(plant, &arr2(&[[10.0, 0.0], [0.0, 1.0]]), &arr2(&[[0.1]]), &[
83 -0.5, -0.6,
84 ])
85 .expect("design");
86 let mut state = ClosedLoopState { x: arr1(&[1.0, 0.5]), x_hat: arr1(&[0.0, 0.0]) };
87 for _ in 0..80 {
88 let _ = controller.step(&mut state).expect("step");
89 }
90 let err = (&state.x - &state.x_hat).mapv(|v: f64| v * v).sum().sqrt();
91 assert!(err < 1e-2, "observer error {err}");
92 assert_relative_eq!(state.x[0], 0.0, epsilon = 0.15);
93 }
94}