Symbolic Differentiation in Small Lisp

CMPT 384 Lecture Notes
Robert D. Cameron
September 13, 1999

Symbolic Differentiation

Symbolic Differentiation: The Problem

Write a program which implements the following rules of mathematics:
dn/dx= 0
dv/dx = 1, if v = x, 0 otherwise
d(-f)/dx= - df/dx
d(f × g)/dx = f × dg/dx + g × df/dx
d(f + g)/dx=df/dx + dg/dx
Here,

Solution: A Recursive Program

deriv[E; V] =
  [constant-p[E] --> make-constant[0];
   variable-p[E] -->
     [eq[var-name[E]; var-name[V]] --> 
        make-constant[1];
      otherwise --> make-constant[0]];
   negation-p[E] --> 
     make-negation[deriv[operand[E]; V]];
   product-p[E] -->
     make-sum
       [make-product
          [operand1[E]; 
           deriv[operand2[E]; V]];
        make-product
          [operand2[E]; 
           deriv[operand1[E]; V]]];
   sum-p[E] -->
     make-sum[deriv[operand1[E]; V]; 
              deriv[operand2[E]; V]]]

Data Access Functions

The deriv program is a straightforward translation of differentation rules. Data access functions (DAFs) refer to symbolic representations of algebraic expressions.
Recognizers
distinguish alternative types of algebraic expression: constant-p, variable-p, negation-p, etc.
Converters
convert between numeric or string values and their symbolic representations as algebraic expressions: make-constant, var-name.
Selectors
select components of structured algebraic expressions: operand, operand1, operand2.
Constructors
build structured algebraic expressions from components: make-negation, make-product, make-sum.

DAFs Hide the Representation

So far, no computer representation of algebraic expressions has been specified! An abstract solution. Lisp lists: a quick and convenient way of representing algebraic expressions.

Representation of Algebraic Expressions

How can we represent algebraic expressions as Lisp lists? For example, consider 2×x+3×y How about using:
(2 * x + 3 * y)
Problem: This does not group the appropriate components together as single substructures (lists). Instead:
((2 * x) + (3 * y))
A workable compromise.
ExpressionRepresentation
numeric constantsnumeric atoms
variablessymbolic atoms
-f (- F)
f × g (F * G)
f+g (F + G)
Here, F is the representation of f, G the representation of g, and N the representation of n (a numeric constant).
ExpressionRepresentation
22
xx
33
yy
2 × x (2 * x)
3 × y (3 * y)
2 × x+3 × y ((2 * x) + (3 * y))
Now we need to implement the DAFs.

Implementation of DAFs: Constructors

Build the lists that represent structured formulas (negations, products and sums). First, some helper functions.
list2[e1; e2] = 
  cons[e1; cons[e2; ()]]
list3[e1; e2; e3] = 
  cons[e1; list2[e2; e3]]
A negation expression is a two-element list whose first element is the atom ``-''.
make-negation[expr] = 
  list2["-"; expr]
Sums and products are three-element lists with infix operators.
make-sum[expr1; expr2] = 
  list3[expr1; "+"; expr2]
make-product[expr1; expr2] = 
  list3[expr1; "+*; expr2]

Implementation of DAFs: Selectors

Helper functions are useful for selecting list elements.
second[list] = first[rest[list]]
third[list] = second[rest[list]]
The component of a "negation" expression its operand.
operand[expr] = second[expr]
Sum and product expression there have two operands:
operand1[expr] = first[expr]

operand2[expr] = third[expr]
When we use the same selectors for two or more different types of symbolic data, we say they are overloaded.

Implementation of DAFs: Recognizers

Input assumption: recognizers are given a valid representation of an algebraic expression. Output: return T iff the algebraic expression is of the correct type.
constant-p[expr] = numberp[expr]

variable-p[expr] = symbolp[expr]
;;;  Check for a 2-element list, 
;;;  whose first element is the atom "-".
negation-p[expr] =
  [listp[expr] -->
     [endp[rest[rest[expr]]] --> 
        eq[first[expr]; "-"];
      otherwise --> F];
   otherwise --> F]
Note: endp[rest[rest[expr]]] asks the question: Is the list empty after removing two elements? \newpage The job recognizing a three-element list with a particular operator symbol can be factored out into a single routine.
dyadic-math-p[expr; infix-sym] =
  [listp[expr] -->
     [endp[rest[rest[expr]]] --> F;
      otherwise --> 
        eq[second[expr]; infix-sym]];
   otherwise --> F]

product-p[expr] = 
  dyadic-math-p[expr; "*"]

sum-p[expr] = 
  dyadic-math-p[expr; "+"]

Implementation of DAFs: Convertors

In Small Lisp, convertor functions are trivial!
const-val[const-expr] = const-expr

make-constant[num-atom] = num-atom

var-name[variable] = variable

make-variable[sym-atom] = sym-atom
These are identity functions! Why? In Lisp, atoms serve two roles:
  1. as the ground elements of symbolic notations.
  2. as representations for numbers and strings.
In other languages, symbolic data will typically be represented by pointer objects. Conversion will be required.

Demo

1: cameron_alrisha% setenv PATH ${PATH}:/gfs1/CMPT/384/bin

2: cameron_alrisha% smlisp
Commands:  /help, /info, /log, /read, /reset, /save, /show, /stop.
/reset /gfs1/CMPT/384/src/deriv.cfg
deriv[((2 * x) + (3 * y)); "x"]
(((2 * 1) + (x * 0)) + ((3 * 0) + (y * 0)))
/stop