|
| 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 | +} |
0 commit comments