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 normalise;
|
||||||
pub mod const_fold;
|
pub mod const_fold;
|
||||||
pub mod compose;
|
pub mod compose;
|
||||||
|
pub mod differentiate;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
UnsupportedExpression,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Evaluator {
|
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 {
|
pub enum Expression {
|
||||||
Symbol(char),
|
Symbol(char),
|
||||||
Integer(i32),
|
Integer(i32),
|
||||||
|
|||||||
Reference in new issue
Block a user