lots of changes
This commit is contained in:
parent
b3963bb852
commit
6be78c763a
18
TODO.md
18
TODO.md
@ -2,21 +2,23 @@
|
||||
1. Multiple functions in one graph.
|
||||
- Backend support
|
||||
- Integrals between functions (too hard to implement, maybe will shelve)
|
||||
- UI
|
||||
- Different colors (kinda)
|
||||
- Display intersection between functions
|
||||
- Different colors
|
||||
2. Rerwite of function parsing code
|
||||
- Non `y=` functions.
|
||||
3. Smart display of graph
|
||||
- Display of intersections between functions
|
||||
4. re-add euler's number (well it works if you use capital e like `E^x`)
|
||||
5. allow constants in min/max integral input (like pi or euler's number)
|
||||
6. sliding values for functions (like a user-interactable slider that adjusts a variable in the function, like desmos)
|
||||
4. Properly parse lowercase `e` as euler's number
|
||||
5. Allow constants in min/max integral input (like pi or euler's number)
|
||||
6. Sliding values for functions (like a user-interactable slider that adjusts a variable in the function, like desmos)
|
||||
7. nth derivative support (again)
|
||||
8. rewrite FunctionEntry to move more information and handling to egui_app (such as config changes)
|
||||
8. Rewrite `FunctionEntry` to move more information and handling to egui_app (such as config changes)
|
||||
9. Threading
|
||||
10. fix integral display
|
||||
10. Fix integral display
|
||||
11. Improve loading indicator
|
||||
- Dynamically hide and unhide it when lagging
|
||||
12. add comments in `parsing::process_func_str` for it to be better understandable
|
||||
12. Add comments in `parsing::process_func_str` for it to be better understandable
|
||||
13. Look into other, better methods of compression that would be faster
|
||||
14. Better handling of panics and errors to display to the user
|
||||
15. Display native/web dark mode detection as it's unneeded and can cause compat issues (see [egui #1305](https://github.com/emilk/egui/issues/1305))
|
||||
16. Add tests for `SerdeValueHelper`
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use crate::function::{FunctionEntry, RiemannSum, EMPTY_FUNCTION_ENTRY};
|
||||
use crate::function::{FunctionEntry, RiemannSum, DEFAULT_FUNCTION_ENTRY};
|
||||
use crate::misc::{JsonFileOutput, SerdeValueHelper};
|
||||
use crate::parsing::{process_func_str, test_func};
|
||||
|
||||
@ -186,7 +186,7 @@ lazy_static::lazy_static! {
|
||||
};
|
||||
}
|
||||
|
||||
// Tests to make sure archived (and compressed) assets match expected data
|
||||
/// Tests to make sure archived (and compressed) assets match expected data
|
||||
#[test]
|
||||
fn test_file_data() {
|
||||
let mut font_data: BTreeMap<String, FontData> = BTreeMap::new();
|
||||
@ -228,7 +228,16 @@ fn test_file_data() {
|
||||
|
||||
let json_data: SerdeValueHelper = SerdeValueHelper::new(include_str!("../assets/text.json"));
|
||||
|
||||
assert_eq!(ASSETS.get_json_file_output(), json_data.parse_values());
|
||||
let asset_json = ASSETS.get_json_file_output();
|
||||
let json_data_parsed = json_data.parse_values();
|
||||
|
||||
assert_eq!(asset_json, json_data_parsed);
|
||||
|
||||
// NOTE: UPDATE THIS STRING IF `license_info` IN `text.json` IS MODIFIED
|
||||
let target_license_info = "The AGPL license ensures that the end user, even if not hosting the program itself, is still guaranteed access to the source code of the project in question.";
|
||||
|
||||
assert_eq!(target_license_info, asset_json.license_info);
|
||||
assert_eq!(target_license_info, json_data_parsed.license_info);
|
||||
}
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
@ -323,7 +332,7 @@ pub struct MathApp {
|
||||
impl Default for MathApp {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
functions: vec![EMPTY_FUNCTION_ENTRY.clone().integral(true)],
|
||||
functions: vec![DEFAULT_FUNCTION_ENTRY.clone().integral(true)],
|
||||
func_strs: vec![String::from(DEFAULT_FUNCION)],
|
||||
last_error: Vec::new(),
|
||||
last_info: (vec![0.0], Duration::ZERO),
|
||||
@ -493,15 +502,7 @@ impl MathApp {
|
||||
if let Some(test_output_value) = test_func(&proc_func_str) {
|
||||
self.last_error.push((i, test_output_value));
|
||||
} else {
|
||||
function.update(
|
||||
proc_func_str,
|
||||
integral_enabled,
|
||||
derivative_enabled,
|
||||
self.settings.integral_min_x,
|
||||
self.settings.integral_max_x,
|
||||
self.settings.integral_num,
|
||||
self.settings.sum,
|
||||
);
|
||||
function.update(proc_func_str, integral_enabled, derivative_enabled);
|
||||
self.last_error = self
|
||||
.last_error
|
||||
.iter()
|
||||
@ -569,7 +570,7 @@ impl epi::App for MathApp {
|
||||
.clicked()
|
||||
{
|
||||
self.functions.push(
|
||||
EMPTY_FUNCTION_ENTRY
|
||||
DEFAULT_FUNCTION_ENTRY
|
||||
.clone()
|
||||
.update_riemann(self.settings.sum),
|
||||
);
|
||||
@ -706,6 +707,10 @@ impl epi::App for MathApp {
|
||||
available_width,
|
||||
self.settings.extrema,
|
||||
self.settings.roots,
|
||||
self.settings.integral_min_x,
|
||||
self.settings.integral_max_x,
|
||||
self.settings.integral_num,
|
||||
self.settings.sum,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
521
src/function.rs
521
src/function.rs
@ -1,14 +1,15 @@
|
||||
#![allow(clippy::too_many_arguments)] // Clippy, shut
|
||||
|
||||
use crate::function_output::FunctionOutput;
|
||||
#[allow(unused_imports)]
|
||||
use crate::misc::{newtons_method, SteppedVector};
|
||||
|
||||
use crate::egui_app::{DEFAULT_FUNCION, DEFAULT_RIEMANN};
|
||||
use crate::function_output::FunctionOutput;
|
||||
use crate::misc::{newtons_method, SteppedVector};
|
||||
use crate::parsing::BackingFunction;
|
||||
|
||||
use eframe::egui::plot::PlotUi;
|
||||
use eframe::egui::{plot::Value, widgets::plot::Bar};
|
||||
use eframe::{egui, epaint};
|
||||
use egui::{
|
||||
plot::{BarChart, Line, PlotUi, Points, Value, Values},
|
||||
widgets::plot::Bar,
|
||||
};
|
||||
use epaint::Color32;
|
||||
use std::fmt::{self, Debug};
|
||||
|
||||
/// Represents the possible variations of Riemann Sums
|
||||
@ -24,7 +25,8 @@ impl fmt::Display for RiemannSum {
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref EMPTY_FUNCTION_ENTRY: FunctionEntry = FunctionEntry::empty();
|
||||
/// Represents a "default" instance of `FunctionEntry`
|
||||
pub static ref DEFAULT_FUNCTION_ENTRY: FunctionEntry = FunctionEntry::default();
|
||||
}
|
||||
|
||||
/// `FunctionEntry` is a function that can calculate values, integrals,
|
||||
@ -68,10 +70,10 @@ pub struct FunctionEntry {
|
||||
sum: RiemannSum,
|
||||
}
|
||||
|
||||
impl FunctionEntry {
|
||||
/// Creates Empty Function instance
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
impl Default for FunctionEntry {
|
||||
/// Creates default FunctionEntry instance (which is empty)
|
||||
fn default() -> FunctionEntry {
|
||||
FunctionEntry {
|
||||
function: BackingFunction::new(DEFAULT_FUNCION),
|
||||
func_str: String::new(),
|
||||
min_x: -1.0,
|
||||
@ -86,12 +88,11 @@ impl FunctionEntry {
|
||||
sum: DEFAULT_RIEMANN,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FunctionEntry {
|
||||
/// Update function settings
|
||||
pub fn update(
|
||||
&mut self, func_str: String, integral: bool, derivative: bool, integral_min_x: f64,
|
||||
integral_max_x: f64, integral_num: usize, sum: RiemannSum,
|
||||
) {
|
||||
pub fn update(&mut self, func_str: String, integral: bool, derivative: bool) {
|
||||
// If the function string changes, just wipe and restart from scratch
|
||||
if func_str != self.func_str {
|
||||
self.func_str = func_str.clone();
|
||||
@ -101,20 +102,6 @@ impl FunctionEntry {
|
||||
|
||||
self.derivative = derivative;
|
||||
self.integral = integral;
|
||||
|
||||
// Makes sure proper arguments are passed when integral is enabled
|
||||
if integral
|
||||
&& (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;
|
||||
self.integral_max_x = integral_max_x;
|
||||
self.integral_num = integral_num;
|
||||
self.sum = sum;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: refactor this
|
||||
@ -126,9 +113,9 @@ impl FunctionEntry {
|
||||
let back_values: Vec<Value> = {
|
||||
if self.output.back.is_none() {
|
||||
self.output.back = Some(
|
||||
(0..self.pixel_width)
|
||||
.map(|x| (x as f64 / resolution as f64) + self.min_x)
|
||||
.map(|x| Value::new(x, self.function.get(x)))
|
||||
crate::misc::resolution_helper(self.pixel_width, self.min_x, resolution)
|
||||
.iter()
|
||||
.map(|x| Value::new(*x, self.function.get(*x)))
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
@ -254,11 +241,62 @@ impl FunctionEntry {
|
||||
self
|
||||
}
|
||||
|
||||
fn newtons_method_helper(&self, threshold: f64, derivative_level: usize) -> Option<Vec<Value>> {
|
||||
let newtons_method_output: Vec<f64> = match derivative_level {
|
||||
0 => newtons_method(
|
||||
threshold,
|
||||
self.min_x..self.max_x,
|
||||
self.output.back.to_owned().unwrap(),
|
||||
&|x: f64| self.function.get(x),
|
||||
&|x: f64| self.function.get_derivative_1(x),
|
||||
),
|
||||
1 => newtons_method(
|
||||
threshold,
|
||||
self.min_x..self.max_x,
|
||||
self.output.derivative.to_owned().unwrap(),
|
||||
&|x: f64| self.function.get_derivative_1(x),
|
||||
&|x: f64| self.function.get_derivative_2(x),
|
||||
),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
if newtons_method_output.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
newtons_method_output
|
||||
.iter()
|
||||
.map(|x| (*x, self.function.get(*x)))
|
||||
.map(|(x, y)| Value::new(x, y))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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,
|
||||
roots: bool, integral_min_x: f64, integral_max_x: f64, integral_num: usize,
|
||||
sum: RiemannSum,
|
||||
) -> f64 {
|
||||
let resolution: f64 = self.pixel_width as f64 / (max_x.abs() + min_x.abs());
|
||||
let resolution_iter =
|
||||
crate::misc::resolution_helper(self.pixel_width, self.min_x, resolution);
|
||||
|
||||
// Makes sure proper arguments are passed when integral is enabled
|
||||
if self.integral
|
||||
&& (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;
|
||||
self.integral_max_x = integral_max_x;
|
||||
self.integral_num = integral_num;
|
||||
self.sum = sum;
|
||||
}
|
||||
|
||||
if pixel_width != self.pixel_width {
|
||||
self.output.invalidate_back();
|
||||
self.output.invalidate_derivative();
|
||||
@ -266,7 +304,6 @@ impl FunctionEntry {
|
||||
self.max_x = max_x;
|
||||
self.pixel_width = pixel_width;
|
||||
} else if ((min_x != self.min_x) | (max_x != self.max_x)) && self.output.back.is_some() {
|
||||
let resolution: f64 = self.pixel_width as f64 / (max_x.abs() + min_x.abs());
|
||||
let back_cache = self.output.back.as_ref().unwrap();
|
||||
|
||||
let x_data: SteppedVector = back_cache
|
||||
@ -275,31 +312,32 @@ impl FunctionEntry {
|
||||
.collect::<Vec<f64>>()
|
||||
.into();
|
||||
|
||||
self.output.back = Some(
|
||||
(0..self.pixel_width)
|
||||
.map(|x| (x as f64 / resolution as f64) + min_x)
|
||||
.map(|x| {
|
||||
if let Some(i) = x_data.get_index(x) {
|
||||
back_cache[i]
|
||||
} else {
|
||||
Value::new(x, self.function.get(x))
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
// assert_eq!(self.output.back.as_ref().unwrap().len(), self.pixel_width);
|
||||
|
||||
let derivative_cache = self.output.derivative.as_ref().unwrap();
|
||||
let new_data = (0..self.pixel_width)
|
||||
.map(|x| (x as f64 / resolution as f64) + min_x)
|
||||
let back_data: Vec<Value> = resolution_iter
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|x| {
|
||||
if let Some(i) = x_data.get_index(x) {
|
||||
derivative_cache[i]
|
||||
back_cache[i]
|
||||
} else {
|
||||
Value::new(x, self.function.get_derivative_1(x))
|
||||
Value::new(x, self.function.get(x))
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(back_data.len(), self.pixel_width);
|
||||
self.output.back = Some(back_data);
|
||||
|
||||
let derivative_cache = self.output.derivative.as_ref().unwrap();
|
||||
let new_data: Vec<Value> = resolution_iter
|
||||
.iter()
|
||||
.map(|x| {
|
||||
if let Some(i) = x_data.get_index(*x) {
|
||||
derivative_cache[i]
|
||||
} else {
|
||||
Value::new(*x, self.function.get_derivative_1(*x))
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(new_data.len(), self.pixel_width);
|
||||
|
||||
self.output.derivative = Some(new_data);
|
||||
} else {
|
||||
@ -326,194 +364,229 @@ impl FunctionEntry {
|
||||
|
||||
// Calculates extrema
|
||||
if do_extrema {
|
||||
self.output.extrema = Some(
|
||||
newtons_method(
|
||||
threshold,
|
||||
self.min_x..self.max_x,
|
||||
self.output.derivative.to_owned().unwrap(),
|
||||
&|x: f64| self.function.get_derivative_1(x),
|
||||
&|x: f64| self.function.get_derivative_2(x),
|
||||
)
|
||||
.iter()
|
||||
.map(|x| Value::new(*x, self.function.get(*x)))
|
||||
.collect(),
|
||||
);
|
||||
self.output.extrema = self.newtons_method_helper(threshold, 1);
|
||||
}
|
||||
|
||||
// Calculates roots
|
||||
if do_roots {
|
||||
self.output.roots = Some(
|
||||
newtons_method(
|
||||
threshold,
|
||||
self.min_x..self.max_x,
|
||||
self.output.back.to_owned().unwrap(),
|
||||
&|x: f64| self.function.get(x),
|
||||
&|x: f64| self.function.get_derivative_1(x),
|
||||
)
|
||||
.iter()
|
||||
.map(|x| Value::new(*x, self.function.get(*x)))
|
||||
.collect(),
|
||||
);
|
||||
self.output.roots = self.newtons_method_helper(threshold, 0);
|
||||
}
|
||||
|
||||
self.output.display(
|
||||
plot_ui,
|
||||
self.get_func_str(),
|
||||
self.function.get_derivative_str(),
|
||||
(self.integral_min_x - self.integral_max_x).abs() / (self.integral_num as f64),
|
||||
self.derivative,
|
||||
extrema,
|
||||
roots,
|
||||
)
|
||||
{
|
||||
let func_str = self.get_func_str();
|
||||
let derivative_str = self.function.get_derivative_str();
|
||||
let step =
|
||||
(self.integral_min_x - self.integral_max_x).abs() / (self.integral_num as f64);
|
||||
let derivative_enabled = self.derivative;
|
||||
// Plot back data
|
||||
plot_ui.line(
|
||||
Line::new(Values::from_values(self.output.back.clone().unwrap()))
|
||||
.color(Color32::RED)
|
||||
.name(func_str),
|
||||
);
|
||||
|
||||
// Plot derivative data
|
||||
if derivative_enabled {
|
||||
if let Some(derivative_data) = self.output.derivative.clone() {
|
||||
plot_ui.line(
|
||||
Line::new(Values::from_values(derivative_data))
|
||||
.color(Color32::GREEN)
|
||||
.name(derivative_str),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Plot extrema points
|
||||
if extrema {
|
||||
if let Some(extrema_data) = self.output.extrema.clone() {
|
||||
plot_ui.points(
|
||||
Points::new(Values::from_values(extrema_data))
|
||||
.color(Color32::YELLOW)
|
||||
.name("Extrema")
|
||||
.radius(5.0),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Plot roots points
|
||||
if roots {
|
||||
if let Some(roots_data) = self.output.roots.clone() {
|
||||
plot_ui.points(
|
||||
Points::new(Values::from_values(roots_data))
|
||||
.color(Color32::LIGHT_BLUE)
|
||||
.name("Root")
|
||||
.radius(5.0),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Plot integral data
|
||||
if let Some(integral_data) = self.output.integral.clone() {
|
||||
plot_ui.bar_chart(
|
||||
BarChart::new(integral_data.0)
|
||||
.color(Color32::BLUE)
|
||||
.width(step),
|
||||
);
|
||||
|
||||
// return value rounded to 8 decimal places
|
||||
crate::misc::decimal_round(integral_data.1, 8)
|
||||
} else {
|
||||
f64::NAN // return NaN if integrals are disabled
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn verify_function(
|
||||
integral_num: usize, pixel_width: usize, function: &mut FunctionEntry,
|
||||
back_values_target: Vec<(f64, f64)>, area_target: f64,
|
||||
) {
|
||||
{
|
||||
let (back_values, bars, derivative) = function.run_back();
|
||||
assert!(derivative.is_some());
|
||||
assert!(bars.is_none());
|
||||
assert_eq!(back_values.len(), pixel_width);
|
||||
let back_values_tuple: Vec<(f64, f64)> =
|
||||
back_values.iter().map(|ele| (ele.x, ele.y)).collect();
|
||||
assert_eq!(back_values_tuple, back_values_target);
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn verify_function(
|
||||
integral_num: usize, pixel_width: usize, function: &mut FunctionEntry,
|
||||
back_values_target: Vec<(f64, f64)>, area_target: f64,
|
||||
) {
|
||||
{
|
||||
let (back_values, bars, derivative) = function.run_back();
|
||||
assert!(derivative.is_some());
|
||||
assert!(bars.is_none());
|
||||
assert_eq!(back_values.len(), pixel_width);
|
||||
let back_values_tuple: Vec<(f64, f64)> =
|
||||
back_values.iter().map(|ele| (ele.x, ele.y)).collect();
|
||||
assert_eq!(back_values_tuple, back_values_target);
|
||||
}
|
||||
|
||||
{
|
||||
*function = function.clone().integral(true);
|
||||
let (back_values, bars, derivative) = function.run_back();
|
||||
assert!(derivative.is_some());
|
||||
assert!(bars.is_some());
|
||||
assert_eq!(back_values.len(), pixel_width);
|
||||
|
||||
assert_eq!(bars.clone().unwrap().1, area_target);
|
||||
|
||||
let vec_bars = bars.unwrap().0;
|
||||
assert_eq!(vec_bars.len(), integral_num);
|
||||
|
||||
let back_values_tuple: Vec<(f64, f64)> =
|
||||
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();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
*function = function.clone().integral(true);
|
||||
let (back_values, bars, derivative) = function.run_back();
|
||||
assert!(derivative.is_some());
|
||||
assert!(bars.is_some());
|
||||
assert_eq!(back_values.len(), pixel_width);
|
||||
#[test]
|
||||
fn left_function_test() {
|
||||
let integral_num = 10;
|
||||
let pixel_width = 10;
|
||||
|
||||
assert_eq!(bars.clone().unwrap().1, area_target);
|
||||
let mut function = FunctionEntry::default()
|
||||
.update_riemann(RiemannSum::Left)
|
||||
.pixel_width(pixel_width)
|
||||
.integral_num(integral_num)
|
||||
.integral_bounds(-1.0, 1.0);
|
||||
|
||||
let vec_bars = bars.unwrap().0;
|
||||
assert_eq!(vec_bars.len(), 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 back_values_tuple: Vec<(f64, f64)> =
|
||||
back_values.iter().map(|ele| (ele.x, ele.y)).collect();
|
||||
assert_eq!(back_values_tuple, back_values_target);
|
||||
let area_target = 0.9600000000000001;
|
||||
|
||||
verify_function(
|
||||
integral_num,
|
||||
pixel_width,
|
||||
&mut function,
|
||||
back_values_target,
|
||||
area_target,
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let (back_values, bars, derivative) = function.run_back();
|
||||
assert!(derivative.is_some());
|
||||
#[test]
|
||||
fn middle_function_test() {
|
||||
let integral_num = 10;
|
||||
let pixel_width = 10;
|
||||
|
||||
assert!(bars.is_some());
|
||||
assert_eq!(back_values.len(), pixel_width);
|
||||
assert_eq!(bars.clone().unwrap().1, area_target);
|
||||
let bars_unwrapped = bars.unwrap();
|
||||
let mut function = FunctionEntry::default()
|
||||
.update_riemann(RiemannSum::Middle)
|
||||
.pixel_width(pixel_width)
|
||||
.integral_num(integral_num)
|
||||
.integral_bounds(-1.0, 1.0);
|
||||
|
||||
assert_eq!(bars_unwrapped.0.iter().len(), 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.92;
|
||||
|
||||
verify_function(
|
||||
integral_num,
|
||||
pixel_width,
|
||||
&mut function,
|
||||
back_values_target,
|
||||
area_target,
|
||||
);
|
||||
}
|
||||
|
||||
#[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;
|
||||
|
||||
verify_function(
|
||||
integral_num,
|
||||
pixel_width,
|
||||
&mut function,
|
||||
back_values_target,
|
||||
area_target,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn left_function_test() {
|
||||
let integral_num = 10;
|
||||
let pixel_width = 10;
|
||||
|
||||
let mut function = FunctionEntry::empty()
|
||||
.update_riemann(RiemannSum::Left)
|
||||
.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.9600000000000001;
|
||||
|
||||
verify_function(
|
||||
integral_num,
|
||||
pixel_width,
|
||||
&mut function,
|
||||
back_values_target,
|
||||
area_target,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn middle_function_test() {
|
||||
let integral_num = 10;
|
||||
let pixel_width = 10;
|
||||
|
||||
let mut function = FunctionEntry::empty()
|
||||
.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;
|
||||
|
||||
verify_function(
|
||||
integral_num,
|
||||
pixel_width,
|
||||
&mut function,
|
||||
back_values_target,
|
||||
area_target,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn right_function_test() {
|
||||
let integral_num = 10;
|
||||
let pixel_width = 10;
|
||||
|
||||
let mut function = FunctionEntry::empty()
|
||||
.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;
|
||||
|
||||
verify_function(
|
||||
integral_num,
|
||||
pixel_width,
|
||||
&mut function,
|
||||
back_values_target,
|
||||
area_target,
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,11 +1,4 @@
|
||||
use crate::misc::decimal_round;
|
||||
use eframe::{
|
||||
egui::{
|
||||
plot::{BarChart, Line, PlotUi, Points, Value, Values},
|
||||
widgets::plot::Bar,
|
||||
},
|
||||
epaint::Color32,
|
||||
};
|
||||
use eframe::egui::{plot::Value, widgets::plot::Bar};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FunctionOutput {
|
||||
@ -45,68 +38,4 @@ impl FunctionOutput {
|
||||
|
||||
/// 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`)
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn display(
|
||||
&self, plot_ui: &mut PlotUi, func_str: &str, derivative_str: &str, step: f64,
|
||||
derivative_enabled: bool, extrema: bool, roots: 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(
|
||||
Line::new(Values::from_values(derivative_data))
|
||||
.color(Color32::GREEN)
|
||||
.name(derivative_str),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Plot extrema points
|
||||
if extrema {
|
||||
if let Some(extrema_data) = self.extrema.clone() {
|
||||
plot_ui.points(
|
||||
Points::new(Values::from_values(extrema_data))
|
||||
.color(Color32::YELLOW)
|
||||
.name("Extrema")
|
||||
.radius(5.0),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Plot roots points
|
||||
if roots {
|
||||
if let Some(roots_data) = self.roots.clone() {
|
||||
plot_ui.points(
|
||||
Points::new(Values::from_values(roots_data))
|
||||
.color(Color32::LIGHT_BLUE)
|
||||
.name("Root")
|
||||
.radius(5.0),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Plot integral data
|
||||
if let Some(integral_data) = self.integral.clone() {
|
||||
plot_ui.bar_chart(
|
||||
BarChart::new(integral_data.0)
|
||||
.color(Color32::BLUE)
|
||||
.width(step),
|
||||
);
|
||||
|
||||
decimal_round(integral_data.1, 8) // return value rounded to 8 decimal places
|
||||
} else {
|
||||
f64::NAN // return NaN if integrals are disabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,15 +15,9 @@ fn main() {
|
||||
|
||||
tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed");
|
||||
|
||||
let options = eframe::NativeOptions {
|
||||
transparent: true,
|
||||
drag_and_drop_support: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
eframe::run_native(
|
||||
"(Yet-to-be-named) Graphing Software",
|
||||
options,
|
||||
eframe::NativeOptions::default(),
|
||||
Box::new(|cc| Box::new(egui_app::MathApp::new(cc))),
|
||||
);
|
||||
}
|
||||
|
||||
169
src/misc.rs
169
src/misc.rs
@ -1,3 +1,6 @@
|
||||
use eframe::egui::plot::Value as EguiValue;
|
||||
use serde_json::Value as JsonValue;
|
||||
|
||||
/// `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
|
||||
@ -98,29 +101,54 @@ impl From<Vec<f64>> for SteppedVector {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stepped_vector_test() {
|
||||
let min: i32 = -10;
|
||||
let max: i32 = 10;
|
||||
let data: Vec<f64> = (min..=max).map(|x| x as f64).collect();
|
||||
let len_data = data.len();
|
||||
let stepped_vector: SteppedVector = data.into();
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub struct JsonFileOutput {
|
||||
pub help_expr: String,
|
||||
pub help_vars: String,
|
||||
pub help_panel: String,
|
||||
pub help_function: String,
|
||||
pub help_other: String,
|
||||
pub license_info: String,
|
||||
}
|
||||
|
||||
assert_eq!(stepped_vector.get_min(), min as f64);
|
||||
assert_eq!(stepped_vector.get_max(), max as f64);
|
||||
/// Helps parsing text data from `text.json`
|
||||
pub struct SerdeValueHelper {
|
||||
value: JsonValue,
|
||||
}
|
||||
|
||||
assert_eq!(stepped_vector.get_index(min as f64), Some(0));
|
||||
assert_eq!(stepped_vector.get_index(max as f64), Some(len_data - 1));
|
||||
|
||||
for i in min..=max {
|
||||
assert_eq!(
|
||||
stepped_vector.get_index(i as f64),
|
||||
Some((i + min.abs()) as usize)
|
||||
);
|
||||
impl SerdeValueHelper {
|
||||
pub fn new(string: &str) -> Self {
|
||||
Self {
|
||||
value: serde_json::from_str(string).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(stepped_vector.get_index((min - 1) as f64), None);
|
||||
assert_eq!(stepped_vector.get_index((max + 1) as f64), None);
|
||||
/// Parses an array of strings at `self.value[key]` as a multiline string
|
||||
fn parse_multiline(&self, key: &str) -> String {
|
||||
(&self.value[key])
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|ele| ele.as_str().unwrap())
|
||||
.fold(String::new(), |s, l| s + l + "\n")
|
||||
.trim_end()
|
||||
.to_owned()
|
||||
}
|
||||
|
||||
/// Parses `self.value[key]` as a single line string
|
||||
fn parse_singleline(&self, key: &str) -> String { self.value[key].as_str().unwrap().to_owned() }
|
||||
|
||||
/// Used to parse `text.json`
|
||||
pub fn parse_values(&self) -> JsonFileOutput {
|
||||
JsonFileOutput {
|
||||
help_expr: self.parse_multiline("help_expr"),
|
||||
help_vars: self.parse_multiline("help_vars"),
|
||||
help_panel: self.parse_multiline("help_panel"),
|
||||
help_function: self.parse_multiline("help_function"),
|
||||
help_other: self.parse_multiline("help_other"),
|
||||
license_info: self.parse_singleline("license_info"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Rounds f64 to `n` decimal places
|
||||
@ -138,11 +166,11 @@ pub fn decimal_round(x: f64, n: usize) -> f64 {
|
||||
/// `f_1` is f'(x) aka the derivative of f(x)
|
||||
/// The function returns a Vector of `x` values where roots occur
|
||||
pub fn newtons_method(
|
||||
threshold: f64, range: std::ops::Range<f64>, data: Vec<eframe::egui::plot::Value>,
|
||||
f: &dyn Fn(f64) -> f64, f_1: &dyn Fn(f64) -> f64,
|
||||
threshold: f64, range: std::ops::Range<f64>, data: Vec<EguiValue>, f: &dyn Fn(f64) -> f64,
|
||||
f_1: &dyn Fn(f64) -> f64,
|
||||
) -> Vec<f64> {
|
||||
let mut output_list: Vec<f64> = Vec::new();
|
||||
let mut last_ele_option: Option<eframe::egui::plot::Value> = None;
|
||||
let mut last_ele_option: Option<EguiValue> = None;
|
||||
for ele in data.iter() {
|
||||
if last_ele_option.is_none() {
|
||||
last_ele_option = Some(*ele);
|
||||
@ -198,48 +226,77 @@ pub fn newtons_method(
|
||||
output_list
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub struct JsonFileOutput {
|
||||
pub help_expr: String,
|
||||
pub help_vars: String,
|
||||
pub help_panel: String,
|
||||
pub help_function: String,
|
||||
pub help_other: String,
|
||||
pub license_info: String,
|
||||
// Returns a vector of length `max_i` starting at value `min_x` with resolution
|
||||
// of `resolution`
|
||||
pub fn resolution_helper(max_i: usize, min_x: f64, resolution: f64) -> Vec<f64> {
|
||||
(0..max_i)
|
||||
.map(|x| (x as f64 / resolution as f64) + min_x)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub struct SerdeValueHelper {
|
||||
value: serde_json::Value,
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
impl SerdeValueHelper {
|
||||
pub fn new(string: &str) -> Self {
|
||||
Self {
|
||||
value: serde_json::from_str(string).unwrap(),
|
||||
/// Tests SteppedVector to ensure everything works properly (helped me find
|
||||
/// a bunch of issues)
|
||||
#[test]
|
||||
fn stepped_vector_test() {
|
||||
let min: i32 = -10;
|
||||
let max: i32 = 10;
|
||||
let data: Vec<f64> = (min..=max).map(|x| x as f64).collect();
|
||||
let len_data = data.len();
|
||||
let stepped_vector: SteppedVector = data.into();
|
||||
|
||||
assert_eq!(stepped_vector.get_min(), min as f64);
|
||||
assert_eq!(stepped_vector.get_max(), max as f64);
|
||||
|
||||
assert_eq!(stepped_vector.get_index(min as f64), Some(0));
|
||||
assert_eq!(stepped_vector.get_index(max as f64), Some(len_data - 1));
|
||||
|
||||
for i in min..=max {
|
||||
assert_eq!(
|
||||
stepped_vector.get_index(i as f64),
|
||||
Some((i + min.abs()) as usize)
|
||||
);
|
||||
}
|
||||
|
||||
assert_eq!(stepped_vector.get_index((min - 1) as f64), None);
|
||||
assert_eq!(stepped_vector.get_index((max + 1) as f64), None);
|
||||
}
|
||||
|
||||
fn parse_multiline(&self, key: &str) -> String {
|
||||
(&self.value[key])
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|ele| ele.as_str().unwrap())
|
||||
.fold(String::new(), |s, l| s + l + "\n")
|
||||
.trim_end()
|
||||
.to_owned()
|
||||
/// Ensures decimal_round returns correct values
|
||||
#[test]
|
||||
fn decimal_round_test() {
|
||||
assert_eq!(decimal_round(0.00001, 1), 0.0);
|
||||
assert_eq!(decimal_round(0.00001, 2), 0.0);
|
||||
assert_eq!(decimal_round(0.00001, 3), 0.0);
|
||||
assert_eq!(decimal_round(0.00001, 4), 0.0);
|
||||
assert_eq!(decimal_round(0.00001, 5), 0.00001);
|
||||
|
||||
assert_eq!(decimal_round(0.12345, 1), 0.1);
|
||||
assert_eq!(decimal_round(0.12345, 2), 0.12);
|
||||
assert_eq!(decimal_round(0.12345, 3), 0.123);
|
||||
assert_eq!(decimal_round(0.12345, 4), 0.1235); // rounds up
|
||||
assert_eq!(decimal_round(0.12345, 5), 0.12345);
|
||||
|
||||
assert_eq!(decimal_round(1.9, 0), 2.0);
|
||||
assert_eq!(decimal_round(1.9, 1), 1.9);
|
||||
}
|
||||
|
||||
fn parse_singleline(&self, key: &str) -> String { self.value[key].as_str().unwrap().to_owned() }
|
||||
/// Tests `resolution_helper` to make sure it returns expected output
|
||||
#[test]
|
||||
fn resolution_helper_test() {
|
||||
assert_eq!(
|
||||
resolution_helper(10, 1.0, 1.0),
|
||||
vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]
|
||||
);
|
||||
|
||||
pub fn parse_values(&self) -> JsonFileOutput {
|
||||
JsonFileOutput {
|
||||
help_expr: self.parse_multiline("help_expr"),
|
||||
help_vars: self.parse_multiline("help_vars"),
|
||||
help_panel: self.parse_multiline("help_panel"),
|
||||
help_function: self.parse_multiline("help_function"),
|
||||
help_other: self.parse_multiline("help_other"),
|
||||
license_info: self.parse_singleline("license_info"),
|
||||
}
|
||||
assert_eq!(
|
||||
resolution_helper(5, -2.0, 1.0),
|
||||
vec![-2.0, -1.0, 0.0, 1.0, 2.0]
|
||||
);
|
||||
|
||||
assert_eq!(resolution_helper(3, -2.0, 1.0), vec![-2.0, -1.0, 0.0]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -204,7 +204,7 @@ pub fn test_func(function_string: &str) -> Option<String> {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// returns if function with string `func_str` is valid after processing
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user