diff --git a/TODO.md b/TODO.md index a8e8205..833eb29 100644 --- a/TODO.md +++ b/TODO.md @@ -9,10 +9,9 @@ 3. Smart display of graph - Display of intersections between functions - Display of roots - - Local maximums and minimums (should be easy) 4. Fix integral line 5. re-add euler's number (well it works if you use capital e like `E^x`) -6. allow constants in min/max integral input +6. allow constants in min/max integral input (like pi or euler's number) 7. sliding values for functions (like a user-interactable slider that adjusts a variable in the function, like desmos) 8. Keybinds 9. nth derivative support (again) \ No newline at end of file diff --git a/src/function.rs b/src/function.rs index 46ee7d8..90c1783 100644 --- a/src/function.rs +++ b/src/function.rs @@ -123,27 +123,19 @@ impl FunctionEntry { ); // assert_eq!(self.output.back.as_ref().unwrap().len(), self.pixel_width); - if self.output.derivative.is_some() { - if self.derivative { - let derivative_cache = self.output.derivative.as_ref().unwrap(); + let derivative_cache = self.output.derivative.as_ref().unwrap(); + let new_data = (0..self.pixel_width) + .map(|x| (x as f64 / resolution as f64) + min_x) + .map(|x| { + if let Some(i) = x_data.get_index(x) { + derivative_cache[i] + } else { + Value::new(x, self.function.derivative(x)) + } + }) + .collect(); - self.output.derivative = Some( - (0..self.pixel_width) - .map(|x| (x as f64 / resolution as f64) + min_x) - .map(|x| { - if let Some(i) = x_data.get_index(x) { - derivative_cache[i] - } else { - Value::new(x, self.function.derivative(x)) - } - }) - .collect(), - ); - // assert_eq!(self.output.derivative.as_ref().unwrap().len(), self.pixel_width); - } else { - self.output.invalidate_derivative(); - } - } + self.output.derivative = Some(new_data); } else { self.output.invalidate_back(); self.output.invalidate_derivative(); @@ -168,19 +160,16 @@ impl FunctionEntry { self.output.back.as_ref().unwrap().clone() }; - let derivative_values: Option> = match self.derivative { - true => { - if self.output.derivative.is_none() { - self.output.derivative = Some( - (0..self.pixel_width) - .map(|x| (x as f64 / resolution as f64) + self.min_x) - .map(|x| Value::new(x, self.function.derivative(x))) - .collect(), - ); - } - Some(self.output.derivative.as_ref().unwrap().clone()) + let derivative_values: Option> = { + if self.output.derivative.is_none() { + self.output.derivative = Some( + (0..self.pixel_width) + .map(|x| (x as f64 / resolution as f64) + self.min_x) + .map(|x| Value::new(x, self.function.derivative(x))) + .collect(), + ); } - false => None, + Some(self.output.derivative.as_ref().unwrap().clone()) }; let integral_data = match self.integral { @@ -287,17 +276,46 @@ impl FunctionEntry { self } + // Finds extrema + fn extrema(&mut self) { + let mut extrama_list: Vec = Vec::new(); + let mut last_ele: Option = None; + for ele in self.output.derivative.as_ref().unwrap().iter() { + if last_ele.is_none() { + last_ele = Some(*ele); + continue; + } + + if last_ele.unwrap().y.signum() != ele.y.signum() { + // Do 10 iterations of newton's method, should be more than accurate + let x = { + let mut x1: f64 = last_ele.unwrap().x; + for _ in 0..10 { + x1 = last_ele.unwrap().x + - (self.function.derivative(x1) / self.function.get_derivative_2(x1)) + } + x1 + }; + extrama_list.push(Value::new(x, self.function.get(x))); + } + last_ele = Some(*ele); + } + self.output.extrema = Some(extrama_list); + } + pub fn display(&mut self, plot_ui: &mut PlotUi) -> f64 { let (back_values, integral, derivative) = self.run_back(); self.output.back = Some(back_values); self.output.integral = integral; self.output.derivative = derivative; + self.extrema(); self.output.display( plot_ui, self.get_func_str(), &self.function.get_derivative_str(), (self.integral_min_x - self.integral_max_x).abs() / (self.integral_num as f64), + self.derivative, ) } } diff --git a/src/function_output.rs b/src/function_output.rs index b11780c..33e34c6 100644 --- a/src/function_output.rs +++ b/src/function_output.rs @@ -1,6 +1,6 @@ use eframe::{ egui::{ - plot::{BarChart, Line, PlotUi, Value, Values}, + plot::{BarChart, Line, PlotUi, Points, Value, Values}, widgets::plot::Bar, }, epaint::Color32, @@ -13,24 +13,16 @@ pub struct FunctionOutput { pub(crate) back: Option>, pub(crate) integral: Option<(Vec, f64)>, pub(crate) derivative: Option>, + pub(crate) extrema: Option>, } impl FunctionOutput { - pub fn new( - back: Option>, integral: Option<(Vec, f64)>, derivative: Option>, - ) -> Self { - Self { - back, - integral, - derivative, - } - } - pub fn new_empty() -> Self { Self { back: None, integral: None, derivative: None, + extrema: None, } } @@ -38,6 +30,7 @@ impl FunctionOutput { self.back = None; self.integral = None; self.derivative = None; + self.extrema = None; } pub fn invalidate_back(&mut self) { self.back = None; } @@ -48,17 +41,30 @@ impl FunctionOutput { pub fn display( &self, plot_ui: &mut PlotUi, func_str: &str, derivative_str: &str, step: f64, + derivative_enabled: bool, ) -> f64 { plot_ui.line( Line::new(Values::from_values(self.back.clone().unwrap())) .color(Color32::RED) .name(func_str), ); - if let Some(derivative_data) = self.derivative.clone() { - plot_ui.line( - Line::new(Values::from_values(derivative_data)) - .color(Color32::GREEN) - .name(derivative_str), + + if derivative_enabled { + if let Some(derivative_data) = self.derivative.clone() { + plot_ui.line( + Line::new(Values::from_values(derivative_data)) + .color(Color32::GREEN) + .name(derivative_str), + ); + } + } + + if let Some(extrema_data) = self.extrema.clone() { + plot_ui.points( + Points::new(Values::from_values(extrema_data)) + .color(Color32::YELLOW) + .name("Extrema") + .radius(5.0), ); } diff --git a/src/parsing.rs b/src/parsing.rs index 37b2e72..2e1edb9 100644 --- a/src/parsing.rs +++ b/src/parsing.rs @@ -1,22 +1,30 @@ use exmex::prelude::*; +lazy_static::lazy_static! { + static ref EMPTY_FUNCTION: FlatEx = exmex::parse::("0").unwrap(); +} + #[derive(Clone)] pub struct BackingFunction { function: FlatEx, derivative_1: FlatEx, - // derivative_2: FlatEx, + derivative_2: FlatEx, } impl BackingFunction { pub fn new(func_str: &str) -> Self { let function = exmex::parse::(func_str).unwrap(); - let derivative_1 = function.partial(0).unwrap_or_else(|_| function.clone()); - // let derivative_2 = function.partial(0).unwrap_or(derivative_1.clone()); + let derivative_1 = function + .partial(0) + .unwrap_or_else(|_| EMPTY_FUNCTION.clone()); + let derivative_2 = function + .partial_iter([0, 0].iter()) + .unwrap_or_else(|_| EMPTY_FUNCTION.clone()); Self { function, derivative_1, - // derivative_2, + derivative_2, } } @@ -27,6 +35,10 @@ impl BackingFunction { pub fn get(&self, x: f64) -> f64 { self.function.eval(&[x]).unwrap_or(f64::NAN) } pub fn derivative(&self, x: f64) -> f64 { self.derivative_1.eval(&[x]).unwrap_or(f64::NAN) } + + pub fn get_derivative_2(&self, x: f64) -> f64 { + self.derivative_2.eval(&[x]).unwrap_or(f64::NAN) + } } lazy_static::lazy_static! {