add support for derivatives

This commit is contained in:
Simon Gardling 2022-03-04 09:19:04 -05:00
parent 7efd523436
commit 3e6e0e5adb
2 changed files with 84 additions and 12 deletions

View File

@ -205,7 +205,10 @@ impl MathApp {
self.last_error = String::new(); self.last_error = String::new();
for (i, function) in self.functions.iter_mut().enumerate() { for (i, function) in self.functions.iter_mut().enumerate() {
let mut integral_toggle: bool = false; let mut integral_toggle: bool = false;
let mut derivative_toggle: bool = false;
let integral_enabled = function.integral; let integral_enabled = function.integral;
let derivative_enabled = function.derivative;
// Entry for a function // Entry for a function
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.label("Function:"); ui.label("Function:");
@ -216,6 +219,7 @@ impl MathApp {
{ {
remove_i = Some(i); remove_i = Some(i);
} }
if ui if ui
.add(Button::new("")) .add(Button::new(""))
.on_hover_text(match integral_enabled { .on_hover_text(match integral_enabled {
@ -226,6 +230,18 @@ impl MathApp {
{ {
integral_toggle = true; integral_toggle = true;
} }
if ui
.add(Button::new("d/dx"))
.on_hover_text(match derivative_enabled {
true => "Calculate Derivative",
false => "Don't Calculate Derivative",
})
.clicked()
{
derivative_toggle = true;
}
ui.text_edit_singleline(&mut self.func_strs[i]); ui.text_edit_singleline(&mut self.func_strs[i]);
}); });
@ -235,15 +251,23 @@ impl MathApp {
integral_enabled integral_enabled
}; };
let derivative: bool = if derivative_toggle {
!derivative_enabled
} else {
derivative_enabled
};
if !self.func_strs[i].is_empty() { if !self.func_strs[i].is_empty() {
let proc_func_str = add_asterisks(self.func_strs[i].clone()); let proc_func_str = add_asterisks(self.func_strs[i].clone());
let func_test_output = test_func(proc_func_str.clone()); let func_test_output = test_func(proc_func_str.clone());
if let Some(test_output_value) = func_test_output { if let Some(test_output_value) = func_test_output {
self.last_error += &format!("(Function #{}) {}\n", i, test_output_value); self.last_error +=
&format!("(Function #{}) {}\n", i, test_output_value);
} else { } else {
function.update( function.update(
proc_func_str, proc_func_str,
integral, integral,
derivative,
Some(self.settings.integral_min_x), Some(self.settings.integral_min_x),
Some(self.settings.integral_max_x), Some(self.settings.integral_max_x),
Some(self.settings.integral_num), Some(self.settings.integral_num),
@ -426,9 +450,13 @@ impl epi::App for MathApp {
function.update_bounds(minx_bounds, maxx_bounds, available_width); function.update_bounds(minx_bounds, maxx_bounds, available_width);
let (back_values, bars) = function.run(); let (back_values, bars, derivative) = function.run();
plot_ui.line(back_values.color(Color32::RED)); plot_ui.line(back_values.color(Color32::RED));
if let Some(derivative_data) = derivative {
plot_ui.line(derivative_data.color(Color32::GREEN));
}
if let Some(bars_data) = bars { if let Some(bars_data) = bars {
let (bar_chart, area) = bars_data; let (bar_chart, area) = bars_data;
plot_ui.bar_chart(bar_chart.color(Color32::BLUE).width(step)); plot_ui.bar_chart(bar_chart.color(Color32::BLUE).width(step));

View File

@ -30,8 +30,10 @@ pub struct Function {
back_cache: Option<Vec<Value>>, back_cache: Option<Vec<Value>>,
front_cache: Option<(Vec<Bar>, f64)>, front_cache: Option<(Vec<Bar>, f64)>,
derivative_cache: Option<Vec<Value>>,
pub(crate) integral: bool, pub(crate) integral: bool,
pub(crate) derivative: bool,
integral_min_x: f64, integral_min_x: f64,
integral_max_x: f64, integral_max_x: f64,
integral_num: usize, integral_num: usize,
@ -41,6 +43,8 @@ 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 {
@ -52,7 +56,9 @@ impl Function {
pixel_width: 100, pixel_width: 100,
back_cache: None, back_cache: None,
front_cache: None, front_cache: None,
derivative_cache: None,
integral: false, integral: false,
derivative: false,
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,
@ -64,7 +70,7 @@ impl Function {
fn run_func(&self, x: f64) -> f64 { (self.function)(x) } fn run_func(&self, x: f64) -> f64 { (self.function)(x) }
pub fn update( pub fn update(
&mut self, func_str: String, integral: bool, integral_min_x: Option<f64>, &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>, integral_max_x: Option<f64>, integral_num: Option<usize>, sum: Option<RiemannSum>,
) { ) {
// If the function string changes, just wipe and restart from scratch // If the function string changes, just wipe and restart from scratch
@ -76,8 +82,10 @@ impl Function {
}); });
self.back_cache = None; self.back_cache = None;
self.front_cache = None; self.front_cache = None;
self.derivative_cache = None;
} }
self.derivative = derivative;
self.integral = integral; self.integral = integral;
// Makes sure proper arguments are passed when integral is enabled // Makes sure proper arguments are passed when integral is enabled
@ -98,6 +106,7 @@ impl Function {
pub fn update_bounds(&mut self, min_x: f64, max_x: f64, pixel_width: usize) { pub fn update_bounds(&mut self, min_x: f64, max_x: f64, pixel_width: usize) {
if pixel_width != self.pixel_width { if pixel_width != self.pixel_width {
self.back_cache = None; self.back_cache = None;
self.derivative_cache = None;
self.min_x = min_x; self.min_x = min_x;
self.max_x = max_x; self.max_x = max_x;
self.pixel_width = pixel_width; self.pixel_width = pixel_width;
@ -123,15 +132,17 @@ impl Function {
}) })
.collect(), .collect(),
); );
self.derivative_cache = None; // TODO: setup this caching system for derivatives
} else { } else {
self.back_cache = None; self.back_cache = None;
self.derivative_cache = None;
self.min_x = min_x; self.min_x = min_x;
self.max_x = max_x; self.max_x = max_x;
self.pixel_width = pixel_width; self.pixel_width = pixel_width;
} }
} }
pub fn run_back(&mut self) -> (Vec<Value>, Option<(Vec<Bar>, f64)>) { pub fn run_back(&mut self) -> (Vec<Value>, Option<(Vec<Bar>, f64)>, Option<Vec<Value>>) {
let back_values: Vec<Value> = { let back_values: Vec<Value> = {
if self.back_cache.is_none() { if self.back_cache.is_none() {
let resolution: f64 = let resolution: f64 =
@ -147,6 +158,28 @@ impl Function {
self.back_cache.as_ref().unwrap().clone() self.back_cache.as_ref().unwrap().clone()
}; };
let derivative_values: Option<Vec<Value>> = match self.derivative {
true => {
if self.derivative_cache.is_none() {
let back_cache = self.back_cache.as_ref().unwrap().clone();
self.derivative_cache = Some(
back_cache
.iter()
.map(|ele| {
let x = ele.x;
let (x1, x2) = (x - EPSILON, x + EPSILON);
let (y1, y2) = (self.run_func(x1), self.run_func(x2));
let slope = (y2 - y1) / (EPSILON * 2.0);
Value::new(x, slope)
})
.collect(),
);
}
Some(self.derivative_cache.as_ref().unwrap().clone())
}
false => None,
};
let front_bars = match self.integral { let front_bars = match self.integral {
true => { true => {
if self.front_cache.is_none() { if self.front_cache.is_none() {
@ -160,11 +193,11 @@ impl Function {
false => None, false => None,
}; };
(back_values, front_bars) (back_values, front_bars, derivative_values)
} }
pub fn run(&mut self) -> (Line, Option<(BarChart, f64)>) { pub fn run(&mut self) -> (Line, Option<(BarChart, f64)>, Option<Line>) {
let (back_values, front_data_option) = self.run_back(); let (back_values, front_data_option, derivative_option) = self.run_back();
( (
Line::new(Values::from_values(back_values)), Line::new(Values::from_values(back_values)),
@ -173,6 +206,11 @@ impl Function {
} else { } else {
None None
}, },
if let Some(derivative_data) = derivative_option {
Some(Line::new(Values::from_values(derivative_data)))
} else {
None
},
) )
} }
@ -243,7 +281,9 @@ fn left_function_test() {
pixel_width: 10, pixel_width: 10,
back_cache: None, back_cache: None,
front_cache: None, front_cache: None,
derivative_cache: None,
integral: false, integral: false,
derivative: false,
integral_min_x: -1.0, integral_min_x: -1.0,
integral_max_x: 1.0, integral_max_x: 1.0,
integral_num: 10, integral_num: 10,
@ -251,7 +291,7 @@ fn left_function_test() {
}; };
{ {
let (back_values, bars) = function.run_back(); let (back_values, bars, _) = function.run_back();
assert!(bars.is_none()); assert!(bars.is_none());
assert_eq!(back_values.len(), 10); assert_eq!(back_values.len(), 10);
let back_values_tuple: Vec<(f64, f64)> = let back_values_tuple: Vec<(f64, f64)> =
@ -275,7 +315,7 @@ fn left_function_test() {
{ {
function = function.integral(true); function = function.integral(true);
let (back_values, bars) = function.run_back(); let (back_values, bars, _) = function.run_back();
assert!(bars.is_some()); assert!(bars.is_some());
assert_eq!(back_values.len(), 10); assert_eq!(back_values.len(), 10);
assert_eq!(bars.clone().unwrap().1, 0.8720000000000001); assert_eq!(bars.clone().unwrap().1, 0.8720000000000001);
@ -294,7 +334,9 @@ fn middle_function_test() {
pixel_width: 10, pixel_width: 10,
back_cache: None, back_cache: None,
front_cache: None, front_cache: None,
derivative_cache: None,
integral: false, integral: false,
derivative: false,
integral_min_x: -1.0, integral_min_x: -1.0,
integral_max_x: 1.0, integral_max_x: 1.0,
integral_num: 10, integral_num: 10,
@ -302,7 +344,7 @@ fn middle_function_test() {
}; };
{ {
let (back_values, bars) = function.run_back(); let (back_values, bars, _) = function.run_back();
assert!(bars.is_none()); assert!(bars.is_none());
assert_eq!(back_values.len(), 10); assert_eq!(back_values.len(), 10);
let back_values_tuple: Vec<(f64, f64)> = let back_values_tuple: Vec<(f64, f64)> =
@ -326,7 +368,7 @@ fn middle_function_test() {
{ {
function = function.integral(true); function = function.integral(true);
let (back_values, bars) = function.run_back(); let (back_values, bars, _) = function.run_back();
assert!(bars.is_some()); assert!(bars.is_some());
assert_eq!(back_values.len(), 10); assert_eq!(back_values.len(), 10);
assert_eq!(bars.clone().unwrap().1, 0.9200000000000002); assert_eq!(bars.clone().unwrap().1, 0.9200000000000002);
@ -345,7 +387,9 @@ fn right_function_test() {
pixel_width: 10, pixel_width: 10,
back_cache: None, back_cache: None,
front_cache: None, front_cache: None,
derivative_cache: None,
integral: false, integral: false,
derivative: false,
integral_min_x: -1.0, integral_min_x: -1.0,
integral_max_x: 1.0, integral_max_x: 1.0,
integral_num: 10, integral_num: 10,
@ -377,7 +421,7 @@ fn right_function_test() {
{ {
function = function.integral(true); function = function.integral(true);
let (back_values, bars) = function.run_back(); let (back_values, bars, _) = function.run_back();
assert!(bars.is_some()); assert!(bars.is_some());
assert_eq!(back_values.len(), 10); assert_eq!(back_values.len(), 10);
assert_eq!(bars.clone().unwrap().1, 0.9680000000000002); assert_eq!(bars.clone().unwrap().1, 0.9680000000000002);