Skip to content

Commit e2f56c4

Browse files
authored
Merge pull request #47 from TorBorve/docs/new-example
Docs/new example
2 parents 4967d17 + 48606a4 commit e2f56c4

5 files changed

Lines changed: 169 additions & 24 deletions

File tree

README.md

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,30 +26,64 @@ control-systems-torbox = "*"
2626

2727
The crate contains a representation for transfer functions:
2828

29-
```rust,no_run
29+
```rust
3030
use control_systems_torbox::*;
3131

3232
let tf_lp = Tf::<f64, Continuous>::new(&[10.0], &[10.0, 1.0]); // 10/(s + 10)
3333
let sys = 1.0 / Tf::s();
3434
let pi_c = 1.0 + 1.0 / Tf::s();
3535

3636
let openloop = sys * pi_c * tf_lp;
37-
let tracking = openloop.clone() / (1.0 + openloop);
37+
let tracking = openloop.feedback(&Tf::new_from_scalar(1.0));
3838
```
3939

4040
State-Space representations is also implemented and you can convert between transfer function and state-space
4141

42-
```rust,no_run
42+
```rust
4343
use control_systems_torbox::*;
44-
use nalgebra::DMatrix;
44+
use nalgebra::dmatrix;
4545

46-
let a = DMatrix::from_row_slice(2, 2, &[0., 0., 1., 0.]);
47-
let b = DMatrix::from_row_slice(2, 1, &[1., 0.]);
48-
let c = DMatrix::from_row_slice(1, 2, &[0., 1.]);
49-
let d = DMatrix::from_row_slice(1, 1, &[0.]);
46+
let a = dmatrix![0., 1.;
47+
0., 0.];
48+
let b = dmatrix![0.;
49+
1.];
50+
let c = dmatrix![1., 0.];
51+
let d = dmatrix![0.];
5052

5153
let sys_ss = Ss::<Continuous>::new(a, b, c, d).unwrap();
52-
let sys_tf = ss2tf(&sys_ss).unwrap();
54+
let sys_tf = sys_ss.to_tf().unwrap();
55+
```
56+
57+
System norms such as H2-norm and H-inf-norm are also possible to calculte
58+
59+
```rust
60+
use control_systems_torbox::*;
61+
62+
let low_pass = 1.0/(Tf::s().powi(2) + 2.0*0.2*Tf::s() + 0.1);
63+
let low_pass = low_pass.to_ss().unwrap();
64+
let h2_norm = low_pass.norm_h2().unwrap();
65+
let hinf_norm = low_pass.norm_hinf().unwrap();
66+
println!("H2 norm is: {}, H-inf norm is: {}", h2_norm, hinf_norm);
67+
```
68+
69+
It is also possible to calculte the poles and zeros of a system
70+
71+
```rust
72+
use control_systems_torbox::*;
73+
74+
let tf = (Tf::s() -1.0)*(Tf::s() + 3.0)/((Tf::s() - 4.0)*(Tf::s() + 5.0));
75+
let ss = tf.to_ss().unwrap();
76+
let poles = ss.poles();
77+
let zeros = ss.zeros().unwrap();
78+
println!("Tf:\n{}", tf);
79+
println!("Poles:");
80+
for pole in poles {
81+
println!("{}", pole);
82+
}
83+
println!("\nZeros:");
84+
for zero in zeros {
85+
println!("{}", zero);
86+
}
5387
```
5488

5589
Both Bodeplot and Nyquistplot is implemented and can be displayed natively:
@@ -61,10 +95,10 @@ let sys = (1.0/Tf::s()) * (1.0 + 1.0/Tf::s());
6195
let sys_clp = sys.clone() / (1.0 + sys.clone());
6296
6397
let mut bode_plot = BodePlot::new(BodePlotOptions::default());
64-
bode_plot.add_system(BodePlotData::new(bode(sys_clp, 0.1, 10.)));
98+
bode_plot.add_system(BodePlotData::new(sys_clp.bode(0.1, 10.)));
6599
bode_plot.show(800, 600, "Bode plot demo").unwrap();
66100
67101
let mut nyq_plot = NyquistPlot::new(NyquistPlotOptions::default());
68-
nyq_plot.add_system(NyquistPlotData::new(nyquist(sys, 0.1, 100.)));
102+
nyq_plot.add_system(NyquistPlotData::new(sys.nyquist(0.1, 100.)));
69103
nyq_plot.show(600, 600, "Nyquist plot demo").unwrap();
70-
```
104+
```

