Skip to content

Commit df3bb00

Browse files
committed
added reference implementations for Ss
1 parent b4ecd9d commit df3bb00

1 file changed

Lines changed: 154 additions & 66 deletions

File tree

src/systems/state_space.rs

Lines changed: 154 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ impl<U: Time + 'static> Ss<U> {
204204
let is_valid = self.is_valid();
205205
assert!(
206206
is_valid.is_ok(),
207-
"Ss is invalid: Message: {}, Ss: {:?}",
207+
"Ss is invalid: Message: {}, Ss: {}",
208208
is_valid.unwrap_err(),
209209
self
210210
);
@@ -278,17 +278,45 @@ impl<U: Time + 'static> Ss<U> {
278278
///
279279
/// # Returns
280280
/// A new system representing the series connection of `self` and `sys2`.
281-
pub fn series(self, sys2: Self) -> Self {
281+
pub fn series(&self, sys2: &Self) -> Self {
282282
assert_eq!(
283283
self.noutputs(),
284284
sys2.ninputs(),
285285
"Self output must be same as sys2 inputs"
286286
);
287287
self.assert_valid();
288288
sys2.assert_valid();
289-
let ret = sys2 * self;
290-
ret.assert_valid();
291-
ret
289+
290+
let nx2 = sys2.order();
291+
let nu2 = sys2.ninputs();
292+
let ny2 = sys2.noutputs();
293+
let nx1 = self.order();
294+
let nu1 = self.ninputs();
295+
let ny1 = self.noutputs();
296+
assert_eq!(nu2, ny1);
297+
298+
let mut a_new = DMatrix::zeros(nx1 + nx2, nx1 + nx2);
299+
a_new.view_mut((0, 0), (nx1, nx1)).copy_from(self.a());
300+
a_new
301+
.view_mut((nx1, 0), (nx2, nx1))
302+
.copy_from(&(sys2.b() * self.c()));
303+
a_new.view_mut((nx1, nx1), (nx2, nx2)).copy_from(sys2.a());
304+
305+
let mut b_new = DMatrix::zeros(nx1 + nx2, nu1);
306+
b_new.view_mut((0, 0), (nx1, nu1)).copy_from(self.b());
307+
b_new
308+
.view_mut((nx1, 0), (nx2, nu1))
309+
.copy_from(&(sys2.b() * self.d()));
310+
311+
let mut c_new = DMatrix::zeros(ny2, nx1 + nx2);
312+
c_new
313+
.view_mut((0, 0), (ny2, nx1))
314+
.copy_from(&(sys2.d() * self.c()));
315+
c_new.view_mut((0, nx1), (ny2, nx2)).copy_from(sys2.c());
316+
317+
let d_new = sys2.d() * self.d();
318+
319+
Ss::<U>::new(a_new, b_new, c_new, d_new).unwrap()
292320
}
293321

294322
/// Connects two systems in **parallel**.
@@ -305,17 +333,36 @@ impl<U: Time + 'static> Ss<U> {
305333
///
306334
/// # Returns
307335
/// A new system representing the parallel connection of `self` and `sys2`.
308-
pub fn parallel(self, sys2: Self) -> Self {
336+
pub fn parallel(&self, sys2: &Self) -> Self {
309337
assert_eq!(
310338
self.shape(),
311339
sys2.shape(),
312340
"System must have same shape to connnect in parallel"
313341
);
314342
self.assert_valid();
315343
sys2.assert_valid();
316-
let ret = self + sys2;
317-
ret.assert_valid();
318-
ret
344+
345+
let nu = self.ninputs();
346+
let ny = self.noutputs();
347+
348+
let nx1 = self.order();
349+
let nx2 = sys2.order();
350+
351+
let mut a_new = DMatrix::zeros(nx1 + nx2, nx1 + nx2);
352+
a_new.view_mut((0, 0), (nx1, nx1)).copy_from(self.a());
353+
a_new.view_mut((nx1, nx1), (nx2, nx2)).copy_from(sys2.a());
354+
355+
let mut b_new = DMatrix::zeros(nx1 + nx2, nu);
356+
b_new.view_mut((0, 0), (nx1, nu)).copy_from(self.b());
357+
b_new.view_mut((nx1, 0), (nx2, nu)).copy_from(sys2.b());
358+
359+
let mut c_new = DMatrix::zeros(ny, nx1 + nx2);
360+
c_new.view_mut((0, 0), (ny, nx1)).copy_from(self.c());
361+
362+
c_new.view_mut((0, nx1), (ny, nx2)).copy_from(sys2.c());
363+
let d_new = self.d() + sys2.d();
364+
365+
Ss::<U>::new(a_new, b_new, c_new, d_new).unwrap()
319366
}
320367

321368
/// **Negative** Feedback connection of `self` with `sys2`.
@@ -414,7 +461,7 @@ impl<U: Time + 'static> Ss<U> {
414461
}
415462
}
416463

