Implement differentiator

This commit is contained in:
cdo committed 2026-09-26 13:08:02 +01:00
1 parent 03a17aa4a0
commit ce336c2bbb
3 files changed
+212 -2

No files matched your search

+3 -1
View File
@@ -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 {
+208
View File
@@ -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
View File
@@ -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),