examples/motion_controller.rs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
use control_systems_torbox::{
2+
BodePlot, BodePlotOptions, Continuous, FrequencyResponse, NyquistPlot,
3+
NyquistPlotOptions, Plot, Ss, Tf, analysis::frequency_response::lin_space,
4+
};
5+
6+
fn main() {
7+
// This example shows how we can create a plant, design a controller for the
8+
// plant and analyse the resulting closed loop system.
9+
10+
// Plant is integrator, 1/s, and a power converter with a bandwith of 10
11+
// rad/s, and a time delay of 0.5s approximated with a second order pade
12+
// approximation
13+
let s = Tf::s();
14+
let plant = 1.0 / &s * 50.0 / (&s + 50.0) * Tf::pade(0.12, 3);
15+
// let plant = plant.to_ss().unwrap();
16+
17+
// We will use a PI controller: Kp*(1 + 1/T_i*1/s)
18+
// We set the integration time to 1 second and find the largest Kp such that
19+
// Ms = ||S(s)||_inf = peak of sensitity function is less than two.
20+
let ms_limit = 2.0;
21+
let t_i = 10.0;
22+
let kps = lin_space(100.0, 0.001, 100); // Kp to test
23+
let mut controller = None;
24+
for kp in kps {
25+
let controller_i = kp * (1.0 + 1.0 / t_i * 1.0 / &s); //.to_ss().unwrap();
26+
let open_loop = controller_i.series(&plant);
27+
// S = 1/(1 + L)
28+
let sensitivity_func =
29+
Ss::new_from_scalar(1.0).feedback(&open_loop.to_ss().unwrap());
30+
if !sensitivity_func.is_stable() {
31+
continue;
32+
}
33+
34+
let max_sensitivity = sensitivity_func.norm_hinf().unwrap();
35+
36+
// println!("Ms = {}", max_sensitivity);
37+
// println!("Stable: {}", sensitivity_func.is_stable());
38+
let mut bode_plot = BodePlot::new(BodePlotOptions::default());
39+
bode_plot.add_system(sensitivity_func.bode(0.1, 100.).into());
40+
bode_plot.add_system(
41+
Tf::<f64, Continuous>::new_from_scalar(ms_limit)
42+
.bode(0.1, 100.)
43+
.into(),
44+
);
45+
// uncomment if you want to see the progression of the search
46+
// bode_plot.show(600, 400, "Candidate Sensitivity Function").unwrap();
47+
if max_sensitivity < ms_limit {
48+
println!("Found Kp = {}, which gives Ms = {}", kp, max_sensitivity);
49+
controller = Some(controller_i);
50+
break;
51+
}
52+
}
53+
let controller = controller.unwrap();
54+
55+
// Lets look at the properties of the controller and closed loop system
56+
57+
println!("Open Loop transfer function");
58+
let open_loop = controller.series(&plant);
59+
let mut bode_plot = BodePlot::new(BodePlotOptions::default());
60+
bode_plot.add_system(open_loop.bode(0.1, 100.0).into());
61+
bode_plot.show(600, 400, "Open Loop").unwrap();
62+
63+
println!("Sensitivity Function bode plot");
64+
let sensitivity_func = Tf::new_from_scalar(1.0).feedback(&open_loop);
65+
bode_plot = BodePlot::new(BodePlotOptions::default());
66+
bode_plot.add_system(sensitivity_func.bode(0.1, 100.).into());
67+
bode_plot.add_system(
68+
Tf::<f64, Continuous>::new_from_scalar(ms_limit)
69+
.bode(0.1, 100.)
70+
.into(),
71+
);
72+
bode_plot.show(600, 400, "Sensitivity Function").unwrap();
73+
74+
println!("Nyquist plot of open loop");
75+
let mut nyq_plot = NyquistPlot::new(NyquistPlotOptions::default());
76+
nyq_plot.add_system(open_loop.nyquist(0.1, 100.).into());
77+
nyq_plot.show(400, 400, "Nyquist Open Loop").unwrap();
78+
79+
println!("Tracking Function bode plot");
80+
let tracking_func = open_loop.feedback(&Tf::new_from_scalar(1.0));
81+
let mut bode_plot = BodePlot::new(BodePlotOptions::default());
82+
bode_plot.add_system(tracking_func.bode(0.1, 100.).into());
83+
bode_plot
84+
.show(600, 400, "Tracking Function Bode Plot")
85+
.unwrap();
86+
87+
println!("Location of poles: [re, im]");
88+
let poles = tracking_func.to_ss().unwrap().poles();
89+
for pole in poles {
90+
print!("[{:.2}, {:.2}] ", pole.re, pole.im);
91+
}
92+
println!();
93+
}