417-
impl<U: Time + 'static> Add for Ss<U> {
464+
impl<'b, U: Time + 'static> Add<&'b Ss<U>> for &Ss<U> {
418465
type Output = Ss<U>;
419466

420467
/// Adds two state-space systems in parallel.
@@ -423,32 +470,26 @@ impl<U: Time + 'static> Add for Ss<U> {
423470
///
424471
/// # Panics
425472
/// Panics if the input-output dimensions of both systems do not match.
426-
fn add(self, rhs: Self) -> Self::Output {
473+
fn add(self, rhs: &'b Ss<U>) -> Self::Output {
427474
assert_eq!(self.shape(), rhs.shape());
428475
self.assert_valid();
429476
rhs.assert_valid();
430477

431-
let nu = self.ninputs();
432-
let ny = self.noutputs();
433-
434-
let nx1 = self.order();
435-
let nx2 = rhs.order();
436-
437-
let mut a_new = DMatrix::zeros(nx1 + nx2, nx1 + nx2);
438-
a_new.view_mut((0, 0), (nx1, nx1)).copy_from(self.a());
439-
a_new.view_mut((nx1, nx1), (nx2, nx2)).copy_from(rhs.a());
440-
441-
let mut b_new = DMatrix::zeros(nx1 + nx2, nu);
442-
b_new.view_mut((0, 0), (nx1, nu)).copy_from(self.b());
443-
b_new.view_mut((nx1, 0), (nx2, nu)).copy_from(rhs.b());
444-
445-
let mut c_new = DMatrix::zeros(ny, nx1 + nx2);
446-
c_new.view_mut((0, 0), (ny, nx1)).copy_from(self.c());
478+
self.parallel(rhs)
479+
}
480+
}
447481

448-
c_new.view_mut((0, nx1), (ny, nx2)).copy_from(rhs.c());
449-
let d_new = self.d() + rhs.d();
482+
impl<U: Time + 'static> Add for Ss<U> {
483+
type Output = Ss<U>;
450484

451-
Ss::<U>::new(a_new, b_new, c_new, d_new).unwrap()
485+
/// Adds two state-space systems in parallel.
486+
///
487+
/// y = y1 + y2
488+
///
489+
/// # Panics
490+
/// Panics if the input-output dimensions of both systems do not match.
491+
fn add(self, rhs: Self) -> Self::Output {
492+
&self + &rhs
452493
}
453494
}
454495

@@ -459,13 +500,26 @@ impl<U: Time + 'static> Neg for Ss<U> {
459500
///
460501
/// I.e. y_new = -y
461502
fn neg(mut self) -> Self::Output {
462-
self.c = -self.c();
463-
self.d = -self.d();
503+
self.c *= -1.0;
504+
self.d *= -1.0;
464505
self.assert_valid();
465506
self
466507
}
467508
}
468509

