Skip to content

Differentiator for Lisp-style s-expressions with property-based testing using Hypothesis

License

Notifications You must be signed in to change notification settings

ishanjmukherjee/symbolic-differentiator

Repository files navigation

Symbolic differentiator

CI

A Python library for symbolic differentiation of mathematical expressions. This library takes Lisp-style mathematical expressions and computes their derivatives with respect to a given variable.

Features

  • Supports + and * with multiple operands.
  • Supports -, / and ^ with two operands.
  • Error handling and input validation.
  • Property-based testing of invariants using Hypothesis.

Installation

  1. Clone the repository:
git clone https://github.com/ishanjmukherjee/symbolic-differentiator.git
cd symbolic-differentiator
  1. Install using pip:
pip install -e .

Usage

The library uses Lisp-style symbolic expressions (s-expressions) for mathematical formulas. The main difference is that we use prefix instead of the more common infix operator notation. Here's how to represent some common expressions:

  • x + 2(+ x 2)
  • x * y(* x y)
  • (x + 1) * (y - 2)(* (+ x 1) (- y 2))
  • x^2(^ x 2)
  • (x + 1)/(y - 1)(/ (+ x 1) (- y 1))

The main advantage of this format is that it makes operator precedence unambiguous.

The main function differentiate() takes an expression string and a variable name (defaults to "x") and returns the derivative as a simplified s-expression string.

Error handling

  • Invalid syntax: Mismatched parentheses, invalid operators
  • Invalid variable names: Empty strings, whitespace, invalid characters
  • Mathematical errors: Division by zero, invalid exponents
  • Unsupported operations: undefined operators (like trig and log for now)

Examples

A more extensive demonstration of usage is in demo.py.

from symbolic_diff import differentiate

# Basic examples
print(differentiate("x"))        # "1"
print(differentiate("(+ x 5)"))  # "1"
print(differentiate("(* x x)"))  # "(+ x x)"
print(differentiate("(^ x 2)"))  # "(* 2 x)"

# More complex examples
print(differentiate("(* (+ x 1) (- x 2))"))         # (+ (+ x -2) (+ x 1))
print(differentiate("(+ (* 2 (^ x 3)) (* -1 x))"))  # (+ (* (* (^ x 2) 3) 2) -1)

# Differentiate with respect to a different variable
print(differentiate("(+ x y)", "y"))  # "1"
print(differentiate("(* x y)", "y"))  # "x"

# Error handling
print(differentiate("(+ x"))     # Error: Missing closing parenthesis
print(differentiate("(/ x 0)"))  # Error: Cannot divide by zero 

Implementation

  • tokenizer.py converts input strings into a sequence of tokens.
    • "(+ x 2)"[LPAREN, OPERATOR(+), VARIABLE(x), NUMBER(2), RPAREN]
  • parser.py converts the token sequence into an abstract syntax tree (AST).
    • [LPAREN, OPERATOR(+), VARIABLE(x), NUMBER(2), RPAREN]ASTNode('OPERATOR', '+', [ASTNode('VARIABLE', 'x', []), ASTNode('NUMBER', '2', [])])
  • differentiator.py performs the core differentiation rules recursively on the AST.
  • simplifier.py simplifies the AST generated by the differentiation function using rules like 1 + 2 + 3 + x6 + x and 1 * 2 * 3 * x6 * x.

Testing

90%+ code coverage using pytest. Since differentiation has a bunch of invariants, Hypothesis works well for testing. From the linked documentation:

Hypothesis is a Python library for creating unit tests which are simpler to write and more powerful when run, finding edge cases in your code you wouldn’t have thought to look for. It is stable, powerful and easy to add to any existing test suite.

It works by letting you write tests that assert that something should be true for every case, not just the ones you happen to think of.

Development

GitHub Actions for continuous integration, running style checks, automated tests, and code coverage reporting.

About

Differentiator for Lisp-style s-expressions with property-based testing using Hypothesis

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages