diff options
| author | Nathan Reiner <nathan@nathanreiner.xyz> | 2024-01-18 18:29:10 +0100 |
|---|---|---|
| committer | Nathan Reiner <nathan@nathanreiner.xyz> | 2024-01-18 18:29:10 +0100 |
| commit | 1713618d4cc0194674f91fd2d24ef2de88f21784 (patch) | |
| tree | 1cb39a43019c071ca127cb9f609c045327798de3 /src/math | |
| parent | 670c1881af4680ce7c248498528d14b98210af3f (diff) | |
create small iced demo
Diffstat (limited to 'src/math')
| -rw-r--r-- | src/math/commonsense.rs | 90 | ||||
| -rw-r--r-- | src/math/complex.rs | 191 | ||||
| -rw-r--r-- | src/math/context.rs | 74 | ||||
| -rw-r--r-- | src/math/expression.rs | 73 | ||||
| -rw-r--r-- | src/math/expression_function.rs | 60 | ||||
| -rw-r--r-- | src/math/function.rs | 68 | ||||
| -rw-r--r-- | src/math/mod.rs | 8 | ||||
| -rw-r--r-- | src/math/operation.rs | 32 | ||||
| -rw-r--r-- | src/math/string.rs | 61 |
9 files changed, 657 insertions, 0 deletions
diff --git a/src/math/commonsense.rs b/src/math/commonsense.rs new file mode 100644 index 0000000..e75ccc5 --- /dev/null +++ b/src/math/commonsense.rs @@ -0,0 +1,90 @@ +use crate::math::complex::Complex; + +pub fn add(a: Complex, b: Complex) -> Complex { + a + b +} + +pub fn sub(a: Complex, b: Complex) -> Complex { + a - b +} + +pub fn mul(a: Complex, b: Complex) -> Complex { + a * b +} + +pub fn div(a: Complex, b: Complex) -> Complex { + a / b +} + +pub fn pow(a: Complex, b: Complex) -> Complex { + a.pow(b) +} + +pub fn sqrt(a: Complex) -> Complex { + a.sqrt() +} + +pub fn sin(a: Complex) -> Complex { + a.sin() +} + +pub fn cos(a: Complex) -> Complex { + a.cos() +} + +pub fn tan(a: Complex) -> Complex { + a.tan() +} + +#[macro_export] +macro_rules! commonsense_functions { + {$($x:expr => $y:expr), *} => { + { + use std::sync::Arc; + let mut h : HashMap<String, Arc<dyn Function>> = HashMap::new(); + h.extend(functions!{ + "sqrt" => &$crate::math::commonsense::sqrt, + "sin" => &$crate::math::commonsense::sin, + "cos" => &$crate::math::commonsense::cos, + "tan" => &$crate::math::commonsense::tan + }); + $( + h.insert($x.to_string(), Arc::new($y)); + )* + h + } + }; +} + +#[macro_export] +macro_rules! commonsense_operations { + {$($x:expr => $y:expr), *} => { + vec![ + Operation::new('+', $crate::math::commonsense::add), + Operation::new('-', $crate::math::commonsense::sub), + Operation::new('*', $crate::math::commonsense::mul), + Operation::new('/', $crate::math::commonsense::div), + Operation::new('^', $crate::math::commonsense::pow) + $( + Operation::new($x, Box::new($y)), + )*] + }; +} + +#[macro_export] +macro_rules! commonsense_variables { + {$($x:expr => $y:expr), *} => { + { + let mut h : HashMap<String, Complex> = HashMap::new(); + h.extend(variables!{ + "pi" => Complex::new(std::f64::consts::PI, 0.0), + "e" => Complex::new(std::f64::consts::E, 0.0) + }); + $( + h.insert($x.to_string(), $y); + )* + h + } + }; +} + diff --git a/src/math/complex.rs b/src/math/complex.rs new file mode 100644 index 0000000..5b95df4 --- /dev/null +++ b/src/math/complex.rs @@ -0,0 +1,191 @@ +use std::f64::consts::E; +use std::num::ParseFloatError; +use std::str::FromStr; + +#[derive(Clone, Copy, Default, Debug)] +pub struct Complex { + pub real: f64, + pub imag: f64, +} + +impl Complex { + pub fn new(real: f64, imag: f64) -> Self { + Self { real, imag } + } + + pub fn is_real(&self) -> bool { + self.imag == 0.0 + } + + pub fn abs(&self) -> f64 { + (self.real.powi(2) + self.imag.powi(2)).sqrt() + } + + pub fn pow(&self, rhs: Self) -> Self { + if self.is_real() && rhs.is_real() { + Complex::new(self.real.powf(rhs.real), 0.0) + } else { + let r = self.abs(); + let x = (self.imag / self.real).atan(); + let lnr = r.ln(); + let alpha = lnr * rhs.imag + x * rhs.real; + let e_exp = E.powf(lnr * rhs.real - rhs.imag * x); + Complex::new(e_exp * alpha.cos(), e_exp * alpha.sin()) + } + } + + pub fn sqrt(&self) -> Self { + self.pow(Complex::new(0.5, 0.0)) + } + + pub fn cos(&self) -> Self { + let e: Complex = E.into(); + let i = Complex::new(0.0, 1.0); + + Complex::new(e.pow(&i * self).real, 0.0) + } + + pub fn sin(&self) -> Self { + let e: Complex = E.into(); + let i = Complex::new(0.0, 1.0); + + Complex::new(e.pow(&i * self).imag, 0.0) + } + + pub fn tan(&self) -> Self { + self.sin() / self.cos() + } +} + +impl std::fmt::Display for Complex { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + if self.imag == 0.0 { + write!(f, "{}", self.real) + } else if self.real == 0.0 { + if self.imag == 1.0 { + write!(f, "i") + } else { + write!(f, "{}i", self.imag) + } + } else if self.imag == 1.0 { + write!(f, "{} + i", self.real) + } else { + write!(f, "{} + {}i", self.real, self.imag) + } + } +} + +#[derive(Debug)] +pub struct ComplexParseError; + +impl From<ParseFloatError> for ComplexParseError { + fn from(_: ParseFloatError) -> Self { + Self {} + } +} + +impl FromStr for Complex { + type Err = ComplexParseError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + let mut c = Complex::default(); + let s = s.replace(' ', ""); + if s.contains('+') { + let (a, b) = s.split_once('+').unwrap(); + if a.contains('i') { + c.imag = a[..a.len() - 1].parse()?; + c.real = b.parse()?; + } else { + c.real = a.parse()?; + c.imag = b[..b.len() - 1].parse()?; + } + } else if s.contains('i') { + if s.len() == 1 { + c.imag = 1.0; + } else { + c.imag = s[..s.len() - 1].parse()?; + } + } else { + c.real = s.parse()?; + } + + Ok(c) + } +} + +impl From<f64> for Complex { + fn from(val: f64) -> Self { + Complex::new(val, 0.0) + } +} + +impl std::ops::Add for &Complex { + type Output = Complex; + + fn add(self, rhs: Self) -> Self::Output { + *self + *rhs + } +} + +impl std::ops::Add for Complex { + type Output = Complex; + + fn add(self, rhs: Self) -> Self::Output { + Complex::new(self.real + rhs.real, self.imag + rhs.imag) + } +} + +impl std::ops::Sub for &Complex { + type Output = Complex; + + fn sub(self, rhs: Self) -> Self::Output { + *self - *rhs + } +} + +impl std::ops::Sub for Complex { + type Output = Complex; + + fn sub(self, rhs: Self) -> Self::Output { + Complex::new(self.real - rhs.real, self.imag - rhs.imag) + } +} + +impl std::ops::Mul for &Complex { + type Output = Complex; + + fn mul(self, rhs: Self) -> Self::Output { + *self * *rhs + } +} + +impl std::ops::Mul for Complex { + type Output = Complex; + + fn mul(self, rhs: Self) -> Self::Output { + Complex::new( + self.real * rhs.real - self.imag * rhs.imag, + self.real * rhs.imag + self.imag * rhs.real, + ) + } +} + +impl std::ops::Div for &Complex { + type Output = Complex; + + fn div(self, rhs: Self) -> Self::Output { + *self / *rhs + } +} + +impl std::ops::Div for Complex { + type Output = Complex; + + fn div(self, rhs: Self) -> Self::Output { + let z = rhs.real * rhs.real + rhs.imag * rhs.imag; + Complex::new( + (self.real * rhs.real + self.imag * rhs.imag) / z, + (self.imag * rhs.real - self.real * rhs.imag) / z, + ) + } +} diff --git a/src/math/context.rs b/src/math/context.rs new file mode 100644 index 0000000..cb0bca0 --- /dev/null +++ b/src/math/context.rs @@ -0,0 +1,74 @@ +use crate::math::complex::Complex; +use crate::math::function::Function; +use crate::math::operation::Operation; +use crate::{commonsense_functions, commonsense_operations, commonsense_variables, functions, variables}; +use std::collections::HashMap; +use std::sync::Arc; + +#[derive(Default, Clone)] +pub struct Context { + ops: Vec<Operation>, + vars: HashMap<String, Complex>, + funcs: HashMap<String, Arc<dyn Function>>, +} + +impl Context { + pub fn new() -> Self { + Self::default() + } + + pub fn commonsense() -> Self { + Self::default() + .with_operations(commonsense_operations!{}) + .with_functions(commonsense_functions!{}) + .with_variables(commonsense_variables!{}) + } + + pub fn with_operations(mut self, ops: Vec<Operation>) -> Self { + self.ops = ops; + self + } + + pub fn with_variables(mut self, vars: HashMap<String, Complex>) -> Self { + self.vars = vars; + self + } + + pub fn with_functions(mut self, funcs: HashMap<String, Arc<dyn Function>>) -> Self { + self.funcs = funcs; + self + } + + pub fn operations(&self) -> &Vec<Operation> { + &self.ops + } + + pub fn set_variable(&mut self, name: &str, value: Complex) { + self.vars.insert(name.to_string(), value); + } + + pub fn set_function(&mut self, name: &str, func: Arc<dyn Function>) { + self.funcs.insert(name.to_string(), func); + } + + pub fn variable(&self, name: &str) -> Option<&Complex> { + self.vars.get(name) + } + + pub fn function(&self, name: &str) -> Option<&Arc<dyn Function>> { + self.funcs.get(name) + } +} + +#[macro_export] +macro_rules! variables { + {$($x:expr => $y:expr), *} => { + { + let mut h : HashMap<String, Complex> = HashMap::new(); + $( + h.insert($x.to_string(), $y); + )* + h + } + }; +} diff --git a/src/math/expression.rs b/src/math/expression.rs new file mode 100644 index 0000000..9d6d4f3 --- /dev/null +++ b/src/math/expression.rs @@ -0,0 +1,73 @@ +use crate::math::complex::Complex; +use crate::math::context::Context; +use crate::math::function::FunctionArgument; +use crate::math::string::{ContainsAndSkipBrackets, SplitMatchingBracket}; + +#[derive(Default)] +pub struct Expression { + repr: String, +} + +impl Expression { + pub fn from_string(str: &str) -> Self { + Self { + repr: str.replace(' ', ""), + } + } + + pub fn content(&self) -> &str { + &self.repr + } + + pub fn evaluate(&self, context: &Context) -> Result<Complex, String> { + let (repr, oprepr) = { + if self.repr.starts_with('(') { + let (oprepr, r) = self.repr.split_on_matching_bracket(); + if r.is_empty() { + let repr = oprepr[1..oprepr.len() - 1].to_string(); + (repr.clone(), repr) + } else { + (self.repr.to_string(), r.to_string()) + } + } else { + (self.repr.to_string(), self.repr.to_string()) + } + }; + + let curop: Option<_> = { + let mut o = None; + for op in context.operations() { + if oprepr.contains_and_skip_brackets(op.sign().to_string().as_str()) { + o = Some(op); + break; + } + } + o + }; + + if let Some(op) = curop { + let (first, rest) = repr.split_once_and_skipt_brackets(op.sign()).unwrap(); + let first_expr = Expression::from_string(first); + let rest_expr = Expression::from_string(rest); + Ok(op.evaluate(first_expr.evaluate(context)?, rest_expr.evaluate(context)?)) + } else if let Ok(r) = repr.parse::<Complex>() { + Ok(r) + } else if let Some((func, args)) = repr.split_once('(') { + let mut argv = Vec::new(); + + for arg in args[..args.len() - 1].split(',') { + argv.push(Expression::from_string(arg).evaluate(context)?); + } + + if let Some(func) = context.function(func) { + Ok(func.eval(FunctionArgument::new(argv), context)?) + } else { + Err(format!("function '{func}' not found")) + } + } else if let Some(res) = context.variable(&repr) { + Ok(*res) + } else { + Err(format!("variable '{repr}' not found")) + } + } +} diff --git a/src/math/expression_function.rs b/src/math/expression_function.rs new file mode 100644 index 0000000..20f4816 --- /dev/null +++ b/src/math/expression_function.rs @@ -0,0 +1,60 @@ +use std::iter::zip; +use std::sync::Arc; + +use crate::math::{ + complex::Complex, + context::Context, + expression::Expression, + function::{Function, FunctionArgument, EmptyFunction} +}; + +#[derive(Default)] +pub struct ExpressionFunction { + expr: Expression, + name: String, + args: Vec<String>, +} + +impl ExpressionFunction { + pub fn from_string(str: String) -> Self { + let str = str.replace(' ', ""); + let (lhs, expr) = str.split_once('=').unwrap(); + let (name, a) = lhs.split_once('(').unwrap(); + let args: Vec<String> = a[0..a.len() - 1] + .split(',') + .map(|s| s.to_string()) + .collect(); + Self { + expr: Expression::from_string(expr), + name: name.to_string(), + args, + } + } + + pub fn name(&self) -> &str { + &self.name + } + + pub fn content(&self) -> &str { + &self.expr.content() + } +} + +impl Function for ExpressionFunction { + fn eval(&self, args: FunctionArgument, ctx: &Context) -> Result<Complex, String> { + if args.len() == self.args.len() { + let mut nctx = ctx.clone(); + for (n, v) in zip(self.args.iter(), args.data().iter()) { + nctx.set_variable(n, *v) + } + nctx.set_function(self.name(), Arc::new(EmptyFunction {})); + self.expr.evaluate(&nctx) + } else { + Err(format!( + "{} takes {} parameters", + self.name, + self.args.len() + )) + } + } +} diff --git a/src/math/function.rs b/src/math/function.rs new file mode 100644 index 0000000..bd782f3 --- /dev/null +++ b/src/math/function.rs @@ -0,0 +1,68 @@ +use crate::math::{complex::Complex, context::Context}; + +pub struct FunctionArgument { + args : Vec<Complex>, +} + +impl FunctionArgument { + pub fn new(args: Vec<Complex>) -> Self { + Self { args } + } + + pub fn get(&self, i : usize) -> Complex { + self.args[i] + } + + pub fn len(&self) -> usize { + self.args.len() + } + + pub fn is_empty(&self) -> bool { + self.args.len() == 0 + } + + pub fn data(&self) -> &[Complex] { + &self.args + } +} + +pub trait Function { + fn eval(&self, args: FunctionArgument, ctx: &Context) -> Result<Complex, String>; +} + +#[macro_export] +macro_rules! functions { + {$($x:expr => $y:expr), *} => { + { + let mut h : HashMap<String, Arc<dyn Function>> = HashMap::new(); + $( + h.insert($x.to_string(), Arc::new($y)); + )* + h + } + }; +} + + +// Some default implementations +impl<T> Function for T +where + T: Fn(Complex) -> Complex, +{ + fn eval(&self, args: FunctionArgument, _ctx: &Context) -> Result<Complex, String> { + if args.len() == 1 { + Ok(self(args.get(0))) + } else { + Err("too many arguments".to_string()) + } + } +} + + +pub struct EmptyFunction {} + +impl Function for EmptyFunction { + fn eval(&self, _args: FunctionArgument, _ctx: &Context) -> Result<Complex, String> { + Err("function not implemented in this scope".to_string()) + } +} diff --git a/src/math/mod.rs b/src/math/mod.rs new file mode 100644 index 0000000..7b9c17c --- /dev/null +++ b/src/math/mod.rs @@ -0,0 +1,8 @@ +pub mod complex; +pub mod context; +pub mod expression; +pub mod function; +pub mod operation; +pub mod string; +pub mod commonsense; +pub mod expression_function; diff --git a/src/math/operation.rs b/src/math/operation.rs new file mode 100644 index 0000000..bcf6510 --- /dev/null +++ b/src/math/operation.rs @@ -0,0 +1,32 @@ +use crate::math::complex::Complex; + +pub type Operator = fn(Complex, Complex) -> Complex; + +#[derive(Clone)] +pub struct Operation { + sign: char, + func: Operator +} + +impl Operation { + pub fn new(sign: char, func: Operator) -> Self { + Self { sign, func } + } + + pub fn sign(&self) -> char { + self.sign + } + + pub fn evaluate(&self, a: Complex, b: Complex) -> Complex { + (self.func)(a, b) + } +} + +#[macro_export] +macro_rules! operations { + {$($x:expr => $y:expr), *} => { + vec![$( + Operation::new($x, Box::new($y)), + )*] + }; +} diff --git a/src/math/string.rs b/src/math/string.rs new file mode 100644 index 0000000..c8d719b --- /dev/null +++ b/src/math/string.rs @@ -0,0 +1,61 @@ +pub trait ContainsAndSkipBrackets { + fn contains_and_skip_brackets(&self, pattern: &str) -> bool; +} + +impl ContainsAndSkipBrackets for String { + fn contains_and_skip_brackets(&self, pattern: &str) -> bool { + if self.contains(pattern) { + let (first, _) = self.split_once(pattern).unwrap(); + let opening_count = first.chars().filter(|c| *c == '(').count(); + let closing_count = first.chars().filter(|c| *c == ')').count(); + return opening_count == closing_count; + } + false + } +} + +pub trait SplitMatchingBracket { + fn split_on_matching_bracket(&self) -> (&str, &str); + fn split_once_and_skipt_brackets(&self, pattern: char) -> Option<(&str, &str)>; +} + +impl SplitMatchingBracket for String { + fn split_on_matching_bracket(&self) -> (&str, &str) { + let mut i = 0; + let mut bracket_balance = -1; + + for c in self.chars() { + i += 1; + match c { + '(' => bracket_balance += 1, + ')' => bracket_balance -= 1, + _ => {} + } + + if bracket_balance < 0 { + break; + } + } + self.split_at(i) + } + + fn split_once_and_skipt_brackets(&self, pattern: char) -> Option<(&str, &str)> { + let mut i = 0; + let mut bracket_balance = 0; + + for c in self.chars() { + i += 1; + match c { + '(' => bracket_balance += 1, + ')' => bracket_balance -= 1, + _ => {} + } + + if bracket_balance <= 0 && c == pattern { + let (a, b) = self.split_at(i); + return Some((&a[0..a.len() - 1], b)) + } + } + None + } +} |