510+
impl<'b, U: Time + 'static> Sub<&'b Ss<U>> for &Ss<U> {
511+
type Output = Ss<U>;
512+
513+
/// Subtracts two state-space systems by adding one and negating the other.
514+
fn sub(self, rhs: &'b Ss<U>) -> Self::Output {
515+
self.assert_valid();
516+
rhs.assert_valid();
517+
518+
// TODO: should not need to use clone
519+
self + &(-rhs.clone())
520+
}
521+
}
522+
469523
impl<U: Time + 'static> Sub for Ss<U> {
470524
type Output = Ss<U>;
471525

@@ -477,7 +531,7 @@ impl<U: Time + 'static> Sub for Ss<U> {
477531
}
478532
}
479533

480-
impl<U: Time + 'static> Mul for Ss<U> {
534+
impl<'b, U: Time + 'static> Mul<&'b Ss<U>> for &Ss<U> {
481535
type Output = Ss<U>;
482536

483537
/// Multiplies two state-space systems in series (cascading connection).
@@ -488,40 +542,49 @@ impl<U: Time + 'static> Mul for Ss<U> {
488542
/// # Panics
489543
/// Panics if the output size of `rhs` does not match the input size of
490544
/// `self`.
491-
fn mul(self, rhs: Self) -> Self::Output {
545+
fn mul(self, rhs: &'b Ss<U>) -> Self::Output {
492546
self.assert_valid();
493547
rhs.assert_valid();
494548

495-
let nx2 = self.order();
496-
let nu2 = self.ninputs();
497-
let ny2 = self.noutputs();
498-
let nx1 = rhs.order();
499-
let nu1 = rhs.ninputs();
500-
let ny1 = rhs.noutputs();
501-
assert_eq!(nu2, ny1);
549+
rhs.series(self)
550+
}
551+
}
502552

503-
let mut a_new = DMatrix::zeros(nx1 + nx2, nx1 + nx2);
504-
a_new.view_mut((0, 0), (nx1, nx1)).copy_from(rhs.a());
505-
a_new
506-
.view_mut((nx1, 0), (nx2, nx1))
507-
.copy_from(&(self.b() * rhs.c()));
508-
a_new.view_mut((nx1, nx1), (nx2, nx2)).copy_from(self.a());
553+
impl<U: Time + 'static> Mul for Ss<U> {
554+
type Output = Ss<U>;
509555

510-
let mut b_new = DMatrix::zeros(nx1 + nx2, nu1);
511-
b_new.view_mut((0, 0), (nx1, nu1)).copy_from(rhs.b());
512-
b_new
513-
.view_mut((nx1, 0), (nx2, nu1))
514-
.copy_from(&(self.b() * rhs.d()));
556+
/// Multiplies two state-space systems in series (cascading connection).
557+
///
558+
/// The resulting system represents `y <- self <- rhs <- u`, where `self`
559+
/// follows `rhs`.
560+
///
561+
/// # Panics
562+
/// Panics if the output size of `rhs` does not match the input size of
563+
/// `self`.
564+
fn mul(self, rhs: Self) -> Self::Output {
565+
&self * &rhs
566+
}
567+
}
515568

516-
let mut c_new = DMatrix::zeros(ny2, nx1 + nx2);
517-
c_new
518-
.view_mut((0, 0), (ny2, nx1))
519-
.copy_from(&(self.d() * rhs.c()));
520-
c_new.view_mut((0, nx1), (ny2, nx2)).copy_from(self.c());
569+
impl<'b, U: Time + 'static> Div<&'b Ss<U>> for &Ss<U> {
570+
type Output = Ss<U>;
521571

522-
let d_new = self.d() * rhs.d();
572+
/// Divides one state-space system by another (right multiplication by
573+
/// inverse).
574+
///
575+
/// # Warning
576+
/// The need for division often arrised due to feedback connections. Such as
577+
/// T = L/(1 + L). For such cases use the `feedback` function, which is more
578+
/// numerically stable.
579+
///
580+
/// # Panics
581+
/// Panics if the inverse of `rhs` cannot be computed.
582+
#[allow(clippy::suspicious_arithmetic_impl)]
583+
fn div(self, rhs: &'b Ss<U>) -> Self::Output {
584+
self.assert_valid();
585+
rhs.assert_valid();
523586

524-
Ss::<U>::new(a_new, b_new, c_new, d_new).unwrap()
587+
self * &rhs.inv().unwrap()
525588
}
526589
}
527590

