rewrite progress
This commit is contained in:
parent
27601d7ddd
commit
0c538cb7fd
@ -104,8 +104,8 @@ impl ChartManager {
|
|||||||
false => x - step,
|
false => x - step,
|
||||||
};
|
};
|
||||||
|
|
||||||
let tmp1: f64 = self.function.run(x);
|
let tmp1: f64 = self.function.run_func(x);
|
||||||
let tmp2: f64 = self.function.run(x2);
|
let tmp2: f64 = self.function.run_func(x2);
|
||||||
|
|
||||||
// Chooses the y value who's absolute value is the smallest
|
// Chooses the y value who's absolute value is the smallest
|
||||||
let mut output = match tmp2.abs() > tmp1.abs() {
|
let mut output = match tmp2.abs() > tmp1.abs() {
|
||||||
|
|||||||
@ -34,6 +34,7 @@ pub struct MathApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Default for MathApp {
|
impl Default for MathApp {
|
||||||
|
#[inline]
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let def_func = "x^2".to_string();
|
let def_func = "x^2".to_string();
|
||||||
let def_min_x = -10.0;
|
let def_min_x = -10.0;
|
||||||
@ -102,15 +103,19 @@ impl MathApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl epi::App for MathApp {
|
impl epi::App for MathApp {
|
||||||
|
|
||||||
|
// The name of the program (displayed when running natively as the window title)
|
||||||
fn name(&self) -> &str { "Integral Demonstration" }
|
fn name(&self) -> &str { "Integral Demonstration" }
|
||||||
|
|
||||||
/// Called once before the first frame.
|
// Called once before the first frame.
|
||||||
|
#[inline]
|
||||||
fn setup(
|
fn setup(
|
||||||
&mut self, _ctx: &egui::Context, _frame: &epi::Frame, _storage: Option<&dyn epi::Storage>,
|
&mut self, _ctx: &egui::Context, _frame: &epi::Frame, _storage: Option<&dyn epi::Storage>,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called each time the UI needs repainting, which may be many times per second.
|
// Called each time the UI needs repainting, which may be many times per second.
|
||||||
|
#[inline]
|
||||||
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
|
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
|
||||||
let Self {
|
let Self {
|
||||||
func_str,
|
func_str,
|
||||||
|
|||||||
182
src/function.rs
Normal file
182
src/function.rs
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
use meval::Expr;
|
||||||
|
use egui::plot::Value;
|
||||||
|
use egui::widgets::plot::Bar;
|
||||||
|
|
||||||
|
const RESOLUTION: f64 = 1000.0;
|
||||||
|
|
||||||
|
|
||||||
|
// Struct that stores and manages the output of a function
|
||||||
|
pub struct FunctionOutput {
|
||||||
|
// The actual line graph
|
||||||
|
back: Vec<Value>,
|
||||||
|
|
||||||
|
// Integral information
|
||||||
|
front: Option<(Vec<Bar>, f64)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FunctionOutput {
|
||||||
|
#[inline]
|
||||||
|
pub fn new(back: Vec<Value>, front: Option<(Vec<Bar>, f64)>) -> Self {
|
||||||
|
Self {
|
||||||
|
back,
|
||||||
|
front,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn has_integral(&self) -> bool {
|
||||||
|
match self.front {
|
||||||
|
Some(x) => true,
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Function {
|
||||||
|
function: Box<dyn Fn(f64) -> f64>,
|
||||||
|
func_str: String,
|
||||||
|
min_x: f64,
|
||||||
|
max_x: f64,
|
||||||
|
back_cache: Cache<Vec<Value>>,
|
||||||
|
front_cache: Cache<(Vec<Bar>, f64)>,
|
||||||
|
|
||||||
|
integral: bool,
|
||||||
|
integral_min_x: f64,
|
||||||
|
integral_max_x: f64,
|
||||||
|
integral_num: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Function {
|
||||||
|
pub fn new(func_str: String, min_x: f64, max_x: f64, integral: bool, integral_min_x: Option<f64>, integral_max_x: Option<f64>, integral_num: Option<usize>) -> Self {
|
||||||
|
|
||||||
|
// Makes sure proper arguments are passed when integral is enabled
|
||||||
|
if integral {
|
||||||
|
if integral_min_x.is_none() {
|
||||||
|
panic!("Invalid arguments: integral_min_x is None, but integral is enabled.")
|
||||||
|
} else if integral_max_x.is_none() {
|
||||||
|
panic!("Invalid arguments: integral_max_x is None, but integral is enabled.")
|
||||||
|
} else if integral_num.is_none() {
|
||||||
|
panic!("Invalid arguments: integral_num is None, but integral is enabled.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let expr: Expr = func_str.parse().unwrap();
|
||||||
|
let func = expr.bind("x").unwrap();
|
||||||
|
Self {
|
||||||
|
function: Box::new(func),
|
||||||
|
func_str,
|
||||||
|
min_x,
|
||||||
|
max_x,
|
||||||
|
back_cache: Cache::new_empty(),
|
||||||
|
front_cache: Cache::new_empty(),
|
||||||
|
integral,
|
||||||
|
integral_min_x: match integral_min_x {
|
||||||
|
Some(x) => x,
|
||||||
|
None => f64::NAN,
|
||||||
|
},
|
||||||
|
integral_max_x: match integral_max_x {
|
||||||
|
Some(x) => x,
|
||||||
|
None => f64::NAN,
|
||||||
|
},
|
||||||
|
integral_num: match integral_num {
|
||||||
|
Some(x) => x,
|
||||||
|
None => f64::NAN,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Runs the internal function to get values
|
||||||
|
#[inline]
|
||||||
|
fn run_func(&self, x: f64) -> f64 { (self.function)(x) }
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn update(&mut self, func_str: String, min_x: f64, max_x: f64, integral: bool, integral_min_x: Option<f64>, integral_max_x: Option<f64>, integral_num: Option<usize>) {
|
||||||
|
|
||||||
|
// If the function string changes, just wipe and restart from scratch
|
||||||
|
if func_str != self.func_str {
|
||||||
|
*self = Self::new(func_str, min_x, max_x, integral, integral_min_x, integral_max_x, integral_num);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (min_x != self.min_x) | (max_x != self.max_x) {
|
||||||
|
self.back_cache.invalidate();
|
||||||
|
self.min_x = min_x;
|
||||||
|
self.max_x = max_x;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Makes sure proper arguments are passed when integral is enabled
|
||||||
|
if integral {
|
||||||
|
if integral_min_x.is_none() {
|
||||||
|
panic!("Invalid arguments: integral_min_x is None, but integral is enabled.")
|
||||||
|
} else if integral_max_x.is_none() {
|
||||||
|
panic!("Invalid arguments: integral_max_x is None, but integral is enabled.")
|
||||||
|
} else if integral_num.is_none() {
|
||||||
|
panic!("Invalid arguments: integral_num is None, but integral is enabled.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (integral_min_x != Some(self.integral_min_x)) | (integral_max_x != Some(self.integral_max_x)) | (integral_num != Some(self.integral_num)) {
|
||||||
|
self.front_cache.invalidate();
|
||||||
|
self.integral_min_x = integral_min_x.expect("");
|
||||||
|
self.integral_max_x = integral_max_x.expect("");
|
||||||
|
self.integral_num = integral_num.expect("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn run(&mut self) -> FunctionOutput {
|
||||||
|
let absrange = (self.max_x - self.min_x).abs();
|
||||||
|
let output: Vec<(f64, f64)> = (1..=self.resolution)
|
||||||
|
.map(|x| ((x as f64 / RESOLUTION) * absrange) + self.min_x_back)
|
||||||
|
.map(|x| (x, self.function.run(x)))
|
||||||
|
.collect();
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn str_compare(&self, other_string: String) -> bool { self.func_str == other_string }
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn get_step(&self) -> f64 { (self.integral_min_x - self.integral_max_x).abs() / (self.num_interval as f64) }
|
||||||
|
|
||||||
|
// Creates and does the math for creating all the rectangles under the graph
|
||||||
|
#[inline]
|
||||||
|
fn integral_rectangles(&self, step: f64) -> (Vec<(f64, f64)>, f64) {
|
||||||
|
let half_step = step / 2.0;
|
||||||
|
let data2: Vec<(f64, f64)> = (0..self.num_interval)
|
||||||
|
.map(|e| {
|
||||||
|
let x: f64 = ((e as f64) * step) + self.integral_min_x;
|
||||||
|
|
||||||
|
// Makes sure rectangles are properly handled on x values below 0
|
||||||
|
let x2: f64 = match x > 0.0 {
|
||||||
|
true => x + step,
|
||||||
|
false => x - step,
|
||||||
|
};
|
||||||
|
|
||||||
|
let tmp1: f64 = self.run_func(x);
|
||||||
|
let tmp2: f64 = self.run_func(x2);
|
||||||
|
|
||||||
|
// Chooses the y value who's absolute value is the smallest
|
||||||
|
let mut output = match tmp2.abs() > tmp1.abs() {
|
||||||
|
true => (x, tmp1),
|
||||||
|
false => (x2, tmp2),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Applies `half_step` in order to make the bar graph display properly
|
||||||
|
if output.0 > 0.0 {
|
||||||
|
output.0 += half_step;
|
||||||
|
} else {
|
||||||
|
output.0 -= half_step;
|
||||||
|
}
|
||||||
|
|
||||||
|
output
|
||||||
|
})
|
||||||
|
.filter(|(_, y)| !y.is_nan())
|
||||||
|
.collect();
|
||||||
|
let area: f64 = data2.iter().map(|(_, y)| y * step).sum(); // sum of all rectangles' areas
|
||||||
|
(data2, area)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,6 +4,7 @@
|
|||||||
mod chart_manager;
|
mod chart_manager;
|
||||||
mod egui_app;
|
mod egui_app;
|
||||||
mod misc;
|
mod misc;
|
||||||
|
mod function;
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|||||||
@ -3,6 +3,7 @@ mod egui_app;
|
|||||||
// These 2 are needed for rust-analyzer to work in vscode.
|
// These 2 are needed for rust-analyzer to work in vscode.
|
||||||
mod chart_manager;
|
mod chart_manager;
|
||||||
mod misc;
|
mod misc;
|
||||||
|
mod function;
|
||||||
|
|
||||||
// For running the program natively! (Because why not?)
|
// For running the program natively! (Because why not?)
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
|||||||
27
src/misc.rs
27
src/misc.rs
@ -5,6 +5,7 @@ EXTREMELY Janky function that tries to put asterisks in the proper places to be
|
|||||||
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.
|
||||||
In the future I may want to completely rewrite this or implement this natively into mevel-rs (which would probably be good to do)
|
In the future I may want to completely rewrite this or implement this natively into mevel-rs (which would probably be good to do)
|
||||||
*/
|
*/
|
||||||
|
#[inline]
|
||||||
pub fn add_asterisks(function_in: String) -> String {
|
pub fn add_asterisks(function_in: String) -> String {
|
||||||
let function = function_in.replace("log10(", "log(").replace("pi", "π"); // pi -> π and log10 -> log
|
let function = function_in.replace("log10(", "log(").replace("pi", "π"); // pi -> π and log10 -> log
|
||||||
let valid_variables: Vec<char> = "xeπ".chars().collect();
|
let valid_variables: Vec<char> = "xeπ".chars().collect();
|
||||||
@ -78,6 +79,7 @@ pub fn add_asterisks(function_in: String) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Tests function to make sure it's able to be parsed. Returns the string of the Error produced, or an empty string if it runs successfully.
|
// Tests function to make sure it's able to be parsed. Returns the string of the Error produced, or an empty string if it runs successfully.
|
||||||
|
#[inline]
|
||||||
pub fn test_func(function_string: String) -> String {
|
pub fn test_func(function_string: String) -> String {
|
||||||
// Factorials do not work, and it would be really difficult to make them work
|
// Factorials do not work, and it would be really difficult to make them work
|
||||||
if function_string.contains('!') {
|
if function_string.contains('!') {
|
||||||
@ -108,35 +110,12 @@ pub fn test_func(function_string: String) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Rounds f64 to specific number of digits
|
// Rounds f64 to specific number of digits
|
||||||
|
#[inline]
|
||||||
pub fn digits_precision(x: f64, digits: usize) -> f64 {
|
pub fn digits_precision(x: f64, digits: usize) -> f64 {
|
||||||
let large_number: f64 = 10.0_f64.powf(digits as f64);
|
let large_number: f64 = 10.0_f64.powf(digits as f64);
|
||||||
(x * large_number).round() / large_number
|
(x * large_number).round() / large_number
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Function {
|
|
||||||
function: Box<dyn Fn(f64) -> f64>,
|
|
||||||
func_str: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Function {
|
|
||||||
pub fn from_string(func_str: String) -> Self {
|
|
||||||
let expr: Expr = func_str.parse().unwrap();
|
|
||||||
let func = expr.bind("x").unwrap();
|
|
||||||
Self {
|
|
||||||
function: Box::new(func),
|
|
||||||
func_str,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn run(&self, x: f64) -> f64 { (self.function)(x) }
|
|
||||||
|
|
||||||
pub fn str_compare(&self, other_string: String) -> bool { self.func_str == other_string }
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn get_string(&self) -> String { self.func_str.clone() }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Cache<T> {
|
pub struct Cache<T> {
|
||||||
backing_data: Option<T>,
|
backing_data: Option<T>,
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user