use nalgebra as na;
extern crate rand;
use core::fmt;
use na::allocator::Allocator;
use na::base::DefaultAllocator;
use na::core::dimension::{Dim, DimMin, DimName};
use std::error::Error;
#[derive(Clone, Debug)]
pub struct LQRController<T, S, C>
where
T: na::RealField,
S: Dim + DimName + DimMin<S>, C: Dim + DimName + DimMin<C>, DefaultAllocator:
Allocator<T, S, S> + Allocator<T, C, C> + Allocator<T, S, C> + Allocator<T, C, S>,
{
pub q: Option<na::OMatrix<T, S, S>>,
pub r: Option<na::OMatrix<T, C, C>>,
pub k: Option<na::OMatrix<T, C, S>>,
ki: T,
integral_error: T,
}
impl<T, S, C> LQRController<T, S, C>
where
T: na::RealField + Copy,
S: Dim + DimName + DimMin<S>,
C: Dim + DimName + DimMin<C>,
DefaultAllocator:
Allocator<T, S, S> + Allocator<T, C, C> + Allocator<T, S, C> + Allocator<T, C, S>,
rand::distributions::Standard: rand::distributions::Distribution<T>,
{
pub fn new() -> Result<LQRController<T, S, C>, &'static str> {
Ok(LQRController {
q: None,
r: None,
k: None,
ki: T::zero(),
integral_error: T::zero(),
})
}
pub fn setup_i_controller(&mut self, ki: T) -> Result<(), Box<dyn Error>> {
if ki != T::zero() {
self.ki = ki;
} else {
Err("You tried setting ki to 0.0 which is not allowed")?;
}
Ok(())
}
pub fn compute_gain(
&mut self,
a: &na::OMatrix<T, S, S>,
b: &na::OMatrix<T, S, C>,
q: &na::OMatrix<T, S, S>,
r: &na::OMatrix<T, C, C>,
epsilon: T,
) -> Result<na::OMatrix<T, C, S>, &'static str> {
let mut a_k = a.clone(); let mut g_k = b.clone()
* r.clone().try_inverse().expect("Couldn't compute inverse")
* b.clone().transpose();
let mut h_k_1 = q.clone();
let mut h_k = na::OMatrix::<T, S, S>::from_fn(|_, _| rand::random());
while {
let error = (h_k_1.clone() - h_k).norm() / h_k_1.norm();
h_k = h_k_1.clone();
error >= epsilon
} {
let temp = (na::OMatrix::<T, S, S>::identity() + &g_k * &h_k)
.try_inverse()
.expect("Couldn't compute inverse");
let a_k_1 = &a_k * &temp * &a_k;
let g_k_1 = &g_k + &a_k * &temp * &g_k * &a_k.transpose();
h_k_1 = &h_k + &a_k.transpose() * &h_k * &temp * &a_k;
a_k = a_k_1;
g_k = g_k_1;
}
self.k =
Some(r.clone().try_inverse().expect("Couldn't compute inverse") * b.transpose() * h_k);
return Ok(self.k.clone().unwrap());
}
pub fn compute_optimal_controls(
&mut self,
current_state: &na::OVector<T, S>,
desired_state: &na::OVector<T, S>,
) -> Result<na::OVector<T, C>, &'static str>
where
T: na::RealField,
DefaultAllocator: Allocator<T, S> + Allocator<T, C> + Allocator<T, C, S>,
{
let error = desired_state - current_state;
let y_error = error[1];
if y_error.abs() < T::from_f64(1e-2).unwrap() {
self.integral_error = T::zero();
} else {
self.integral_error += y_error * self.ki.clone();
}
let mut controls = &self.k.clone().unwrap() * error;
controls[0] -= self.integral_error.clone();
Ok(controls)
}
}
impl<T, S, C> fmt::Display for LQRController<T, S, C>
where
T: na::RealField,
S: Dim + DimName + DimMin<S>,
C: Dim + DimName + DimMin<C>,
DefaultAllocator:
Allocator<T, S, S> + Allocator<T, C, C> + Allocator<T, S, C> + Allocator<T, C, S>,
rand::distributions::Standard: rand::distributions::Distribution<T>,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"LQR controller
Q: {:?}
R: {:?}
ki: {:}",
self.q, self.r, self.ki,
)
}
}
pub struct ClosestIndexError;
pub fn find_closest_indices<T, S, N>(
current_state: &na::OVector<T, S>,
trajectory: &na::OMatrix<T, S, N>,
) -> Result<((usize, T), (usize, T)), ClosestIndexError>
where
T: na::RealField + PartialOrd + Copy,
S: Dim + DimName + DimMin<S>, N: Dim + DimName, DefaultAllocator: Allocator<T, S>
+ Allocator<T, S, N>
+ Allocator<T, na::Const<1_usize>, N>
+ nalgebra::allocator::Allocator<T, N>,
{
if trajectory.ncols() < 2 {
return Err(ClosestIndexError);
}
let mut state = Vec::with_capacity(current_state.len());
for i in current_state.iter() {
state.push(i.clone());
}
let state = vec![state; trajectory.ncols()].concat();
let current_state_broadcasted = na::OMatrix::<T, S, N>::from_vec(state);
let errors = trajectory - current_state_broadcasted;
let errors = errors.map(|x| x.powi(2));
let errors = errors.row_sum_tr();
let (idx, error) = errors.argmin();
let (lower, upper) = if idx == 0 {
((0, error.sqrt()), (0, error.sqrt()))
} else if idx == errors.len() - 1 {
(
(errors.len() - 1, error.sqrt()),
(errors.len() - 1, error.sqrt()),
)
} else {
if errors[idx + 1] > errors[idx - 1] {
((idx, error.sqrt()), (idx + 1, errors[idx + 1].sqrt()))
} else {
((idx - 1, errors[idx - 1].sqrt()), (idx, error.sqrt()))
}
};
if lower.0 != 0 && upper.0 != 0 && lower.0 > upper.0 {
return Err(ClosestIndexError);
}
Ok((lower, upper))
}
pub fn compute_target<T, S, N>(
trajectory: &na::OMatrix<T, S, N>,
lower: (usize, T),
upper: (usize, T),
) -> na::OVector<T, S>
where
T: na::RealField + Copy,
S: Dim + DimName + DimMin<S>, N: Dim + DimName, DefaultAllocator: Allocator<T, S> + Allocator<T, S, N>,
{
trajectory
.column(lower.0)
.component_mul(&na::OVector::from_element(upper.1 / (lower.1 + upper.1)))
+ trajectory
.column(upper.0)
.component_mul(&na::OVector::from_element(lower.1 / (lower.1 + upper.1)))
}