use crate::error::ControlError;
use crate::linear_algebra::{Matrix, Vector, solve_discrete_lyapunov, solve_discrete_riccati};
use crate::scalar::Numeric;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Lqr<const N: usize, const M: usize, T: Numeric = f64> {
gain: Matrix<M, N, T>,
closed_loop: Matrix<N, N, T>,
cost_to_go: Matrix<N, N, T>,
state_cost: Matrix<N, N, T>,
}
impl<const N: usize, const M: usize, T: Numeric> Lqr<N, M, T> {
pub fn new(
state_transition: Matrix<N, N, T>,
input_model: Matrix<N, M, T>,
state_cost: Matrix<N, N, T>,
input_cost: Matrix<M, M, T>,
) -> Result<Self, ControlError> {
let cost_to_go =
solve_discrete_riccati(state_transition, input_model, state_cost, input_cost)?;
let input_weight = input_cost + input_model.transpose() * cost_to_go * input_model;
let coupling = input_model.transpose() * cost_to_go * state_transition;
let gain = input_weight.cholesky()?.solve_matrix::<N>(coupling);
let closed_loop = state_transition - input_model * gain;
Ok(Self {
gain,
closed_loop,
cost_to_go,
state_cost,
})
}
#[inline]
pub fn gain(&self) -> Matrix<M, N, T> {
self.gain
}
#[inline]
pub fn closed_loop(&self) -> Matrix<N, N, T> {
self.closed_loop
}
#[inline]
pub fn cost_to_go(&self) -> Matrix<N, N, T> {
self.cost_to_go
}
#[inline]
pub fn control(&self, state: Vector<N, T>) -> Vector<M, T> {
-(self.gain * state)
}
#[inline]
pub fn control_tracking(
&self,
state: Vector<N, T>,
reference: Vector<N, T>,
feedforward: Vector<M, T>,
) -> Vector<M, T> {
feedforward - self.gain * (state - reference)
}
pub fn certify_stability(&self) -> Result<Matrix<N, N, T>, ControlError> {
let certificate = solve_discrete_lyapunov(self.closed_loop, self.state_cost)?;
let _ = certificate.cholesky()?;
Ok(certificate)
}
}