src/analysis/frequency_response.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,11 @@ fn freq_response_ss_mat(
195195
assert_eq!(m, 1, "Function is only tested for SISO systems");
196196
assert_eq!(p, 1, "Function is only tested for SISO systems");
197197

198+
if n == 0 {
199+
let res = DMatrix::from_iterator(p, m, d.iter().map(|&x| c64(x, 0.0)));
200+
return Ok(res);
201+
}
202+
198203
let baleig = CString::new("A").unwrap(); // balance A and compute condition number
199204
let inita = CString::new("G").unwrap(); // general A matrix
200205

@@ -272,7 +277,7 @@ fn freq_response_ss_mat(
272277

273278
#[cfg(test)]
274279
mod tests {
275-
use crate::{FrequencyResponse, Tf};
280+
use crate::{Continuous, FrequencyResponse, Ss, Tf, utils::traits::Mag2Db};
276281

277282
use super::log_space;
278283
use approx::assert_abs_diff_eq;
@@ -317,4 +322,15 @@ mod tests {
317322
assert_abs_diff_eq!(res_tf.im, res_tf.im, epsilon = 1e-3);
318323
}
319324
}
325+
326+
#[test]
327+
fn ss_freq_resp_edge_cases() {
328+
let gain = 3.0;
329+
let ss = Ss::<Continuous>::new_from_scalar(gain);
330+
let mag_phase_freq = ss.bode(0.1, 10.);
331+
for [mag, phase, _] in mag_phase_freq {
332+
assert_abs_diff_eq!(mag, gain.mag2db(), epsilon = 1e-6);
333+
assert_abs_diff_eq!(phase, 0.);
334+
}
335+
}
320336
}

src/lib.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,21 @@ let sys = 1.0 / Tf::s();
2424
let pi_c = 1.0 + 1.0 / Tf::s();
2525
2626
let openloop = sys * pi_c * tf_lp;
27-
let tracking = openloop.clone() / (1.0 + openloop);
27+
let tracking = openloop.feedback(&Tf::new_from_scalar(1.0));
2828
```
2929
3030
State-Space representations is also implemented and you can convert between transfer function and state-space
3131
3232
```rust
3333
use control_systems_torbox::*;
34-
use nalgebra::DMatrix;
35-
36-
let a = DMatrix::from_row_slice(2, 2, &[0., 0., 1., 0.]);
37-
let b = DMatrix::from_row_slice(2, 1, &[1., 0.]);
38-
let c = DMatrix::from_row_slice(1, 2, &[0., 1.]);
39-
let d = DMatrix::from_row_slice(1, 1, &[0.]);
34+
use nalgebra::dmatrix;
35+
36+
let a = dmatrix![0., 1.;
37+
0., 0.];
38+
let b = dmatrix![0.;
39+
1.];
40+
let c = dmatrix![1., 0.];
41+
let d = dmatrix![0.];
4042
4143
let sys_ss = Ss::<Continuous>::new(a, b, c, d).unwrap();
4244
let sys_tf = sys_ss.to_tf().unwrap();

src/systems/state_space.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ impl<U: Time + 'static> Ss<U> {
394394
/// # Panics
395395
/// This function will panic if the input-output dimensions of `self` and
396396
/// `sys2` do not match.
397-
pub fn feedback(self, sys2: Self) -> Self {
397+
pub fn feedback(self, sys2: &Self) -> Self {
398398
assert_eq!(self.ninputs(), sys2.noutputs());
399399
assert_eq!(self.noutputs(), sys2.ninputs());
400400
self.assert_valid();
@@ -960,12 +960,12 @@ mod tests {
960960
let ss1 = (Tf::s() / (Tf::s() + 1.0)).to_ss().unwrap();
961961
let ss2 = (1.0 / Tf::s()).to_ss().unwrap();
962962

963-
let ss_fb = ss1.clone().feedback(ss2.clone());
963+
let ss_fb = ss1.clone().feedback(&ss2);
964964
let tf_fb = ss_fb.to_tf().unwrap();
965965
assert_abs_diff_eq!(tf_fb, Tf::s() / (Tf::s() + 2.0));
966966

967967
let ss2 = Ss::<Continuous>::new_from_scalar(1.0);
968-
let ss_fb = ss1.clone().feedback(ss2.clone());
968+
let ss_fb = ss1.clone().feedback(&ss2);
969969
let tf_fb = ss_fb.to_tf().unwrap();
970970
assert_abs_diff_eq!(tf_fb, 0.5 * Tf::s() / (Tf::s() + 0.5));
971971

@@ -989,7 +989,7 @@ mod tests {
989989
let ss1 = tf1.to_ss_method(ControllableCF).unwrap();
990990
let ss2 = tf2.to_ss_method(ObservableCF).unwrap();
991991

992-
let ss_fb = ss1.feedback(ss2);
992+
let ss_fb = ss1.feedback(&ss2);
993993
let ss_fb = ss_fb.to_tf().unwrap();
994994
let tf_fb = tf1.feedback(&tf2);
995995

0 commit comments

Comments
 (0)