diff --git a/src/eval.rs b/src/eval.rs index db97273..f61f2c5 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -2,6 +2,7 @@ pub mod normalise; pub mod const_fold; pub mod compose; pub mod differentiate; +pub mod simplify; #[derive(Debug, PartialEq)] pub enum Error { diff --git a/src/eval/simplify.rs b/src/eval/simplify.rs new file mode 100644 index 0000000..ac1cb14 --- /dev/null +++ b/src/eval/simplify.rs @@ -0,0 +1,279 @@ +use crate::expression::Expression; +use crate::eval::{Error, Evaluator}; + +pub struct Simplifier; + +impl Simplifier { + pub fn new() -> Self { + Self {} + } +} + +impl Evaluator for Simplifier { + type Expr = Expression; + type Error = Error; + + fn eval(&mut self, input: Expression) -> Result { + Ok(simplify(input)) + } +} + +fn simplify(expr: Expression) -> Expression { + match expr { + Expression::Sum(x, y) => recurse(sum, x, y), + Expression::Product(x, y) => recurse(product, x, y), + Expression::Exponent(x, y) => recurse(exponent, x, y), + _ => expr + } +} + +fn sum( + x: std::boxed::Box, + y: std::boxed::Box) + -> Expression +{ + if y.integer() == Some(0) { + *x + } else if x.integer() == Some(0) { + *y + } else { + Expression::Sum(x, y) + } +} + +fn product( + x: std::boxed::Box, + y: std::boxed::Box) + -> Expression +{ + if y.integer() == Some(0) || x.integer() == Some(0) { + Expression::Integer(0) + } else if y.integer() == Some(1) { + *x + } else if x.integer() == Some(1) { + *y + } else { + Expression::Product(x, y) + } +} + +fn exponent( + base: std::boxed::Box, + power: std::boxed::Box) + -> Expression +{ + if power.integer() == Some(0) { + Expression::Integer(1) + } else if base.integer() == Some(0) { + Expression::Integer(0) + } else if power.integer() == Some(1) || base.integer() == Some(1) { + *base + } else { + Expression::Exponent(base, power) + } +} + +fn recurse( + f: impl Fn( + std::boxed::Box, + std::boxed::Box) -> Expression, + x: std::boxed::Box, + y: std::boxed::Box) + -> Expression +{ + f(boxed(x), boxed(y)) +} + +fn boxed( + mut expr: std::boxed::Box) -> std::boxed::Box +{ + *expr = simplify(*expr); + expr +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn integer_is_unchanged() { + let input = Expression::Integer(1); + let result = Simplifier::new().eval(input).unwrap(); + assert_eq!(result, Expression::Integer(1)); + } + + #[test] + fn symbol_is_unchanged() { + let input = Expression::Symbol('x'); + let result = Simplifier::new().eval(input).unwrap(); + assert_eq!(result, Expression::Symbol('x')); + } + + #[test] + fn sum_of_symbol_with_zero_reduces() { + let input = Expression::sum( + Expression::Symbol('x'), + Expression::Integer(0)); + let result = Simplifier::new().eval(input).unwrap(); + assert_eq!(result, Expression::Symbol('x')); + } + + #[test] + fn sum_of_zero_with_symbol_reduces() { + let input = Expression::sum( + Expression::Integer(0), + Expression::Symbol('x')); + let result = Simplifier::new().eval(input).unwrap(); + assert_eq!(result, Expression::Symbol('x')); + } + + #[test] + fn sum_of_non_zero_integer_with_symbol_is_unchanged() { + let input = Expression::sum( + Expression::Integer(1), + Expression::Symbol('x')); + let result = Simplifier::new().eval(input).unwrap(); + assert_eq!(result, Expression::sum( + Expression::Integer(1), + Expression::Symbol('x'))); + } + + #[test] + fn nested_sum_with_zero_is_reduced() { + let input = Expression::sum( + Expression::sum( + Expression::Symbol('x'), + Expression::Integer(0)), + Expression::Symbol('y')); + let result = Simplifier::new().eval(input).unwrap(); + assert_eq!( + result, + Expression::sum( + Expression::Symbol('x'), + Expression::Symbol('y'))); + } + + #[test] + fn product_of_symbol_with_zero_becomes_zero() { + let input = Expression::product( + Expression::Symbol('x'), + Expression::Integer(0)); + let result = Simplifier::new().eval(input).unwrap(); + assert_eq!(result, Expression::Integer(0)); + } + + #[test] + fn product_of_zero_with_symbol_becomes_zero() { + let input = Expression::product( + Expression::Integer(0), + Expression::Symbol('x')); + let result = Simplifier::new().eval(input).unwrap(); + assert_eq!(result, Expression::Integer(0)); + } + + #[test] + fn product_of_symbol_with_one_reduces() { + let input = Expression::product( + Expression::Symbol('x'), + Expression::Integer(1)); + let result = Simplifier::new().eval(input).unwrap(); + assert_eq!(result, Expression::Symbol('x')); + } + + #[test] + fn product_of_one_with_symbol_reduces() { + let input = Expression::product( + Expression::Integer(1), + Expression::Symbol('x')); + let result = Simplifier::new().eval(input).unwrap(); + assert_eq!(result, Expression::Symbol('x')); + } + + #[test] + fn product_of_two_with_symbol_is_unchanged() { + let input = Expression::product( + Expression::Integer(2), + Expression::Symbol('x')); + let result = Simplifier::new().eval(input).unwrap(); + assert_eq!(result, Expression::product( + Expression::Integer(2), + Expression::Symbol('x'))); + } + + #[test] + fn nested_product_with_one_is_reduced() { + let input = Expression::product( + Expression::product( + Expression::Symbol('x'), + Expression::Integer(1)), + Expression::Symbol('y')); + let result = Simplifier::new().eval(input).unwrap(); + assert_eq!( + result, + Expression::product( + Expression::Symbol('x'), + Expression::Symbol('y'))); + } + + #[test] + fn exponent_of_symbol_with_zero_becomes_one() { + let input = Expression::exponent( + Expression::Symbol('x'), + Expression::Integer(0)); + let result = Simplifier::new().eval(input).unwrap(); + assert_eq!(result, Expression::Integer(1)); + } + + #[test] + fn exponent_of_zero_with_symbol_becomes_zero() { + let input = Expression::exponent( + Expression::Integer(0), + Expression::Symbol('x')); + let result = Simplifier::new().eval(input).unwrap(); + assert_eq!(result, Expression::Integer(0)); + } + + #[test] + fn exponent_of_symbol_with_one_reduces() { + let input = Expression::exponent( + Expression::Symbol('x'), + Expression::Integer(1)); + let result = Simplifier::new().eval(input).unwrap(); + assert_eq!(result, Expression::Symbol('x')); + } + + #[test] + fn exponent_of_one_with_symbol_becomes_one() { + let input = Expression::exponent( + Expression::Integer(1), + Expression::Symbol('x')); + let result = Simplifier::new().eval(input).unwrap(); + assert_eq!(result, Expression::Integer(1)); + } + + #[test] + fn exponent_of_symbol_with_two_is_unchanged() { + let input = Expression::exponent( + Expression::Symbol('x'), + Expression::Integer(2)); + let result = Simplifier::new().eval(input).unwrap(); + assert_eq!(result, Expression::exponent( + Expression::Symbol('x'), + Expression::Integer(2))); + } + + #[test] + fn nested_exponent_with_one_is_reduced() { + let input = Expression::exponent( + Expression::exponent( + Expression::Symbol('x'), + Expression::Integer(1)), + Expression::Symbol('y')); + let result = Simplifier::new().eval(input).unwrap(); + assert_eq!( + result, + Expression::exponent( + Expression::Symbol('x'), + Expression::Symbol('y'))); + } +}