use ndarray::{s, Array1, Array2, Axis, LinalgScalar};
pub trait Dynamics<T: LinalgScalar> {
fn dynamics(&self, t: T, x: &Array1<T>, u: &Array1<T>) -> Array1<T>;
fn output(&self, t: T, x: &Array1<T>, u: &Array1<T>) -> Array1<T>;
fn n_state(&self) -> usize;
fn n_input(&self) -> usize;
fn n_output(&self) -> usize;
}
pub trait MasDynamics<T: LinalgScalar>: Dynamics<T> {
fn mas_dynamics(&self, i: usize) -> Result<&dyn Dynamics<T>, &str>;
fn n_agents(&self) -> usize;
}
pub fn compact_dynamics<T: LinalgScalar>(
mas_dynamics: &dyn MasDynamics<T>,
t: T,
x: &Array1<T>,
u: &Array1<T>,
) -> Array1<T> {
let all_dynamics: Vec<_> = (0..mas_dynamics.n_agents())
.map(|i| mas_dynamics.mas_dynamics(i).unwrap())
.collect();
let x_nexts: Vec<_> = all_dynamics
.iter()
.scan((0, 0), |(i_x, i_u), &d| {
let x_i = x.slice(s![*i_x..*i_x + d.n_state()]).to_owned();
let u_i = u.slice(s![*i_u..*i_u + d.n_input()]).to_owned();
*i_x += d.n_state();
*i_u += d.n_input();
Some(d.dynamics(t, &x_i, &u_i))
})
.collect();
let x_nexts_view: Vec<_> = x_nexts.iter().map(|v| v.view()).collect();
ndarray::concatenate(Axis(0), x_nexts_view.as_slice()).unwrap()
}
pub fn compact_output<T: LinalgScalar>(
mas_dynamics: &dyn MasDynamics<T>,
t: T,
x: &Array1<T>,
u: &Array1<T>,
) -> Array1<T> {
let all_dyn: Vec<_> = (0..mas_dynamics.n_agents())
.map(|i| mas_dynamics.mas_dynamics(i).unwrap())
.collect();
let outputs: Vec<_> = all_dyn
.iter()
.scan((0, 0), |(i_x, i_u), &d| {
let x_i = x.slice(s![*i_x..*i_x + d.n_state()]).to_owned();
let u_i = u.slice(s![*i_u..*i_u + d.n_input()]).to_owned();
*i_x += d.n_state();
*i_u += d.n_input();
Some(d.output(t, &x_i, &u_i))
})
.collect();
let outputs_views: Vec<_> = outputs.iter().map(|v| v.view()).collect();
ndarray::concatenate(Axis(0), outputs_views.as_slice()).unwrap()
}
#[derive(Debug)]
pub struct LtiDynamics<T: LinalgScalar> {
pub a_mat: Array2<T>,
pub b_mat: Array2<T>,
pub c_mat: Array2<T>,
pub d_mat: Array2<T>,
}
impl<T: LinalgScalar> LtiDynamics<T> {
pub fn new(a_mat: Array2<T>, b_mat: Array2<T>) -> LtiDynamics<T> {
let n_state = a_mat.ncols();
let n_input = b_mat.ncols();
LtiDynamics {
a_mat,
b_mat,
c_mat: Array2::zeros((0, n_state)),
d_mat: Array2::zeros((0, n_input)),
}
}
}
impl<T: LinalgScalar> Dynamics<T> for LtiDynamics<T> {
fn n_input(&self) -> usize {
self.b_mat.ncols()
}
fn n_state(&self) -> usize {
self.a_mat.ncols()
}
fn n_output(&self) -> usize {
self.c_mat.nrows()
}
fn dynamics(&self, _t: T, x: &Array1<T>, u: &Array1<T>) -> Array1<T> {
self.a_mat.dot(x) + self.b_mat.dot(u)
}
fn output(&self, _t: T, x: &Array1<T>, u: &Array1<T>) -> Array1<T> {
self.c_mat.dot(x) + self.d_mat.dot(u)
}
}
#[cfg(test)]
mod tests {
use super::*;
use ndarray::array;
#[test]
fn test_linear_dynamics() {
let _dyn = LtiDynamics::new(array![[0., 1.], [0., 0.]], array![[0.], [1.]]);
assert_eq!(_dyn.n_input(), 1);
assert_eq!(_dyn.n_state(), 2);
assert_eq!(
_dyn.dynamics(0., &array![1., 1.], &array![2.]),
array![1., 2.]
);
}
}