From 9935285c98f238b8397adf5fafd79fd34b64fc6b Mon Sep 17 00:00:00 2001 From: Simon Gardling Date: Thu, 10 Mar 2022 13:38:28 -0500 Subject: [PATCH] documentation, comments, and cleanup --- src/egui_app.rs | 8 +++--- src/function.rs | 59 ++++++++++++++++++++++++++++++------------ src/function_output.rs | 19 +++++++++++--- src/misc.rs | 51 ++++++++++++++++++++++++++---------- 4 files changed, 98 insertions(+), 39 deletions(-) diff --git a/src/egui_app.rs b/src/egui_app.rs index b11434f..a227cd5 100644 --- a/src/egui_app.rs +++ b/src/egui_app.rs @@ -462,10 +462,10 @@ impl MathApp { proc_func_str, integral_enabled, derivative_enabled, - Some(self.settings.integral_min_x), - Some(self.settings.integral_max_x), - Some(self.settings.integral_num), - Some(self.settings.sum), + self.settings.integral_min_x, + self.settings.integral_max_x, + self.settings.integral_num, + self.settings.sum, ); self.last_error = self .last_error diff --git a/src/function.rs b/src/function.rs index ad4ada6..3f5b248 100644 --- a/src/function.rs +++ b/src/function.rs @@ -11,6 +11,7 @@ use eframe::egui::plot::PlotUi; use eframe::egui::{plot::Value, widgets::plot::Bar}; use std::fmt::{self, Debug}; +/// Represents the possible variations of Riemann Sums #[derive(PartialEq, Debug, Copy, Clone)] pub enum RiemannSum { Left, @@ -26,26 +27,44 @@ lazy_static::lazy_static! { pub static ref EMPTY_FUNCTION_ENTRY: FunctionEntry = FunctionEntry::empty(); } +/// `FunctionEntry` is a function that can calculate values, integrals, derivatives, etc etc #[derive(Clone)] pub struct FunctionEntry { + /// The `BackingFunction` instance that is used to generate `f(x)`, `f'(x)`, and `f''(x)` function: BackingFunction, + + /// Stores a function string (that hasn't been processed via `process_func_str`) to display to the user func_str: String, + + /// Minimum and Maximum values of what do display min_x: f64, max_x: f64, + + /// How many horizontal pixels? (used for calculating the step at which to generate values at) pixel_width: usize, + /// output/cached data output: FunctionOutput, + /// If calculating/displayingintegrals are enabled pub(crate) integral: bool, + + /// If displaying derivatives are enabled (note, they are still calculated for other purposes) pub(crate) derivative: bool, + + /// Minumum and maximum range of integral integral_min_x: f64, integral_max_x: f64, + + /// Number of rectangles used to approximate the integral via a Riemann Sum integral_num: usize, + + /// The type of RiemannSum to use sum: RiemannSum, } impl FunctionEntry { - // Creates Empty Function instance + /// Creates Empty Function instance pub fn empty() -> Self { Self { function: BackingFunction::new(DEFAULT_FUNCION), @@ -63,9 +82,10 @@ impl FunctionEntry { } } + /// Update function settings pub fn update( - &mut self, func_str: String, integral: bool, derivative: bool, integral_min_x: Option, - integral_max_x: Option, integral_num: Option, sum: Option, + &mut self, func_str: String, integral: bool, derivative: bool, integral_min_x: f64, + integral_max_x: f64, integral_num: usize, sum: RiemannSum, ) { // If the function string changes, just wipe and restart from scratch if func_str != self.func_str { @@ -79,21 +99,21 @@ impl FunctionEntry { // Makes sure proper arguments are passed when integral is enabled if integral - && (integral_min_x != Some(self.integral_min_x)) - | (integral_max_x != Some(self.integral_max_x)) - | (integral_num != Some(self.integral_num)) - | (sum != Some(self.sum)) + && (integral_min_x != self.integral_min_x) + | (integral_max_x != self.integral_max_x) + | (integral_num != self.integral_num) + | (sum != self.sum) { self.output.invalidate_integral(); - self.integral_min_x = integral_min_x.expect("integral_min_x is None"); - self.integral_max_x = integral_max_x.expect("integral_max_x is None"); - self.integral_num = integral_num.expect("integral_num is None"); - self.sum = sum.expect("sum is None"); + self.integral_min_x = integral_min_x; + self.integral_max_x = integral_max_x; + self.integral_num = integral_num; + self.sum = sum; } } // TODO: refactor this - // Returns back values, integral data (Bars and total area), and Derivative values + /// Returns back values, integral data (Bars and total area), and Derivative values pub fn run_back(&mut self) -> (Vec, Option<(Vec, f64)>, Option>) { let resolution: f64 = (self.pixel_width as f64 / (self.max_x - self.min_x).abs()) as f64; let back_values: Vec = { @@ -137,7 +157,7 @@ impl FunctionEntry { (back_values, integral_data, derivative_values) } - // Creates and does the math for creating all the rectangles under the graph + /// Creates and does the math for creating all the rectangles under the graph fn integral_rectangles(&self) -> (Vec<(f64, f64)>, f64) { if self.integral_min_x.is_nan() { panic!("integral_min_x is NaN") @@ -180,9 +200,10 @@ impl FunctionEntry { (data2, area) } + /// Returns `func_str` pub fn get_func_str(&self) -> &str { &self.func_str } - // Updates riemann value and invalidates integral_cache if needed + /// Updates riemann value and invalidates integral_cache if needed pub fn update_riemann(mut self, riemann: RiemannSum) -> Self { if self.sum != riemann { self.sum = riemann; @@ -191,24 +212,27 @@ impl FunctionEntry { self } - // Toggles integral - pub fn integral(mut self, integral: bool) -> Self { - self.integral = integral; + /// Sets whether integral is enabled or not + pub fn integral(mut self, enabled: bool) -> Self { + self.integral = enabled; self } + /// Sets number of rectangles to use to calculate the integral #[allow(dead_code)] pub fn integral_num(mut self, integral_num: usize) -> Self { self.integral_num = integral_num; self } + /// Sets the number of horizontal pixels #[allow(dead_code)] pub fn pixel_width(mut self, pixel_width: usize) -> Self { self.pixel_width = pixel_width; self } + /// Sets the bounds of the integral #[allow(dead_code)] pub fn integral_bounds(mut self, min_x: f64, max_x: f64) -> Self { if min_x >= max_x { @@ -220,6 +244,7 @@ impl FunctionEntry { self } + /// 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, extrema: bool, roots: bool, diff --git a/src/function_output.rs b/src/function_output.rs index edbd725..87bd98a 100644 --- a/src/function_output.rs +++ b/src/function_output.rs @@ -1,3 +1,4 @@ +use crate::misc::decimal_round; use eframe::{ egui::{ plot::{BarChart, Line, PlotUi, Points, Value, Values}, @@ -6,8 +7,6 @@ use eframe::{ epaint::Color32, }; -use crate::misc::digits_precision; - #[derive(Clone)] pub struct FunctionOutput { pub(crate) back: Option>, @@ -18,6 +17,7 @@ pub struct FunctionOutput { } impl FunctionOutput { + /// Creates empty instance of `FunctionOutput` pub fn new_empty() -> Self { Self { back: None, @@ -28,6 +28,7 @@ impl FunctionOutput { } } + /// Invalidate all data (setting it all to `None`) pub fn invalidate_whole(&mut self) { self.back = None; self.integral = None; @@ -36,22 +37,29 @@ impl FunctionOutput { self.roots = None; } + /// Invalidate `back` data pub fn invalidate_back(&mut self) { self.back = None; } + /// Invalidate Integral data pub fn invalidate_integral(&mut self) { self.integral = None; } + /// Invalidate Derivative data pub fn invalidate_derivative(&mut self) { self.derivative = None; } + /// Display output on PlotUi `plot_ui` + /// Returns `f64` containing rounded integral area (if integrals are disabled, it returns `f64::NAN`) pub fn display( &self, plot_ui: &mut PlotUi, func_str: &str, derivative_str: &str, step: f64, derivative_enabled: bool, ) -> f64 { + // Plot back data plot_ui.line( Line::new(Values::from_values(self.back.clone().unwrap())) .color(Color32::RED) .name(func_str), ); + // Plot derivative data if derivative_enabled { if let Some(derivative_data) = self.derivative.clone() { plot_ui.line( @@ -62,6 +70,7 @@ impl FunctionOutput { } } + // Plot extrema points if let Some(extrema_data) = self.extrema.clone() { plot_ui.points( Points::new(Values::from_values(extrema_data)) @@ -71,6 +80,7 @@ impl FunctionOutput { ); } + // Plot roots points if let Some(roots_data) = self.roots.clone() { plot_ui.points( Points::new(Values::from_values(roots_data)) @@ -80,6 +90,7 @@ impl FunctionOutput { ); } + // Plot integral data if let Some(integral_data) = self.integral.clone() { plot_ui.bar_chart( BarChart::new(integral_data.0) @@ -87,9 +98,9 @@ impl FunctionOutput { .width(step), ); - digits_precision(integral_data.1, 8) + decimal_round(integral_data.1, 8) // return value rounded to 8 decimal places } else { - f64::NAN + f64::NAN // return NaN if integrals are disabled } } } diff --git a/src/misc.rs b/src/misc.rs index 16a0f3c..a1047a8 100644 --- a/src/misc.rs +++ b/src/misc.rs @@ -2,22 +2,24 @@ use std::ops::Range; use eframe::egui::plot::Value; +// Handles logging based on if the target is wasm (or not) and if `debug_assertions` is enabled or not cfg_if::cfg_if! { if #[cfg(target_arch = "wasm32")] { use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { - // Use `js_namespace` here to bind `console.log(..)` instead of just - // `log(..)` + // `console.log(...)` #[wasm_bindgen(js_namespace = console)] fn log(s: &str); } + /// Used for logging normal messages #[allow(dead_code)] pub fn log_helper(s: &str) { log(s); } + /// Used for debug messages, only does anything if `debug_assertions` is enabled #[allow(dead_code)] #[allow(unused_variables)] pub fn debug_log(s: &str) { @@ -25,11 +27,13 @@ cfg_if::cfg_if! { log(s); } } else { + /// Used for logging normal messages #[allow(dead_code)] pub fn log_helper(s: &str) { println!("{}", s); } + /// Used for debug messages, only does anything if `debug_assertions` is enabled #[allow(dead_code)] #[allow(unused_variables)] pub fn debug_log(s: &str) { @@ -39,28 +43,43 @@ cfg_if::cfg_if! { } } +/// `SteppedVector` is used in order to efficiently sort through an ordered `Vec` +/// Used in order to speedup the processing of cached data when moving horizontally without zoom in `FunctionEntry`. Before this struct, the index was calculated with `.iter().position(....` which was horribly inefficient pub struct SteppedVector { + // Actual data being referenced. HAS to be sorted from maximum value to minumum data: Vec, + + // Minimum value min: f64, + + // Maximum value max: f64, + + // Since all entries in `data` are evenly spaced, this field stores the step between 2 adjacent elements step: f64, } impl SteppedVector { + /// Returns `Option` with index of element with value `x`. and `None` if `x` does not exist in `data` pub fn get_index(&self, x: f64) -> Option { + // if `x` is outside range, just go ahead and return `None` as it *shouldn't* be in `data` if (x > self.max) | (self.min > x) { return None; } - // Should work.... + // Do some math in order to calculate the expected index value let possible_i = ((x + self.min) / self.step) as usize; + + // Make sure that the index is valid by checking the data returned vs the actual data (just in case) if self.data[possible_i] == x { + // It is valid! Some(possible_i) } else { + // (For some reason) it wasn't! None } - // Not really needed as the above code should handle everything + // Old (inefficent) code /* for (i, ele) in self.data.iter().enumerate() { if ele > &x { @@ -74,12 +93,16 @@ impl SteppedVector { } } +// Convert `Vec` into `SteppedVector` impl From> for SteppedVector { - // Note: input `data` is assumed to be sorted from min to max + /// Note: input `data` is assumed to be sorted properly + /// `data` is a Vector of 64 bit floating point numbers ordered from max -> min fn from(data: Vec) -> SteppedVector { - let max = data[0]; - let min = data[data.len() - 1]; - let step = (max - min).abs() / ((data.len() - 1) as f64); + let max = data[0]; // The max value should be the first element + let min = data[data.len() - 1]; // The minimum value should be the last element + let step = (max - min).abs() / ((data.len() - 1) as f64); // Calculate the step between elements + + // Create and return the struct SteppedVector { data, min, @@ -89,10 +112,10 @@ impl From> for SteppedVector { } } -// Rounds f64 to specific number of digits -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 +// Rounds f64 to specific number of decimal places +pub fn decimal_round(x: f64, n: usize) -> f64 { + let large_number: f64 = 10.0_f64.powf(n as f64); // 10^n + (x * large_number).round() / large_number // round and devide in order to cut off after the `n`th decimal place } /// Implements newton's method of finding roots. @@ -100,8 +123,8 @@ pub fn digits_precision(x: f64, digits: usize) -> f64 { /// `range` is the range of valid x values (used to stop calculation when the point won't display anyways) /// `data` is the data to iterate over (a Vector of egui's `Value` struct) /// `f` is f(x) -/// `f_` is f'(x) -/// The function returns a list of `x` values where roots occur +/// `f_1` is f'(x) +/// The function returns a Vector of `x` values where roots occur pub fn newtons_method( threshold: f64, range: Range, data: Vec, f: &dyn Fn(f64) -> f64, f_1: &dyn Fn(f64) -> f64,