some more refactoring
This commit is contained in:
@@ -11,7 +11,7 @@ mod suggestions;
|
|||||||
pub use crate::{
|
pub use crate::{
|
||||||
autocomplete::{AutoComplete, Movement},
|
autocomplete::{AutoComplete, Movement},
|
||||||
autocomplete_hashmap::compile_hashmap,
|
autocomplete_hashmap::compile_hashmap,
|
||||||
parsing::{process_func_str, BackingFunction},
|
parsing::{process_func_str, BackingFunction, FlatExWrapper},
|
||||||
splitting::{split_function, split_function_chars, SplitType},
|
splitting::{split_function, split_function_chars, SplitType},
|
||||||
suggestions::{generate_hint, get_last_term, Hint, HINT_EMPTY, SUPPORTED_FUNCTIONS},
|
suggestions::{generate_hint, get_last_term, Hint, HINT_EMPTY, SUPPORTED_FUNCTIONS},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ use exmex::prelude::*;
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub(crate) struct FlatExWrapper<'a> {
|
pub struct FlatExWrapper {
|
||||||
func: Option<FlatEx<f64>>,
|
func: Option<FlatEx<f64>>,
|
||||||
func_str: Option<&'a str>,
|
func_str: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> FlatExWrapper<'a> {
|
impl FlatExWrapper {
|
||||||
const EMPTY: FlatExWrapper<'a> = FlatExWrapper {
|
const EMPTY: FlatExWrapper = FlatExWrapper {
|
||||||
func: None,
|
func: None,
|
||||||
func_str: None,
|
func_str: None,
|
||||||
};
|
};
|
||||||
@@ -25,7 +25,7 @@ impl<'a> FlatExWrapper<'a> {
|
|||||||
const fn is_none(&self) -> bool { self.func.is_none() }
|
const fn is_none(&self) -> bool { self.func.is_none() }
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn eval(&'a self, x: &[f64]) -> f64 {
|
pub fn eval(&self, x: &[f64]) -> f64 {
|
||||||
self.func
|
self.func
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|f| f.eval(x).unwrap_or(f64::NAN))
|
.map(|f| f.eval(x).unwrap_or(f64::NAN))
|
||||||
@@ -33,7 +33,7 @@ impl<'a> FlatExWrapper<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn partial(&'a self, x: usize) -> Self {
|
fn partial(&self, x: usize) -> Self {
|
||||||
self.func
|
self.func
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|f| f.clone().partial(x).map(Self::new).unwrap_or(Self::EMPTY))
|
.map(|f| f.clone().partial(x).map(Self::new).unwrap_or(Self::EMPTY))
|
||||||
@@ -41,17 +41,19 @@ impl<'a> FlatExWrapper<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn get_string(&'a mut self) -> &'a str {
|
fn get_string(&mut self) -> String {
|
||||||
if let Some(func_str) = self.func_str {
|
match self.func_str {
|
||||||
return func_str;
|
Some(ref func_str) => func_str.clone(),
|
||||||
|
None => {
|
||||||
|
let calculated = self.func.as_ref().map(|f| f.unparse()).unwrap_or("");
|
||||||
|
self.func_str = Some(calculated.to_owned());
|
||||||
|
calculated.to_owned()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let calculated = self.func.as_ref().map(|f| f.unparse()).unwrap_or("");
|
|
||||||
self.func_str = Some(calculated);
|
|
||||||
return calculated;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn partial_iter(&'a self, n: usize) -> Self {
|
fn partial_iter(&self, n: usize) -> Self {
|
||||||
self.func
|
self.func
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|f| {
|
.map(|f| {
|
||||||
@@ -64,20 +66,24 @@ impl<'a> FlatExWrapper<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> const Default for FlatExWrapper<'a> {
|
impl const Default for FlatExWrapper {
|
||||||
fn default() -> FlatExWrapper<'a> { FlatExWrapper::EMPTY }
|
fn default() -> FlatExWrapper { FlatExWrapper::EMPTY }
|
||||||
}
|
}
|
||||||
/// Function that includes f(x), f'(x), f'(x)'s string representation, and f''(x)
|
/// Function that includes f(x), f'(x), f'(x)'s string representation, and f''(x)
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub struct BackingFunction<'a> {
|
pub struct BackingFunction {
|
||||||
/// f(x)
|
/// f(x)
|
||||||
function: FlatExWrapper<'a>,
|
function: FlatExWrapper,
|
||||||
|
|
||||||
/// Temporary cache for nth derivative
|
/// Temporary cache for nth derivative
|
||||||
nth_derivative: HashMap<usize, FlatExWrapper<'a>>,
|
nth_derivative: HashMap<usize, FlatExWrapper>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> BackingFunction<'a> {
|
impl Default for BackingFunction {
|
||||||
|
fn default() -> Self { Self::new("").unwrap() }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BackingFunction {
|
||||||
pub const fn is_none(&self) -> bool { self.function.is_none() }
|
pub const fn is_none(&self) -> bool { self.function.is_none() }
|
||||||
|
|
||||||
/// Create new [`BackingFunction`] instance
|
/// Create new [`BackingFunction`] instance
|
||||||
@@ -123,19 +129,31 @@ impl<'a> BackingFunction<'a> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(&'a mut self, derivative: usize, x: f64) -> f64 {
|
// TODO rewrite this logic, it's a mess
|
||||||
match derivative {
|
pub fn generate_derivative(&mut self, derivative: usize) {
|
||||||
0 => self.function.eval(&[x]),
|
if derivative == 0 {
|
||||||
|
return;
|
||||||
_ => match self.nth_derivative.get(&derivative) {
|
|
||||||
Some(func) => func.eval(&[x]),
|
|
||||||
None => {
|
|
||||||
let new_func = self.function.partial_iter(derivative);
|
|
||||||
self.nth_derivative.insert(derivative, new_func.clone());
|
|
||||||
new_func.eval(&[x])
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !self.nth_derivative.contains_key(&derivative) {
|
||||||
|
let new_func = self.function.partial_iter(derivative);
|
||||||
|
self.nth_derivative.insert(derivative, new_func.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_function_derivative(&self, derivative: usize) -> &FlatExWrapper {
|
||||||
|
if derivative == 0 {
|
||||||
|
return &self.function;
|
||||||
|
} else {
|
||||||
|
return self
|
||||||
|
.nth_derivative
|
||||||
|
.get(&derivative)
|
||||||
|
.unwrap_or(&FlatExWrapper::EMPTY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&mut self, derivative: usize, x: f64) -> f64 {
|
||||||
|
self.get_function_derivative(derivative).eval(&[x])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ impl fmt::Display for Riemann {
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct FunctionEntry {
|
pub struct FunctionEntry {
|
||||||
/// The `BackingFunction` instance that is used to generate `f(x)`, `f'(x)`, and `f''(x)`
|
/// The `BackingFunction` instance that is used to generate `f(x)`, `f'(x)`, and `f''(x)`
|
||||||
function: BackingFunction<'static>,
|
function: BackingFunction,
|
||||||
|
|
||||||
/// Stores a function string (that hasn't been processed via `process_func_str`) to display to the user
|
/// Stores a function string (that hasn't been processed via `process_func_str`) to display to the user
|
||||||
pub raw_func_str: String,
|
pub raw_func_str: String,
|
||||||
@@ -98,7 +98,7 @@ impl<'de> Deserialize<'de> for FunctionEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let helper = Helper::deserialize(deserializer)?;
|
let helper = Helper::deserialize(deserializer)?;
|
||||||
let mut new_func_entry = FunctionEntry::EMPTY;
|
let mut new_func_entry = FunctionEntry::default();
|
||||||
let gen_func = BackingFunction::new(&helper.raw_func_str);
|
let gen_func = BackingFunction::new(&helper.raw_func_str);
|
||||||
match gen_func {
|
match gen_func {
|
||||||
Ok(func) => new_func_entry.function = func,
|
Ok(func) => new_func_entry.function = func,
|
||||||
@@ -121,7 +121,25 @@ impl<'de> Deserialize<'de> for FunctionEntry {
|
|||||||
|
|
||||||
impl const Default for FunctionEntry {
|
impl const Default for FunctionEntry {
|
||||||
/// Creates default FunctionEntry instance (which is empty)
|
/// Creates default FunctionEntry instance (which is empty)
|
||||||
fn default() -> FunctionEntry {}
|
fn default() -> FunctionEntry {
|
||||||
|
FunctionEntry {
|
||||||
|
function: BackingFunction::default(),
|
||||||
|
raw_func_str: String::new(),
|
||||||
|
integral: false,
|
||||||
|
derivative: false,
|
||||||
|
nth_derviative: false,
|
||||||
|
back_data: Vec::new(),
|
||||||
|
integral_data: None,
|
||||||
|
derivative_data: Vec::new(),
|
||||||
|
extrema_data: Vec::new(),
|
||||||
|
root_data: Vec::new(),
|
||||||
|
nth_derivative_data: None,
|
||||||
|
autocomplete: AutoComplete::EMPTY,
|
||||||
|
test_result: None,
|
||||||
|
curr_nth: 3,
|
||||||
|
settings_opened: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FunctionEntry {
|
impl FunctionEntry {
|
||||||
@@ -149,6 +167,7 @@ impl FunctionEntry {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if invalidate_nth {
|
if invalidate_nth {
|
||||||
|
self.function.generate_derivative(self.curr_nth);
|
||||||
self.clear_nth();
|
self.clear_nth();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -180,7 +199,7 @@ impl FunctionEntry {
|
|||||||
|
|
||||||
/// 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(
|
fn integral_rectangles(
|
||||||
&self, integral_min_x: f64, integral_max_x: f64, sum: Riemann, integral_num: usize,
|
&mut self, integral_min_x: f64, integral_max_x: f64, sum: Riemann, integral_num: usize,
|
||||||
) -> (Vec<(f64, f64)>, f64) {
|
) -> (Vec<(f64, f64)>, f64) {
|
||||||
let step = (integral_max_x - integral_min_x) / (integral_num as f64);
|
let step = (integral_max_x - integral_min_x) / (integral_num as f64);
|
||||||
|
|
||||||
@@ -217,22 +236,24 @@ impl FunctionEntry {
|
|||||||
|
|
||||||
/// Helps with processing newton's method depending on level of derivative
|
/// Helps with processing newton's method depending on level of derivative
|
||||||
fn newtons_method_helper(
|
fn newtons_method_helper(
|
||||||
&self, threshold: f64, derivative_level: usize, range: &std::ops::Range<f64>,
|
&mut self, threshold: f64, derivative_level: usize, range: &std::ops::Range<f64>,
|
||||||
) -> Vec<PlotPoint> {
|
) -> Vec<PlotPoint> {
|
||||||
|
self.function.generate_derivative(derivative_level);
|
||||||
|
self.function.generate_derivative(derivative_level + 1);
|
||||||
let newtons_method_output: Vec<f64> = match derivative_level {
|
let newtons_method_output: Vec<f64> = match derivative_level {
|
||||||
0 => newtons_method_helper(
|
0 => newtons_method_helper(
|
||||||
threshold,
|
threshold,
|
||||||
range,
|
range,
|
||||||
self.back_data.as_slice(),
|
self.back_data.as_slice(),
|
||||||
&|x: f64| self.function.get(0, x),
|
&self.function.get_function_derivative(0),
|
||||||
&|x: f64| self.function.get(1, x),
|
&self.function.get_function_derivative(1),
|
||||||
),
|
),
|
||||||
1 => newtons_method_helper(
|
1 => newtons_method_helper(
|
||||||
threshold,
|
threshold,
|
||||||
range,
|
range,
|
||||||
self.derivative_data.as_slice(),
|
self.derivative_data.as_slice(),
|
||||||
&|x: f64| self.function.get(1, x),
|
&self.function.get_function_derivative(1),
|
||||||
&|x: f64| self.function.get(2, x),
|
&self.function.get_function_derivative(2),
|
||||||
),
|
),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
@@ -281,6 +302,7 @@ impl FunctionEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if self.derivative_data.is_empty() {
|
if self.derivative_data.is_empty() {
|
||||||
|
self.function.generate_derivative(1);
|
||||||
let data: Vec<PlotPoint> = resolution_iter
|
let data: Vec<PlotPoint> = resolution_iter
|
||||||
.clone()
|
.clone()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ impl Default for FunctionManager {
|
|||||||
let mut vec: Functions = Vec::with_capacity(COLORS.len());
|
let mut vec: Functions = Vec::with_capacity(COLORS.len());
|
||||||
vec.push((
|
vec.push((
|
||||||
create_id(11414819524356497634), // Random number here to avoid call to crate::misc::random_u64()
|
create_id(11414819524356497634), // Random number here to avoid call to crate::misc::random_u64()
|
||||||
FunctionEntry::EMPTY,
|
FunctionEntry::default(),
|
||||||
));
|
));
|
||||||
Self { functions: vec }
|
Self { functions: vec }
|
||||||
}
|
}
|
||||||
@@ -261,7 +261,7 @@ impl FunctionManager {
|
|||||||
pub fn push_empty(&mut self) {
|
pub fn push_empty(&mut self) {
|
||||||
self.functions.push((
|
self.functions.push((
|
||||||
create_id(random_u64().expect("unable to generate random id")),
|
create_id(random_u64().expect("unable to generate random id")),
|
||||||
FunctionEntry::EMPTY,
|
FunctionEntry::default(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
11
src/misc.rs
11
src/misc.rs
@@ -3,6 +3,7 @@ use egui_plot::{Line, PlotPoint, PlotPoints, Points};
|
|||||||
use emath::Pos2;
|
use emath::Pos2;
|
||||||
use getrandom::getrandom;
|
use getrandom::getrandom;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
use parsing::FlatExWrapper;
|
||||||
|
|
||||||
/// Implements traits that are useful when dealing with Vectors of egui's `Value`
|
/// Implements traits that are useful when dealing with Vectors of egui's `Value`
|
||||||
pub trait EguiHelper {
|
pub trait EguiHelper {
|
||||||
@@ -79,8 +80,8 @@ pub fn decimal_round(x: f64, n: usize) -> f64 {
|
|||||||
/// `f_1` is f'(x) aka the derivative of f(x)
|
/// `f_1` is f'(x) aka the derivative of f(x)
|
||||||
/// The function returns a Vector of `x` values where roots occur
|
/// The function returns a Vector of `x` values where roots occur
|
||||||
pub fn newtons_method_helper(
|
pub fn newtons_method_helper(
|
||||||
threshold: f64, range: &std::ops::Range<f64>, data: &[PlotPoint], f: &dyn Fn(f64) -> f64,
|
threshold: f64, range: &std::ops::Range<f64>, data: &[PlotPoint], f: &FlatExWrapper,
|
||||||
f_1: &dyn Fn(f64) -> f64,
|
f_1: &FlatExWrapper,
|
||||||
) -> Vec<f64> {
|
) -> Vec<f64> {
|
||||||
data.iter()
|
data.iter()
|
||||||
.tuple_windows()
|
.tuple_windows()
|
||||||
@@ -98,19 +99,19 @@ pub fn newtons_method_helper(
|
|||||||
/// `f_1` is f'(x) aka the derivative of f(x)
|
/// `f_1` is f'(x) aka the derivative of f(x)
|
||||||
/// The function returns an `Option<f64>` of the x value at which a root occurs
|
/// The function returns an `Option<f64>` of the x value at which a root occurs
|
||||||
pub fn newtons_method(
|
pub fn newtons_method(
|
||||||
f: &dyn Fn(f64) -> f64, f_1: &dyn Fn(f64) -> f64, start_x: f64, range: &std::ops::Range<f64>,
|
f: &FlatExWrapper, f_1: &FlatExWrapper, start_x: f64, range: &std::ops::Range<f64>,
|
||||||
threshold: f64,
|
threshold: f64,
|
||||||
) -> Option<f64> {
|
) -> Option<f64> {
|
||||||
let mut x1: f64 = start_x;
|
let mut x1: f64 = start_x;
|
||||||
let mut x2: f64;
|
let mut x2: f64;
|
||||||
let mut derivative: f64;
|
let mut derivative: f64;
|
||||||
loop {
|
loop {
|
||||||
derivative = f_1(x1);
|
derivative = f_1.eval(&[x1]);
|
||||||
if !derivative.is_finite() {
|
if !derivative.is_finite() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
x2 = x1 - (f(x1) / derivative);
|
x2 = x1 - (f.eval(&[x1]) / derivative);
|
||||||
if !x2.is_finite() | !range.contains(&x2) {
|
if !x2.is_finite() | !range.contains(&x2) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ static DERIVATIVE_TARGET: [(f64, f64); 11] = [
|
|||||||
fn do_test(sum: Riemann, area_target: f64) {
|
fn do_test(sum: Riemann, area_target: f64) {
|
||||||
let settings = app_settings_constructor(sum, -1.0, 1.0, 10, 10, -1.0, 1.0);
|
let settings = app_settings_constructor(sum, -1.0, 1.0, 10, 10, -1.0, 1.0);
|
||||||
|
|
||||||
let mut function = FunctionEntry::EMPTY;
|
let mut function = FunctionEntry::default();
|
||||||
function.update_string("x^2");
|
function.update_string("x^2");
|
||||||
function.integral = true;
|
function.integral = true;
|
||||||
function.derivative = true;
|
function.derivative = true;
|
||||||
|
|||||||
@@ -141,10 +141,18 @@ fn invalid_hashed_storage() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn newtons_method() {
|
fn newtons_method() {
|
||||||
|
use parsing::BackingFunction;
|
||||||
|
use parsing::FlatExWrapper;
|
||||||
|
fn get_flatexwrapper(func: &str) -> FlatExWrapper {
|
||||||
|
let mut backing_func = BackingFunction::new(func).unwrap();
|
||||||
|
backing_func.get_function_derivative(0).clone()
|
||||||
|
}
|
||||||
|
|
||||||
use ytbn_graphing_software::newtons_method;
|
use ytbn_graphing_software::newtons_method;
|
||||||
|
|
||||||
let data = newtons_method(
|
let data = newtons_method(
|
||||||
&|x: f64| x.powf(2.0) - 1.0,
|
&get_flatexwrapper("x^2 -1"),
|
||||||
&|x: f64| 2.0 * x,
|
&get_flatexwrapper("2x"),
|
||||||
3.0,
|
3.0,
|
||||||
&(0.0..5.0),
|
&(0.0..5.0),
|
||||||
f64::EPSILON,
|
f64::EPSILON,
|
||||||
@@ -152,49 +160,13 @@ fn newtons_method() {
|
|||||||
assert_eq!(data, Some(1.0));
|
assert_eq!(data, Some(1.0));
|
||||||
|
|
||||||
let data = newtons_method(
|
let data = newtons_method(
|
||||||
&|x: f64| x.sin(),
|
&get_flatexwrapper("sin(x)"),
|
||||||
&|x: f64| x.cos(),
|
&get_flatexwrapper("cos(x)"),
|
||||||
3.0,
|
3.0,
|
||||||
&(2.95..3.18),
|
&(2.95..3.18),
|
||||||
f64::EPSILON,
|
f64::EPSILON,
|
||||||
);
|
);
|
||||||
assert_eq!(data, Some(std::f64::consts::PI));
|
assert_eq!(data, Some(std::f64::consts::PI));
|
||||||
|
|
||||||
let data = newtons_method(
|
|
||||||
&|x: f64| x.sin(),
|
|
||||||
&|_: f64| f64::NAN,
|
|
||||||
0.0,
|
|
||||||
&(-10.0..10.0),
|
|
||||||
f64::EPSILON,
|
|
||||||
);
|
|
||||||
assert_eq!(data, None);
|
|
||||||
|
|
||||||
let data = newtons_method(
|
|
||||||
&|_: f64| f64::NAN,
|
|
||||||
&|x: f64| x.sin(),
|
|
||||||
0.0,
|
|
||||||
&(-10.0..10.0),
|
|
||||||
f64::EPSILON,
|
|
||||||
);
|
|
||||||
assert_eq!(data, None);
|
|
||||||
|
|
||||||
let data = newtons_method(
|
|
||||||
&|_: f64| f64::INFINITY,
|
|
||||||
&|x: f64| x.sin(),
|
|
||||||
0.0,
|
|
||||||
&(-10.0..10.0),
|
|
||||||
f64::EPSILON,
|
|
||||||
);
|
|
||||||
assert_eq!(data, None);
|
|
||||||
|
|
||||||
let data = newtons_method(
|
|
||||||
&|x: f64| x.sin(),
|
|
||||||
&|_: f64| f64::INFINITY,
|
|
||||||
0.0,
|
|
||||||
&(-10.0..10.0),
|
|
||||||
f64::EPSILON,
|
|
||||||
);
|
|
||||||
assert_eq!(data, None);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user