extern crate nalgebra as na;
pub trait StateSpaceModel {
fn mat_a(&self) -> &na::DMatrix<f64>;
fn mat_b(&self) -> &na::DMatrix<f64>;
fn mat_c(&self) -> &na::DMatrix<f64>;
fn mat_d(&self) -> &na::DMatrix<f64>;
}
pub trait Discrete {
fn sampling_dt(&self) -> f64;
}
pub trait Pole {
fn poles(&self) -> Vec<na::Complex<f64>>;
}
#[derive(Debug, Clone)]
pub struct ContinuousStateSpaceModel {
mat_a: na::DMatrix<f64>,
mat_b: na::DMatrix<f64>,
mat_c: na::DMatrix<f64>,
mat_d: na::DMatrix<f64>,
}
impl ContinuousStateSpaceModel {
pub fn from_matrices(
mat_a: &na::DMatrix<f64>,
mat_b: &na::DMatrix<f64>,
mat_c: &na::DMatrix<f64>,
mat_d: &na::DMatrix<f64>,
) -> ContinuousStateSpaceModel {
ContinuousStateSpaceModel {
mat_a: mat_a.clone(),
mat_b: mat_b.clone(),
mat_c: mat_c.clone(),
mat_d: mat_d.clone(),
}
}
fn build_controllable_canonical_form(tf: &TransferFunction) -> ContinuousStateSpaceModel {
let n_states = tf.denominator_coeffs.len();
let mut mat_a = na::DMatrix::<f64>::zeros(n_states, n_states);
mat_a
.view_range_mut(0..n_states - 1, 1..)
.copy_from(&na::DMatrix::<f64>::identity(n_states - 1, n_states - 1));
for (i, value) in tf.denominator_coeffs.iter().rev().enumerate() {
mat_a[(n_states - 1, i)] = -value.clone();
}
let mut mat_b = na::DMatrix::<f64>::zeros(tf.numerator_coeffs.len(), 1);
mat_b[(tf.numerator_coeffs.len() - 1, 0)] = 1.0f64;
let mut mat_c = na::DMatrix::<f64>::zeros(tf.numerator_coeffs.len(), 1);
for (i, value) in tf.numerator_coeffs.iter().rev().enumerate() {
mat_c[(i, 0)] = value.clone();
}
let mat_d = na::dmatrix![tf.constant];
ContinuousStateSpaceModel {
mat_a: mat_a,
mat_b: mat_b,
mat_c: mat_c,
mat_d: mat_d,
}
}
pub fn state_space_size(&self) -> usize {
return self.mat_a.ncols();
}
}
impl StateSpaceModel for ContinuousStateSpaceModel {
fn mat_a(&self) -> &na::DMatrix<f64> {
return &self.mat_a;
}
fn mat_b(&self) -> &na::DMatrix<f64> {
return &self.mat_b;
}
fn mat_c(&self) -> &na::DMatrix<f64> {
return &self.mat_c;
}
fn mat_d(&self) -> &na::DMatrix<f64> {
return &self.mat_d;
}
}
impl Pole for ContinuousStateSpaceModel {
fn poles(&self) -> Vec<na::Complex<f64>> {
self.mat_a.complex_eigenvalues().iter().cloned().collect()
}
}
#[derive(Debug, Clone)]
pub struct DiscreteStateSpaceModel {
mat_a: na::DMatrix<f64>,
mat_b: na::DMatrix<f64>,
mat_c: na::DMatrix<f64>,
mat_d: na::DMatrix<f64>,
sampling_dt: f64,
}
impl StateSpaceModel for DiscreteStateSpaceModel {
fn mat_a(&self) -> &na::DMatrix<f64> {
return &self.mat_a;
}
fn mat_b(&self) -> &na::DMatrix<f64> {
return &self.mat_b;
}
fn mat_c(&self) -> &na::DMatrix<f64> {
return &self.mat_c;
}
fn mat_d(&self) -> &na::DMatrix<f64> {
return &self.mat_d;
}
}
impl DiscreteStateSpaceModel {
pub fn from_matrices(
mat_a: &na::DMatrix<f64>,
mat_b: &na::DMatrix<f64>,
mat_c: &na::DMatrix<f64>,
mat_d: &na::DMatrix<f64>,
sampling_dt: f64,
) -> DiscreteStateSpaceModel {
DiscreteStateSpaceModel {
mat_a: mat_a.clone(),
mat_b: mat_b.clone(),
mat_c: mat_c.clone(),
mat_d: mat_d.clone(),
sampling_dt: sampling_dt,
}
}
pub fn from_continuous_matrix_forward_euler(
mat_ac: &na::DMatrix<f64>,
mat_bc: &na::DMatrix<f64>,
mat_cc: &na::DMatrix<f64>,
mat_dc: &na::DMatrix<f64>,
sampling_dt: f64,
) -> DiscreteStateSpaceModel {
let mat_i = na::DMatrix::<f64>::identity(mat_ac.nrows(), mat_ac.nrows());
let mat_a = (mat_i - mat_ac.scale(sampling_dt)).try_inverse().unwrap();
let mat_b = &mat_a * mat_bc.scale(sampling_dt);
let mat_c = mat_cc.clone();
let mat_d = mat_dc.clone();
DiscreteStateSpaceModel {
mat_a: mat_a,
mat_b: mat_b,
mat_c: mat_c,
mat_d: mat_d,
sampling_dt: sampling_dt,
}
}
pub fn from_continuous_ss_forward_euler(
model: &ContinuousStateSpaceModel,
sampling_dt: f64,
) -> DiscreteStateSpaceModel {
Self::from_continuous_matrix_forward_euler(
model.mat_a(),
model.mat_b(),
model.mat_c(),
model.mat_d(),
sampling_dt,
)
}
}
impl Pole for DiscreteStateSpaceModel {
fn poles(&self) -> Vec<na::Complex<f64>> {
self.mat_a.complex_eigenvalues().iter().cloned().collect()
}
}
impl Discrete for DiscreteStateSpaceModel {
fn sampling_dt(&self) -> f64 {
return self.sampling_dt;
}
}
struct TransferFunction {
numerator_coeffs: Vec<f64>,
denominator_coeffs: Vec<f64>,
constant: f64,
}
impl TransferFunction {
fn new(
numerator_coeffs: &[f64],
denominator_coeffs: &[f64],
constant: f64,
) -> TransferFunction {
TransferFunction {
numerator_coeffs: numerator_coeffs.to_vec(),
denominator_coeffs: denominator_coeffs.to_vec(),
constant: constant,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compute_state_space_model_nominal() {
let tf = TransferFunction::new(&[1.0, 2.0, 3.0], &[1.0, 4.0, 6.0], 8.0);
let ss_model = ContinuousStateSpaceModel::build_controllable_canonical_form(&tf);
assert_eq!(ss_model.mat_a().shape(), (3, 3));
assert_eq!(ss_model.mat_a()[(2, 0)], -6.0f64);
assert_eq!(ss_model.mat_a()[(2, 1)], -4.0f64);
assert_eq!(ss_model.mat_a()[(2, 2)], -1.0f64);
assert_eq!(ss_model.mat_a()[(0, 1)], 1.0f64);
assert_eq!(ss_model.mat_a()[(1, 2)], 1.0f64);
assert_eq!(ss_model.mat_b().shape(), (3, 1));
assert_eq!(ss_model.mat_b()[(0, 0)], 0.0f64);
assert_eq!(ss_model.mat_b()[(1, 0)], 0.0f64);
assert_eq!(ss_model.mat_b()[(2, 0)], 1.0f64);
assert_eq!(ss_model.mat_c().shape(), (3, 1));
assert_eq!(ss_model.mat_c()[(0, 0)], 3.0f64);
assert_eq!(ss_model.mat_c()[(1, 0)], 2.0f64);
assert_eq!(ss_model.mat_c()[(2, 0)], 1.0f64);
assert_eq!(ss_model.mat_d().shape(), (1, 1));
assert_eq!(ss_model.mat_d()[(0, 0)], 8.0f64);
}
#[test]
fn test_compute_poles_pure_real() {
let ss_model = DiscreteStateSpaceModel::from_matrices(
&nalgebra::dmatrix![2.0, 0.0; 0.0, 1.0],
&nalgebra::dmatrix![],
&nalgebra::dmatrix![],
&nalgebra::dmatrix![],
0.05,
);
let poles = ss_model.poles();
assert_eq!(poles.len(), 2);
assert_eq!(poles[0].re, 2.0);
assert_eq!(poles[0].im, 0.0);
assert_eq!(poles[1].re, 1.0);
assert_eq!(poles[1].im, 0.0);
}
#[test]
fn test_compute_poles_pure_im() {
let ss_model = DiscreteStateSpaceModel::from_matrices(
&nalgebra::dmatrix![0.0, -1.0; 1.0, 0.0],
&nalgebra::dmatrix![],
&nalgebra::dmatrix![],
&nalgebra::dmatrix![],
0.05,
);
let poles = ss_model.poles();
assert_eq!(poles.len(), 2);
assert_eq!(poles[0].re, 0.0);
assert_eq!(poles[0].im, 1.0);
assert_eq!(poles[1].re, 0.0);
assert_eq!(poles[1].im, -1.0);
}
#[test]
fn test_compute_poles_real_and_imaginary_part() {
let ss_model = DiscreteStateSpaceModel::from_matrices(
&nalgebra::dmatrix![1.0, -2.0; 2.0, 1.0],
&nalgebra::dmatrix![],
&nalgebra::dmatrix![],
&nalgebra::dmatrix![],
0.05,
);
let poles = ss_model.poles();
assert_eq!(poles.len(), 2);
assert_eq!(poles[0].re, 1.0);
assert_eq!(poles[0].im, 2.0);
assert_eq!(poles[1].re, 1.0);
assert_eq!(poles[1].im, -2.0);
}
}