documentation, comments, and cleanup
This commit is contained in:
parent
a8597374c2
commit
9935285c98
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
51
src/misc.rs
51
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<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,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user