refactoring of function and derivative code

This commit is contained in:
Simon Gardling 2022-03-04 13:54:25 -05:00
parent d16b5c882a
commit 2e55768972
2 changed files with 83 additions and 66 deletions

View File

@ -1,7 +1,7 @@
#![allow(clippy::too_many_arguments)] // Clippy, shut #![allow(clippy::too_many_arguments)] // Clippy, shut
#[allow(unused_imports)] #[allow(unused_imports)]
use crate::misc::{debug_log, SteppedVector}; use crate::misc::{debug_log, BackingFunction, BoxFunction, SteppedVector, EPSILON};
use eframe::egui::{ use eframe::egui::{
plot::{BarChart, Line, Value, Values}, plot::{BarChart, Line, Value, Values},
@ -22,7 +22,7 @@ impl fmt::Display for RiemannSum {
} }
pub struct Function { pub struct Function {
function: Box<dyn Fn(f64) -> f64>, function: BackingFunction,
func_str: String, func_str: String,
min_x: f64, min_x: f64,
max_x: f64, max_x: f64,
@ -34,6 +34,7 @@ pub struct Function {
pub(crate) integral: bool, pub(crate) integral: bool,
pub(crate) derivative: bool, pub(crate) derivative: bool,
pub(crate) nth_derivative: u64,
integral_min_x: f64, integral_min_x: f64,
integral_max_x: f64, integral_max_x: f64,
integral_num: usize, integral_num: usize,
@ -43,13 +44,11 @@ pub struct Function {
// x^2 function, set here so we don't have to regenerate it every time a new function is made // x^2 function, set here so we don't have to regenerate it every time a new function is made
fn default_function(x: f64) -> f64 { x.powi(2) } fn default_function(x: f64) -> f64 { x.powi(2) }
const EPSILON: f64 = 5.0e-7;
impl Function { impl Function {
// Creates Empty Function instance // Creates Empty Function instance
pub fn empty() -> Self { pub fn empty() -> Self {
Self { Self {
function: Box::new(default_function), function: BackingFunction::new(Box::new(default_function)),
func_str: String::new(), func_str: String::new(),
min_x: -1.0, min_x: -1.0,
max_x: 1.0, max_x: 1.0,
@ -59,6 +58,7 @@ impl Function {
derivative_cache: None, derivative_cache: None,
integral: false, integral: false,
derivative: false, derivative: false,
nth_derivative: 1,
integral_min_x: f64::NAN, integral_min_x: f64::NAN,
integral_max_x: f64::NAN, integral_max_x: f64::NAN,
integral_num: 0, integral_num: 0,
@ -67,7 +67,7 @@ impl Function {
} }
// Runs the internal function to get values // Runs the internal function to get values
fn run_func(&self, x: f64) -> f64 { (self.function)(x) } fn run_func(&self, x: f64) -> f64 { self.function.get(x) }
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: Option<f64>,
@ -76,10 +76,10 @@ impl Function {
// 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.clone();
self.function = Box::new({ self.function = BackingFunction::new(Box::new({
let expr: Expr = func_str.parse().unwrap(); let expr: Expr = func_str.parse().unwrap();
expr.bind("x").unwrap() expr.bind("x").unwrap()
}); }));
self.back_cache = None; self.back_cache = None;
self.front_cache = None; self.front_cache = None;
self.derivative_cache = None; self.derivative_cache = None;
@ -143,10 +143,7 @@ impl Function {
if let Some(i) = x_data.get_index(x) { if let Some(i) = x_data.get_index(x) {
derivative_cache[i] derivative_cache[i]
} else { } else {
let (x1, x2) = (x - EPSILON, x + EPSILON); Value::new(x, self.function.derivative(x, self.nth_derivative))
let (y1, y2) = (self.run_func(x1), self.run_func(x2));
let slope = (y2 - y1) / (EPSILON * 2.0);
Value::new(x, slope)
} }
}) })
.collect(), .collect(),
@ -299,29 +296,39 @@ impl Function {
self.integral = integral; self.integral = integral;
self self
} }
// Toggles integral
pub fn integral_num(mut self, integral_num: usize) -> Self {
self.integral_num = integral_num;
self
}
pub fn pixel_width(mut self, pixel_width: usize) -> Self {
self.pixel_width = pixel_width;
self
}
pub fn integral_bounds(mut self, min_x: f64, max_x: f64) -> Self {
if min_x >= max_x {
panic!("integral_bounds: min_x is larger than max_x");
}
self.integral_min_x = min_x;
self.integral_max_x = max_x;
self
}
} }
#[test] #[test]
fn left_function_test() { fn left_function_test() {
let pixel_width = 10;
let integral_num = 10; let integral_num = 10;
let pixel_width = 10;
let mut function = Function { let mut function = Function::empty()
function: Box::new(default_function), .update_riemann(RiemannSum::Left)
func_str: String::from("x^2"), .pixel_width(pixel_width)
min_x: -1.0, .integral_num(integral_num)
max_x: 1.0, .integral_bounds(-1.0, 1.0);
pixel_width,
back_cache: None,
front_cache: None,
derivative_cache: None,
integral: false,
derivative: false,
integral_min_x: -1.0,
integral_max_x: 1.0,
integral_num,
sum: RiemannSum::Left,
};
let back_values_target = vec![ let back_values_target = vec![
(-1.0, 1.0), (-1.0, 1.0),
@ -431,31 +438,20 @@ fn left_function_test() {
assert_eq!(vec_integral.len(), integral_num); assert_eq!(vec_integral.len(), integral_num);
assert_eq!(vec_integral, vec_integral_target); assert_eq!(vec_integral, vec_integral_target);
assert_eq!(vec_integral[vec_integral.len()-1].1, area_target); assert_eq!(vec_integral[vec_integral.len() - 1].1, area_target);
} }
} }
#[test] #[test]
fn middle_function_test() { fn middle_function_test() {
let pixel_width = 10;
let integral_num = 10; let integral_num = 10;
let pixel_width = 10;
let mut function = Function { let mut function = Function::empty()
function: Box::new(default_function), .update_riemann(RiemannSum::Middle)
func_str: String::from("x^2"), .pixel_width(pixel_width)
min_x: -1.0, .integral_num(integral_num)
max_x: 1.0, .integral_bounds(-1.0, 1.0);
pixel_width,
back_cache: None,
front_cache: None,
derivative_cache: None,
integral: false,
derivative: false,
integral_min_x: -1.0,
integral_max_x: 1.0,
integral_num,
sum: RiemannSum::Middle,
};
let back_values_target = vec![ let back_values_target = vec![
(-1.0, 1.0), (-1.0, 1.0),
@ -565,31 +561,20 @@ fn middle_function_test() {
assert_eq!(vec_integral.len(), integral_num); assert_eq!(vec_integral.len(), integral_num);
assert_eq!(vec_integral, vec_integral_target); assert_eq!(vec_integral, vec_integral_target);
assert_eq!(vec_integral[vec_integral.len()-1].1, area_target); assert_eq!(vec_integral[vec_integral.len() - 1].1, area_target);
} }
} }
#[test] #[test]
fn right_function_test() { fn right_function_test() {
let pixel_width = 10;
let integral_num = 10; let integral_num = 10;
let pixel_width = 10;
let mut function = Function { let mut function = Function::empty()
function: Box::new(default_function), .update_riemann(RiemannSum::Right)
func_str: String::from("x^2"), .pixel_width(pixel_width)
min_x: -1.0, .integral_num(integral_num)
max_x: 1.0, .integral_bounds(-1.0, 1.0);
pixel_width,
back_cache: None,
front_cache: None,
derivative_cache: None,
integral: false,
derivative: false,
integral_min_x: -1.0,
integral_max_x: 1.0,
integral_num,
sum: RiemannSum::Right,
};
let back_values_target = vec![ let back_values_target = vec![
(-1.0, 1.0), (-1.0, 1.0),
@ -699,6 +684,6 @@ fn right_function_test() {
assert_eq!(vec_integral.len(), integral_num); assert_eq!(vec_integral.len(), integral_num);
assert_eq!(vec_integral, vec_integral_target); assert_eq!(vec_integral, vec_integral_target);
assert_eq!(vec_integral[vec_integral.len()-1].1, area_target); assert_eq!(vec_integral[vec_integral.len() - 1].1, area_target);
} }
} }

View File

@ -34,6 +34,38 @@ pub fn debug_log(s: &str) {
#[allow(dead_code)] #[allow(dead_code)]
pub fn debug_log(_s: &str) {} pub fn debug_log(_s: &str) {}
pub const EPSILON: f64 = 5.0e-7;
pub type BoxFunction = Box<dyn Fn(f64) -> f64>;
pub struct BackingFunction {
function: BoxFunction,
}
impl BackingFunction {
pub fn new(function: BoxFunction) -> BackingFunction { BackingFunction { function } }
pub fn get(&self, x: f64) -> f64 { (self.function)(x) }
pub fn derivative(&self, x: f64, n: u64) -> f64 {
if n == 0 {
return self.get(x);
}
let (x1, x2) = (x - EPSILON, x + EPSILON);
let (y1, y2) = (self.derivative(x1, n - 1), (self.derivative(x2, n - 1)));
(y2 - y1) / (EPSILON * 2.0)
}
}
impl From<BoxFunction> for BackingFunction {
fn from(boxfunction: BoxFunction) -> BackingFunction {
BackingFunction {
function: boxfunction,
}
}
}
/* /*
EXTREMELY Janky function that tries to put asterisks in the proper places to be parsed. This is so cursed. But it works, and I hopefully won't ever have to touch it again. EXTREMELY Janky function that tries to put asterisks in the proper places to be parsed. This is so cursed. But it works, and I hopefully won't ever have to touch it again.
One limitation though, variables with multiple characters like `pi` cannot be multiplied (like `pipipipi` won't result in `pi*pi*pi*pi`). But that's such a niche use case (and that same thing could be done by using exponents) that it doesn't really matter. One limitation though, variables with multiple characters like `pi` cannot be multiplied (like `pipipipi` won't result in `pi*pi*pi*pi`). But that's such a niche use case (and that same thing could be done by using exponents) that it doesn't really matter.