From e46040d616578dac1f36ec831894071694916d70 Mon Sep 17 00:00:00 2001 From: Simon Gardling Date: Tue, 12 Apr 2022 12:47:36 -0400 Subject: [PATCH] nth derivative --- TODO.md | 11 ++++---- src/consts.rs | 4 +-- src/function.rs | 75 +++++++++++++++++++++++++++++++++++++++++++++++++ src/math_app.rs | 10 +++++++ src/parsing.rs | 31 ++++++++++++++++++++ 5 files changed, 123 insertions(+), 8 deletions(-) diff --git a/TODO.md b/TODO.md index b6e96bf..73552d7 100644 --- a/TODO.md +++ b/TODO.md @@ -9,9 +9,8 @@ - Display of intersections between functions 4. Allow constants in min/max integral input (like pi or euler's number) 5. Sliding values for functions (like a user-interactable slider that adjusts a variable in the function, like desmos) -6. nth derivative support (again) -7. Threading (Partially mplemented when running natively) -8. Fix integral display -9. Look into other, better methods of compression that would be faster -10. Better handling of panics and errors to display to the user -11. Turn Dynamic Iterator functions into traits +6. Threading (Partially implemented when running natively) +7. Fix integral display +8. Look into other, better methods of compression that would be faster +9. Better handling of panics and errors to display to the user +10. Turn Dynamic Iterator functions into traits diff --git a/src/consts.rs b/src/consts.rs index 11153e1..cf4cff0 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -50,7 +50,7 @@ pub const DEFAULT_INTEGRAL_NUM: usize = 100; /// Colors used for plotting // Colors commented out are used elsewhere and are not included here for better user experience -pub const COLORS: &[Color32; 14] = &[ +pub const COLORS: &[Color32; 13] = &[ Color32::RED, // Color32::GREEN, // Color32::YELLOW, @@ -65,7 +65,7 @@ pub const COLORS: &[Color32; 14] = &[ Color32::LIGHT_GRAY, Color32::LIGHT_RED, Color32::DARK_GRAY, - Color32::DARK_RED, + // Color32::DARK_RED, Color32::KHAKI, Color32::DARK_GREEN, Color32::DARK_BLUE, diff --git a/src/function.rs b/src/function.rs index bee2307..78b5ef5 100644 --- a/src/function.rs +++ b/src/function.rs @@ -8,6 +8,7 @@ use eframe::{egui, epaint}; use egui::{ plot::{BarChart, PlotUi, Value}, widgets::plot::Bar, + Checkbox, Context, }; use epaint::Color32; use std::fmt::{self, Debug}; @@ -52,15 +53,21 @@ pub struct FunctionEntry { /// If displaying derivatives are enabled (note, they are still calculated for other purposes) pub derivative: bool, + pub nth_derviative: bool, + back_data: Vec, integral_data: Option<(Vec, f64)>, derivative_data: Vec, extrema_data: Vec, root_data: Vec, + nth_derivative_data: Option>, autocomplete: AutoComplete<'static>, test_result: Option, + curr_nth: usize, + + pub settings_opened: bool, } impl Default for FunctionEntry { @@ -73,13 +80,17 @@ impl Default for FunctionEntry { max_x: 1.0, integral: false, derivative: false, + nth_derviative: false, back_data: Vec::new(), integral_data: None, derivative_data: Vec::new(), extrema_data: Vec::new(), root_data: Vec::new(), + nth_derivative_data: None, autocomplete: AutoComplete::default(), test_result: None, + curr_nth: 3, + settings_opened: false, } } } @@ -94,6 +105,32 @@ impl FunctionEntry { self.update_string(&output_string); } + pub fn settings(&mut self, ctx: &Context) { + let mut invalidate_nth = false; + egui::Window::new(format!("Settings: {}", self.raw_func_str)) + .open(&mut self.settings_opened) + .default_pos([200.0, 200.0]) + .resizable(false) + .collapsible(false) + .show(ctx, |ui| { + ui.add(Checkbox::new( + &mut self.nth_derviative, + "Display Nth Derivative", + )); + + if ui + .add(egui::Slider::new(&mut self.curr_nth, 3..=5).text("Nth Derivative")) + .changed() + { + invalidate_nth = true; + } + }); + + if invalidate_nth { + self.invalidate_nth(); + } + } + /// Get function's cached test result pub fn get_test_result(&self) -> &Option { &self.test_result } @@ -263,6 +300,24 @@ impl FunctionEntry { } else { self.invalidate_derivative(); } + + if self.nth_derviative && let Some(nth_derivative_data) = &self.nth_derivative_data { + let new_nth_derivative_data: Vec = dyn_iter(&resolution_iter) + .map(|x| { + if let Some(i) = x_data.get_index(x) { + (*nth_derivative_data)[i] + } else { + Value::new(*x, self.function.get_nth_derivative(self.curr_nth, *x)) + } + }) + .collect(); + + debug_assert_eq!(new_nth_derivative_data.len(), settings.plot_width + 1); + + self.nth_derivative_data = Some(new_nth_derivative_data); + } else { + self.invalidate_nth(); + } } else { self.invalidate_back(); self.invalidate_derivative(); @@ -287,6 +342,14 @@ impl FunctionEntry { debug_assert_eq!(data.len(), settings.plot_width + 1); self.derivative_data = data; } + + if self.nth_derviative && self.nth_derivative_data.is_none() { + let data: Vec = dyn_iter(&resolution_iter) + .map(|x| Value::new(*x, self.function.get_nth_derivative(self.curr_nth, *x))) + .collect(); + debug_assert_eq!(data.len(), settings.plot_width + 1); + self.nth_derivative_data = Some(data); + } } if self.integral { @@ -370,6 +433,15 @@ impl FunctionEntry { ); } + if self.nth_derviative && let Some(nth_derviative) = &self.nth_derivative_data { + plot_ui.line( + (*nth_derviative) + .to_line() + .color(Color32::DARK_RED) + .name(self.function.get_nth_derivative_str()), + ); + } + // Plot integral data match &self.integral_data { Some(integral_data) => { @@ -391,6 +463,7 @@ impl FunctionEntry { self.invalidate_back(); self.invalidate_integral(); self.invalidate_derivative(); + self.invalidate_nth(); self.extrema_data.clear(); self.root_data.clear(); } @@ -404,6 +477,8 @@ impl FunctionEntry { /// Invalidate Derivative data pub fn invalidate_derivative(&mut self) { self.derivative_data.clear(); } + pub fn invalidate_nth(&mut self) { self.nth_derivative_data = None } + /// Runs asserts to make sure everything is the expected value #[cfg(test)] pub fn tests( diff --git a/src/math_app.rs b/src/math_app.rs index 259be8f..d13accb 100644 --- a/src/math_app.rs +++ b/src/math_app.rs @@ -464,9 +464,19 @@ impl MathApp { .clicked(), ); + function.settings_opened.bitxor_assign( + ui.add(Button::new("⚙")) + .on_hover_text(match function.settings_opened { + true => "Close Settings", + false => "Open Settings", + }) + .clicked(), + ); + // Contains the function string in a text box that the user can edit function.auto_complete(ui, i as i32) }); + function.settings(ctx); } // Remove function if the user requests it diff --git a/src/parsing.rs b/src/parsing.rs index 399a050..918b96c 100644 --- a/src/parsing.rs +++ b/src/parsing.rs @@ -16,6 +16,8 @@ pub struct BackingFunction { derivative_1_str: String, /// f''(x) derivative_2: FlatEx, + + nth_derivative: Option<(usize, FlatEx, String)>, } impl BackingFunction { @@ -66,6 +68,7 @@ impl BackingFunction { derivative_1, derivative_1_str, derivative_2, + nth_derivative: None, }) } @@ -84,6 +87,34 @@ impl BackingFunction { pub fn get_derivative_2(&self, x: f64) -> f64 { self.derivative_2.eval(&[x]).unwrap_or(f64::NAN) } + + pub fn get_nth_derivative_str(&self) -> &str { &self.nth_derivative.as_ref().unwrap().2 } + + pub fn get_nth_derivative(&mut self, n: usize, x: f64) -> f64 { + match n { + 0 => self.get(x), + 1 => self.get_derivative_1(x), + 2 => self.get_derivative_2(x), + _ => { + if let Some((curr_n, curr_n_func, _)) = &self.nth_derivative { + if curr_n == &n { + return curr_n_func.eval(&[x]).unwrap_or(f64::NAN); + } + } + let new_func = self + .function + .partial_iter((1..=n).map(|_| 0).collect::>().iter()) + .unwrap_or_else(|_| EMPTY_FUNCTION.clone()); + + self.nth_derivative = Some(( + n, + new_func.clone(), + new_func.unparse().to_owned().replace("{x}", "x"), + )); + return new_func.eval(&[x]).unwrap_or(f64::NAN); + } + } + } } const VALID_VARIABLES: [char; 5] = ['x', 'X', 'e', 'E', 'π'];