use super::lqr::{LqrError, LqrSolve, dlqr_dense, lqr_dense};
use crate::control::estimation::{EstimatorError, LqeSolve, dlqe_dense, lqe_dense};
use crate::control::lti::{
ContinuousStateSpace, ContinuousTime, DiscreteStateSpace, DiscreteTime, StateSpace,
StateSpaceError, state_space::ObserverControllerComposition,
};
use crate::sparse::compensated::CompensatedField;
use core::fmt;
use faer::MatRef;
use faer_traits::RealField;
use num_traits::Float;
#[derive(Clone, Debug)]
pub struct LqgSolve<T, Domain>
where
T: CompensatedField,
T::Real: Float,
{
pub regulator: LqrSolve<T>,
pub estimator: LqeSolve<T>,
pub controller: StateSpace<T, Domain>,
pub closed_loop: StateSpace<T, Domain>,
}
impl<T, Domain> LqgSolve<T, Domain>
where
T: CompensatedField,
T::Real: Float,
{
#[must_use]
pub fn regulator_gain(&self) -> MatRef<'_, T> {
self.regulator.gain.as_ref()
}
#[must_use]
pub fn observer_gain(&self) -> MatRef<'_, T> {
self.estimator.gain.as_ref()
}
}
#[derive(Debug)]
pub enum LqgError {
Lqr(LqrError),
Estimator(EstimatorError),
StateSpace(StateSpaceError),
}
impl fmt::Display for LqgError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(self, f)
}
}
impl core::error::Error for LqgError {}
impl From<LqrError> for LqgError {
fn from(value: LqrError) -> Self {
Self::Lqr(value)
}
}
impl From<EstimatorError> for LqgError {
fn from(value: EstimatorError) -> Self {
Self::Estimator(value)
}
}
impl From<StateSpaceError> for LqgError {
fn from(value: StateSpaceError) -> Self {
Self::StateSpace(value)
}
}
pub fn lqg_dense<T>(
a: MatRef<'_, T>,
b: MatRef<'_, T>,
c: MatRef<'_, T>,
d: MatRef<'_, T>,
q: MatRef<'_, T>,
r: MatRef<'_, T>,
w: MatRef<'_, T>,
v: MatRef<'_, T>,
) -> Result<LqgSolve<T, ContinuousTime>, LqgError>
where
T: CompensatedField,
T::Real: Float + RealField,
{
let system = ContinuousStateSpace::new(a.to_owned(), b.to_owned(), c.to_owned(), d.to_owned())?;
let regulator = lqr_dense(a, b, q, r)?;
let estimator = lqe_dense(a, c, w, v)?;
let ObserverControllerComposition {
controller,
closed_loop,
} = system.observer_controller_augmented(regulator.gain.as_ref(), estimator.gain.as_ref())?;
Ok(LqgSolve {
regulator,
estimator,
controller,
closed_loop,
})
}
pub fn dlqg_dense<T>(
a: MatRef<'_, T>,
b: MatRef<'_, T>,
c: MatRef<'_, T>,
d: MatRef<'_, T>,
sample_time: T::Real,
q: MatRef<'_, T>,
r: MatRef<'_, T>,
w: MatRef<'_, T>,
v: MatRef<'_, T>,
) -> Result<LqgSolve<T, DiscreteTime<T::Real>>, LqgError>
where
T: CompensatedField,
T::Real: Float + RealField,
{
let system = DiscreteStateSpace::new(
a.to_owned(),
b.to_owned(),
c.to_owned(),
d.to_owned(),
sample_time,
)?;
let regulator = dlqr_dense(a, b, q, r)?;
let estimator = dlqe_dense(a, c, w, v)?;
let ObserverControllerComposition {
controller,
closed_loop,
} = system.observer_controller_augmented(regulator.gain.as_ref(), estimator.gain.as_ref())?;
Ok(LqgSolve {
regulator,
estimator,
controller,
closed_loop,
})
}
impl<T> ContinuousStateSpace<T>
where
T: CompensatedField,
T::Real: Float + RealField,
{
pub fn lqg(
&self,
q: MatRef<'_, T>,
r: MatRef<'_, T>,
w: MatRef<'_, T>,
v: MatRef<'_, T>,
) -> Result<LqgSolve<T, ContinuousTime>, LqgError> {
lqg_dense(self.a(), self.b(), self.c(), self.d(), q, r, w, v)
}
}
impl<T> DiscreteStateSpace<T>
where
T: CompensatedField,
T::Real: Float + RealField,
{
pub fn dlqg(
&self,
q: MatRef<'_, T>,
r: MatRef<'_, T>,
w: MatRef<'_, T>,
v: MatRef<'_, T>,
) -> Result<LqgSolve<T, DiscreteTime<T::Real>>, LqgError> {
dlqg_dense(
self.a(),
self.b(),
self.c(),
self.d(),
self.sample_time(),
q,
r,
w,
v,
)
}
}
#[cfg(test)]
mod test {
use super::{dlqg_dense, lqg_dense};
use crate::control::lti::state_space::{ContinuousStateSpace, DiscreteStateSpace};
use crate::control::{dlqe_dense, dlqr_dense, lqe_dense, lqr_dense};
use faer::Mat;
fn assert_close(lhs: &Mat<f64>, rhs: &Mat<f64>, tol: f64) {
assert_eq!(lhs.nrows(), rhs.nrows());
assert_eq!(lhs.ncols(), rhs.ncols());
for col in 0..lhs.ncols() {
for row in 0..lhs.nrows() {
let err = (lhs[(row, col)] - rhs[(row, col)]).abs();
assert!(
err <= tol,
"entry ({row}, {col}) mismatch: lhs={}, rhs={}, err={err}, tol={tol}",
lhs[(row, col)],
rhs[(row, col)],
);
}
}
}
#[test]
fn continuous_lqg_matches_regulator_estimator_and_composition() {
let system = ContinuousStateSpace::new(
Mat::from_fn(1, 1, |_, _| 1.0f64),
Mat::from_fn(1, 1, |_, _| 2.0f64),
Mat::from_fn(1, 1, |_, _| 3.0f64),
Mat::from_fn(1, 1, |_, _| 4.0f64),
)
.unwrap();
let q = Mat::from_fn(1, 1, |_, _| 1.0f64);
let r = Mat::from_fn(1, 1, |_, _| 1.0f64);
let w = Mat::from_fn(1, 1, |_, _| 1.0f64);
let v = Mat::from_fn(1, 1, |_, _| 1.0f64);
let lqg = lqg_dense(
system.a(),
system.b(),
system.c(),
system.d(),
q.as_ref(),
r.as_ref(),
w.as_ref(),
v.as_ref(),
)
.unwrap();
let lqr = lqr_dense(system.a(), system.b(), q.as_ref(), r.as_ref()).unwrap();
let lqe = lqe_dense(system.a(), system.c(), w.as_ref(), v.as_ref()).unwrap();
let composed = system
.observer_controller_augmented(lqr.gain.as_ref(), lqe.gain.as_ref())
.unwrap();
assert_close(&lqg.regulator.gain, &lqr.gain, 1.0e-12);
assert_close(&lqg.estimator.gain, &lqe.gain, 1.0e-12);
assert_close(&lqg.controller.a, &composed.controller.a, 1.0e-12);
assert_close(&lqg.controller.b, &composed.controller.b, 1.0e-12);
assert_close(&lqg.controller.c, &composed.controller.c, 1.0e-12);
assert_close(&lqg.controller.d, &composed.controller.d, 1.0e-12);
assert_close(&lqg.closed_loop.a, &composed.closed_loop.a, 1.0e-12);
assert_close(&lqg.closed_loop.b, &composed.closed_loop.b, 1.0e-12);
assert_close(&lqg.closed_loop.c, &composed.closed_loop.c, 1.0e-12);
assert_close(&lqg.closed_loop.d, &composed.closed_loop.d, 1.0e-12);
}
#[test]
fn discrete_lqg_matches_regulator_estimator_and_methods() {
let system = DiscreteStateSpace::new(
Mat::from_fn(1, 1, |_, _| 1.2f64),
Mat::from_fn(1, 1, |_, _| 1.0f64),
Mat::from_fn(1, 1, |_, _| 0.5f64),
Mat::from_fn(1, 1, |_, _| 0.25f64),
0.1,
)
.unwrap();
let q = Mat::from_fn(1, 1, |_, _| 1.0f64);
let r = Mat::from_fn(1, 1, |_, _| 1.0f64);
let w = Mat::from_fn(1, 1, |_, _| 1.0f64);
let v = Mat::from_fn(1, 1, |_, _| 1.0f64);
let free = dlqg_dense(
system.a(),
system.b(),
system.c(),
system.d(),
system.sample_time(),
q.as_ref(),
r.as_ref(),
w.as_ref(),
v.as_ref(),
)
.unwrap();
let method = system
.dlqg(q.as_ref(), r.as_ref(), w.as_ref(), v.as_ref())
.unwrap();
let dlqr = dlqr_dense(system.a(), system.b(), q.as_ref(), r.as_ref()).unwrap();
let dlqe = dlqe_dense(system.a(), system.c(), w.as_ref(), v.as_ref()).unwrap();
assert_close(&free.regulator.gain, &dlqr.gain, 1.0e-12);
assert_close(&free.estimator.gain, &dlqe.gain, 1.0e-12);
assert_close(&free.controller.a, &method.controller.a, 1.0e-12);
assert_close(&free.controller.b, &method.controller.b, 1.0e-12);
assert_close(&free.controller.c, &method.controller.c, 1.0e-12);
assert_close(&free.controller.d, &method.controller.d, 1.0e-12);
assert_close(&free.closed_loop.a, &method.closed_loop.a, 1.0e-12);
assert_close(&free.closed_loop.b, &method.closed_loop.b, 1.0e-12);
assert_close(&free.closed_loop.c, &method.closed_loop.c, 1.0e-12);
assert_close(&free.closed_loop.d, &method.closed_loop.d, 1.0e-12);
}
}