documentation, comments, and cleanup

This commit is contained in:
Simon Gardling 2022-03-10 13:38:28 -05:00
parent a8597374c2
commit 9935285c98
4 changed files with 98 additions and 39 deletions

View File

@ -462,10 +462,10 @@ impl MathApp {
proc_func_str, proc_func_str,
integral_enabled, integral_enabled,
derivative_enabled, derivative_enabled,
Some(self.settings.integral_min_x), self.settings.integral_min_x,
Some(self.settings.integral_max_x), self.settings.integral_max_x,
Some(self.settings.integral_num), self.settings.integral_num,
Some(self.settings.sum), self.settings.sum,
); );
self.last_error = self self.last_error = self
.last_error .last_error

View File

@ -11,6 +11,7 @@ use eframe::egui::plot::PlotUi;
use eframe::egui::{plot::Value, widgets::plot::Bar}; use eframe::egui::{plot::Value, widgets::plot::Bar};
use std::fmt::{self, Debug}; use std::fmt::{self, Debug};
/// Represents the possible variations of Riemann Sums
#[derive(PartialEq, Debug, Copy, Clone)] #[derive(PartialEq, Debug, Copy, Clone)]
pub enum RiemannSum { pub enum RiemannSum {
Left, Left,
@ -26,26 +27,44 @@ lazy_static::lazy_static! {
pub static ref EMPTY_FUNCTION_ENTRY: FunctionEntry = FunctionEntry::empty(); pub static ref EMPTY_FUNCTION_ENTRY: FunctionEntry = FunctionEntry::empty();
} }
/// `FunctionEntry` is a function that can calculate values, integrals, derivatives, etc etc
#[derive(Clone)] #[derive(Clone)]
pub struct FunctionEntry { pub struct FunctionEntry {
/// The `BackingFunction` instance that is used to generate `f(x)`, `f'(x)`, and `f''(x)`
function: BackingFunction, function: BackingFunction,
/// Stores a function string (that hasn't been processed via `process_func_str`) to display to the user
func_str: String, func_str: String,
/// Minimum and Maximum values of what do display
min_x: f64, min_x: f64,
max_x: f64, max_x: f64,
/// How many horizontal pixels? (used for calculating the step at which to generate values at)
pixel_width: usize, pixel_width: usize,
/// output/cached data
output: FunctionOutput, output: FunctionOutput,
/// If calculating/displayingintegrals are enabled
pub(crate) integral: bool, pub(crate) integral: bool,
/// If displaying derivatives are enabled (note, they are still calculated for other purposes)
pub(crate) derivative: bool, pub(crate) derivative: bool,
/// Minumum and maximum range of integral
integral_min_x: f64, integral_min_x: f64,
integral_max_x: f64, integral_max_x: f64,
/// Number of rectangles used to approximate the integral via a Riemann Sum
integral_num: usize, integral_num: usize,
/// The type of RiemannSum to use
sum: RiemannSum, sum: RiemannSum,
} }
impl FunctionEntry { impl FunctionEntry {
// Creates Empty Function instance /// Creates Empty Function instance
pub fn empty() -> Self { pub fn empty() -> Self {
Self { Self {
function: BackingFunction::new(DEFAULT_FUNCION), function: BackingFunction::new(DEFAULT_FUNCION),
@ -63,9 +82,10 @@ impl FunctionEntry {
} }
} }
/// Update function settings
pub fn update( pub fn update(
&mut self, func_str: String, integral: bool, derivative: bool, integral_min_x: Option<f64>, &mut self, func_str: String, integral: bool, derivative: bool, integral_min_x: f64,
integral_max_x: Option<f64>, integral_num: Option<usize>, sum: Option<RiemannSum>, integral_max_x: f64, integral_num: usize, sum: RiemannSum,
) { ) {
// If the function string changes, just wipe and restart from scratch // If the function string changes, just wipe and restart from scratch
if func_str != self.func_str { if func_str != self.func_str {
@ -79,21 +99,21 @@ impl FunctionEntry {
// Makes sure proper arguments are passed when integral is enabled // Makes sure proper arguments are passed when integral is enabled
if integral if integral
&& (integral_min_x != Some(self.integral_min_x)) && (integral_min_x != self.integral_min_x)
| (integral_max_x != Some(self.integral_max_x)) | (integral_max_x != self.integral_max_x)
| (integral_num != Some(self.integral_num)) | (integral_num != self.integral_num)
| (sum != Some(self.sum)) | (sum != self.sum)
{ {
self.output.invalidate_integral(); self.output.invalidate_integral();
self.integral_min_x = integral_min_x.expect("integral_min_x is None"); self.integral_min_x = integral_min_x;
self.integral_max_x = integral_max_x.expect("integral_max_x is None"); self.integral_max_x = integral_max_x;
self.integral_num = integral_num.expect("integral_num is None"); self.integral_num = integral_num;
self.sum = sum.expect("sum is None"); self.sum = sum;
} }
} }
// TODO: refactor this // 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<Value>, Option<(Vec<Bar>, f64)>, Option<Vec<Value>>) { pub fn run_back(&mut self) -> (Vec<Value>, Option<(Vec<Bar>, f64)>, Option<Vec<Value>>) {
let resolution: f64 = (self.pixel_width as f64 / (self.max_x - self.min_x).abs()) as f64; let resolution: f64 = (self.pixel_width as f64 / (self.max_x - self.min_x).abs()) as f64;
let back_values: Vec<Value> = { let back_values: Vec<Value> = {
@ -137,7 +157,7 @@ impl FunctionEntry {
(back_values, integral_data, derivative_values) (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) { fn integral_rectangles(&self) -> (Vec<(f64, f64)>, f64) {
if self.integral_min_x.is_nan() { if self.integral_min_x.is_nan() {
panic!("integral_min_x is NaN") panic!("integral_min_x is NaN")
@ -180,9 +200,10 @@ impl FunctionEntry {
(data2, area) (data2, area)
} }
/// Returns `func_str`
pub fn get_func_str(&self) -> &str { &self.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 { pub fn update_riemann(mut self, riemann: RiemannSum) -> Self {
if self.sum != riemann { if self.sum != riemann {
self.sum = riemann; self.sum = riemann;
@ -191,24 +212,27 @@ impl FunctionEntry {
self self
} }
// Toggles integral /// Sets whether integral is enabled or not
pub fn integral(mut self, integral: bool) -> Self { pub fn integral(mut self, enabled: bool) -> Self {
self.integral = integral; self.integral = enabled;
self self
} }
/// Sets number of rectangles to use to calculate the integral
#[allow(dead_code)] #[allow(dead_code)]
pub fn integral_num(mut self, integral_num: usize) -> Self { pub fn integral_num(mut self, integral_num: usize) -> Self {
self.integral_num = integral_num; self.integral_num = integral_num;
self self
} }
/// Sets the number of horizontal pixels
#[allow(dead_code)] #[allow(dead_code)]
pub fn pixel_width(mut self, pixel_width: usize) -> Self { pub fn pixel_width(mut self, pixel_width: usize) -> Self {
self.pixel_width = pixel_width; self.pixel_width = pixel_width;
self self
} }
/// Sets the bounds of the integral
#[allow(dead_code)] #[allow(dead_code)]
pub fn integral_bounds(mut self, min_x: f64, max_x: f64) -> Self { pub fn integral_bounds(mut self, min_x: f64, max_x: f64) -> Self {
if min_x >= max_x { if min_x >= max_x {
@ -220,6 +244,7 @@ impl FunctionEntry {
self self
} }
/// Calculates and displays the function on PlotUI `plot_ui`
pub fn display( pub fn display(
&mut self, plot_ui: &mut PlotUi, min_x: f64, max_x: f64, pixel_width: usize, extrema: bool, &mut self, plot_ui: &mut PlotUi, min_x: f64, max_x: f64, pixel_width: usize, extrema: bool,
roots: bool, roots: bool,

View File

@ -1,3 +1,4 @@
use crate::misc::decimal_round;
use eframe::{ use eframe::{
egui::{ egui::{
plot::{BarChart, Line, PlotUi, Points, Value, Values}, plot::{BarChart, Line, PlotUi, Points, Value, Values},
@ -6,8 +7,6 @@ use eframe::{
epaint::Color32, epaint::Color32,
}; };
use crate::misc::digits_precision;
#[derive(Clone)] #[derive(Clone)]
pub struct FunctionOutput { pub struct FunctionOutput {
pub(crate) back: Option<Vec<Value>>, pub(crate) back: Option<Vec<Value>>,
@ -18,6 +17,7 @@ pub struct FunctionOutput {
} }
impl FunctionOutput { impl FunctionOutput {
/// Creates empty instance of `FunctionOutput`
pub fn new_empty() -> Self { pub fn new_empty() -> Self {
Self { Self {
back: None, back: None,
@ -28,6 +28,7 @@ impl FunctionOutput {
} }
} }
/// Invalidate all data (setting it all to `None`)
pub fn invalidate_whole(&mut self) { pub fn invalidate_whole(&mut self) {
self.back = None; self.back = None;
self.integral = None; self.integral = None;
@ -36,22 +37,29 @@ impl FunctionOutput {
self.roots = None; self.roots = None;
} }
/// Invalidate `back` data
pub fn invalidate_back(&mut self) { self.back = None; } pub fn invalidate_back(&mut self) { self.back = None; }
/// Invalidate Integral data
pub fn invalidate_integral(&mut self) { self.integral = None; } pub fn invalidate_integral(&mut self) { self.integral = None; }
/// Invalidate Derivative data
pub fn invalidate_derivative(&mut self) { self.derivative = None; } 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( pub fn display(
&self, plot_ui: &mut PlotUi, func_str: &str, derivative_str: &str, step: f64, &self, plot_ui: &mut PlotUi, func_str: &str, derivative_str: &str, step: f64,
derivative_enabled: bool, derivative_enabled: bool,
) -> f64 { ) -> f64 {
// Plot back data
plot_ui.line( plot_ui.line(
Line::new(Values::from_values(self.back.clone().unwrap())) Line::new(Values::from_values(self.back.clone().unwrap()))
.color(Color32::RED) .color(Color32::RED)
.name(func_str), .name(func_str),
); );
// Plot derivative data
if derivative_enabled { if derivative_enabled {
if let Some(derivative_data) = self.derivative.clone() { if let Some(derivative_data) = self.derivative.clone() {
plot_ui.line( plot_ui.line(
@ -62,6 +70,7 @@ impl FunctionOutput {
} }
} }
// Plot extrema points
if let Some(extrema_data) = self.extrema.clone() { if let Some(extrema_data) = self.extrema.clone() {
plot_ui.points( plot_ui.points(
Points::new(Values::from_values(extrema_data)) Points::new(Values::from_values(extrema_data))
@ -71,6 +80,7 @@ impl FunctionOutput {
); );
} }
// Plot roots points
if let Some(roots_data) = self.roots.clone() { if let Some(roots_data) = self.roots.clone() {
plot_ui.points( plot_ui.points(
Points::new(Values::from_values(roots_data)) Points::new(Values::from_values(roots_data))
@ -80,6 +90,7 @@ impl FunctionOutput {
); );
} }
// Plot integral data
if let Some(integral_data) = self.integral.clone() { if let Some(integral_data) = self.integral.clone() {
plot_ui.bar_chart( plot_ui.bar_chart(
BarChart::new(integral_data.0) BarChart::new(integral_data.0)
@ -87,9 +98,9 @@ impl FunctionOutput {
.width(step), .width(step),
); );
digits_precision(integral_data.1, 8) decimal_round(integral_data.1, 8) // return value rounded to 8 decimal places
} else { } else {
f64::NAN f64::NAN // return NaN if integrals are disabled
} }
} }
} }

View File

@ -2,22 +2,24 @@ use std::ops::Range;
use eframe::egui::plot::Value; 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! { cfg_if::cfg_if! {
if #[cfg(target_arch = "wasm32")] { if #[cfg(target_arch = "wasm32")] {
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
#[wasm_bindgen] #[wasm_bindgen]
extern "C" { extern "C" {
// Use `js_namespace` here to bind `console.log(..)` instead of just // `console.log(...)`
// `log(..)`
#[wasm_bindgen(js_namespace = console)] #[wasm_bindgen(js_namespace = console)]
fn log(s: &str); fn log(s: &str);
} }
/// Used for logging normal messages
#[allow(dead_code)] #[allow(dead_code)]
pub fn log_helper(s: &str) { pub fn log_helper(s: &str) {
log(s); log(s);
} }
/// Used for debug messages, only does anything if `debug_assertions` is enabled
#[allow(dead_code)] #[allow(dead_code)]
#[allow(unused_variables)] #[allow(unused_variables)]
pub fn debug_log(s: &str) { pub fn debug_log(s: &str) {
@ -25,11 +27,13 @@ cfg_if::cfg_if! {
log(s); log(s);
} }
} else { } else {
/// Used for logging normal messages
#[allow(dead_code)] #[allow(dead_code)]
pub fn log_helper(s: &str) { pub fn log_helper(s: &str) {
println!("{}", s); println!("{}", s);
} }
/// Used for debug messages, only does anything if `debug_assertions` is enabled
#[allow(dead_code)] #[allow(dead_code)]
#[allow(unused_variables)] #[allow(unused_variables)]
pub fn debug_log(s: &str) { 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<f64>`
/// 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 { pub struct SteppedVector {
// Actual data being referenced. HAS to be sorted from maximum value to minumum
data: Vec<f64>, data: Vec<f64>,
// Minimum value
min: f64, min: f64,
// Maximum value
max: f64, max: f64,
// Since all entries in `data` are evenly spaced, this field stores the step between 2 adjacent elements
step: f64, step: f64,
} }
impl SteppedVector { impl SteppedVector {
/// Returns `Option<usize>` with index of element with value `x`. and `None` if `x` does not exist in `data`
pub fn get_index(&self, x: f64) -> Option<usize> { pub fn get_index(&self, x: f64) -> Option<usize> {
// 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) { if (x > self.max) | (self.min > x) {
return None; 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; 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 { if self.data[possible_i] == x {
// It is valid!
Some(possible_i) Some(possible_i)
} else { } else {
// (For some reason) it wasn't!
None None
} }
// Not really needed as the above code should handle everything // Old (inefficent) code
/* /*
for (i, ele) in self.data.iter().enumerate() { for (i, ele) in self.data.iter().enumerate() {
if ele > &x { if ele > &x {
@ -74,12 +93,16 @@ impl SteppedVector {
} }
} }
// Convert `Vec<f64>` into `SteppedVector`
impl From<Vec<f64>> for SteppedVector { impl From<Vec<f64>> 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<f64>) -> SteppedVector { fn from(data: Vec<f64>) -> SteppedVector {
let max = data[0]; let max = data[0]; // The max value should be the first element
let min = data[data.len() - 1]; let min = data[data.len() - 1]; // The minimum value should be the last element
let step = (max - min).abs() / ((data.len() - 1) as f64); let step = (max - min).abs() / ((data.len() - 1) as f64); // Calculate the step between elements
// Create and return the struct
SteppedVector { SteppedVector {
data, data,
min, min,
@ -89,10 +112,10 @@ impl From<Vec<f64>> for SteppedVector {
} }
} }
// Rounds f64 to specific number of digits // Rounds f64 to specific number of decimal places
pub fn digits_precision(x: f64, digits: usize) -> f64 { pub fn decimal_round(x: f64, n: usize) -> f64 {
let large_number: f64 = 10.0_f64.powf(digits as f64); let large_number: f64 = 10.0_f64.powf(n as f64); // 10^n
(x * large_number).round() / large_number (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. /// 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) /// `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) /// `data` is the data to iterate over (a Vector of egui's `Value` struct)
/// `f` is f(x) /// `f` is f(x)
/// `f_` is f'(x) /// `f_1` is f'(x)
/// The function returns a list of `x` values where roots occur /// The function returns a Vector of `x` values where roots occur
pub fn newtons_method( pub fn newtons_method(
threshold: f64, range: Range<f64>, data: Vec<Value>, f: &dyn Fn(f64) -> f64, threshold: f64, range: Range<f64>, data: Vec<Value>, f: &dyn Fn(f64) -> f64,
f_1: &dyn Fn(f64) -> f64, f_1: &dyn Fn(f64) -> f64,