aboutsummaryrefslogtreecommitdiff
path: root/src/math
diff options
context:
space:
mode:
authorNathan Reiner <nathan@nathanreiner.xyz>2024-01-18 18:29:10 +0100
committerNathan Reiner <nathan@nathanreiner.xyz>2024-01-18 18:29:10 +0100
commit1713618d4cc0194674f91fd2d24ef2de88f21784 (patch)
tree1cb39a43019c071ca127cb9f609c045327798de3 /src/math
parent670c1881af4680ce7c248498528d14b98210af3f (diff)
create small iced demo
Diffstat (limited to 'src/math')
-rw-r--r--src/math/commonsense.rs90
-rw-r--r--src/math/complex.rs191
-rw-r--r--src/math/context.rs74
-rw-r--r--src/math/expression.rs73
-rw-r--r--src/math/expression_function.rs60
-rw-r--r--src/math/function.rs68
-rw-r--r--src/math/mod.rs8
-rw-r--r--src/math/operation.rs32
-rw-r--r--src/math/string.rs61
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
+ }
+}