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,
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

View File

@ -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<f64>,
integral_max_x: Option<f64>, integral_num: Option<usize>, sum: Option<RiemannSum>,
&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<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 back_values: Vec<Value> = {
@ -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,

View File

@ -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<Vec<Value>>,
@ -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
}
}
}

View File

@ -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<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 {
// Actual data being referenced. HAS to be sorted from maximum value to minumum
data: Vec<f64>,
// 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<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> {
// 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<f64>` into `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 {
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<Vec<f64>> 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<f64>, data: Vec<Value>, f: &dyn Fn(f64) -> f64,
f_1: &dyn Fn(f64) -> f64,