From 0c538cb7fdc8eaf556901c111f08e8c4a5c6c0de Mon Sep 17 00:00:00 2001 From: Simon Gardling Date: Fri, 25 Feb 2022 09:47:41 -0500 Subject: [PATCH] rewrite progress --- src/chart_manager.rs | 4 +- src/egui_app.rs | 9 ++- src/function.rs | 182 +++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + src/main.rs | 1 + src/misc.rs | 27 +------ 6 files changed, 196 insertions(+), 28 deletions(-) create mode 100644 src/function.rs diff --git a/src/chart_manager.rs b/src/chart_manager.rs index 53c825f..a22595c 100644 --- a/src/chart_manager.rs +++ b/src/chart_manager.rs @@ -104,8 +104,8 @@ impl ChartManager { false => x - step, }; - let tmp1: f64 = self.function.run(x); - let tmp2: f64 = self.function.run(x2); + let tmp1: f64 = self.function.run_func(x); + let tmp2: f64 = self.function.run_func(x2); // Chooses the y value who's absolute value is the smallest let mut output = match tmp2.abs() > tmp1.abs() { diff --git a/src/egui_app.rs b/src/egui_app.rs index 576bdbd..969a807 100644 --- a/src/egui_app.rs +++ b/src/egui_app.rs @@ -34,6 +34,7 @@ pub struct MathApp { } impl Default for MathApp { + #[inline] fn default() -> Self { let def_func = "x^2".to_string(); let def_min_x = -10.0; @@ -102,15 +103,19 @@ impl MathApp { } impl epi::App for MathApp { + + // The name of the program (displayed when running natively as the window title) fn name(&self) -> &str { "Integral Demonstration" } - /// Called once before the first frame. + // Called once before the first frame. + #[inline] fn setup( &mut self, _ctx: &egui::Context, _frame: &epi::Frame, _storage: Option<&dyn epi::Storage>, ) { } - /// Called each time the UI needs repainting, which may be many times per second. + // Called each time the UI needs repainting, which may be many times per second. + #[inline] fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) { let Self { func_str, diff --git a/src/function.rs b/src/function.rs new file mode 100644 index 0000000..09cfc25 --- /dev/null +++ b/src/function.rs @@ -0,0 +1,182 @@ +use meval::Expr; +use egui::plot::Value; +use egui::widgets::plot::Bar; + +const RESOLUTION: f64 = 1000.0; + + +// Struct that stores and manages the output of a function +pub struct FunctionOutput { + // The actual line graph + back: Vec, + + // Integral information + front: Option<(Vec, f64)>, +} + +impl FunctionOutput { + #[inline] + pub fn new(back: Vec, front: Option<(Vec, f64)>) -> Self { + Self { + back, + front, + } + } + + #[inline] + fn has_integral(&self) -> bool { + match self.front { + Some(x) => true, + None => false, + } + } +} + +pub struct Function { + function: Box f64>, + func_str: String, + min_x: f64, + max_x: f64, + back_cache: Cache>, + front_cache: Cache<(Vec, f64)>, + + integral: bool, + integral_min_x: f64, + integral_max_x: f64, + integral_num: usize, +} + +impl Function { + pub fn new(func_str: String, min_x: f64, max_x: f64, integral: bool, integral_min_x: Option, integral_max_x: Option, integral_num: Option) -> Self { + + // Makes sure proper arguments are passed when integral is enabled + if integral { + if integral_min_x.is_none() { + panic!("Invalid arguments: integral_min_x is None, but integral is enabled.") + } else if integral_max_x.is_none() { + panic!("Invalid arguments: integral_max_x is None, but integral is enabled.") + } else if integral_num.is_none() { + panic!("Invalid arguments: integral_num is None, but integral is enabled.") + } + } + + let expr: Expr = func_str.parse().unwrap(); + let func = expr.bind("x").unwrap(); + Self { + function: Box::new(func), + func_str, + min_x, + max_x, + back_cache: Cache::new_empty(), + front_cache: Cache::new_empty(), + integral, + integral_min_x: match integral_min_x { + Some(x) => x, + None => f64::NAN, + }, + integral_max_x: match integral_max_x { + Some(x) => x, + None => f64::NAN, + }, + integral_num: match integral_num { + Some(x) => x, + None => f64::NAN, + }, + } + } + + // Runs the internal function to get values + #[inline] + fn run_func(&self, x: f64) -> f64 { (self.function)(x) } + + #[inline] + pub fn update(&mut self, func_str: String, min_x: f64, max_x: f64, integral: bool, integral_min_x: Option, integral_max_x: Option, integral_num: Option) { + + // If the function string changes, just wipe and restart from scratch + if func_str != self.func_str { + *self = Self::new(func_str, min_x, max_x, integral, integral_min_x, integral_max_x, integral_num); + return; + } + + + if (min_x != self.min_x) | (max_x != self.max_x) { + self.back_cache.invalidate(); + self.min_x = min_x; + self.max_x = max_x; + } + + // Makes sure proper arguments are passed when integral is enabled + if integral { + if integral_min_x.is_none() { + panic!("Invalid arguments: integral_min_x is None, but integral is enabled.") + } else if integral_max_x.is_none() { + panic!("Invalid arguments: integral_max_x is None, but integral is enabled.") + } else if integral_num.is_none() { + panic!("Invalid arguments: integral_num is None, but integral is enabled.") + } + + if (integral_min_x != Some(self.integral_min_x)) | (integral_max_x != Some(self.integral_max_x)) | (integral_num != Some(self.integral_num)) { + self.front_cache.invalidate(); + self.integral_min_x = integral_min_x.expect(""); + self.integral_max_x = integral_max_x.expect(""); + self.integral_num = integral_num.expect(""); + } + } + + } + + + #[inline] + pub fn run(&mut self) -> FunctionOutput { + let absrange = (self.max_x - self.min_x).abs(); + let output: Vec<(f64, f64)> = (1..=self.resolution) + .map(|x| ((x as f64 / RESOLUTION) * absrange) + self.min_x_back) + .map(|x| (x, self.function.run(x))) + .collect(); + output + } + + #[inline] + pub fn str_compare(&self, other_string: String) -> bool { self.func_str == other_string } + + #[inline] + pub fn get_step(&self) -> f64 { (self.integral_min_x - self.integral_max_x).abs() / (self.num_interval as f64) } + + // Creates and does the math for creating all the rectangles under the graph + #[inline] + fn integral_rectangles(&self, step: f64) -> (Vec<(f64, f64)>, f64) { + let half_step = step / 2.0; + let data2: Vec<(f64, f64)> = (0..self.num_interval) + .map(|e| { + let x: f64 = ((e as f64) * step) + self.integral_min_x; + + // Makes sure rectangles are properly handled on x values below 0 + let x2: f64 = match x > 0.0 { + true => x + step, + false => x - step, + }; + + let tmp1: f64 = self.run_func(x); + let tmp2: f64 = self.run_func(x2); + + // Chooses the y value who's absolute value is the smallest + let mut output = match tmp2.abs() > tmp1.abs() { + true => (x, tmp1), + false => (x2, tmp2), + }; + + // Applies `half_step` in order to make the bar graph display properly + if output.0 > 0.0 { + output.0 += half_step; + } else { + output.0 -= half_step; + } + + output + }) + .filter(|(_, y)| !y.is_nan()) + .collect(); + let area: f64 = data2.iter().map(|(_, y)| y * step).sum(); // sum of all rectangles' areas + (data2, area) + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index c876304..0fe484c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ mod chart_manager; mod egui_app; mod misc; +mod function; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; diff --git a/src/main.rs b/src/main.rs index d1b9f53..653586c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ mod egui_app; // These 2 are needed for rust-analyzer to work in vscode. mod chart_manager; mod misc; +mod function; // For running the program natively! (Because why not?) #[cfg(not(target_arch = "wasm32"))] diff --git a/src/misc.rs b/src/misc.rs index 30fa7ca..a5170c7 100644 --- a/src/misc.rs +++ b/src/misc.rs @@ -5,6 +5,7 @@ EXTREMELY Janky function that tries to put asterisks in the proper places to be One limitation though, variables with multiple characters like `pi` cannot be multiplied (like `pipipipi` won't result in `pi*pi*pi*pi`). But that's such a niche use case (and that same thing could be done by using exponents) that it doesn't really matter. In the future I may want to completely rewrite this or implement this natively into mevel-rs (which would probably be good to do) */ +#[inline] pub fn add_asterisks(function_in: String) -> String { let function = function_in.replace("log10(", "log(").replace("pi", "π"); // pi -> π and log10 -> log let valid_variables: Vec = "xeπ".chars().collect(); @@ -78,6 +79,7 @@ pub fn add_asterisks(function_in: String) -> String { } // Tests function to make sure it's able to be parsed. Returns the string of the Error produced, or an empty string if it runs successfully. +#[inline] pub fn test_func(function_string: String) -> String { // Factorials do not work, and it would be really difficult to make them work if function_string.contains('!') { @@ -108,35 +110,12 @@ pub fn test_func(function_string: String) -> String { } // Rounds f64 to specific number of digits +#[inline] pub fn digits_precision(x: f64, digits: usize) -> f64 { let large_number: f64 = 10.0_f64.powf(digits as f64); (x * large_number).round() / large_number } -pub struct Function { - function: Box f64>, - func_str: String, -} - -impl Function { - pub fn from_string(func_str: String) -> Self { - let expr: Expr = func_str.parse().unwrap(); - let func = expr.bind("x").unwrap(); - Self { - function: Box::new(func), - func_str, - } - } - - #[inline] - pub fn run(&self, x: f64) -> f64 { (self.function)(x) } - - pub fn str_compare(&self, other_string: String) -> bool { self.func_str == other_string } - - #[allow(dead_code)] - pub fn get_string(&self) -> String { self.func_str.clone() } -} - pub struct Cache { backing_data: Option, }