diff --git a/src/eval.rs b/src/eval.rs index 00a85f2..db97273 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -1,9 +1,11 @@ pub mod normalise; pub mod const_fold; pub mod compose; +pub mod differentiate; -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum Error { + UnsupportedExpression, } pub trait Evaluator { diff --git a/src/eval/differentiate.rs b/src/eval/differentiate.rs new file mode 100644 index 0000000..41c3ca5 --- /dev/null +++ b/src/eval/differentiate.rs @@ -0,0 +1,208 @@ +use crate::expression::Expression; +use crate::eval::{Error, Evaluator}; + +pub struct Differentiator { + var: char, +} + +impl Differentiator { + pub fn new(var: char) -> Self { + Differentiator { var } + } + + fn diff(&self, expr: Expression) -> Result { + if expr.is_constant() { + Ok(Expression::Integer(0)) + } else { + match expr { + Expression::Symbol(c) if c == self.var => + Ok(Expression::Integer(1)), + Expression::Sum(x, y) => self.diff_sum(x, y), + Expression::Product(x, y) => self.diff_product(x, y), + Expression::Exponent(x, y) if y.is_constant() => + self.diff_power(x, y), + _ => Err(Error::UnsupportedExpression), + } + } + } + + fn diff_sum( + &self, + u: std::boxed::Box, + v: std::boxed::Box) -> Result { + let diff_u = self.diff_boxed(u)?; + let diff_v = self.diff_boxed(v)?; + Ok(Expression::Sum(diff_u, diff_v)) + } + + fn diff_product( + &self, + u: std::boxed::Box, + v: std::boxed::Box) -> Result { + let diff_u = self.diff_boxed(u.clone())?; + let diff_v = self.diff_boxed(v.clone())?; + Ok(Expression::Sum( + std::boxed::Box::new(Expression::Product(diff_u, v)), + std::boxed::Box::new(Expression::Product(u, diff_v)))) + } + + fn diff_power( + &self, + base: std::boxed::Box, + power: std::boxed::Box) -> Result { + let new_power = std::boxed::Box::new( + Expression::Difference( + power.clone(), + std::boxed::Box::new(Expression::Integer(1)))); + let diff_base = self.diff_boxed(base.clone())?; + Ok(Expression::product( + Expression::Product(power, diff_base), + Expression::Exponent(base, new_power))) + } + + fn diff_boxed(&self, mut expr: std::boxed::Box) + -> Result, Error> { + *expr = self.diff(*expr)?; + Ok(expr) + } +} + +impl Evaluator for Differentiator { + type Expr = Expression; + type Error = Error; + + fn eval(&mut self, input: Expression) -> Result { + self.diff(input) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn derivative_of_integer_is_0() { + let input = Expression::Integer(1); + let result = Differentiator::new('x').eval(input).unwrap(); + assert_eq!(result, Expression::Integer(0)); + } + + #[test] + fn derivative_of_half_is_zero() { + let input = Expression::quotient( + Expression::Integer(1), + Expression::Integer(2)); + let result = Differentiator::new('x').eval(input).unwrap(); + assert_eq!(result, Expression::Integer(0)); + } + + #[test] + fn derivative_of_x_is_1() { + let input = Expression::Symbol('x'); + let result = Differentiator::new('x').eval(input).unwrap(); + assert_eq!(result, Expression::Integer(1)); + } + + #[test] + fn derivative_of_sum_is_sum_of_derivatives() { + let input = Expression::sum( + Expression::Symbol('x'), + Expression::Integer(1)); + let result = Differentiator::new('x').eval(input).unwrap(); + assert_eq!( + result, + Expression::sum( + Expression::Integer(1), + Expression::Integer(0))); + } + + #[test] + fn derivative_of_product_uses_product_rule() { + let input = Expression::product( + Expression::Integer(2), + Expression::Symbol('x')); + let result = Differentiator::new('x').eval(input).unwrap(); + assert_eq!( + result, + Expression::sum( + Expression::product( + Expression::Integer(0), + Expression::Symbol('x')), + Expression::product( + Expression::Integer(2), + Expression::Integer(1)))); + } + + #[test] + fn derivative_of_exponent_of_x_uses_power_rule_and_chain_rule() { + let input = Expression::exponent( + Expression::Symbol('x'), + Expression::Integer(2)); + let result = Differentiator::new('x').eval(input).unwrap(); + assert_eq!( + result, + Expression::product( + Expression::product( + Expression::Integer(2), + Expression::Integer(1)), + Expression::exponent( + Expression::Symbol('x'), + Expression::difference( + Expression::Integer(2), + Expression::Integer(1))))); + } + + #[test] + fn derivative_of_exponent_of_sum_of_exponent_uses_power_rule_and_chain_rule() + { + let input = Expression::exponent( + Expression::sum( + Expression::exponent( + Expression::Symbol('x'), + Expression::Integer(2)), + Expression::Integer(1)), + Expression::Integer(2)); + let result = Differentiator::new('x').eval(input).unwrap(); + assert_eq!( + result, + Expression::product( + Expression::product( + Expression::Integer(2), + Expression::sum( + Expression::product( + Expression::product( + Expression::Integer(2), + Expression::Integer(1)), + Expression::exponent( + Expression::Symbol('x'), + Expression::difference( + Expression::Integer(2), + Expression::Integer(1)))), + Expression::Integer(0))), + Expression::exponent( + Expression::sum( + Expression::exponent( + Expression::Symbol('x'), + Expression::Integer(2)), + Expression::Integer(1)), + Expression::difference( + Expression::Integer(2), + Expression::Integer(1))))); + } + + #[test] + fn other_symbols_are_unsupported() { + let input = Expression::Symbol('y'); + let result = Differentiator::new('x').eval(input).unwrap_err(); + assert_eq!(result, Error::UnsupportedExpression); + } + + #[test] + fn non_constant_exponents_are_unsupported() { + let input = Expression::exponent( + Expression::Symbol('x'), + Expression::Symbol('x')); + let result = Differentiator::new('x').eval(input).unwrap_err(); + assert_eq!(result, Error::UnsupportedExpression); + } +} diff --git a/src/expression.rs b/src/expression.rs index 7d58f86..e3187a6 100644 --- a/src/expression.rs +++ b/src/expression.rs @@ -1,4 +1,4 @@ -#[derive(Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub enum Expression { Symbol(char), Integer(i32),