diff --git a/parsing/src/lib.rs b/parsing/src/lib.rs index 25fa0b5..5632e51 100644 --- a/parsing/src/lib.rs +++ b/parsing/src/lib.rs @@ -11,7 +11,7 @@ mod suggestions; pub use crate::{ autocomplete::{AutoComplete, Movement}, autocomplete_hashmap::compile_hashmap, - parsing::{process_func_str, BackingFunction}, + parsing::{process_func_str, BackingFunction, FlatExWrapper}, splitting::{split_function, split_function_chars, SplitType}, suggestions::{generate_hint, get_last_term, Hint, HINT_EMPTY, SUPPORTED_FUNCTIONS}, }; diff --git a/parsing/src/parsing.rs b/parsing/src/parsing.rs index 4941868..cbed0dc 100644 --- a/parsing/src/parsing.rs +++ b/parsing/src/parsing.rs @@ -2,13 +2,13 @@ use exmex::prelude::*; use std::collections::HashMap; #[derive(Clone, PartialEq)] -pub(crate) struct FlatExWrapper<'a> { +pub struct FlatExWrapper { func: Option>, - func_str: Option<&'a str>, + func_str: Option, } -impl<'a> FlatExWrapper<'a> { - const EMPTY: FlatExWrapper<'a> = FlatExWrapper { +impl FlatExWrapper { + const EMPTY: FlatExWrapper = FlatExWrapper { func: None, func_str: None, }; @@ -25,7 +25,7 @@ impl<'a> FlatExWrapper<'a> { const fn is_none(&self) -> bool { self.func.is_none() } #[inline] - fn eval(&'a self, x: &[f64]) -> f64 { + pub fn eval(&self, x: &[f64]) -> f64 { self.func .as_ref() .map(|f| f.eval(x).unwrap_or(f64::NAN)) @@ -33,7 +33,7 @@ impl<'a> FlatExWrapper<'a> { } #[inline] - fn partial(&'a self, x: usize) -> Self { + fn partial(&self, x: usize) -> Self { self.func .as_ref() .map(|f| f.clone().partial(x).map(Self::new).unwrap_or(Self::EMPTY)) @@ -41,17 +41,19 @@ impl<'a> FlatExWrapper<'a> { } #[inline] - fn get_string(&'a mut self) -> &'a str { - if let Some(func_str) = self.func_str { - return func_str; + fn get_string(&mut self) -> String { + match self.func_str { + Some(ref func_str) => func_str.clone(), + None => { + let calculated = self.func.as_ref().map(|f| f.unparse()).unwrap_or(""); + self.func_str = Some(calculated.to_owned()); + calculated.to_owned() + } } - let calculated = self.func.as_ref().map(|f| f.unparse()).unwrap_or(""); - self.func_str = Some(calculated); - return calculated; } #[inline] - fn partial_iter(&'a self, n: usize) -> Self { + fn partial_iter(&self, n: usize) -> Self { self.func .as_ref() .map(|f| { @@ -64,20 +66,24 @@ impl<'a> FlatExWrapper<'a> { } } -impl<'a> const Default for FlatExWrapper<'a> { - fn default() -> FlatExWrapper<'a> { FlatExWrapper::EMPTY } +impl const Default for FlatExWrapper { + fn default() -> FlatExWrapper { FlatExWrapper::EMPTY } } /// Function that includes f(x), f'(x), f'(x)'s string representation, and f''(x) #[derive(Clone, PartialEq)] -pub struct BackingFunction<'a> { +pub struct BackingFunction { /// f(x) - function: FlatExWrapper<'a>, + function: FlatExWrapper, /// Temporary cache for nth derivative - nth_derivative: HashMap>, + nth_derivative: HashMap, } -impl<'a> BackingFunction<'a> { +impl Default for BackingFunction { + fn default() -> Self { Self::new("").unwrap() } +} + +impl BackingFunction { pub const fn is_none(&self) -> bool { self.function.is_none() } /// Create new [`BackingFunction`] instance @@ -123,19 +129,31 @@ impl<'a> BackingFunction<'a> { }) } - pub fn get(&'a mut self, derivative: usize, x: f64) -> f64 { - match derivative { - 0 => self.function.eval(&[x]), - - _ => match self.nth_derivative.get(&derivative) { - Some(func) => func.eval(&[x]), - None => { - let new_func = self.function.partial_iter(derivative); - self.nth_derivative.insert(derivative, new_func.clone()); - new_func.eval(&[x]) - } - }, + // TODO rewrite this logic, it's a mess + pub fn generate_derivative(&mut self, derivative: usize) { + if derivative == 0 { + return; } + + if !self.nth_derivative.contains_key(&derivative) { + let new_func = self.function.partial_iter(derivative); + self.nth_derivative.insert(derivative, new_func.clone()); + } + } + + pub fn get_function_derivative(&self, derivative: usize) -> &FlatExWrapper { + if derivative == 0 { + return &self.function; + } else { + return self + .nth_derivative + .get(&derivative) + .unwrap_or(&FlatExWrapper::EMPTY); + } + } + + pub fn get(&mut self, derivative: usize, x: f64) -> f64 { + self.get_function_derivative(derivative).eval(&[x]) } } diff --git a/src/function_entry.rs b/src/function_entry.rs index b65d9d0..1e37b8f 100644 --- a/src/function_entry.rs +++ b/src/function_entry.rs @@ -31,7 +31,7 @@ impl fmt::Display for Riemann { #[derive(Clone)] pub struct FunctionEntry { /// The `BackingFunction` instance that is used to generate `f(x)`, `f'(x)`, and `f''(x)` - function: BackingFunction<'static>, + function: BackingFunction, /// Stores a function string (that hasn't been processed via `process_func_str`) to display to the user pub raw_func_str: String, @@ -98,7 +98,7 @@ impl<'de> Deserialize<'de> for FunctionEntry { } let helper = Helper::deserialize(deserializer)?; - let mut new_func_entry = FunctionEntry::EMPTY; + let mut new_func_entry = FunctionEntry::default(); let gen_func = BackingFunction::new(&helper.raw_func_str); match gen_func { Ok(func) => new_func_entry.function = func, @@ -121,7 +121,25 @@ impl<'de> Deserialize<'de> for FunctionEntry { impl const Default for FunctionEntry { /// Creates default FunctionEntry instance (which is empty) - fn default() -> FunctionEntry {} + fn default() -> FunctionEntry { + FunctionEntry { + function: BackingFunction::default(), + raw_func_str: String::new(), + 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::EMPTY, + test_result: None, + curr_nth: 3, + settings_opened: false, + } + } } impl FunctionEntry { @@ -149,6 +167,7 @@ impl FunctionEntry { }); if invalidate_nth { + self.function.generate_derivative(self.curr_nth); self.clear_nth(); } } @@ -180,7 +199,7 @@ impl FunctionEntry { /// Creates and does the math for creating all the rectangles under the graph fn integral_rectangles( - &self, integral_min_x: f64, integral_max_x: f64, sum: Riemann, integral_num: usize, + &mut self, integral_min_x: f64, integral_max_x: f64, sum: Riemann, integral_num: usize, ) -> (Vec<(f64, f64)>, f64) { let step = (integral_max_x - integral_min_x) / (integral_num as f64); @@ -217,22 +236,24 @@ impl FunctionEntry { /// Helps with processing newton's method depending on level of derivative fn newtons_method_helper( - &self, threshold: f64, derivative_level: usize, range: &std::ops::Range, + &mut self, threshold: f64, derivative_level: usize, range: &std::ops::Range, ) -> Vec { + self.function.generate_derivative(derivative_level); + self.function.generate_derivative(derivative_level + 1); let newtons_method_output: Vec = match derivative_level { 0 => newtons_method_helper( threshold, range, self.back_data.as_slice(), - &|x: f64| self.function.get(0, x), - &|x: f64| self.function.get(1, x), + &self.function.get_function_derivative(0), + &self.function.get_function_derivative(1), ), 1 => newtons_method_helper( threshold, range, self.derivative_data.as_slice(), - &|x: f64| self.function.get(1, x), - &|x: f64| self.function.get(2, x), + &self.function.get_function_derivative(1), + &self.function.get_function_derivative(2), ), _ => unreachable!(), }; @@ -281,6 +302,7 @@ impl FunctionEntry { } if self.derivative_data.is_empty() { + self.function.generate_derivative(1); let data: Vec = resolution_iter .clone() .into_iter() diff --git a/src/function_manager.rs b/src/function_manager.rs index 0334b78..94c1071 100644 --- a/src/function_manager.rs +++ b/src/function_manager.rs @@ -23,7 +23,7 @@ impl Default for FunctionManager { let mut vec: Functions = Vec::with_capacity(COLORS.len()); vec.push(( create_id(11414819524356497634), // Random number here to avoid call to crate::misc::random_u64() - FunctionEntry::EMPTY, + FunctionEntry::default(), )); Self { functions: vec } } @@ -261,7 +261,7 @@ impl FunctionManager { pub fn push_empty(&mut self) { self.functions.push(( create_id(random_u64().expect("unable to generate random id")), - FunctionEntry::EMPTY, + FunctionEntry::default(), )); } diff --git a/src/misc.rs b/src/misc.rs index ff3121d..b8ee1b5 100644 --- a/src/misc.rs +++ b/src/misc.rs @@ -3,6 +3,7 @@ use egui_plot::{Line, PlotPoint, PlotPoints, Points}; use emath::Pos2; use getrandom::getrandom; use itertools::Itertools; +use parsing::FlatExWrapper; /// Implements traits that are useful when dealing with Vectors of egui's `Value` pub trait EguiHelper { @@ -79,8 +80,8 @@ pub fn decimal_round(x: f64, n: usize) -> f64 { /// `f_1` is f'(x) aka the derivative of f(x) /// The function returns a Vector of `x` values where roots occur pub fn newtons_method_helper( - threshold: f64, range: &std::ops::Range, data: &[PlotPoint], f: &dyn Fn(f64) -> f64, - f_1: &dyn Fn(f64) -> f64, + threshold: f64, range: &std::ops::Range, data: &[PlotPoint], f: &FlatExWrapper, + f_1: &FlatExWrapper, ) -> Vec { data.iter() .tuple_windows() @@ -98,19 +99,19 @@ pub fn newtons_method_helper( /// `f_1` is f'(x) aka the derivative of f(x) /// The function returns an `Option` of the x value at which a root occurs pub fn newtons_method( - f: &dyn Fn(f64) -> f64, f_1: &dyn Fn(f64) -> f64, start_x: f64, range: &std::ops::Range, + f: &FlatExWrapper, f_1: &FlatExWrapper, start_x: f64, range: &std::ops::Range, threshold: f64, ) -> Option { let mut x1: f64 = start_x; let mut x2: f64; let mut derivative: f64; loop { - derivative = f_1(x1); + derivative = f_1.eval(&[x1]); if !derivative.is_finite() { return None; } - x2 = x1 - (f(x1) / derivative); + x2 = x1 - (f.eval(&[x1]) / derivative); if !x2.is_finite() | !range.contains(&x2) { return None; } diff --git a/tests/function.rs b/tests/function.rs index bdef150..689328e 100644 --- a/tests/function.rs +++ b/tests/function.rs @@ -50,7 +50,7 @@ static DERIVATIVE_TARGET: [(f64, f64); 11] = [ fn do_test(sum: Riemann, area_target: f64) { let settings = app_settings_constructor(sum, -1.0, 1.0, 10, 10, -1.0, 1.0); - let mut function = FunctionEntry::EMPTY; + let mut function = FunctionEntry::default(); function.update_string("x^2"); function.integral = true; function.derivative = true; diff --git a/tests/misc.rs b/tests/misc.rs index 3c0ca57..9cb8255 100644 --- a/tests/misc.rs +++ b/tests/misc.rs @@ -141,10 +141,18 @@ fn invalid_hashed_storage() { #[test] fn newtons_method() { + use parsing::BackingFunction; + use parsing::FlatExWrapper; + fn get_flatexwrapper(func: &str) -> FlatExWrapper { + let mut backing_func = BackingFunction::new(func).unwrap(); + backing_func.get_function_derivative(0).clone() + } + use ytbn_graphing_software::newtons_method; + let data = newtons_method( - &|x: f64| x.powf(2.0) - 1.0, - &|x: f64| 2.0 * x, + &get_flatexwrapper("x^2 -1"), + &get_flatexwrapper("2x"), 3.0, &(0.0..5.0), f64::EPSILON, @@ -152,49 +160,13 @@ fn newtons_method() { assert_eq!(data, Some(1.0)); let data = newtons_method( - &|x: f64| x.sin(), - &|x: f64| x.cos(), + &get_flatexwrapper("sin(x)"), + &get_flatexwrapper("cos(x)"), 3.0, &(2.95..3.18), f64::EPSILON, ); assert_eq!(data, Some(std::f64::consts::PI)); - - let data = newtons_method( - &|x: f64| x.sin(), - &|_: f64| f64::NAN, - 0.0, - &(-10.0..10.0), - f64::EPSILON, - ); - assert_eq!(data, None); - - let data = newtons_method( - &|_: f64| f64::NAN, - &|x: f64| x.sin(), - 0.0, - &(-10.0..10.0), - f64::EPSILON, - ); - assert_eq!(data, None); - - let data = newtons_method( - &|_: f64| f64::INFINITY, - &|x: f64| x.sin(), - 0.0, - &(-10.0..10.0), - f64::EPSILON, - ); - assert_eq!(data, None); - - let data = newtons_method( - &|x: f64| x.sin(), - &|_: f64| f64::INFINITY, - 0.0, - &(-10.0..10.0), - f64::EPSILON, - ); - assert_eq!(data, None); } #[test]