diff --git a/src/consts.rs b/src/consts.rs new file mode 100644 index 0000000..465ed06 --- /dev/null +++ b/src/consts.rs @@ -0,0 +1,29 @@ +use crate::function::Riemann; +use std::ops::RangeInclusive; + +// Hard-Coded limits + +/// Range of acceptable input values for integral_num +pub const INTEGRAL_NUM_RANGE: RangeInclusive = 1..=50000; +/// Minimum X value for calculating an Integral +pub const INTEGRAL_X_MIN: f64 = -1000.0; +/// Maximum X value for calculating an Integral + +pub const INTEGRAL_X_MAX: f64 = 1000.0; +/// Range of acceptable x coordinates for calculating an integral +pub const INTEGRAL_X_RANGE: RangeInclusive = INTEGRAL_X_MIN..=INTEGRAL_X_MAX; + +// Default values + +/// Default Riemann Sum to calculate +pub const DEFAULT_RIEMANN: Riemann = Riemann::Left; + +/// Default minimum X value to display +pub const DEFAULT_MIN_X: f64 = -10.0; + +/// Default Maxmimum X value to display + +pub const DEFAULT_MAX_X: f64 = 10.0; + +/// Default number of integral boxes +pub const DEFAULT_INTEGRAL_NUM: usize = 100; diff --git a/src/egui_app.rs b/src/egui_app.rs index 22648b6..1cfcf7a 100644 --- a/src/egui_app.rs +++ b/src/egui_app.rs @@ -1,7 +1,8 @@ -use crate::function::{FunctionEntry, RiemannSum, DEFAULT_FUNCTION_ENTRY}; +use crate::function::{FunctionEntry, Riemann, DEFAULT_FUNCTION_ENTRY}; use crate::misc::{option_vec_printer, JsonFileOutput, SerdeValueHelper}; use crate::parsing::{process_func_str, test_func}; +use crate::consts::*; use const_format::formatc; use eframe::{egui, epi}; use egui::plot::Plot; @@ -12,12 +13,7 @@ use egui::{ use epi::Frame; use instant::Duration; use shadow_rs::shadow; -use std::{ - collections::BTreeMap, - io::Read, - ops::{BitXorAssign, RangeInclusive}, - str, -}; +use std::{collections::BTreeMap, io::Read, ops::BitXorAssign, str}; shadow!(build); @@ -31,18 +27,6 @@ const BUILD_INFO: &str = formatc!( &build::RUST_VERSION, ); -// Sets some hard-coded limits to the application -const INTEGRAL_NUM_RANGE: RangeInclusive = 1..=100000; -const INTEGRAL_X_MIN: f64 = -1000.0; -const INTEGRAL_X_MAX: f64 = 1000.0; -const INTEGRAL_X_RANGE: RangeInclusive = INTEGRAL_X_MIN..=INTEGRAL_X_MAX; - -// Default values -pub const DEFAULT_RIEMANN: RiemannSum = RiemannSum::Left; -const DEFAULT_MIN_X: f64 = -10.0; -const DEFAULT_MAX_X: f64 = 10.0; -const DEFAULT_INTEGRAL_NUM: usize = 100; - // Stores data loaded from files struct Assets { // Stores `FontDefinitions` @@ -270,7 +254,7 @@ pub struct AppSettings { pub show_side_panel: bool, /// Stores the type of Rienmann sum that should be calculated - pub sum: RiemannSum, + pub sum: Riemann, /// Min and Max range for calculating an integral pub integral_min_x: f64, @@ -371,9 +355,9 @@ impl MathApp { ComboBox::from_label("Riemann Sum Type") .selected_text(self.settings.sum.to_string()) .show_ui(ui, |ui| { - ui.selectable_value(&mut self.settings.sum, RiemannSum::Left, "Left"); - ui.selectable_value(&mut self.settings.sum, RiemannSum::Middle, "Middle"); - ui.selectable_value(&mut self.settings.sum, RiemannSum::Right, "Right"); + ui.selectable_value(&mut self.settings.sum, Riemann::Left, "Left"); + ui.selectable_value(&mut self.settings.sum, Riemann::Middle, "Middle"); + ui.selectable_value(&mut self.settings.sum, Riemann::Right, "Right"); }); let riemann_changed = prev_sum == self.settings.sum; @@ -511,7 +495,7 @@ impl MathApp { if let Some(test_output_value) = test_func(&proc_func_str) { self.last_error.push((i, test_output_value)); } else { - function.update(proc_func_str, integral_enabled, derivative_enabled); + function.update(&proc_func_str, integral_enabled, derivative_enabled); self.last_error = self .last_error .iter() @@ -717,7 +701,6 @@ impl epi::App for MathApp { plot_ui, minx_bounds, maxx_bounds, - available_width, width_changed, settings_copy, ) diff --git a/src/function.rs b/src/function.rs index 5bcc944..9c95168 100644 --- a/src/function.rs +++ b/src/function.rs @@ -2,7 +2,7 @@ use crate::egui_app::AppSettings; use crate::function_output::FunctionOutput; -use crate::misc::{dyn_iter, newtons_method_helper, resolution_helper, step_helper, SteppedVector}; +use crate::misc::*; use crate::parsing::BackingFunction; use eframe::{egui, epaint}; use egui::{ @@ -17,13 +17,13 @@ use rayon::iter::ParallelIterator; /// Represents the possible variations of Riemann Sums #[derive(PartialEq, Debug, Copy, Clone)] -pub enum RiemannSum { +pub enum Riemann { Left, Middle, Right, } -impl fmt::Display for RiemannSum { +impl fmt::Display for Riemann { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self) } } @@ -76,11 +76,11 @@ impl Default for FunctionEntry { impl FunctionEntry { /// Update function settings - pub fn update(&mut self, func_str: String, integral: bool, derivative: bool) { + pub fn update(&mut self, func_str: &str, integral: bool, derivative: bool) { // If the function string changes, just wipe and restart from scratch if func_str != self.func_str { - self.func_str = func_str.clone(); - self.function = BackingFunction::new(&func_str); + self.func_str = func_str.to_string(); + self.function = BackingFunction::new(func_str); self.output.invalidate_whole(); } @@ -91,7 +91,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: RiemannSum, integral_num: usize, + &self, integral_min_x: f64, integral_max_x: f64, sum: Riemann, integral_num: usize, ) -> (Vec<(f64, f64)>, f64) { if integral_min_x.is_nan() { panic!("integral_min_x is NaN") @@ -112,9 +112,9 @@ impl FunctionEntry { }; let y = match sum { - RiemannSum::Left => self.function.get(left_x), - RiemannSum::Right => self.function.get(right_x), - RiemannSum::Middle => { + Riemann::Left => self.function.get(left_x), + Riemann::Right => self.function.get(right_x), + Riemann::Middle => { (self.function.get(left_x) + self.function.get(right_x)) / 2.0 } }; @@ -162,13 +162,12 @@ impl FunctionEntry { } } - /// Calculates and displays the function on PlotUI `plot_ui` - pub fn display( - &mut self, plot_ui: &mut PlotUi, min_x: f64, max_x: f64, pixel_width: usize, - width_changed: bool, settings: AppSettings, - ) -> Option { - let resolution: f64 = pixel_width as f64 / (max_x.abs() + min_x.abs()); - let resolution_iter = resolution_helper(pixel_width + 1, min_x, resolution); + /// Does the calculations and stores results in `self.output` + pub fn calculate( + &mut self, min_x: f64, max_x: f64, width_changed: bool, settings: AppSettings, + ) { + let resolution: f64 = settings.pixel_width as f64 / (max_x.abs() + min_x.abs()); + let resolution_iter = resolution_helper(settings.pixel_width + 1, min_x, resolution); // Makes sure proper arguments are passed when integral is enabled if self.integral && settings.integral_changed { @@ -204,7 +203,7 @@ impl FunctionEntry { } }) .collect(); - assert_eq!(back_data.len(), pixel_width + 1); + assert_eq!(back_data.len(), settings.pixel_width + 1); self.output.back = Some(back_data); let derivative_cache = self.output.derivative.as_ref().unwrap(); @@ -218,7 +217,7 @@ impl FunctionEntry { }) .collect(); - assert_eq!(new_derivative_data.len(), pixel_width + 1); + assert_eq!(new_derivative_data.len(), settings.pixel_width + 1); self.output.derivative = Some(new_derivative_data); } else { @@ -237,7 +236,7 @@ impl FunctionEntry { let data: Vec = dyn_iter(&resolution_iter) .map(|x| Value::new(*x, self.function.get(*x))) .collect(); - assert_eq!(data.len(), pixel_width + 1); + assert_eq!(data.len(), settings.pixel_width + 1); self.output.back = Some(data); } @@ -250,7 +249,7 @@ impl FunctionEntry { let data: Vec = dyn_iter(&resolution_iter) .map(|x| Value::new(*x, self.function.get_derivative_1(*x))) .collect(); - assert_eq!(data.len(), pixel_width + 1); + assert_eq!(data.len(), settings.pixel_width + 1); self.output.derivative = Some(data); } @@ -286,6 +285,14 @@ impl FunctionEntry { if settings.roots && (min_max_changed | self.output.roots.is_none()) { self.output.roots = self.newtons_method_helper(threshold, 0); } + } + + /// Calculates and displays the function on PlotUI `plot_ui` + pub fn display( + &mut self, plot_ui: &mut PlotUi, min_x: f64, max_x: f64, width_changed: bool, + settings: AppSettings, + ) -> Option { + self.calculate(min_x, max_x, width_changed, settings); let func_str = self.get_func_str(); let derivative_str = self.function.get_derivative_str(); @@ -347,159 +354,110 @@ impl FunctionEntry { None } } + + #[cfg(test)] + fn assert( + &self, settings: AppSettings, back_target: Vec<(f64, f64)>, integral_enabled: bool, + derivative_enabled: bool, roots_enabled: bool, extrema_enabled: bool, + ) { + assert!(self.output.back.is_some()); + let back_data = self.output.back.as_ref().unwrap().clone(); + assert_eq!(back_data.len(), settings.pixel_width + 1); + let back_vec_tuple = value_vec_to_tuple(back_data); + assert_eq!(back_vec_tuple, back_target); + + assert_eq!(integral_enabled, self.integral); + assert_eq!(derivative_enabled, self.derivative); + + assert_eq!(self.output.roots.is_some(), roots_enabled); + assert_eq!(self.output.extrema.is_some(), extrema_enabled); + } + + #[cfg(test)] + pub fn tests( + &mut self, settings: AppSettings, back_values_target: Vec<(f64, f64)>, area_target: f64, + min_x: f64, max_x: f64, + ) { + { + self.calculate(min_x, max_x, true, settings); + self.assert( + settings, + back_values_target, + true, + true, + settings.roots, + settings.extrema, + ); + assert_eq!(self.output.integral.clone().unwrap().1, area_target); + } + } } -/* #[cfg(test)] mod tests { use super::*; - fn verify_function( - integral_num: usize, pixel_width: usize, function: &mut FunctionEntry, - back_values_target: Vec<(f64, f64)>, area_target: f64, - ) { - { - let (back_values, bars, derivative) = function.run_back(-1.0, 1.0); - assert!(derivative.is_some()); - assert!(bars.is_none()); - assert_eq!(back_values.len(), pixel_width); - let back_values_tuple: Vec<(f64, f64)> = - back_values.iter().map(|ele| (ele.x, ele.y)).collect(); - assert_eq!(back_values_tuple, back_values_target); + fn app_settings_constructor( + sum: Riemann, integral_min_x: f64, integral_max_x: f64, pixel_width: usize, + integral_num: usize, + ) -> AppSettings { + crate::egui_app::AppSettings { + help_open: false, + info_open: false, + show_side_panel: false, + sum, + integral_min_x, + integral_max_x, + integral_changed: true, + integral_num, + dark_mode: false, + extrema: false, + roots: false, + pixel_width, } + } - { - *function = function.clone().integral(true); - let (back_values, bars, derivative) = function.run_back(-1.0, 1.0); - assert!(derivative.is_some()); - assert!(bars.is_some()); - assert_eq!(back_values.len(), pixel_width); + static BACK_TARGET: [(f64, f64); 11] = [ + (-1.0, 1.0), + (-0.8, 0.6400000000000001), + (-0.6, 0.36), + (-0.4, 0.16000000000000003), + (-0.19999999999999996, 0.03999999999999998), + (0.0, 0.0), + (0.19999999999999996, 0.03999999999999998), + (0.3999999999999999, 0.15999999999999992), + (0.6000000000000001, 0.3600000000000001), + (0.8, 0.6400000000000001), + (1.0, 1.0), + ]; - assert_eq!(bars.clone().unwrap().1, area_target); + fn do_test(sum: Riemann, area_target: f64) { + let settings = app_settings_constructor(sum, -1.0, 1.0, 10, 10); - let vec_bars = bars.unwrap().0; - assert_eq!(vec_bars.len(), integral_num); + let mut function = FunctionEntry::default(); + function.update("x^2", true, true); - let back_values_tuple: Vec<(f64, f64)> = - back_values.iter().map(|ele| (ele.x, ele.y)).collect(); - assert_eq!(back_values_tuple, back_values_target); - } - - { - let (back_values, bars, derivative) = function.run_back(-1.0, 1.0); - assert!(derivative.is_some()); - - assert!(bars.is_some()); - assert_eq!(back_values.len(), pixel_width); - assert_eq!(bars.clone().unwrap().1, area_target); - let bars_unwrapped = bars.unwrap(); - - assert_eq!(bars_unwrapped.0.iter().len(), integral_num); - } + function.tests(settings, BACK_TARGET.to_vec(), area_target, -1.0, 1.0); } #[test] fn left_function_test() { - let integral_num = 10; - let pixel_width = 10; - - let mut function = FunctionEntry::default() - .update_riemann(RiemannSum::Left) - .pixel_width(pixel_width) - .integral_num(integral_num); - - let back_values_target = vec![ - (-1.0, 1.0), - (-0.8, 0.6400000000000001), - (-0.6, 0.36), - (-0.4, 0.16000000000000003), - (-0.19999999999999996, 0.03999999999999998), - (0.0, 0.0), - (0.19999999999999996, 0.03999999999999998), - (0.3999999999999999, 0.15999999999999992), - (0.6000000000000001, 0.3600000000000001), - (0.8, 0.6400000000000001), - ]; - let area_target = 0.9600000000000001; - verify_function( - integral_num, - pixel_width, - &mut function, - back_values_target, - area_target, - ); + do_test(Riemann::Left, area_target); } #[test] fn middle_function_test() { - let integral_num = 10; - let pixel_width = 10; - - let mut function = FunctionEntry::default() - .update_riemann(RiemannSum::Middle) - .pixel_width(pixel_width) - .integral_num(integral_num) - .integral_bounds(-1.0, 1.0); - - let back_values_target = vec![ - (-1.0, 1.0), - (-0.8, 0.6400000000000001), - (-0.6, 0.36), - (-0.4, 0.16000000000000003), - (-0.19999999999999996, 0.03999999999999998), - (0.0, 0.0), - (0.19999999999999996, 0.03999999999999998), - (0.3999999999999999, 0.15999999999999992), - (0.6000000000000001, 0.3600000000000001), - (0.8, 0.6400000000000001), - ]; - let area_target = 0.92; - verify_function( - integral_num, - pixel_width, - &mut function, - back_values_target, - area_target, - ); + do_test(Riemann::Middle, area_target); } #[test] fn right_function_test() { - let integral_num = 10; - let pixel_width = 10; - - let mut function = FunctionEntry::default() - .update_riemann(RiemannSum::Right) - .pixel_width(pixel_width) - .integral_num(integral_num) - .integral_bounds(-1.0, 1.0); - - let back_values_target = vec![ - (-1.0, 1.0), - (-0.8, 0.6400000000000001), - (-0.6, 0.36), - (-0.4, 0.16000000000000003), - (-0.19999999999999996, 0.03999999999999998), - (0.0, 0.0), - (0.19999999999999996, 0.03999999999999998), - (0.3999999999999999, 0.15999999999999992), - (0.6000000000000001, 0.3600000000000001), - (0.8, 0.6400000000000001), - ]; - let area_target = 0.8800000000000001; - verify_function( - integral_num, - pixel_width, - &mut function, - back_values_target, - area_target, - ); + do_test(Riemann::Right, area_target); } } -*/ diff --git a/src/lib.rs b/src/lib.rs index b0fa33a..32b1f1f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ #![allow(clippy::unused_unit)] // Fixes clippy keep complaining about wasm_bindgen #![feature(const_mut_refs)] +mod consts; mod egui_app; mod function; mod function_output; diff --git a/src/main.rs b/src/main.rs index 9866301..1098dd9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ #![feature(const_mut_refs)] +mod consts; mod egui_app; mod function; mod function_output; diff --git a/src/misc.rs b/src/misc.rs index fc367ba..920271a 100644 --- a/src/misc.rs +++ b/src/misc.rs @@ -270,6 +270,13 @@ pub fn step_helper(max_i: usize, min_x: f64, step: f64) -> Vec { .collect() } +/// Extracts x and y values from `egui::plot::Value` in `data`. Returns +/// `Vec<(f64, f64)>` +#[allow(dead_code)] +pub fn value_vec_to_tuple(data: Vec) -> Vec<(f64, f64)> { + data.iter().map(|ele| (ele.x, ele.y)).collect() +} + #[cfg(test)] mod tests { use super::*;