Skip to content
Snippets Groups Projects
Select Git revision
  • dd7bfe04e894689f2c70c60b06a07fed988d560b
  • master default
  • anders.blomdell
  • typeref
  • pragma
  • compiler-refactoring
  • labcomm2013
  • v2014.1
  • v2014.0
  • v2013.0
10 results

thr_decoder.c

Blame
  • Forked from Anders Blomdell / LabComm
    Source project has a limited visibility.
    lib.rs 21.36 KiB
    #![warn(clippy::all, rust_2018_idioms)]
    
    mod transfer_functions;
    
    #[allow(unused_imports)]
    use basic_print::basic_print; // basic print for print-debugging
    
    use frequency_response_app::FreqResp;
    use pole_position_app::PolePos;
    
    pub struct ControlApp {
        cur_app_idx: Option<usize>,
        apps: Vec<Box<dyn CentralApp>>,
    }
    
    trait CentralApp {
        fn get_label(&self) -> &str;
        fn draw_app(&mut self, ui: &mut egui::Ui);
    }
    
    impl eframe::App for ControlApp {
        fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
            // Set light theme so we don't have to mess with colors for plots
            ctx.set_visuals(egui::Visuals::light());
    
            egui::TopBottomPanel::top("app_selection_panel").show(ctx, |ui| {
                if self.top_bar(ui) {
                    #[cfg(not(target_arch = "wasm32"))] // no quit on web pages!
                    _frame.close();
                }
            });
    
            egui::CentralPanel::default().show(ctx, |ui| {
                ui.centered_and_justified(|ui| {
                    match self.cur_app_idx {
                        None => {
                            ui.label("Select an application in the bar above.");
                        }
                        Some(idx) => {
                            self.apps[idx].draw_app(ui);
                        }
                    };
                });
            });
        }
    }
    
    impl ControlApp {
        pub fn new(_cc: &eframe::CreationContext<'_>) -> Self {
            // This is also where you can customized the look at feel of egui using
            // `cc.egui_ctx.set_visuals` and `cc.egui_ctx.set_fonts`.
    
            let apps: Vec<Box<dyn CentralApp>> = vec![
                Box::new(PolePos::new("Pole Positioning".to_string())),
                Box::new(FreqResp::new("Frequency Response".to_string())),
            ];
    
            if cfg!(debug_assertions) {
                ControlApp {
                    cur_app_idx: Some(0),
                    apps,
                }
            } else {
                ControlApp {
                    cur_app_idx: None,
                    apps,
                }
            }
        }
    
        fn top_bar(&mut self, ui: &mut egui::Ui) -> bool {
            #[allow(unused_mut)]
            let mut quit = false;
    
            ui.horizontal_wrapped(|ui| {
                ui.heading("Control Apps");
                ui.separator();
    
                for (idx, app) in self.apps.iter().enumerate() {
                    let checked = match self.cur_app_idx {
                        Some(cur) => cur == idx,
                        None => false,
                    };
                    if ui.selectable_label(checked, app.get_label()).clicked() {
                        if checked {
                            self.cur_app_idx = None;
                        } else {
                            self.cur_app_idx = Some(idx);
                        }
                    }
                }
    
                ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
                    #[cfg(not(target_arch = "wasm32"))] // no quit on web pages!
                    {
                        if ui.button("Quit").clicked() {
                            quit = true;
                        }
                        ui.separator();
                    }
                    egui::warn_if_debug_build(ui);
                });
            });
    
            return quit;
        }
    }
    
    mod pole_position_app {
        #[allow(unused_imports)]
        use basic_print::basic_print; // basic print for print-debugging
    
        use egui::{Ui, Vec2};
    
        use crate::transfer_functions::*;
        use crate::CentralApp;
    
        use super::tf_plots;
    
        #[derive(PartialEq, Debug, Clone, Copy)]
        enum Systems {
            FirstOrder,
            RealSecondOrder,
            RealSecondOrderAndZero,
            ComplexSecondOrder,
        }
    
        #[derive(PartialEq, Debug, Clone, Copy)]
        enum Display {
            StepResponse,
            BodeDiagram,
        }
    
        #[derive(Debug)]
        pub struct PolePos {
            label: String,
    
            systype: Systems,
            display: Display,
    
            first: FirstOrderSystem,
            second: RealSecondOrderSystem,
            second_zero: RealSecondOrderSystemAndZero,
            complex: ComplexSecondOrderSystem,
    
            pole_drag_offset: Option<(f64, f64)>,
        }
    
        impl PolePos {
            pub fn new(label: String) -> PolePos {
                PolePos {
                    label,
                    systype: Systems::FirstOrder,
                    display: Display::StepResponse,
                    first: FirstOrderSystem { 
                        T: 1.0, T_lower: 0.1, T_upper: 500.0,
                        K: 1.0, K_lower: -2.0, K_upper: 2.0,
                        L: 0.0, L_lower: 0.0, L_upper: 5.0,
                    },
                    second: RealSecondOrderSystem {
                        T1: 1.0, T2: 2.0, T_lower: 0.1, T_upper: 500.0,
                        K: 1.0, K_lower: -2.0, K_upper: 2.0,
                        L: 0.0, L_lower: 0.0, L_upper: 5.0,
                    },
                    second_zero: RealSecondOrderSystemAndZero {
                        T1: 0.5, T2: 0.75, T_lower: 0.1, T_upper: 500.0,
                        Tz: 1.0, Tz_lower: -10.0, Tz_upper: 10.0,
                        K: 1.0, K_lower: -2.0, K_upper: 2.0,
                        L: 0.0, L_lower: 0.0, L_upper: 5.0,
                    },
                    complex: ComplexSecondOrderSystem {
                        d: 0.7, d_lower: 0.01, d_upper: 5.0,
                        w: 1.0, w_lower: 0.01, w_upper: 5.0,
                        K: 1.0, K_lower: -2.0, K_upper: 2.0,
                        L: 0.0, L_lower: 0.0, L_upper: 5.0,
                    },
                    pole_drag_offset: None,
                }
            }
    
            fn pole_plot(&mut self, ui: &mut Ui, width: f32, height: f32) {
                let (dragged, pointer_coordinate) = match self.systype {
                    Systems::FirstOrder => tf_plots::pole_plot(&self.first, ui, width, height),
                    Systems::RealSecondOrder => tf_plots::pole_plot(&self.second, ui, width, height),
                    Systems::RealSecondOrderAndZero => tf_plots::pole_plot(&self.second_zero, ui, width, height),
                    Systems::ComplexSecondOrder => tf_plots::pole_plot(&self.complex, ui, width, height),
                };
    
                // Handle dragging
                if dragged {
                    if let Some((re,im)) = pointer_coordinate { // This should never fail
                        match self.systype {
                            Systems::FirstOrder => self.first.adjust_pole_zero(re, im),
                            Systems::RealSecondOrder => self.second.adjust_pole_zero(re, im),
                            Systems::RealSecondOrderAndZero => self.second_zero.adjust_pole_zero(re, im),
                            Systems::ComplexSecondOrder => self.complex.adjust_pole_zero(re, im),
                        };
                    }
                } else {
                    self.pole_drag_offset = None;
                }
            }
    
            fn step_response_plot(&mut self, ui: &mut Ui, width: f32, height: f32) {
                let (_dragged, _pointer_coordinate) = match self.systype {
                    Systems::FirstOrder => tf_plots::step_response_plot(&self.first, ui, width, height),
                    Systems::RealSecondOrder => tf_plots::step_response_plot(&self.second, ui, width, height),
                    Systems::RealSecondOrderAndZero => tf_plots::step_response_plot(&self.second_zero, ui, width, height),
                    Systems::ComplexSecondOrder => tf_plots::step_response_plot(&self.complex, ui, width, height),
                };
            }
    
            fn bode_plot(&mut self, ui: &mut Ui, width: f32, height: f32) {
                let (_amp_dragged, _amp_pointer, _ph_dragged, _ph_pointer) = match self.systype {
                    Systems::FirstOrder => tf_plots::bode_plot(&self.first, ui, width, height),
                    Systems::RealSecondOrder => tf_plots::bode_plot(&self.second, ui, width, height),
                    Systems::RealSecondOrderAndZero => tf_plots::bode_plot(&self.second_zero, ui, width, height),
                    Systems::ComplexSecondOrder => tf_plots::bode_plot(&self.complex, ui, width, height),
                };
            }
    
            fn order_selection(&mut self, ui: &mut Ui) {
                egui::ComboBox::from_label("System type")
                    .selected_text(match self.systype {
                        Systems::FirstOrder => "First order",
                        Systems::RealSecondOrder => "Second order",
                        Systems::RealSecondOrderAndZero => "Second order with zero",
                        Systems::ComplexSecondOrder => "Second order complex",
                    })
                    .show_ui(ui, |ui| {
                        ui.selectable_value(&mut self.systype, Systems::FirstOrder, "First order");
                        ui.selectable_value(&mut self.systype, Systems::RealSecondOrder, "Second order");
                        ui.selectable_value(&mut self.systype, Systems::RealSecondOrderAndZero, "Second order with zero");
                        ui.selectable_value(&mut self.systype, Systems::ComplexSecondOrder, "Second order complex");
                    }
                );
            }
    
            fn display_selection(&mut self, ui: &mut Ui) {
                ui.heading("Select Display");
                ui.horizontal(|ui| {
                    ui.radio_value(&mut self.display, Display::StepResponse, "Step Response");
                    ui.radio_value(&mut self.display, Display::BodeDiagram, "Bode Diagram");
                });
            }
    
            fn parameter_sliders(&mut self, ui: &mut Ui) {
                match self.systype {
                    Systems::FirstOrder => {
                        ui.heading("G(s) = K * exp(-Ls) / (s*T + 1)");
                        ui.add(egui::Slider::new(&mut self.first.T, self.first.T_lower..=self.first.T_upper).text("T").logarithmic(true));
                        ui.add(egui::Slider::new(&mut self.first.K, self.first.K_lower..=self.first.K_upper).text("K"));
                        ui.add(egui::Slider::new(&mut self.first.L, self.first.L_lower..=self.first.L_upper).text("L"));
                    }
                    Systems::RealSecondOrder => {
                        ui.heading("G(s) = K * exp(-Ls) / ((s*T1 + 1) * (s*T2 + 1))");
                        ui.add(egui::Slider::new(&mut self.second.T1, self.second.T_lower..=self.second.T_upper).text("T1").logarithmic(true));
                        ui.add(egui::Slider::new(&mut self.second.T2, self.second.T_lower..=self.second.T_upper).text("T2").logarithmic(true));
                        ui.add(egui::Slider::new(&mut self.second.K, self.second.K_lower..=self.second.K_upper).text("K"));
                        ui.add(egui::Slider::new(&mut self.second.L, self.second.L_lower..=self.second.L_upper).text("L"));
                    }
                    Systems::RealSecondOrderAndZero => {
                        ui.heading("G(s) = K * exp(-Ls) * (s*Tz-1) / ((s*T1 + 1) * (s*T2 + 1))");
                        ui.add(egui::Slider::new(&mut self.second_zero.T1, self.second_zero.T_lower..=self.second_zero.T_upper).text("T1").logarithmic(true));
                        ui.add(egui::Slider::new(&mut self.second_zero.T2, self.second_zero.T_lower..=self.second_zero.T_upper).text("T2").logarithmic(true));
                        ui.add(egui::Slider::new(&mut self.second_zero.Tz, self.second_zero.Tz_lower..=self.second_zero.Tz_upper).text("Tz"));
                        ui.add(egui::Slider::new(&mut self.second_zero.K, self.second_zero.K_lower..=self.second_zero.K_upper).text("K"));
                        ui.add(egui::Slider::new(&mut self.second_zero.L, self.second_zero.L_lower..=self.second_zero.L_upper).text("L"));
                    }
                    Systems::ComplexSecondOrder => {
                        ui.heading("G(s) = K * exp(-Ls) * ω^2 / (s^2 + 2δωs + ω^2)");
                        ui.add(egui::Slider::new(&mut self.complex.d, self.complex.d_lower..=self.complex.d_upper).text("δ"));
                        ui.add(egui::Slider::new(&mut self.complex.w, self.complex.w_lower..=self.complex.w_upper).text("ω"));
                        ui.add(egui::Slider::new(&mut self.complex.K, self.complex.K_lower..=self.complex.K_upper).text("K"));
                        ui.add(egui::Slider::new(&mut self.complex.L, self.complex.L_lower..=self.complex.L_upper).text("L"));
                    }
                };
            }
        }
    
        impl CentralApp for PolePos {
            fn draw_app(&mut self, ui: &mut Ui) {
                // ui.spacing_mut().item_spacing.x = 10.0;
    
                let max_width = 550.0;
                let Vec2 { x, y } = ui.available_size();
                let is_vertical = x < max_width;
    
                if is_vertical {
                    egui::Grid::new("app_grid").num_columns(1).show(ui, |ui| {
                        ui.vertical(|ui| {
                            self.order_selection(ui);
                            ui.separator();
                            self.display_selection(ui);
                            ui.separator();
                            self.parameter_sliders(ui);
                            ui.separator();
                        });
                        ui.end_row();
    
                        let Vec2 { x, y } = ui.available_size();
                        let mut width = x;
                        let mut height = y / 2.0;
    
                        if width >= height * 1.75 {
                            width = height * 1.75
                        } else {
                            height = width / 1.75
                        }
    
                        self.pole_plot(ui, width, height);
                        ui.end_row();
    
                        match self.display {
                            Display::StepResponse => self.step_response_plot(ui, width, height),
                            Display::BodeDiagram => self.bode_plot(ui, width, height),
                        };
                    });
                } else {
                    let mut width = (x / 2.0).min(max_width);
                    let mut height = y / 2.0;
    
                    if width >= height * 1.75 {
                        width = height * 1.75
                    } else {
                        height = width / 1.75
                    }
    
                    egui::Grid::new("app_grid").num_columns(2).show(ui, |ui| {
                        ui.vertical(|ui| {
                            self.order_selection(ui);
                            ui.add_space(20.0);
                            self.parameter_sliders(ui);
                        });
    
                        self.pole_plot(ui, width, height);
    
                        ui.end_row();
                        self.step_response_plot(ui, width, height);
                        self.bode_plot(ui, width, height);
                    });
                }
            }
    
            fn get_label(&self) -> &str {
                &self.label
            }
        }
    }
    
    
    
    
    mod tf_plots {
        use egui::plot::{ Line, LineStyle, MarkerShape, Plot, PlotPoints, PlotUi, Points, };
        use egui::{ Align, Color32, InnerResponse, Layout, Ui, Vec2, };
    
        use std::f64::consts::PI;
        use std::ops::Range;
    
        use crate::transfer_functions::*;
    
        // Helper that give a sane default plot window. Looks can be modified with the second to last
        // argument and what is plotted is given by the last. Returns whether the plot is dragged by
        // the mouse and the plot coordinate of the mouse.
        fn plot_show(
            ui: &mut Ui,
            title: &str,
            width: f32,
            height: f32,
            x_bounds: Range<f64>,
            y_bounds: Range<f64>,
            plot_mod_fn: impl FnOnce(Plot) -> Plot,
            build_fn: impl FnOnce(&mut PlotUi),
        ) -> (bool, Option<(f64, f64)>)
        {
            let InnerResponse {
                response: _,
                inner: (dragged, pointer_coordinate),
            } = ui.allocate_ui_with_layout(
                Vec2 {
                    x: width,
                    y: height,
                },
                Layout::top_down(Align::LEFT),
                |ui| {
                    ui.heading(title);
    
                    let mut plot = Plot::new(title)
                        .allow_scroll(false)
                        .allow_zoom(true)
                        .allow_boxed_zoom(false)
                        .allow_drag(false)
                        .show_x(false)
                        .show_y(false)
                        .include_x(x_bounds.start)
                        .include_x(x_bounds.end)
                        .include_y(y_bounds.start)
                        .include_y(y_bounds.end)
                        .set_margin_fraction(Vec2 { x: 0.0, y: 0.0 });
    
                    plot = plot_mod_fn(plot);
    
                    let InnerResponse {
                        response: show_response,
                        inner: pointer_coordinate,
                    } = plot.show(ui, |plot_ui| {
                        build_fn(plot_ui);
                        plot_ui.pointer_coordinate().map(|pp| (pp.x, pp.y))
                    });
    
                    (show_response.dragged(), pointer_coordinate)
                },
            );
    
            (dragged, pointer_coordinate)
        }
    
        pub fn pole_plot(
            tf: &impl TransferFunction,
            ui: &mut Ui,
            width: f32,
            height: f32,
        ) -> (bool, Option<(f64, f64)>)
        {
            // Plot params
            let marker_radius = 10.0;
            let re_bounds = -3.55..1.1;
            let im_bounds = -1.5..1.5;
    
            // Plot points
            let pole_data = Points::new(tf.poles());
            let zero_data = Points::new(tf.zeros());
            let unit_circle = Line::new(PlotPoints::from_parametric_callback(
                |t| (t.sin(), t.cos()),
                0.0..(2.1 * PI),
                100,
            ));
    
            // Plot
            plot_show(
                ui,
                "Pole Placement",
                width,
                height,
                re_bounds,
                im_bounds,
                |plot| plot.data_aspect(1.0),
                |plot_ui| {
                    plot_ui.line(unit_circle.color(Color32::GRAY));
                    plot_ui.points(
                        pole_data.shape(MarkerShape::Cross)
                            .color(Color32::BLACK)
                            .radius(marker_radius),
                    );
                    plot_ui.points(
                        zero_data.shape(MarkerShape::Circle)
                            .color(Color32::BLACK)
                            .radius(marker_radius)
                            .filled(false),
                    );
                },
            )
        }
    
        pub fn step_response_plot(
            tf: &impl TransferFunction,
            ui: &mut Ui,
            width: f32,
            height: f32,
        ) -> (bool, Option<(f64, f64)>)
        {
            // Plot params
            let n_samples = 100;
            let t_end = 10.0;
            let pad_ratio = 0.1;
    
            // Calculate plot bounds
            let t_bounds = (0.0 - t_end * pad_ratio)..(t_end + t_end * pad_ratio);
            let y_bounds = (0.0 - pad_ratio)..(1.5 + pad_ratio);
    
            // Calc plot data
            let step = (t_bounds.end - t_bounds.start) / ((n_samples - 1) as f64);
            let mut points: Vec<[f64; 2]> = Vec::new();
            for i in 0..n_samples {
                let t = t_bounds.start + step * (i as f64);
                points.push([t, tf.step_response(t)]);
            }
            let data = Line::new(points);
    
            // Plot
            plot_show(
                ui,
                "Step Response",
                width,
                height,
                t_bounds,
                y_bounds,
                |plot| plot,
                |plot_ui| {
                    plot_ui.line(data.color(Color32::RED).style(LineStyle::Solid));
                },
            )
        }
    
        pub fn bode_plot(
            tf: &impl TransferFunction,
            ui: &mut Ui,
            width: f32,
            height: f32,
        ) -> (bool, Option<(f64, f64)>, bool, Option<(f64, f64)>)
        {
            // Plot params
            let n_samples = 200;
            let w_bounds_exp = -3.0..2.0;
    
            // Calc plot data
            let step = (w_bounds_exp.end - w_bounds_exp.start) / ((n_samples - 1) as f64);
            let mut amp_points: Vec<[f64; 2]> = Vec::new();
            let mut phase_points: Vec<[f64; 2]> = Vec::new();
            for i in 0..n_samples {
                let we = w_bounds_exp.start + step * (i as f64);
                let w = 10f64.powf(we);
                amp_points.push([we, tf.bode_amplitude(w).log10()]);
                phase_points.push([we, tf.bode_phase(w)]);
            }
            let amp_data = Line::new(amp_points);
            let phase_data = Line::new(phase_points);
    
            // Plot
            let InnerResponse {
                response: _,
                inner: (amp_dragged, amp_pointer, ph_dragged, ph_pointer),
            } = ui.allocate_ui_with_layout(
                Vec2 {
                    x: width,
                    y: height,
                },
                Layout::top_down(Align::LEFT),
                |ui| {
                    let height = (height - ui.spacing().item_spacing.y) / 2.0;
                    let (amp_dragged, amp_pointer) = plot_show(
                        ui,
                        "Bode Plot - Amplitude",
                        width,
                        height,
                        w_bounds_exp.clone(),
                        -4.0..15f64.log10(),
                        |plot| plot,
                        |plot_ui| {
                            plot_ui.line(amp_data.color(Color32::RED).style(LineStyle::Solid));
                        },
                        );
                    let (ph_dragged, ph_pointer) = plot_show(
                        ui,
                        "Bode Plot - Phase",
                        width,
                        height,
                        w_bounds_exp.clone(),
                        -PI / 0.95..PI / 4.0,
                        |plot| plot,
                        |plot_ui| {
                            plot_ui.line(phase_data.color(Color32::RED).style(LineStyle::Solid));
                        },
                        );
                    (amp_dragged, amp_pointer, ph_dragged, ph_pointer)
                },
                );
    
            (amp_dragged, amp_pointer, ph_dragged, ph_pointer)
        }
    }
    
    
    
    
    
    mod frequency_response_app {
        use crate::CentralApp;
    
        pub struct FreqResp {
            label: String,
        }
    
        impl FreqResp {
            pub fn new(label: String) -> FreqResp {
                FreqResp { label }
            }
        }
    
        impl CentralApp for FreqResp {
            fn draw_app(&mut self, ui: &mut egui::Ui) {
                ui.label("Frequency response app currently not implemented.");
            }
    
            fn get_label(&self) -> &str {
                &self.label
            }
        }
    }