├── .gitignore ├── Cargo.toml ├── README.md └── src ├── expr.rs ├── expr ├── eval.rs ├── expansion.rs ├── operations.rs └── simplify.rs ├── lib.rs ├── main.rs └── symbol.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "symbolic_math" 3 | version = "0.1.2" 4 | edition = "2021" 5 | description = "A Rust library for performing symbolic mathematics. Supports basic arithmetic operations, expression simplification, and expansion, and evaluation." 6 | license = "MIT" 7 | repository = "https://github.com/nathan-barry/symbolic_math" 8 | documentation = "https://docs.rs/symbolic_math" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Symbolic_Math 2 | 3 | `symbolic_math` is a Rust library that facilitates representation and manipulation of mathematical expressions. The library offers structures to model various mathematical operations, including addition, subtraction, multiplication, division, and exponentiation. These expressions can contain constants, symbols, or other complex expressions. Furthermore, it provides tools to evaluate and simplify these expressions. 4 | 5 | ## Key Components 6 | 7 | - `Expr`: An enum representing different types of mathematical expressions. 8 | - `Symbol`: A struct representing a symbolic variable. 9 | 10 | This library also provides several implementations for `Expr`, including: 11 | 12 | - Constructors for creating new instances of `Expr`. 13 | - A `Display` implementation to convert an `Expr` instance to a string. 14 | - A `simplify` method to simplify an `Expr` instance. 15 | - An `expand` method for basic expansion of an `Expr` instance. 16 | - An `eval` method to evaluate an `Expr` instance. 17 | 18 | The `operators` module includes operator overloads for `Expr`, enabling the combination of `Expr` instances using standard mathematical operators. 19 | 20 | ## Usage 21 | 22 | To include `symbolic_math` in your project, add the following to your `Cargo.toml`: 23 | 24 | ```toml 25 | [dependencies] 26 | symbolic_math = "0.1.1" 27 | ``` 28 | 29 | You can then use it in your code as follows: 30 | 31 | ```rust 32 | use symbolic_math::expr::Expr; 33 | use symbolic_math::symbol::Symbol; 34 | use std::collections::HashMap; 35 | 36 | let x = Expr::new_var("x"); 37 | let y = Expr::new_var("y"); 38 | let z = Expr::new_var("z"); 39 | let res = (x.clone() + x.clone() + y.clone() * y.clone()).pow(z); 40 | println!("{}", res); // prints: "(2x + y^2)^z" 41 | println!("{}", res.simplify()); // prints: "(2x + y^2)^z" 42 | 43 | let mut vars: HashMap = HashMap::new(); 44 | vars.insert(Symbol::new("x"), 4.0); 45 | vars.insert(Symbol::new("y"), 3.0); 46 | vars.insert(Symbol::new("z"), 2.0); 47 | println!("{}", res.eval(&vars).unwrap()); // prints: "289" 48 | ``` 49 | 50 | For more detailed information on how to use `symbolic_math`, refer to the documentation for each individual type and method. 51 | -------------------------------------------------------------------------------- /src/expr.rs: -------------------------------------------------------------------------------- 1 | pub mod operations; 2 | pub mod eval; 3 | pub mod simplify; 4 | pub mod expansion; 5 | 6 | use std::fmt::{self, Formatter, Display}; 7 | use crate::symbol::Symbol; 8 | 9 | /// Represents a mathematical expression. 10 | /// 11 | /// Expressions can be constants (floating point numbers), symbolic variables, or operations 12 | /// (addition, subtraction, multiplication, division, exponentiation, negation). Each operation 13 | /// can contain other expressions, allowing complex, nested expressions to be represented. 14 | #[derive(Debug, Clone, PartialEq)] 15 | pub enum Expr { 16 | /// A constant (floating point number). 17 | Const(f64), 18 | /// A symbolic variable. 19 | Symbol(Symbol), 20 | /// Addition of two expressions. 21 | Add(Box, Box), 22 | /// Subtraction of two expressions. 23 | Sub(Box, Box), 24 | /// Multiplication of two expressions. 25 | Mul(Box, Box), 26 | /// Division of two expressions. 27 | Div(Box, Box), 28 | /// Exponentiation of two expressions. 29 | Pow(Box, Box), 30 | /// Negation of an expression. 31 | Neg(Box), 32 | } 33 | 34 | // Constructors 35 | impl Expr { 36 | /// Constructs a new symbolic variable with the given name. 37 | /// 38 | /// # Examples 39 | /// 40 | /// ``` 41 | /// use symbolic_math::expr::Expr; 42 | /// 43 | /// let x = Expr::new_var("x"); 44 | /// ``` 45 | pub fn new_var(str: &str) -> Expr { 46 | Expr::Symbol(Symbol::new(str)) 47 | } 48 | 49 | /// Constructs a new constant value. 50 | /// 51 | /// # Examples 52 | /// 53 | /// ``` 54 | /// use symbolic_math::expr::Expr; 55 | /// 56 | /// let two = Expr::new_val(2.0); 57 | /// ``` 58 | pub fn new_val(val: f64) -> Expr { 59 | Expr::Const(val) 60 | } 61 | 62 | } 63 | 64 | // Borrows Data 65 | impl Expr { 66 | /// If the expression is a symbolic variable, returns the symbol; otherwise, returns `None`. 67 | /// 68 | /// # Examples 69 | /// 70 | /// ``` 71 | /// use symbolic_math::expr::Expr; 72 | /// use symbolic_math::symbol::Symbol; 73 | /// 74 | /// let x = Expr::new_var("x"); 75 | /// assert_eq!(x.get_symbol().unwrap(), Symbol::new("x")); 76 | /// ``` 77 | pub fn get_symbol(&self) -> Option { 78 | match self { 79 | Expr::Symbol(s) => Some(s.clone()), 80 | _ => None 81 | } 82 | } 83 | 84 | /// Return any symbols in the expression. 85 | /// 86 | /// # Examples 87 | /// 88 | /// ``` 89 | /// use symbolic_math::expr::Expr; 90 | /// use symbolic_math::symbol::Symbol; 91 | /// 92 | /// let x = Expr::new_var("y") + (Expr::new_var("x") * Expr::new_val(42.)); 93 | /// assert_eq!(x.symbols(), vec![&Symbol::new("y"), &Symbol::new("x")]); 94 | /// ```` 95 | pub fn symbols(&self) -> Vec<&Symbol> { 96 | match self { 97 | Expr::Symbol(s) => vec![s], 98 | Expr::Add(a, b) => a.symbols().into_iter().chain(b.symbols().into_iter()).collect(), 99 | Expr::Sub(a, b) => a.symbols().into_iter().chain(b.symbols().into_iter()).collect(), 100 | Expr::Mul(a, b) => a.symbols().into_iter().chain(b.symbols().into_iter()).collect(), 101 | Expr::Div(a, b) => a.symbols().into_iter().chain(b.symbols().into_iter()).collect(), 102 | Expr::Const(_) => Vec::new(), 103 | Expr::Pow(a, b) => a.symbols().into_iter().chain(b.symbols().into_iter()).collect(), 104 | Expr::Neg(e) => e.symbols(), 105 | } 106 | } 107 | } 108 | 109 | impl Display for Expr { 110 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 111 | match self { 112 | Expr::Const(c) => write!(f, "{}", c), 113 | Expr::Symbol(s) => write!(f, "{}", s.name), 114 | Expr::Add(lhs, rhs) => write!(f, "({} + {})", lhs, rhs), 115 | Expr::Sub(lhs, rhs) => write!(f, "({} - {})", lhs, rhs), 116 | Expr::Mul(lhs, rhs) => { 117 | if let Expr::Const(c) = **lhs { 118 | if let Expr::Symbol(_) = **rhs { 119 | return write!(f, "{}{}", c, rhs); 120 | } 121 | } else if let Expr::Const(c) = **rhs { 122 | if let Expr::Symbol(_) = **lhs { 123 | return write!(f, "{}{}", c, lhs); 124 | } 125 | } 126 | write!(f, "({} * {})", lhs, rhs) 127 | } 128 | Expr::Div(lhs, rhs) => write!(f, "({} / {})", lhs, rhs), 129 | Expr::Pow(lhs, rhs) => write!(f, "({} ^ {})", lhs, rhs), 130 | Expr::Neg(expr) => write!(f, "-{}", expr), 131 | } 132 | } 133 | } 134 | 135 | #[cfg(test)] 136 | mod tests { 137 | use super::*; 138 | 139 | #[test] 140 | fn add_const() { 141 | let lhs = Expr::Const(2.0); 142 | let rhs = Expr::Const(4.0); 143 | assert_eq!(Expr::Add(Box::new(lhs.clone()), Box::new(rhs.clone())), lhs + rhs); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/expr/eval.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use crate::expr::Expr; 3 | use crate::symbol::Symbol; 4 | 5 | /// Enum representing possible errors that can occur while evaluating an expression. 6 | #[derive(Debug)] 7 | pub enum EvalError { 8 | SymbolNotFound(Symbol), 9 | UndefinedOperation, 10 | } 11 | 12 | impl Expr { 13 | /// Evaluates the current expression using the given map of symbols to values. 14 | /// 15 | /// If an error occurs during the evaluation, such as not finding a symbol in the map 16 | /// or attempting an undefined operation, it returns an `Err(EvalError)`. 17 | /// 18 | /// # Arguments 19 | /// 20 | /// * `&self` - A reference to the current instance of `Expr`. 21 | /// * `vars` - A map from symbols to their corresponding values. 22 | /// 23 | /// # Example 24 | /// 25 | /// ``` 26 | /// use symbolic_math::expr::Expr; 27 | /// use symbolic_math::symbol::Symbol; 28 | /// use std::collections::HashMap; 29 | /// 30 | /// let x = Expr::new_var("x"); 31 | /// let y = Expr::new_var("y"); 32 | /// let expr = x * y; 33 | /// let mut vars = HashMap::new(); 34 | /// vars.insert(Symbol::new("x"), 3.0); 35 | /// vars.insert(Symbol::new("y"), 9.0); 36 | /// assert_eq!(expr.eval(&vars).unwrap(), 27.0); 37 | /// ``` 38 | pub fn eval(&self, vars: &HashMap) -> Result { 39 | match self { 40 | Expr::Const(c) => Ok(*c), 41 | Expr::Symbol(s) => vars.get(&s).cloned().ok_or(EvalError::SymbolNotFound(s.clone())), 42 | Expr::Add(lhs, rhs) => { 43 | let lhs_val = lhs.eval(vars)?; 44 | let rhs_val = rhs.eval(vars)?; 45 | Ok(round(lhs_val + rhs_val)) 46 | } 47 | Expr::Sub(lhs, rhs) => { 48 | let lhs_val = lhs.eval(vars)?; 49 | let rhs_val = rhs.eval(vars)?; 50 | Ok(round(lhs_val - rhs_val)) 51 | } 52 | Expr::Mul(lhs, rhs) => { 53 | let lhs_val = lhs.eval(vars)?; 54 | let rhs_val = rhs.eval(vars)?; 55 | Ok(round(lhs_val * rhs_val)) 56 | } 57 | Expr::Div(lhs, rhs) => { 58 | let lhs_val = lhs.eval(vars)?; 59 | let rhs_val = rhs.eval(vars)?; 60 | Ok(round(lhs_val / rhs_val)) 61 | } 62 | Expr::Pow(lhs, rhs) => { 63 | let base_val = lhs.eval(vars)?; 64 | let exp_val = rhs.eval(vars)?; 65 | let res = base_val.powf(exp_val); 66 | if res.is_nan() || res.is_infinite() { 67 | Err(EvalError::UndefinedOperation) 68 | } else { 69 | Ok(round(res)) 70 | } 71 | } 72 | Expr::Neg(expr) => { 73 | let expr_val = expr.eval(vars)?; 74 | Ok(-expr_val) 75 | } 76 | } 77 | } 78 | } 79 | 80 | /// Rounds a given `f64` value to the 14th decimal place. 81 | /// 82 | /// This function is used in the `eval` method above to round the results of floating 83 | /// point operations, mitigating the effects of floating point precision errors. 84 | /// 85 | /// # Arguments 86 | /// 87 | /// * `val` - The `f64` value to be rounded. 88 | fn round(val: f64) -> f64 { 89 | (val * 10e14).round() / 10e14 90 | } 91 | 92 | #[cfg(test)] 93 | mod tests { 94 | use super::*; 95 | use std::collections::HashMap; 96 | 97 | #[test] 98 | fn eval_basic_operations() { 99 | let x = Expr::new_var("x"); 100 | let y = Expr::new_var("y"); 101 | let mut vars: HashMap = HashMap::new(); 102 | vars.insert(x.get_symbol().unwrap(), 2.0); 103 | vars.insert(y.get_symbol().unwrap(), 3.0); 104 | 105 | let res_add = x.clone() + y.clone(); 106 | assert_eq!(res_add.eval(&vars).unwrap(), 5.0); 107 | let res_sub = x.clone() - y.clone(); 108 | assert_eq!(res_sub.eval(&vars).unwrap(), -1.0); 109 | let res_mul = x.clone() * y.clone(); 110 | assert_eq!(res_mul.eval(&vars).unwrap(), 6.0); 111 | let res_div = y.clone() / x.clone(); 112 | assert_eq!(res_div.eval(&vars).unwrap(), 1.5); 113 | 114 | let res_complicated = (res_add.pow(res_sub) * res_div) * res_mul; 115 | assert_eq!(res_complicated.eval(&vars).unwrap(), 1.8); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/expr/expansion.rs: -------------------------------------------------------------------------------- 1 | use crate::expr::Expr; 2 | 3 | impl Expr { 4 | /// Expands the current expression to a possibly expanded form. 5 | /// 6 | /// The method expands mathematical expressions based on several 7 | /// algebraic rules. 8 | /// 9 | /// # Example 10 | /// 11 | /// ``` 12 | /// use symbolic_math::expr::Expr; 13 | /// 14 | /// let x = Expr::new_var("x"); 15 | /// let y = Expr::new_var("y"); 16 | /// let res = (x.clone() + y.clone()) * Expr::new_val(2.0); 17 | /// assert_eq!(res.expand(), x * Expr::new_val(2.0) + y * Expr::new_val(2.0)); 18 | /// ``` 19 | pub fn expand(&self) -> Expr { 20 | match self { 21 | Expr::Mul(lhs, rhs) => { 22 | let lhs = lhs.expand(); 23 | let rhs = rhs.expand(); 24 | match (&lhs, &rhs) { 25 | // (a + b) * c -> a*c + b*c 26 | (Expr::Add(a, b), c) | (c, Expr::Add(a, b)) => 27 | Expr::Add(Box::new(Expr::Mul(a.clone(), Box::new(c.clone()))), 28 | Box::new(Expr::Mul(b.clone(), Box::new(c.clone())))).expand(), 29 | // c * (a - b) -> c*a - c*b 30 | (Expr::Sub(a, b), c) | (c, Expr::Sub(a, b)) => 31 | Expr::Sub(Box::new(Expr::Mul(Box::new(c.clone()), a.clone())), 32 | Box::new(Expr::Mul(Box::new(c.clone()), b.clone()))).expand(), 33 | _ => Expr::Mul(Box::new(lhs), Box::new(rhs)), 34 | } 35 | }, 36 | Expr::Add(lhs, rhs) => Expr::Add(Box::new(lhs.expand()), Box::new(rhs.expand())), 37 | Expr::Sub(lhs, rhs) => Expr::Sub(Box::new(lhs.expand()), Box::new(rhs.expand())), 38 | Expr::Div(lhs, rhs) => Expr::Div(Box::new(lhs.expand()), Box::new(rhs.expand())), 39 | Expr::Pow(lhs, rhs) => Expr::Pow(Box::new(lhs.expand()), Box::new(rhs.expand())), 40 | _ => self.clone(), 41 | } 42 | } 43 | } 44 | 45 | #[cfg(test)] 46 | mod tests { 47 | use super::*; 48 | 49 | #[test] 50 | fn test_expansion() { 51 | // Testing (a + b) * c -> a*c + b*c 52 | let x = Expr::new_var("x"); 53 | let y = Expr::new_var("y"); 54 | let res = (x.clone() + y.clone()) * Expr::new_val(2.0); 55 | assert_eq!(res.expand(), x.clone() * Expr::new_val(2.0) + y.clone() * Expr::new_val(2.0)); 56 | 57 | // Testing c * (a - b) -> c*a - c*b 58 | let a = Expr::new_val(3.0); 59 | let b = Expr::new_val(2.0); 60 | let c = Expr::new_var("c"); 61 | let res = c.clone() * (a.clone() - b.clone()); 62 | assert_eq!(res.expand(), c.clone() * a.clone() - c.clone() * b.clone()); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/expr/operations.rs: -------------------------------------------------------------------------------- 1 | use std::ops; 2 | use crate::expr::Expr; 3 | 4 | // Takes ownership 5 | impl Expr { 6 | /// Raises an `Expr` instance to the power of another, creating a new `Expr::Pow` variant. 7 | /// 8 | /// This method consumes the original `Expr` instances, and produces a new one that 9 | /// represents the mathematical operation of exponentiation. 10 | /// 11 | /// # Arguments 12 | /// 13 | /// * `self` - The base of the exponentiation. 14 | /// * `expr` - The exponent in the exponentiation. 15 | /// 16 | /// # Examples 17 | /// 18 | /// ``` 19 | /// use symbolic_math::expr::Expr; 20 | /// 21 | /// let a = Expr::new_val(2.0); 22 | /// let b = Expr::new_val(3.0); 23 | /// let result = a.pow(b); 24 | /// ``` 25 | /// 26 | /// Note: This function consumes the `Expr` instances that it operates on. 27 | pub fn pow(self, expr: Expr) -> Expr { 28 | Expr::Pow(Box::new(self), Box::new(expr)) 29 | } 30 | } 31 | 32 | // Add Overload Operation implementations 33 | impl ops::Add for Expr { 34 | type Output = Expr; 35 | 36 | fn add(self, rhs: Expr) -> Expr { 37 | Expr::Add(Box::new(self), Box::new(rhs)) 38 | } 39 | } 40 | 41 | impl ops::Add for Expr { 42 | type Output = Expr; 43 | 44 | fn add(self, rhs: f64) -> Expr { 45 | Expr::Add(Box::new(self), Box::new(Expr::new_val(rhs))) 46 | } 47 | } 48 | 49 | impl ops::Add for f64 { 50 | type Output = Expr; 51 | 52 | fn add(self, rhs: Expr) -> Expr { 53 | Expr::Add(Box::new(Expr::new_val(self)), Box::new(rhs)) 54 | } 55 | } 56 | 57 | // Sub Overload Operation implementations 58 | impl ops::Sub for Expr { 59 | type Output = Expr; 60 | 61 | fn sub(self, rhs: Expr) -> Expr { 62 | Expr::Sub(Box::new(self), Box::new(rhs)) 63 | } 64 | } 65 | 66 | impl ops::Sub for Expr { 67 | type Output = Expr; 68 | 69 | fn sub(self, rhs: f64) -> Expr { 70 | Expr::Sub(Box::new(self), Box::new(Expr::new_val(rhs))) 71 | } 72 | } 73 | 74 | impl ops::Sub for f64 { 75 | type Output = Expr; 76 | 77 | fn sub(self, rhs: Expr) -> Expr { 78 | Expr::Sub(Box::new(Expr::new_val(self)), Box::new(rhs)) 79 | } 80 | } 81 | 82 | // Mul Overload Operation implementations 83 | impl ops::Mul for Expr { 84 | type Output = Expr; 85 | 86 | fn mul(self, rhs: Expr) -> Expr { 87 | Expr::Mul(Box::new(self), Box::new(rhs)) 88 | } 89 | } 90 | 91 | impl ops::Mul for Expr { 92 | type Output = Expr; 93 | 94 | fn mul(self, rhs: f64) -> Expr { 95 | Expr::Mul(Box::new(self), Box::new(Expr::new_val(rhs))) 96 | } 97 | } 98 | 99 | impl ops::Mul for f64 { 100 | type Output = Expr; 101 | 102 | fn mul(self, rhs: Expr) -> Expr { 103 | Expr::Mul(Box::new(Expr::new_val(self)), Box::new(rhs)) 104 | } 105 | } 106 | 107 | // Div Overload Operation implementations 108 | impl ops::Div for Expr { 109 | type Output = Expr; 110 | 111 | fn div(self, rhs: Expr) -> Expr { 112 | Expr::Div(Box::new(self), Box::new(rhs)) 113 | } 114 | } 115 | 116 | impl ops::Div for Expr { 117 | type Output = Expr; 118 | 119 | fn div(self, rhs: f64) -> Expr { 120 | Expr::Div(Box::new(self), Box::new(Expr::new_val(rhs))) 121 | } 122 | } 123 | 124 | impl ops::Div for f64 { 125 | type Output = Expr; 126 | 127 | fn div(self, rhs: Expr) -> Expr { 128 | Expr::Div(Box::new(Expr::new_val(self)), Box::new(rhs)) 129 | } 130 | } 131 | 132 | // Neg Overload Operation implementations 133 | impl ops::Neg for Expr { 134 | type Output = Expr; 135 | 136 | fn neg(self) -> Expr { 137 | Expr::Neg(Box::new(self)) 138 | } 139 | } 140 | 141 | #[cfg(test)] 142 | mod tests { 143 | use super::*; 144 | 145 | #[test] 146 | fn test_add() { 147 | let x = Expr::new_var("x"); 148 | assert_eq!(x.clone() + Expr::new_val(2.0), x.clone() + 2.0); 149 | assert_eq!(Expr::new_val(2.0) + x.clone(), 2.0 + x.clone()); 150 | } 151 | 152 | #[test] 153 | fn test_sub() { 154 | let x = Expr::new_var("x"); 155 | assert_eq!(x.clone() - Expr::new_val(2.0), x.clone() - 2.0); 156 | assert_eq!(Expr::new_val(2.0) - x.clone(), 2.0 - x.clone()); 157 | } 158 | 159 | #[test] 160 | fn test_mul() { 161 | let x = Expr::new_var("x"); 162 | assert_eq!(x.clone() * Expr::new_val(2.0), x.clone() * 2.0); 163 | assert_eq!(Expr::new_val(2.0) * x.clone(), 2.0 * x.clone()); 164 | } 165 | 166 | #[test] 167 | fn test_div() { 168 | let x = Expr::new_var("x"); 169 | assert_eq!(x.clone() / Expr::new_val(2.0), x.clone() / 2.0); 170 | assert_eq!(Expr::new_val(2.0) / x.clone(), 2.0 / x.clone()); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/expr/simplify.rs: -------------------------------------------------------------------------------- 1 | use crate::expr::Expr; 2 | 3 | impl Expr { 4 | /// Simplifies the current expression to a possibly simpler form. 5 | /// 6 | /// The method simplifies the mathematical expressions based on several 7 | /// algebraic rules. 8 | /// 9 | /// # Example 10 | /// 11 | /// ``` 12 | /// use symbolic_math::expr::Expr; 13 | /// 14 | /// let x1 = Expr::new_var("x"); 15 | /// let x2 = Expr::new_var("x"); 16 | /// let res = x1 + x2; 17 | /// assert_eq!(res.simplify(), Expr::new_val(2.0) * Expr::new_var("x")); 18 | /// ``` 19 | pub fn simplify(&self) -> Expr { 20 | match self { 21 | Expr::Add(lhs, rhs) => { 22 | let lhs = lhs.simplify(); 23 | let rhs = rhs.simplify(); 24 | match (&lhs, &rhs) { 25 | // lhs == rhs, return 2 * lhs 26 | (lhs, rhs) if *lhs == *rhs => 27 | Expr::Mul(Box::new(Expr::new_val(2.0)), Box::new(lhs.clone())), 28 | // cx + x, return (c+1)x 29 | (Expr::Mul(c, inside), out) 30 | | (Expr::Mul(inside, c), out) 31 | | (out, Expr::Mul(inside, c)) 32 | | (out, Expr::Mul(c, inside)) 33 | if ((**inside == *out ) && c.is_const()) => 34 | Expr::Mul(Box::new(Expr::new_val(c.get_const() + 1.0)), Box::new(out.clone())), 35 | // Both constants, return mul 36 | (Expr::Const(c1), Expr::Const(c2)) => 37 | Expr::new_val(c1 + c2), 38 | // Constant == 0, return Expr unchanged 39 | (Expr::Const(c), x) 40 | | (x, Expr::Const(c)) 41 | if *c == 0.0 => x.clone(), 42 | // Else 43 | _ => Expr::Add(Box::new(lhs), Box::new(rhs)), 44 | } 45 | }, 46 | Expr::Sub(lhs, rhs) => { 47 | let lhs = lhs.simplify(); 48 | let rhs = rhs.simplify(); 49 | match (&lhs, &rhs) { 50 | // Both constants, return diff 51 | (Expr::Const(c1), Expr::Const(c2)) => Expr::new_val(c1 - c2), 52 | // Constant == 0, return Expr unchanged 53 | (Expr::Const(c), x) 54 | | (x, Expr::Const(c)) 55 | if *c == 0.0 => x.clone(), 56 | // Else 57 | _ => Expr::Sub(Box::new(lhs), Box::new(rhs)), 58 | } 59 | }, 60 | Expr::Mul(lhs, rhs) => { 61 | let lhs = lhs.simplify(); 62 | let rhs = rhs.simplify(); 63 | match (&lhs, &rhs) { 64 | // lhs == rhs, return lhs^2 65 | (lhs, rhs) if *lhs == *rhs => 66 | Expr::Pow(Box::new(lhs.clone()), Box::new(Expr::new_val(2.0))), 67 | // x^a * x^b, return x^(a+b) 68 | (Expr::Pow(base1, a), Expr::Pow(base2, b)) if *base1 == *base2 => 69 | Expr::Pow(base1.clone(), Box::new(Expr::Add(a.clone(), b.clone()))), 70 | // Both constants, return mul 71 | (Expr::Const(c1), Expr::Const(c2)) => Expr::new_val(c1 * c2), 72 | // Constant == 1, return Expr unchanged 73 | (x, Expr::Const(c)) 74 | | (Expr::Const(c), x) 75 | if *c == 1.0 => x.clone(), 76 | // Constant == 0, return 0 77 | (Expr::Const(c), _) 78 | | (_, Expr::Const(c)) 79 | if *c == 0.0 => Expr::Const(0.0), 80 | // Constant == -1, return Neg 81 | (Expr::Const(c), x) 82 | | (x, Expr::Const(c)) 83 | if *c == -1.0 => Expr::Neg(Box::new(x.clone())), 84 | // Else 85 | _ => Expr::Mul(Box::new(lhs), Box::new(rhs)), 86 | } 87 | }, 88 | Expr::Div(lhs, rhs) => { 89 | let lhs = lhs.simplify(); 90 | let rhs = rhs.simplify(); 91 | match (&lhs, &rhs) { 92 | // Both constants, return div 93 | (Expr::Const(c1), Expr::Const(c2)) => Expr::new_val(c1 / c2), 94 | // Symbol, constant == 1, return symbol 95 | (x, Expr::Const(c)) 96 | | (Expr::Const(c), x) 97 | if *c == 1.0 => x.clone(), 98 | // 0 divided by x, return 0 99 | (Expr::Const(c), _) if *c == 0.0 => Expr::Const(0.0), 100 | // Else 101 | _ => Expr::Div(Box::new(lhs), Box::new(rhs)), 102 | } 103 | }, 104 | Expr::Pow(lhs, rhs) => { 105 | let lhs = lhs.simplify(); 106 | let rhs = rhs.simplify(); 107 | match (&lhs, &rhs) { 108 | // (x^a)^b, returns x^(a*b) 109 | (Expr::Pow(base, p1), p2) => 110 | Expr::Pow( 111 | base.clone(), 112 | Box::new(Expr::Mul(p1.clone(), Box::new(p2.clone()))) 113 | ), 114 | // x^1, returns x 115 | (x, Expr::Const(c)) if *c == 1.0 => x.clone(), 116 | // x^0, returns 1 117 | (_, Expr::Const(c)) if *c == 0.0 => Expr::Const(1.0), // TODO: Only if x != 0 118 | // 1^x, returns 1 119 | (Expr::Const(c), _) if *c == 1.0 => Expr::Const(1.0), 120 | // Else 121 | _ => Expr::Pow(Box::new(lhs), Box::new(rhs)) 122 | } 123 | }, 124 | _ => self.clone() 125 | } 126 | } 127 | 128 | /// Checks if the current expression is a constant. 129 | /// 130 | /// Returns `true` if the current instance of `Expr` is a `Const` variant, and 131 | /// `false` otherwise. 132 | /// 133 | /// # Example 134 | /// 135 | /// ``` 136 | /// use symbolic_math::expr::Expr; 137 | /// 138 | /// let expr = Expr::new_val(2.0); 139 | /// assert_eq!(expr.is_const(), true); 140 | /// ``` 141 | pub fn is_const(&self) -> bool { 142 | if let Expr::Const(_) = self { true } else { false } 143 | } 144 | 145 | /// Returns the `f64` value inside the `Const` variant of `Expr`. 146 | /// 147 | /// # Panics 148 | /// 149 | /// This function will panic if called on a non-`Const` `Expr`. 150 | /// 151 | /// # Example 152 | /// 153 | /// ``` 154 | /// use symbolic_math::expr::Expr; 155 | /// 156 | /// let expr = Expr::new_val(2.0); 157 | /// assert_eq!(expr.get_const(), 2.0); 158 | /// ``` 159 | pub fn get_const(&self) -> f64 { 160 | match self { 161 | Expr::Const(c) => *c, 162 | _ => panic!("Cannot call get_const on non-const Expr") 163 | } 164 | } 165 | } 166 | 167 | #[cfg(test)] 168 | mod tests { 169 | use super::*; 170 | 171 | #[test] 172 | fn add_const() { 173 | let c1 = Expr::new_val(2.0); 174 | let c2 = Expr::new_val(4.0); 175 | let res = c1 + c2; 176 | 177 | assert_eq!(res.simplify(), Expr::new_val(6.0)); 178 | } 179 | 180 | #[test] 181 | fn sub_const() { 182 | let c1 = Expr::new_val(2.0); 183 | let c2 = Expr::new_val(4.0); 184 | let res = c1 - c2; 185 | 186 | assert_eq!(res.simplify(), Expr::new_val(-2.0)); 187 | } 188 | 189 | #[test] 190 | fn mul_const() { 191 | let c1 = Expr::new_val(2.0); 192 | let c2 = Expr::new_val(4.0); 193 | let res = c1 * c2; 194 | 195 | assert_eq!(res.simplify(), Expr::new_val(8.0)); 196 | } 197 | 198 | #[test] 199 | fn div_const() { 200 | let c1 = Expr::new_val(2.0); 201 | let c2 = Expr::new_val(4.0); 202 | let res = c1 / c2; 203 | 204 | assert_eq!(res.simplify(), Expr::new_val(0.5)); 205 | } 206 | 207 | #[test] 208 | fn add_like_terms() { 209 | let x1 = Expr::new_var("x"); 210 | let x2 = Expr::new_var("x"); 211 | let res = x1 + x2; 212 | 213 | assert_eq!(res.simplify(), Expr::new_val(2.0) * Expr::new_var("x")); 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Symbolic_Math 2 | //! 3 | //! `symbolic_math` is a library for representing and manipulating mathematical expressions. It provides structures 4 | //! to model different mathematical operations including addition, subtraction, multiplication, division, and power. 5 | //! The expressions can be comprised of constants, symbols, or other expressions. The library also provides facilities 6 | //! for evaluating and simplifying these expressions. 7 | //! 8 | //! The main types provided by this library are: 9 | //! 10 | //! * `Expr`: An enum representing different kinds of mathematical expressions. 11 | //! * `Symbol`: A struct representing a symbolic variable. 12 | //! 13 | //! The library also provides several implementations for `Expr`: 14 | //! 15 | //! * Constructors for creating new `Expr` instances. 16 | //! * A `Display` implementation for converting an `Expr` to a string. 17 | //! * A `simplify` method for simplifying an `Expr`. 18 | //! * A `expand` method for basic expanding of an `Expr`. 19 | //! * An `eval` method for evaluating an `Expr`. 20 | //! 21 | //! The library also includes operator overloads for `Expr`, located in the `operators` module, 22 | //! which allow `Expr` instances to be combined using standard mathematical operators. 23 | //! 24 | //! ## Examples 25 | //! 26 | //! To use this library, add the following to your `Cargo.toml`: 27 | //! 28 | //! ```toml 29 | //! [dependencies] 30 | //! symbolic_math = "0.1.1" 31 | //! ``` 32 | //! 33 | //! Then, you can use it in your code like so: 34 | //! 35 | //! ```rust 36 | //! use symbolic_math::expr::Expr; 37 | //! use symbolic_math::symbol::Symbol; 38 | //! use std::collections::HashMap; 39 | //! 40 | //! let x = Expr::new_var("x"); 41 | //! let y = Expr::new_var("y"); 42 | //! let z = Expr::new_var("z"); 43 | //! let res = (x.clone() + x.clone() + y.clone() * y.clone()).pow(z); 44 | //! println!("{}", res); // prints: "(((x + x) + (y * y)) ^ z)" 45 | //! println!("{}", res.simplify()); // prints: "((2x + (y ^ 2)) ^ z)" 46 | //! 47 | //! let mut vars: HashMap = HashMap::new(); 48 | //! vars.insert(Symbol::new("x"), 4.0); 49 | //! vars.insert(Symbol::new("y"), 3.0); 50 | //! vars.insert(Symbol::new("z"), 2.0); 51 | //! println!("{}", res.eval(&vars).unwrap()); // prints: "289" 52 | //! ``` 53 | //! 54 | //! See the documentation for each individual type and method for more information on how to use this library. 55 | 56 | pub mod symbol; 57 | pub mod expr; 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use symbolic_math::expr::Expr; 3 | use symbolic_math::symbol::Symbol; 4 | 5 | fn main() { 6 | // let x = Expr::new_var("x"); 7 | // let y = Expr::new_var("y"); 8 | // let c = Expr::new_val(3.0); 9 | // let p = Expr::new_val(2.0); 10 | // let mut vars: HashMap = HashMap::new(); 11 | // vars.insert(x.get_symbol().unwrap(), 1.0); 12 | // vars.insert(y.get_symbol().unwrap(), 2.0); 13 | 14 | // let res = (x+y*c.pow(p)).eval(&vars).unwrap(); 15 | // println!("1: {}", &res); 16 | 17 | // let x1 = Expr::new_var("x"); 18 | // let x2 = Expr::new_var("x"); 19 | // let x3 = Expr::new_var("x"); 20 | // let res = x1 + x2; 21 | // println!("2: {}", &res); 22 | // println!("2 simplify: {}", &res.simplify()); 23 | 24 | // let x1 = Expr::new_var("x"); 25 | // let x2 = Expr::new_var("x"); 26 | // let res2 = x1*x2; 27 | // println!("3: {}", &res2); 28 | // println!("3 simplify: {}", &res2.simplify()); 29 | 30 | // let res2 = res + x3; 31 | // println!("4: {}", &res2.simplify()); 32 | 33 | // let a = Expr::new_var("a"); 34 | // let b = Expr::new_var("b"); 35 | // let c = Expr::new_var("c"); 36 | // let res4 = a.pow(b).pow(c); 37 | // println!("5: {}", &res4.simplify()); 38 | 39 | let x = Expr::new_var("x"); 40 | let y = Expr::new_var("y"); 41 | let z = Expr::new_var("z"); 42 | let res = (x.clone() + x.clone() + y.clone() * y.clone()).pow(z); 43 | println!("{}", res); // prints: "(((x + x) + (y * y)) ^ z)" 44 | println!("{}", res.simplify()); // prints: "((2x + (y ^ 2)) ^ z)" 45 | 46 | let mut vars: HashMap = HashMap::new(); 47 | vars.insert(Symbol::new("x"), 4.0); 48 | vars.insert(Symbol::new("y"), 3.0); 49 | vars.insert(Symbol::new("z"), 2.0); 50 | println!("{}", res.eval(&vars).unwrap()); // prints: "289" 51 | 52 | println!("{:?}", res.symbols()); 53 | } 54 | -------------------------------------------------------------------------------- /src/symbol.rs: -------------------------------------------------------------------------------- 1 | /// Represents a symbolic variable in a mathematical expression. 2 | /// 3 | /// `Symbol` holds a `String` that is its name. It provides functionality to 4 | /// create a new `Symbol` from a string slice. 5 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 6 | pub struct Symbol { 7 | /// The name of the symbolic variable. 8 | pub name: String, 9 | } 10 | 11 | impl Symbol { 12 | /// Creates a new `Symbol` from a string slice. 13 | /// 14 | /// The function takes a string slice as input and returns a `Symbol` with the input as its name. 15 | /// 16 | /// Note: Should normally use s.get_symbol() for Expr::Symbol(s) instead. 17 | pub fn new(name: &str) -> Symbol { 18 | Symbol { name: name.into() } 19 | } 20 | } 21 | 22 | #[cfg(test)] 23 | mod tests { 24 | use super::*; 25 | 26 | #[test] 27 | fn correct_name() { 28 | let symbol = Symbol::new("x"); 29 | assert_eq!(symbol.name, "x"); 30 | } 31 | } 32 | --------------------------------------------------------------------------------