a lot of refactoring and re-implementing tests for FunctionEntry

This commit is contained in:
Simon Gardling 2022-03-24 11:26:43 -04:00
parent 3d5149c489
commit 8cbc63fbc7
6 changed files with 150 additions and 171 deletions

29
src/consts.rs Normal file
View File

@ -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<usize> = 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<f64> = 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;

View File

@ -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::misc::{option_vec_printer, JsonFileOutput, SerdeValueHelper};
use crate::parsing::{process_func_str, test_func}; use crate::parsing::{process_func_str, test_func};
use crate::consts::*;
use const_format::formatc; use const_format::formatc;
use eframe::{egui, epi}; use eframe::{egui, epi};
use egui::plot::Plot; use egui::plot::Plot;
@ -12,12 +13,7 @@ use egui::{
use epi::Frame; use epi::Frame;
use instant::Duration; use instant::Duration;
use shadow_rs::shadow; use shadow_rs::shadow;
use std::{ use std::{collections::BTreeMap, io::Read, ops::BitXorAssign, str};
collections::BTreeMap,
io::Read,
ops::{BitXorAssign, RangeInclusive},
str,
};
shadow!(build); shadow!(build);
@ -31,18 +27,6 @@ const BUILD_INFO: &str = formatc!(
&build::RUST_VERSION, &build::RUST_VERSION,
); );
// Sets some hard-coded limits to the application
const INTEGRAL_NUM_RANGE: RangeInclusive<usize> = 1..=100000;
const INTEGRAL_X_MIN: f64 = -1000.0;
const INTEGRAL_X_MAX: f64 = 1000.0;
const INTEGRAL_X_RANGE: RangeInclusive<f64> = 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 // Stores data loaded from files
struct Assets { struct Assets {
// Stores `FontDefinitions` // Stores `FontDefinitions`
@ -270,7 +254,7 @@ pub struct AppSettings {
pub show_side_panel: bool, pub show_side_panel: bool,
/// Stores the type of Rienmann sum that should be calculated /// Stores the type of Rienmann sum that should be calculated
pub sum: RiemannSum, pub sum: Riemann,
/// Min and Max range for calculating an integral /// Min and Max range for calculating an integral
pub integral_min_x: f64, pub integral_min_x: f64,
@ -371,9 +355,9 @@ impl MathApp {
ComboBox::from_label("Riemann Sum Type") ComboBox::from_label("Riemann Sum Type")
.selected_text(self.settings.sum.to_string()) .selected_text(self.settings.sum.to_string())
.show_ui(ui, |ui| { .show_ui(ui, |ui| {
ui.selectable_value(&mut self.settings.sum, RiemannSum::Left, "Left"); ui.selectable_value(&mut self.settings.sum, Riemann::Left, "Left");
ui.selectable_value(&mut self.settings.sum, RiemannSum::Middle, "Middle"); ui.selectable_value(&mut self.settings.sum, Riemann::Middle, "Middle");
ui.selectable_value(&mut self.settings.sum, RiemannSum::Right, "Right"); ui.selectable_value(&mut self.settings.sum, Riemann::Right, "Right");
}); });
let riemann_changed = prev_sum == self.settings.sum; 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) { if let Some(test_output_value) = test_func(&proc_func_str) {
self.last_error.push((i, test_output_value)); self.last_error.push((i, test_output_value));
} else { } else {
function.update(proc_func_str, integral_enabled, derivative_enabled); function.update(&proc_func_str, integral_enabled, derivative_enabled);
self.last_error = self self.last_error = self
.last_error .last_error
.iter() .iter()
@ -717,7 +701,6 @@ impl epi::App for MathApp {
plot_ui, plot_ui,
minx_bounds, minx_bounds,
maxx_bounds, maxx_bounds,
available_width,
width_changed, width_changed,
settings_copy, settings_copy,
) )

View File

@ -2,7 +2,7 @@
use crate::egui_app::AppSettings; use crate::egui_app::AppSettings;
use crate::function_output::FunctionOutput; 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 crate::parsing::BackingFunction;
use eframe::{egui, epaint}; use eframe::{egui, epaint};
use egui::{ use egui::{
@ -17,13 +17,13 @@ use rayon::iter::ParallelIterator;
/// Represents the possible variations of Riemann Sums /// Represents the possible variations of Riemann Sums
#[derive(PartialEq, Debug, Copy, Clone)] #[derive(PartialEq, Debug, Copy, Clone)]
pub enum RiemannSum { pub enum Riemann {
Left, Left,
Middle, Middle,
Right, Right,
} }
impl fmt::Display for RiemannSum { impl fmt::Display for Riemann {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self) } fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self) }
} }
@ -76,11 +76,11 @@ impl Default for FunctionEntry {
impl FunctionEntry { impl FunctionEntry {
/// Update function settings /// 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 the function string changes, just wipe and restart from scratch
if func_str != self.func_str { if func_str != self.func_str {
self.func_str = func_str.clone(); self.func_str = func_str.to_string();
self.function = BackingFunction::new(&func_str); self.function = BackingFunction::new(func_str);
self.output.invalidate_whole(); self.output.invalidate_whole();
} }
@ -91,7 +91,7 @@ impl FunctionEntry {
/// Creates and does the math for creating all the rectangles under the /// Creates and does the math for creating all the rectangles under the
/// graph /// graph
fn integral_rectangles( 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) { ) -> (Vec<(f64, f64)>, f64) {
if integral_min_x.is_nan() { if integral_min_x.is_nan() {
panic!("integral_min_x is NaN") panic!("integral_min_x is NaN")
@ -112,9 +112,9 @@ impl FunctionEntry {
}; };
let y = match sum { let y = match sum {
RiemannSum::Left => self.function.get(left_x), Riemann::Left => self.function.get(left_x),
RiemannSum::Right => self.function.get(right_x), Riemann::Right => self.function.get(right_x),
RiemannSum::Middle => { Riemann::Middle => {
(self.function.get(left_x) + self.function.get(right_x)) / 2.0 (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` /// Does the calculations and stores results in `self.output`
pub fn display( pub fn calculate(
&mut self, plot_ui: &mut PlotUi, min_x: f64, max_x: f64, pixel_width: usize, &mut self, min_x: f64, max_x: f64, width_changed: bool, settings: AppSettings,
width_changed: bool, settings: AppSettings, ) {
) -> Option<f64> { let resolution: f64 = settings.pixel_width as f64 / (max_x.abs() + min_x.abs());
let resolution: f64 = pixel_width as f64 / (max_x.abs() + min_x.abs()); let resolution_iter = resolution_helper(settings.pixel_width + 1, min_x, resolution);
let resolution_iter = resolution_helper(pixel_width + 1, min_x, resolution);
// Makes sure proper arguments are passed when integral is enabled // Makes sure proper arguments are passed when integral is enabled
if self.integral && settings.integral_changed { if self.integral && settings.integral_changed {
@ -204,7 +203,7 @@ impl FunctionEntry {
} }
}) })
.collect(); .collect();
assert_eq!(back_data.len(), pixel_width + 1); assert_eq!(back_data.len(), settings.pixel_width + 1);
self.output.back = Some(back_data); self.output.back = Some(back_data);
let derivative_cache = self.output.derivative.as_ref().unwrap(); let derivative_cache = self.output.derivative.as_ref().unwrap();
@ -218,7 +217,7 @@ impl FunctionEntry {
}) })
.collect(); .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); self.output.derivative = Some(new_derivative_data);
} else { } else {
@ -237,7 +236,7 @@ impl FunctionEntry {
let data: Vec<Value> = dyn_iter(&resolution_iter) let data: Vec<Value> = dyn_iter(&resolution_iter)
.map(|x| Value::new(*x, self.function.get(*x))) .map(|x| Value::new(*x, self.function.get(*x)))
.collect(); .collect();
assert_eq!(data.len(), pixel_width + 1); assert_eq!(data.len(), settings.pixel_width + 1);
self.output.back = Some(data); self.output.back = Some(data);
} }
@ -250,7 +249,7 @@ impl FunctionEntry {
let data: Vec<Value> = dyn_iter(&resolution_iter) let data: Vec<Value> = dyn_iter(&resolution_iter)
.map(|x| Value::new(*x, self.function.get_derivative_1(*x))) .map(|x| Value::new(*x, self.function.get_derivative_1(*x)))
.collect(); .collect();
assert_eq!(data.len(), pixel_width + 1); assert_eq!(data.len(), settings.pixel_width + 1);
self.output.derivative = Some(data); self.output.derivative = Some(data);
} }
@ -286,6 +285,14 @@ impl FunctionEntry {
if settings.roots && (min_max_changed | self.output.roots.is_none()) { if settings.roots && (min_max_changed | self.output.roots.is_none()) {
self.output.roots = self.newtons_method_helper(threshold, 0); 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<f64> {
self.calculate(min_x, max_x, width_changed, settings);
let func_str = self.get_func_str(); let func_str = self.get_func_str();
let derivative_str = self.function.get_derivative_str(); let derivative_str = self.function.get_derivative_str();
@ -347,159 +354,110 @@ impl FunctionEntry {
None 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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
fn verify_function( fn app_settings_constructor(
integral_num: usize, pixel_width: usize, function: &mut FunctionEntry, sum: Riemann, integral_min_x: f64, integral_max_x: f64, pixel_width: usize,
back_values_target: Vec<(f64, f64)>, area_target: f64, integral_num: usize,
) { ) -> AppSettings {
{ crate::egui_app::AppSettings {
let (back_values, bars, derivative) = function.run_back(-1.0, 1.0); help_open: false,
assert!(derivative.is_some()); info_open: false,
assert!(bars.is_none()); show_side_panel: false,
assert_eq!(back_values.len(), pixel_width); sum,
let back_values_tuple: Vec<(f64, f64)> = integral_min_x,
back_values.iter().map(|ele| (ele.x, ele.y)).collect(); integral_max_x,
assert_eq!(back_values_tuple, back_values_target); integral_changed: true,
integral_num,
dark_mode: false,
extrema: false,
roots: false,
pixel_width,
} }
}
{ static BACK_TARGET: [(f64, f64); 11] = [
*function = function.clone().integral(true); (-1.0, 1.0),
let (back_values, bars, derivative) = function.run_back(-1.0, 1.0); (-0.8, 0.6400000000000001),
assert!(derivative.is_some()); (-0.6, 0.36),
assert!(bars.is_some()); (-0.4, 0.16000000000000003),
assert_eq!(back_values.len(), pixel_width); (-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; let mut function = FunctionEntry::default();
assert_eq!(vec_bars.len(), integral_num); function.update("x^2", true, true);
let back_values_tuple: Vec<(f64, f64)> = function.tests(settings, BACK_TARGET.to_vec(), area_target, -1.0, 1.0);
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);
}
} }
#[test] #[test]
fn left_function_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; let area_target = 0.9600000000000001;
verify_function( do_test(Riemann::Left, area_target);
integral_num,
pixel_width,
&mut function,
back_values_target,
area_target,
);
} }
#[test] #[test]
fn middle_function_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; let area_target = 0.92;
verify_function( do_test(Riemann::Middle, area_target);
integral_num,
pixel_width,
&mut function,
back_values_target,
area_target,
);
} }
#[test] #[test]
fn right_function_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; let area_target = 0.8800000000000001;
verify_function( do_test(Riemann::Right, area_target);
integral_num,
pixel_width,
&mut function,
back_values_target,
area_target,
);
} }
} }
*/

View File

@ -1,6 +1,7 @@
#![allow(clippy::unused_unit)] // Fixes clippy keep complaining about wasm_bindgen #![allow(clippy::unused_unit)] // Fixes clippy keep complaining about wasm_bindgen
#![feature(const_mut_refs)] #![feature(const_mut_refs)]
mod consts;
mod egui_app; mod egui_app;
mod function; mod function;
mod function_output; mod function_output;

View File

@ -1,5 +1,6 @@
#![feature(const_mut_refs)] #![feature(const_mut_refs)]
mod consts;
mod egui_app; mod egui_app;
mod function; mod function;
mod function_output; mod function_output;

View File

@ -270,6 +270,13 @@ pub fn step_helper(max_i: usize, min_x: f64, step: f64) -> Vec<f64> {
.collect() .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<EguiValue>) -> Vec<(f64, f64)> {
data.iter().map(|ele| (ele.x, ele.y)).collect()
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;