@@ -540,9 +603,7 @@ impl<U: Time + 'static> Div for Ss<U> {
540603
/// Panics if the inverse of `rhs` cannot be computed.
541604
#[allow(clippy::suspicious_arithmetic_impl)]
542605
fn div(self, rhs: Self) -> Self::Output {
543-
self.assert_valid();
544-
rhs.assert_valid();
545-
self * rhs.inv().unwrap()
606+
&self / &rhs
546607
}
547608
}
548609

@@ -557,6 +618,14 @@ macro_rules! impl_compound_assign {
557618
*self = self.clone().$method(rhs)
558619
}
559620
}
621+
622+
impl<'a, U: Time + 'static> $assign_trait<&'a $struct_type<U>> for $struct_type<U>
623+
{
624+
fn $assign_method(&mut self, rhs: &'a $struct_type<U>) {
625+
*self = <&Self as $trait<&Self>>::$method(self, rhs);
626+
}
627+
}
628+
560629
)*
561630
};
562631
}
@@ -621,14 +690,15 @@ mod tests {
621690
use core::f64;
622691

623692
use approx::assert_abs_diff_eq;
693+
use num_complex::c64;
624694
use rand::Rng;
625695

626696
use super::*;
627697
use crate::{
628698
analysis::frequency_response::lin_space,
629699
systems::Tf,
630700
transformations::SsRealization::{ControllableCF, ObservableCF},
631-
utils::traits::Continuous,
701+
utils::traits::Continuous, FrequencyResponse,
632702
};
633703

634704
fn rand_proper_tf<U: Rng>(
@@ -854,11 +924,11 @@ mod tests {
854924
assert_abs_diff_eq!(tf_fb, 0.5 * Tf::s() / (Tf::s() + 0.5));
855925

856926
let ss_add = ss1.clone() + ss2.clone();
857-
let ss_parallel = ss1.clone().parallel(ss2.clone());
927+
let ss_parallel = ss1.parallel(&ss2);
858928
assert_eq!(ss_add, ss_parallel);
859929

860930
let ss_mul = ss2.clone() * ss1.clone();
861-
let ss_series = ss1.series(ss2);
931+
let ss_series = ss1.series(&ss2);
862932
assert_eq!(ss_mul, ss_series);
863933
}
864934

@@ -875,7 +945,7 @@ mod tests {
875945

876946
let ss_fb = ss1.feedback(ss2);
877947
let ss_fb = ss_fb.to_tf().unwrap();
878-
let tf_fb = tf1.feedback(tf2);
948+
let tf_fb = tf1.feedback(&tf2);
879949

880950
assert_abs_diff_eq!(tf_fb, ss_fb, epsilon = 1e-1);
881951
}
@@ -894,4 +964,22 @@ mod tests {
894964
.unwrap();
895965
println!("3: {sys}after");
896966
}
967+
968+
#[test]
969+
fn ss_reference_arithmetic() {
970+
let mut ss = (1.0 / Tf::s().powi(2)).to_ss().unwrap();
971+
let ss_org = ss.clone();
972+
973+
ss += &ss_org;
974+
ss += &ss_org;
975+
976+
let ans = (ss_org.clone() + ss_org.clone()) + ss_org.clone();
977+
978+
let freq = c64(0.0, 1.2);
979+
let resp1 = ss.freq_response(&freq);
980+
let resp2 = ans.freq_response(&freq);
981+
982+
assert_abs_diff_eq!(resp1.re, resp2.re, epsilon = 1e-9);
983+
assert_abs_diff_eq!(resp1.im, resp2.im, epsilon = 1e-9);
984+
}
897985
}

0 commit comments

Comments
 (0)