Implement differentiator
This commit is contained in:
1 parent
03a17aa4a0
commit
ce336c2bbb
3 files changed
+212
-2
No files matched your search
+3
-1
@@ -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 {
|
||||
|
||||
@@ -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<Expression, Error> {
|
||||
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<Expression>,
|
||||
v: std::boxed::Box<Expression>) -> Result<Expression, Error> {
|
||||
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<Expression>,
|
||||
v: std::boxed::Box<Expression>) -> Result<Expression, Error> {
|
||||
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<Expression>,
|
||||
power: std::boxed::Box<Expression>) -> Result<Expression, Error> {
|
||||
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<Expression>)
|
||||
-> Result<std::boxed::Box<Expression>, Error> {
|
||||
*expr = self.diff(*expr)?;
|
||||
Ok(expr)
|
||||
}
|
||||
}
|
||||
|
||||
impl Evaluator for Differentiator {
|
||||
type Expr = Expression;
|
||||
type Error = Error;
|
||||
|
||||
fn eval(&mut self, input: Expression) -> Result<Expression, Error> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Expression {
|
||||
Symbol(char),
|
||||
Integer(i32),
|
||||
|
||||
Reference in new issue
Block a user