MC logo

Symbolic Differentiation

  CSc 404 Documentation And Examples

Here is a Tom's Lisp program which performs symbolic differentiation. It is based on an example given by Ravi Sethi in Programming Languages: Concepts and Constructs, Addison Wesley, 1989. (And probably in more recent editions as well.) I have adapted it to work with Tom's Lisp (the original is for Scheme), and extended the simplifier slightly.

Download

;
; This is essentially the program given by Ravi Sethi in "Programming
; Languages: Concepts and Constructs," which performs symbolic
; differentiation of a polynomial.
;

; First, establish an error code for bad parsing.
(errcode ERR_BADEXPR)

;
; The polynomials are represented in Lisp notation, so sums (+ ...)
; and produces are (* ...), etc.  The top-level function decides what
; the sort of thing it's working on, and applies an appropriate worker
; function as below.
(define (d x E)
    (cond
        ((constant? E) (diff-constant x E))
        ((variable? E) (diff-variable x E))
        ((sum? E) (diff-sum x E))
        ((product? E) (diff-product x E))
        (#t (error ERR_BADEXPR "Cannot parse expression."))
    )
)

;
; A constant, for our implementation, is an integer, and the differential
; of a constant is zero.
(setq constant? int?)
(define (diff-constant x E) 0)

;
; A variable is just a id atom, and the dx x = 1, dx y = 0.
(setq variable? id?)
(define (diff-variable x E) 
    (if (equal? x E) 1 0)
)

;
; Utilities for sums and products.
(define (terms E) (cdr E))
(define (factors E) (cdr E))
(define (sum? E)
    (and (pair? E) (equal? '+ (car E)))
)
(define (product? E)
    (and (pair? E) (equal? '* (car E)))
)
(define (make-sum x) (cons '+ x))
(define (make-product x) (cons '* x))

;
; The differential of a sum is the sum of the differentials:
; d (E1 + E2) = d E1 + d E2
(define (diff-sum x E)
    (make-sum
        (map (lambda (expr) (d x expr)) (terms E))
    )
)
; This beast simply takes the terms of E, runs d x on each, then makes the
; sum of the result.

;
; The differential of a produce uses the chain rule: 
;   d E1 E2 = E1 d E2 + E2 d E1
; This is actually implemented by chain-rule; the diff-product function
; filters out the cases where the product has fewer than two factors.
(define (diff-product x E)
    (let
        ; See how many things we're got.
        ((nfact (length (factors E))))
        (cond
            ; No factors.  Just a long-winded way to write the constant 1,
            ; whose differential is zero.
            ((equal? nfact 0) 0)

            ; One factor.  Not really much of a product.  Loose the multiply
            ; and take the differential of the single factor.
            ((equal? nfact 1) (d x (car (factors E))))

            ; Real case.
            (#t (chain-rule x E))
        )
    )
)

;
; Here's the actual chain rule function.
;   d E1 E2 = E1 d E2 + E2 d E1
(define (chain-rule x E)
    (let
        (
             (E1 (car (factors E)))                     ; First factor.
             (E2 (make-product (cdr (factors E))))      ; (* Other factors)
             (dE1 (d x E1))                             ; d E1
             (dE2 (d x E2))                             ; d E2
        )
        (make-sum
            (list
                (make-product (list E1 dE2))
                (make-product (list E2 dE1))
            )
        )
    )
)

;
; Some basic simplification.  Not easy to do "completely," but this helps
; a lot.

;
; Top-level simplify.
(define (simplify E)
    (cond
        ((sum? E) (simplify-sum E))
        ((product? E) (simplify-product E))
        (#t E)
    )
)

;
; The sum and product simplifiers are mainly calls to simpl, with some
; appropriate control parameters.  The parameters are the corresponding
; identifier and make- function, and the identity for that operation.
(define (simplify-sum E)
    (simpl sum? make-sum 0 E)
)
(define (simplify-product E)
    (simpl product? make-product 1 E)
)

;
; Here's the simplifier.
(define (simpl isit? addop ident E)
    (let
        (
            (parts (cdr E))                 ; Terms or factors.
            (sparts (map simplify parts))   ; Terms or factors simplified.
            (fparts (flat isit? sparts))    ; Simp (* x (* y z)) to (* x y z)
            (zout (replace-zero fparts))    ; Reduce (* ... 0 ...) to 0.
            (unid (select (lambda (x) (not (equal? x ident))) zout))
                                            ; Remove identity (0 for + 1 for *)
        )
        (proper addop ident unid)           ; Cleanup; see below.
    )
)

;
; The flat function looks for subexpressions of the same operator and merges
; them in.  For instance, change (+ x y (+ z w) (+ q 4) g) to
; (+ x y z w q 4 g).
(define (flat isit args)
    (cond
        ((null? args) ())               ; Empty is empty.
        ((not (pair? args)) (list args)); I don't see how this happens, but ok.
        ((isit (car args))              ; If first arg same op, combine.
            (append (flat isit (cdar args)) (flat isit (cdr args)))
        )
        (#t                             ; Default: go on to the next.
            (cons (car args) (flat isit (cdr args)))
        )
    )
)

;
; This simply adds the operator back to a list of terms or factors, but it
; avoids turning the empty list into (+) or the singleton list int (* 17).
(define (proper addop ident args)
    (cond
        ((null? args) ident)            ; () becomes 0 or 1.
        ((null? (cdr args)) (car args)) ; (x) becomes x
        (#t (addop args))               ; (x y z) to (+/* x y z) 
    )
)

;
; See if the expression is a multiplication containing zero (hence equal
; to zero).
(define (is-zero-mult? E)
    (and (product? E) 
         (not (equal? (select (lambda (x) (equal? x 0)) (factors E)) ()))  )
)

;
; Replace zero-valued multiply items with zero.  This works only on
; the top level.
(define (replace-zero list)
    (if (null? list) ()
        (cons (if (is-zero-mult? (car list)) 0 (car list)) 
                (replace-zero (cdr list))
        )
    )
)