cleanup and improved function tests

This commit is contained in:
Simon Gardling
2022-05-13 11:38:00 -04:00
parent 82ef9c24a6
commit 2d63c1b5f7
8 changed files with 183 additions and 66 deletions

View File

@@ -318,23 +318,23 @@ impl FunctionEntry {
Vec<Option<Value>>,
) = dyn_iter(&resolution_iter)
.map(|x| {
if let Some(i) = x_data.get_index(x) {
return (
if let Some(i) = x_data.get_index(*x) {
(
self.back_data[i],
derivative_required.then(|| self.derivative_data[i]),
do_nth_derivative.then(|| unsafe {
nth_derivative_data.map(|data| data[i]).unwrap_unchecked()
}),
);
)
} else {
return (
(
Value::new(*x, self.function.get(*x)),
derivative_required
.then(|| Value::new(*x, self.function.get_derivative_1(*x))),
do_nth_derivative.then(|| {
Value::new(*x, self.function.get_nth_derivative(self.curr_nth, *x))
}),
);
)
}
})
.collect::<Vec<(Value, Option<Value>, Option<Value>)>>()
@@ -556,6 +556,7 @@ impl FunctionEntry {
/// Invalidate Derivative data
pub fn invalidate_derivative(&mut self) { self.derivative_data.clear(); }
/// Invalidates `n`th derivative data
pub fn invalidate_nth(&mut self) { self.nth_derivative_data = None }
/// Runs asserts to make sure everything is the expected value
@@ -566,7 +567,6 @@ impl FunctionEntry {
) {
{
self.calculate(&min_x, &max_x, true, &settings);
let back_target = back_target;
assert!(!self.back_data.is_empty());
assert_eq!(self.back_data.len(), settings.plot_width + 1);
let back_vec_tuple = self.back_data.to_tuple();
@@ -585,6 +585,86 @@ impl FunctionEntry {
assert_eq!(self.integral_data.clone().unwrap().1, area_target);
}
{
self.calculate(&(min_x + 1.0), &(max_x + 1.0), true, &settings);
assert_eq!(
self.derivative_data
.to_tuple()
.iter()
.take(6)
.cloned()
.map(|(a, b)| (a.round(), b.round())) // round to account for floating point issues
.collect::<Vec<(f64, f64)>>(),
derivative_target
.iter()
.rev()
.take(6)
.rev()
.cloned()
.map(|(a, b)| (a.round(), b.round())) // round to account for floating point issues
.collect::<Vec<(f64, f64)>>()
);
assert_eq!(
self.back_data
.to_tuple()
.iter()
.take(6)
.cloned()
.map(|(a, b)| (a.round(), b.round())) // round to account for floating point issues
.collect::<Vec<(f64, f64)>>(),
back_target
.iter()
.rev()
.take(6)
.rev()
.cloned()
.map(|(a, b)| (a.round(), b.round())) // round to account for floating point issues
.collect::<Vec<(f64, f64)>>()
);
}
{
self.calculate(&(min_x - 1.0), &(max_x - 1.0), true, &settings);
assert_eq!(
self.derivative_data
.to_tuple()
.iter()
.rev()
.take(6)
.rev()
.cloned()
.map(|(a, b)| (a.round(), b.round())) // round to account for floating point issues
.collect::<Vec<(f64, f64)>>(),
derivative_target
.iter()
.take(6)
.cloned()
.map(|(a, b)| (a.round(), b.round())) // round to account for floating point issues
.collect::<Vec<(f64, f64)>>()
);
assert_eq!(
self.back_data
.to_tuple()
.iter()
.rev()
.take(6)
.rev()
.cloned()
.map(|(a, b)| (a.round(), b.round())) // round to account for floating point issues
.collect::<Vec<(f64, f64)>>(),
back_target
.iter()
.take(6)
.cloned()
.map(|(a, b)| (a.round(), b.round())) // round to account for floating point issues
.collect::<Vec<(f64, f64)>>()
);
}
{
self.update_string("sin(x)");
assert!(self.get_test_result().is_none());

View File

@@ -7,6 +7,8 @@
#![feature(const_default_impls)]
#![feature(const_fn_floating_point_arithmetic)]
#![feature(const_assume)]
#![feature(const_option_ext)]
#![feature(const_slice_index)]
#[macro_use]
extern crate static_assertions;
@@ -27,7 +29,7 @@ pub use crate::{
math_app::AppSettings,
misc::{
decimal_round, format_bytes, hashed_storage_create, hashed_storage_read,
option_vec_printer, resolution_helper, SteppedVector,
option_vec_printer, resolution_helper, step_helper, SteppedVector,
},
};

View File

@@ -3,6 +3,12 @@
#![feature(stmt_expr_attributes)]
#![feature(const_trait_impl)]
#![feature(core_intrinsics)]
#![feature(const_convert)]
#![feature(const_default_impls)]
#![feature(const_fn_floating_point_arithmetic)]
#![feature(const_assume)]
#![feature(const_option_ext)]
#![feature(const_slice_index)]
#[macro_use]
extern crate static_assertions;

View File

@@ -240,7 +240,7 @@ impl MathApp {
loading_element.remove();
Self {
functions: get_functions().unwrap_or(FunctionManager::default()),
functions: get_functions().unwrap_or_default(),
last_info: (vec![None], None),
dark_mode: true, // dark mode is default and is previously set
text: data.text,

View File

@@ -3,9 +3,6 @@ use std::intrinsics::assume;
use eframe::egui::plot::{Line, Points, Value, Values};
use itertools::Itertools;
#[cfg(threading)]
use rayon::prelude::*;
#[cfg(not(threading))]
#[inline]
pub fn dyn_iter<'a, T>(input: &'a [T]) -> impl Iterator<Item = &'a T>
@@ -21,6 +18,8 @@ pub fn dyn_iter<'a, I>(input: &'a I) -> <&'a I as IntoParallelIterator>::Iter
where
&'a I: IntoParallelIterator,
{
use rayon::prelude::*;
input.par_iter()
}
@@ -39,6 +38,7 @@ pub fn dyn_mut_iter<'a, I>(input: &'a mut I) -> <&'a mut I as IntoParallelIterat
where
&'a mut I: IntoParallelIterator,
{
use rayon::prelude::*;
input.par_iter_mut()
}
@@ -79,37 +79,40 @@ pub struct SteppedVector<'a> {
/// Actual data being referenced. HAS to be sorted from minimum to maximum
data: &'a [f64],
/// Minimum value
min: f64,
/// Maximum value
max: f64,
/// Since all entries in `data` are evenly spaced, this field stores the step between 2 adjacent elements
step: f64,
}
impl<'a> SteppedVector<'a> {
/// Returns `Option<usize>` with index of element with value `x`. and `None` if `x` does not exist in `data`
pub fn get_index(&self, x: &f64) -> Option<usize> {
// If `x` is outside range, just go ahead and return `None` as it *shouldn't* be in `data`
if (x > &self.max) | (&self.min > x) {
pub fn get_index(&self, x: f64) -> Option<usize> {
unsafe {
assume(!self.step.is_nan());
assume(self.step > 0.0);
assume(self.step.is_sign_positive());
}
let max = self.get_max();
if &x > max {
return None;
}
if x == &self.min {
return Some(0);
let min = self.get_min();
if min > &x {
return None;
}
if x == &self.max {
if &x == min {
return Some(0);
} else if &x == max {
return Some(self.data.len() - 1);
}
// Do some math in order to calculate the expected index value
let possible_i = ((x - self.min) / self.step) as usize;
let possible_i = ((x - min).abs() / self.step) as usize;
// Make sure that the index is valid by checking the data returned vs the actual data (just in case)
if &self.data[possible_i] == x {
if self.data[possible_i] == x {
// It is valid!
Some(possible_i)
} else {
@@ -118,14 +121,26 @@ impl<'a> SteppedVector<'a> {
}
}
#[allow(dead_code)]
pub const fn get_min(&self) -> f64 { self.min }
#[inline]
pub const fn get_min(&self) -> &f64 {
unsafe {
assume(!self.data.is_empty());
assume(!self.data.is_empty());
self.data.get_unchecked(0)
}
}
#[inline]
pub const fn get_max(&self) -> &f64 {
unsafe {
assume(!self.data.is_empty());
assume(!self.data.is_empty());
self.data.last().unwrap_unchecked()
}
}
#[allow(dead_code)]
pub const fn get_max(&self) -> f64 { self.max }
#[allow(dead_code)]
pub fn get_data(&self) -> &'a [f64] { &self.data }
pub fn get_data(&self) -> &'a [f64] { self.data }
}
// Convert `&[f64]` into [`SteppedVector`]
@@ -140,8 +155,8 @@ impl<'a> From<&'a [f64]> for SteppedVector<'a> {
}
// length of data subtracted by 1 (represents the maximum index value)
let max: f64 = data[data.len() - 1]; // The max value should be the first element
let min: f64 = data[0]; // The minimum value should be the last element
let max: f64 = data[data.len() - 1]; // The max value should be the last element
let min: f64 = data[0]; // The minimum value should be the first element
debug_assert!(max > min);
@@ -149,12 +164,7 @@ impl<'a> From<&'a [f64]> for SteppedVector<'a> {
let step = (max - min).abs() / (data.len() as f64);
// Create and return the struct
SteppedVector {
data,
min,
max,
step,
}
SteppedVector { data, step }
}
}
@@ -200,7 +210,7 @@ pub fn newtons_method_helper(
unsafe {
assume(!data.is_empty());
assume(data.len() > 0);
assume(!data.is_empty());
}
data.iter()
@@ -255,7 +265,7 @@ where
unsafe {
assume(!data.is_empty());
assume(data.len() > 0);
assume(!data.is_empty());
}
let max_i: i32 = (data.len() as i32) - 1;
@@ -300,10 +310,10 @@ const HASH_LENGTH: usize = 8;
#[allow(dead_code)]
pub fn hashed_storage_create(hash: &[u8], data: &[u8]) -> String {
debug_assert_eq!(hash.len(), HASH_LENGTH);
debug_assert!(data.len() > 0);
debug_assert!(!data.is_empty());
unsafe {
assume(data.len() > 0);
assume(!data.is_empty());
assume(!data.is_empty());
assume(hash.len() == HASH_LENGTH);
assume(!hash.is_empty());
@@ -322,7 +332,7 @@ pub fn hashed_storage_read(data: String) -> (String, Vec<u8>) {
debug_assert!(data.len() > HASH_LENGTH);
unsafe {
assume(!data.is_empty());
assume(data.len() > 0);
assume(!data.is_empty());
}
// can't use data.as_bytes() here for some reason, seems to break on wasm?
@@ -330,14 +340,14 @@ pub fn hashed_storage_read(data: String) -> (String, Vec<u8>) {
let (hash, cached_data) = decoded_1.split_at(8);
debug_assert_eq!(hash.len(), HASH_LENGTH);
debug_assert!(cached_data.len() > 0);
debug_assert!(!cached_data.is_empty());
unsafe {
assume(!cached_data.is_empty());
assume(cached_data.len() > 0);
assume(!cached_data.is_empty());
assume(!hash.is_empty());
assume(hash.len() > 0);
assume(!hash.is_empty());
}
(