# symdiff.tcl --
#
#       Symbolic differentiation package for Tcl
#
# This package implements a command, "math::calculus::symdiff::symdiff",
# which accepts a Tcl expression and a variable name, and if the expression
# is readily differentiable, returns a Tcl expression that evaluates the
# derivative.
#
# Copyright (c) 2005, 2010 by Kevin B. Kenny.  All rights reserved.
#
# See the file "license.terms" for information on usage and redistribution
# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
#
# RCS: @(#) $Id: symdiff.tcl,v 1.2 2011/01/13 02:49:53 andreas_kupries Exp $


# This package requires the 'tclparser' from http://tclpro.sf.net/
# to analyze the expressions presented to it.

package require Tcl 8.5 9
package require grammar::aycock 1.1
package provide math::calculus::symdiff 1.0.2

namespace eval math {}
namespace eval math::calculus {}
namespace eval math::calculus::symdiff {
    namespace export jacobian symdiff
    namespace eval differentiate {}
}

# math::calculus::symdiff::jacobian --
#
#	Differentiate a set of expressions with respect to a set of
#	model variables
#
# Parameters:
#	model -- A list of alternating {variable name} {expr}
#
# Results:
#	Returns a list of lists.  The ith sublist is the gradient vector
#	of the ith expr in the model; that is, the jth element of
#	the ith sublist is the derivative of the ith expr with respect
#	to the jth variable.
#
#	Returns an error if any expression cannot be differentiated with
#	respect to any of the elements of the list, or if the list has
#	no elements or an odd number of elements.

proc math::calculus::symdiff::jacobian {list} {
    set l [llength $list]
    if {$l == 0 || $l%2 != 0} {
	return -code error "list of variables and expressions must have an odd number of elements"
    }
    set J {}
    foreach {- expr} $list {
	set gradient {}
	foreach {var -} $list {
	    lappend gradient [symdiff $expr $var]
	}
	lappend J $gradient
    }
    return $J
}

# math::calculus::symdiff::symdiff --
#
#       Differentiate an expression with respect to a variable.
#
# Parameters:
#       expr -- expression to differentiate (Must be a Tcl expression,
#               without command substitution.)
#       var -- Name of the variable to differentiate the expression
#              with respect to.
#
# Results:
#       Returns a Tcl expression that evaluates the derivative.

proc math::calculus::symdiff::symdiff {expr var} {
    variable parser
    set parsetree [$parser parse {*}[Lexer $expr] [namespace current]]
    return [ToInfix [differentiate::MakeDeriv $parsetree $var]]
}

# math::calculus::symdiff::Parser --
#
#	Parser for the mathematical expressions that this package can
#	differentiate.

namespace eval math::calculus::symdiff {
    variable parser [grammar::aycock::parser {
	expression ::= expression addop term {
	    set result [${clientData}::MakeOperator [lindex $_ 1]]
	    lappend result [lindex $_ 0] [lindex $_ 2]
	}
	expression ::= term {
	    lindex $_ 0
	}

	addop ::= + {
	    lindex $_ 0
	}
	addop ::= - {
	    lindex $_ 0
	}

	term ::= term mulop factor {
	    set result [${clientData}::MakeOperator [lindex $_ 1]]
	    lappend result [lindex $_ 0] [lindex $_ 2]
	}
	term ::= factor {
	    lindex $_ 0
	}
	mulop ::= * {
	    lindex $_ 0
	}
	mulop ::= / {
	    lindex $_ 0
	}

	factor ::= addop factor {
	    set result [${clientData}::MakeOperator [lindex $_ 0]]
	    lappend result [lindex $_ 1]
	}
	factor ::= expon {
	    lindex $_ 0
	}

	expon ::= primary ** expon {
	    set result [${clientData}::MakeOperator [lindex $_ 1]]
	    lappend result [lindex $_ 0] [lindex $_ 2]
	}
	expon ::= primary {
	    lindex $_ 0
	}

	primary ::= {$} bareword {
	    ${clientData}::MakeVariable [lindex $_ 1]
	}
	primary ::= number {
	    ${clientData}::MakeConstant [lindex $_ 0]
	}
	primary ::= bareword ( arglist ) {
	    set result [${clientData}::MakeOperator [lindex $_ 0]]
	    lappend result {*}[lindex $_ 2]
	}
	primary ::= ( expression ) {
	    lindex $_ 1
	}

	arglist ::= expression {
	    set _
	}
	arglist ::= arglist , expression {
	    linsert [lindex $_ 0] end [lindex $_ 2]
	}
    }]
}

# math::calculus::symdiff::Lexer --
#
#	Lexer for the arithmetic expressions that the 'symdiff' package
#	can differentiate.
#
# Results:
#	Returns a two element list. The first element is a list of the
#	lexical values of the tokens that were found in the expression;
#	the second is a list of the semantic values of the tokens. The
#	two sublists are the same length.

proc math::calculus::symdiff::Lexer {expression} {
    set start 0
    set tokens {}
    set values {}
    while {$expression ne {}} {
	if {[regexp {^\*\*(.*)} $expression -> rest]} {

	    # Exponentiation

	    lappend tokens **
	    lappend values **
	} elseif {[regexp {^([-+/*$(),])(.*)} $expression -> token rest]} {

	    # Single-character operators

	    lappend tokens $token
	    lappend values $token
	} elseif {[regexp {^([[:alpha:]][[:alnum:]_]*)(.*)} \
		       $expression -> token rest]} {

	    # Variable and function names

	    lappend tokens bareword
	    lappend values $token
	} elseif {[regexp -nocase -expanded {
	    ^((?:
	       (?: [[:digit:]]+ (?:[.][[:digit:]]*)? )
	       | (?: [.][[:digit:]]+ ) )
	      (?: e [-+]? [[:digit:]]+ )? )
	    (.*)
	}\
		       $expression -> token rest]} {

	    # Numbers

	    lappend tokens number
	    lappend values $token
	} elseif {[regexp {^[[:space:]]+(.*)} $expression -> rest]} {

	    # Whitespace

	} else {

	    # Anything else is an error

	    return -code error \
		-errorcode [list MATH SYMDIFF INVCHAR \
				[string index $expression 0]] \
		[list invalid character [string index $expression 0]] \
	}
	set expression $rest
    }
    return [list $tokens $values]
}

# math::calculus::symdiff::ToInfix --
#
#       Converts a parse tree to infix notation.
#
# Parameters:
#       tree - Parse tree to convert
#
# Results:
#       Returns the parse tree as a Tcl expression.

proc math::calculus::symdiff::ToInfix {tree} {
    set a [lindex $tree 0]
    set kind [lindex $a 0]
    switch -exact $kind {
        constant -
        text {
            set result [lindex $tree 1]
        }
        var {
            set result \$[lindex $tree 1]
        }
        operator {
            set name [lindex $a 1]
            if {([string is alnum $name] && $name ne {eq} && $name ne {ne})
                || [llength $tree] == 2} {
                set result $name
                append result \(
                set sep ""
                foreach arg [lrange $tree 1 end] {
                    append result $sep [ToInfix $arg]
                    set sep ", "
                }
                append result \)
            } elseif {[llength $tree] == 3} {
                set result \(
                append result [ToInfix [lindex $tree 1]]
                append result " " $name " "
                append result [ToInfix [lindex $tree 2]]
                append result \)
            } else {
                error "symdiff encountered a malformed parse, can't happen"
            }
        }
        default {
            error "symdiff can't synthesize a $kind expression"
        }
    }
    return $result
}

# math::calculus::symdiff::differentiate::MakeDeriv --
#
#       Differentiates a Tcl expression represented as a parse tree.
#
# Parameters:
#       tree -- Parse tree from MakeParseTreeForExpr
#       var -- Variable to differentiate with respect to
#
# Results:
#       Returns the parse tree of the derivative.

proc math::calculus::symdiff::differentiate::MakeDeriv {tree var} {
    return [eval [linsert $tree 1 $var]]
}

# math::calculus::symdiff::differentiate::ChainRule --
#
#       Applies the Chain Rule to evaluate the derivative of a unary
#       function.
#
# Parameters:
#       var -- Variable to differentiate with respect to.
#       derivMaker -- Command prefix for differentiating the function.
#       u -- Function argument.
#
# Results:
#       Returns a parse tree representing the derivative of f($u).
#
# ChainRule differentiates $u with respect to $var by calling MakeDeriv,
# makes the derivative of f($u) with respect to $u by calling derivMaker
# passing $u as a parameter, and then returns a parse tree representing
# the product of the results.

proc math::calculus::symdiff::differentiate::ChainRule {var derivMaker u} {
    lappend derivMaker $u
    set result [MakeProd [MakeDeriv $u $var] [eval $derivMaker]]
}

# math::calculus::symdiff::differentiate::constant --
#
#       Differentiate a constant.
#
# Parameters:
#       var -- Variable to differentiate with respect to - unused
#       constant -- Constant expression to differentiate - ignored
#
# Results:
#       Returns a parse tree of the derivative, which is, of course, the
#       constant zero.

proc math::calculus::symdiff::differentiate::constant {var constant} {
    return [MakeConstant 0.0]
}

# math::calculus::symdiff::differentiate::var --
#
#       Differentiate a variable expression.
#
# Parameters:
#       var - Variable with which to differentiate.
#       exprVar - Expression being differentiated, which is a single
#                 variable.
#
# Results:
#       Returns a parse tree of the derivative.
#
# The derivative is the constant unity if the variables are the same
# and the constant zero if they are different.

proc math::calculus::symdiff::differentiate::var {var exprVar} {
    if {$exprVar eq $var} {
        return [MakeConstant 1.0]
    } else {
        return [MakeConstant 0.0]
    }
}

# math::calculus::symdiff::differentiate::operator + --
#
#       Forms the derivative of a sum.
#
# Parameters:
#       var -- Variable to differentiate with respect to.
#       args -- One or two arguments giving augend and addend. If only
#               one argument is supplied, this is unary +.
#
# Results:
#       Returns a parse tree representing the derivative.
#
# Of course, the derivative of a sum is the sum of the derivatives.

proc {math::calculus::symdiff::differentiate::operator +} {var args} {
    if {[llength $args] == 1} {
        set u [lindex $args 0]
        set result [eval [linsert $u 1 $var]]
    } elseif {[llength $args] == 2} {
        foreach {u v} $args break
        set du [eval [linsert $u 1 $var]]
        set dv [eval [linsert $v 1 $var]]
        set result [MakeSum $du $dv]
    } else {
        error "symdiff encountered a malformed parse, can't happen"
    }
    return $result
}

# math::calculus::symdiff::differentiate::operator - --
#
#       Forms the derivative of a difference.
#
# Parameters:
#       var -- Variable to differentiate with respect to.
#       args -- One or two arguments giving minuend and subtrahend. If only
#               one argument is supplied, this is unary -.
#
# Results:
#       Returns a parse tree representing the derivative.
#
# Of course, the derivative of a sum is the sum of the derivatives.

proc {math::calculus::symdiff::differentiate::operator -} {var args} {
    if {[llength $args] == 1} {
        set u [lindex $args 0]
        set du [eval [linsert $u 1 $var]]
        set result [MakeUnaryMinus $du]
    } elseif {[llength $args] == 2} {
        foreach {u v} $args break
        set du [eval [linsert $u 1 $var]]
        set dv [eval [linsert $v 1 $var]]
        set result [MakeDifference $du $dv]
    } else {
        error "symdiff encounered a malformed parse, can't happen"
    }
    return $result
}

# math::calculus::symdiff::differentiate::operator * --
#
#       Forms the derivative of a product.
#
# Parameters:
#       var -- Variable to differentiate with respect to.
#       u, v -- Multiplicand and multiplier.
#
# Results:
#       Returns a parse tree representing the derivative.
#
# The familiar freshman calculus product rule.

proc {math::calculus::symdiff::differentiate::operator *} {var u v} {
    set du [eval [linsert $u 1 $var]]
    set dv [eval [linsert $v 1 $var]]
    set result [MakeSum [MakeProd $dv $u] [MakeProd $du $v]]
    return $result
}

# math::calculus::symdiff::differentiate::operator / --
#
#       Forms the derivative of a quotient.
#
# Parameters:
#       var -- Variable to differentiate with respect to.
#       u, v -- Dividend and divisor.
#
# Results:
#       Returns a parse tree representing the derivative.
#
# The familiar freshman calculus quotient rule.

proc {math::calculus::symdiff::differentiate::operator /} {var u v} {
    set du [eval [linsert $u 1 $var]]
    set dv [eval [linsert $v 1 $var]]
    set result [MakeQuotient \
                    [MakeDifference \
                         $du \
                         [MakeQuotient \
                              [MakeProd $dv $u] \
                              $v]] \
                    $v]
    return $result
}

# math::calculus::symdiff::differentiate::operator acos --
#
#       Differentiates the 'acos' function.
#
# Parameters:
#       var -- Variable to differentiate with respect to.
#       u -- Argument to the acos() function.
#
# Results:
#       Returns a parse tree of the derivative.
#
# Applies the Chain Rule: D(acos(u))=-D(u)/sqrt(1 - u*u)
# (Might it be better to factor 1-u*u into (1+u)(1-u)? Less likely to be
# catastrophic cancellation if u is near 1?)

proc {math::calculus::symdiff::differentiate::operator acos} {var u} {
    set du [eval [linsert $u 1 $var]]
    set result [MakeQuotient [MakeUnaryMinus $du] \
                    [MakeFunCall sqrt \
                         [MakeDifference [MakeConstant 1.0] \
                              [MakeProd $u $u]]]]
    return $result
}

# math::calculus::symdiff::differentiate::operator asin --
#
#       Differentiates the 'asin' function.
#
# Parameters:
#       var -- Variable to differentiate with respect to.
#       u -- Argument to the asin() function.
#
# Results:
#       Returns a parse tree of the derivative.
#
# Applies the Chain Rule: D(asin(u))=D(u)/sqrt(1 - u*u)
# (Might it be better to factor 1-u*u into (1+u)(1-u)? Less likely to be
# catastrophic cancellation if u is near 1?)

proc {math::calculus::symdiff::differentiate::operator asin} {var u} {
    set du [eval [linsert $u 1 $var]]
    set result [MakeQuotient $du \
                    [MakeFunCall sqrt \
                         [MakeDifference [MakeConstant 1.0] \
                              [MakeProd $u $u]]]]
    return $result
}

# math::calculus::symdiff::differentiate::operator atan --
#
#       Differentiates the 'atan' function.
#
# Parameters:
#       var -- Variable to differentiate with respect to.
#       u -- Argument to the atan() function.
#
# Results:
#       Returns a parse tree of the derivative.
#
# Applies the Chain Rule: D(atan(u))=D(u)/(1 + $u*$u)

proc {math::calculus::symdiff::differentiate::operator atan} {var u} {
    set du [eval [linsert $u 1 $var]]
    set result [MakeQuotient $du \
                    [MakeSum [MakeConstant 1.0] \
                         [MakeProd $u $u]]]
}

# math::calculus::symdiff::differentiate::operator atan2 --
#
#       Differentiates the 'atan2' function.
#
# Parameters:
#       var -- Variable to differentiate with respect to.
#       f, g -- Arguments to the atan() function.
#
# Results:
#       Returns a parse tree of the derivative.
#
# Applies the Chain and Quotient Rules:
#       D(atan2(f, g)) = (D(f)*g - D(g)*f)/(f*f + g*g)

proc {math::calculus::symdiff::differentiate::operator atan2} {var f g} {
    set df [eval [linsert $f 1 $var]]
    set dg [eval [linsert $g 1 $var]]
    return [MakeQuotient \
                [MakeDifference \
                     [MakeProd $df $g] \
                     [MakeProd $f $dg]] \
                [MakeSum \
                     [MakeProd $f $f] \
                     [MakeProd $g $g]]]
}

# math::calculus::symdiff::differentiate::operator cos --
#
#       Differentiates the 'cos' function.
#
# Parameters:
#       var -- Variable to differentiate with respect to.
#       u -- Argument to the cos() function.
#
# Results:
#       Returns a parse tree of the derivative.
#
# Applies the Chain Rule: D(cos(u))=-sin(u)*D(u)

proc {math::calculus::symdiff::differentiate::operator cos} {var u} {
    return [ChainRule $var MakeMinusSin $u]
}
proc math::calculus::symdiff::differentiate::MakeMinusSin {operand} {
    return [MakeUnaryMinus [MakeFunCall sin $operand]]
}

# math::calculus::symdiff::differentiate::operator cosh --
#
#       Differentiates the 'cosh' function.
#
# Parameters:
#       var -- Variable to differentiate with respect to.
#       u -- Argument to the cosh() function.
#
# Results:
#       Returns a parse tree of the derivative.
#
# Applies the Chain Rule: D(cosh(u))=sinh(u)*D(u)

proc {math::calculus::symdiff::differentiate::operator cosh} {var u} {
    set result [ChainRule $var [list MakeFunCall sinh] $u]
    return $result
}

# math::calculus::symdiff::differentiate::operator exp --
#
#       Differentiate the exponential function
#
# Parameters:
#       var -- Variable to differentiate with respect to.
#       u -- Argument of the exponential function.
#
# Results:
#       Returns a parse tree of the derivative.
#
# Uses the Chain Rule D(exp(u)) = exp(u)*D(u).

proc {math::calculus::symdiff::differentiate::operator exp} {var u} {
    set result [ChainRule $var [list MakeFunCall exp] $u]
    return $result
}

# math::calculus::symdiff::differentiate::operator hypot --
#
#       Differentiate the 'hypot' function
#
# Parameters:
#       var - Variable to differentiate with respect to.
#       f, g - Arguments to the 'hypot' function
#
# Results:
#       Returns a parse tree of the derivative
#
# Uses a number of algebraic simplifications to arrive at:
#       D(hypot(f,g)) = (f*D(f)+g*D(g))/hypot(f,g)

proc {math::calculus::symdiff::differentiate::operator hypot} {var f g} {
    set df [eval [linsert $f 1 $var]]
    set dg [eval [linsert $g 1 $var]]
    return [MakeQuotient \
                [MakeSum \
                     [MakeProd $df $f] \
                     [MakeProd $dg $g]] \
                [MakeFunCall hypot $f $g]]
}

# math::calculus::symdiff::differentiate::operator log --
#
#       Differentiates a logarithm.
#
# Parameters:
#       var -- Variable to differentiate with respect to.
#       u -- Argument to the log() function.
#
# Results:
#       Returns a parse tree of the derivative.
#
# D(log(u))==D(u)/u

proc {math::calculus::symdiff::differentiate::operator log} {var u} {
    set du [eval [linsert $u 1 $var]]
    set result [MakeQuotient $du $u]
    return $result
}

# math::calculus::symdiff::differentiate::operator log10 --
#
#       Differentiates a common logarithm.
#
# Parameters:
#       var -- Variable to differentiate with respect to.
#       u -- Argument to the log10() function.
#
# Results:
#       Returns a parse tree of the derivative.
#
# D(log(u))==D(u)/(u * log(10))

proc {math::calculus::symdiff::differentiate::operator log10} {var u} {
    set du [eval [linsert $u 1 $var]]
    set result [MakeQuotient $du \
                    [MakeProd [MakeConstant [expr log(10.)]] $u]]
    return $result
}

# math::calculus::symdiff::differentiate::operator ** --
#
#       Differentiate an exponential.
#
# Parameters:
#       var -- Variable to differentiate with respect to
#       f, g -- Base and exponent
#
# Results:
#       Returns the parse tree of the derivative.
#
# Handles the special case where g is constant as
#    D(f**g) == g*f**(g-1)*D(f)
# Otherwise, uses the general power formula
#    D(f**g) == (f**g) * (((D(f)*g)/f) + (D(g)*log(f)))

proc {math::calculus::symdiff::differentiate::operator **} {var f g} {
    set df [eval [linsert $f 1 $var]]
    if {[IsConstant $g]} {
        set gm1 [MakeConstant [expr {[ConstantValue $g] - 1}]]
        set result [MakeProd $df [MakeProd $g [MakePower $f $gm1]]]

    } else {
        set dg [eval [linsert $g 1 $var]]
        set result [MakeProd [MakePower $f $g] \
                        [MakeSum \
                             [MakeQuotient [MakeProd $df $g] $f] \
                             [MakeProd $dg [MakeFunCall log $f]]]]
    }
    return $result
}
interp alias {} {math::calculus::symdiff::differentiate::operator pow} \
    {} {math::calculus::symdiff::differentiate::operator **}

# math::calculus::symdiff::differentiate::operator sin --
#
#       Differentiates the 'sin' function.
#
# Parameters:
#       var -- Variable to differentiate with respect to.
#       u -- Argument to the sin() function.
#
# Results:
#       Returns a parse tree of the derivative.
#
# Applies the Chain Rule: D(sin(u))=cos(u)*D(u)

proc {math::calculus::symdiff::differentiate::operator sin} {var u} {
    set result [ChainRule $var [list MakeFunCall cos] $u]
    return $result
}

# math::calculus::symdiff::differentiate::operator sinh --
#
#       Differentiates the 'sinh' function.
#
# Parameters:
#       var -- Variable to differentiate with respect to.
#       u -- Argument to the sinh() function.
#
# Results:
#       Returns a parse tree of the derivative.
#
# Applies the Chain Rule: D(sin(u))=cosh(u)*D(u)

proc {math::calculus::symdiff::differentiate::operator sinh} {var u} {
    set result [ChainRule $var [list MakeFunCall cosh] $u]
    return $result
}

# math::calculus::symdiff::differentiate::operator sqrt --
#
#       Differentiate the 'sqrt' function.
#
# Parameters:
#       var -- Variable to differentiate with respect to
#       u -- Parameter of 'sqrt' as a parse tree.
#
# Results:
#       Returns a parse tree representing the derivative.
#
# D(sqrt(u))==D(u)/(2*sqrt(u))

proc {math::calculus::symdiff::differentiate::operator sqrt} {var u} {
    set du [eval [linsert $u 1 $var]]
    set result [MakeQuotient $du [MakeProd [MakeConstant 2.0] \
                                      [MakeFunCall sqrt $u]]]
    return $result
}

# math::calculus::symdiff::differentiate::operator tan --
#
#       Differentiates the 'tan' function.
#
# Parameters:
#       var -- Variable to differentiate with respect to.
#       u -- Argument to the tan() function.
#
# Results:
#       Returns a parse tree of the derivative.
#
# Applies the Chain Rule: D(tan(u))=D(u)/(cos(u)*cos(u))

proc {math::calculus::symdiff::differentiate::operator tan} {var u} {
    set du [eval [linsert $u 1 $var]]
    set cosu [MakeFunCall cos $u]
    return [MakeQuotient $du [MakeProd $cosu $cosu]]
}

# math::calculus::symdiff::differentiate::operator tanh --
#
#       Differentiates the 'tanh' function.
#
# Parameters:
#       var -- Variable to differentiate with respect to.
#       u -- Argument to the tanh() function.
#
# Results:
#       Returns a parse tree of the derivative.
#
# Applies the Chain Rule: D(tanh(u))=D(u)/(cosh(u)*cosh(u))

proc {math::calculus::symdiff::differentiate::operator tanh} {var u} {
    set du [eval [linsert $u 1 $var]]
    set coshu [MakeFunCall cosh $u]
    return [MakeQuotient $du [MakeProd $coshu $coshu]]
}

# math::calculus::symdiff::MakeFunCall --
#
#       Makes a parse tree for a function call
#
# Parameters:
#       fun -- Name of the function to call
#       args -- Arguments to the function, expressed as parse trees
#
# Results:
#       Returns a parse tree for the result of calling the function.
#
# Performs the peephole optimization of replacing a function with
# constant parameters with its value.

proc math::calculus::symdiff::MakeFunCall {fun args} {
    set constant 1
    set exp $fun
    append exp \(
    set sep ""
    foreach a $args {
        if {[IsConstant $a]} {
            append exp $sep [ConstantValue $a]
            set sep ","
        } else {
            set constant 0
            break
        }
    }
    if {$constant} {
        append exp \)
        return [MakeConstant [expr $exp]]
    }
    set result [MakeOperator $fun]
    foreach arg $args {
        lappend result $arg
    }
    return $result
}

# math::calculus::symdiff::MakeSum --
#
#       Makes the parse tree for a sum.
#
# Parameters:
#       left, right -- Parse trees for augend and addend
#
# Results:
#       Returns the parse tree for the sum.
#
# Performs the following peephole optimizations:
# (1) a + (-b) = a - b
# (2) (-a) + b = b - a
# (3) 0 + a = a
# (4) a + 0 = a
# (5) The sum of two constants may be reduced to a constant

proc math::calculus::symdiff::MakeSum {left right} {
    if {[IsUnaryMinus $right]} {
        return [MakeDifference $left [UnaryMinusArg $right]]
    }
    if {[IsUnaryMinus $left]} {
        return [MakeDifference $right [UnaryMinusArg $left]]
    }
    if {[IsConstant $left]} {
        set v [ConstantValue $left]
        if {$v == 0} {
            return $right
        } elseif {[IsConstant $right]} {
            return [MakeConstant [expr {[ConstantValue $left]
                                        + [ConstantValue $right]}]]
        }
    } elseif {[IsConstant $right]} {
        set v [ConstantValue $right]
        if {$v == 0} {
            return $left
        }
    }
    set result [MakeOperator +]
    lappend result $left $right
    return $result
}

# math::calculus::symdiff::MakeDifference --
#
#       Makes the parse tree for a difference
#
# Parameters:
#       left, right -- Minuend and subtrahend, expressed as parse trees
#
# Results:
#       Returns a parse tree expressing the difference
#
# Performs the following peephole optimizations:
# (1) a - (-b) = a + b
# (2) -a - b = -(a + b)
# (3) 0 - b = -b
# (4) a - 0 = a
# (5) The difference of any two constants can be reduced to a constant.

proc math::calculus::symdiff::MakeDifference {left right} {
    if {[IsUnaryMinus $right]} {
        return [MakeSum $left [UnaryMinusArg $right]]
    }
    if {[IsUnaryMinus $left]} {
        return [MakeUnaryMinus [MakeSum [UnaryMinusArg $left] $right]]
    }
    if {[IsConstant $left]} {
        set v [ConstantValue $left]
        if {$v == 0} {
            return [MakeUnaryMinus $right]
        } elseif {[IsConstant $right]} {
            return [MakeConstant [expr {[ConstantValue $left]
                                        - [ConstantValue $right]}]]
        }
    } elseif {[IsConstant $right]} {
        set v [ConstantValue $right]
        if {$v == 0} {
            return $left
        }
    }
    set result [MakeOperator -]
    lappend result $left $right
    return $result
}

# math::calculus::symdiff::MakeProd --
#
#       Constructs the parse tree for a product, left*right.
#
# Parameters:
#       left, right - Multiplicand and multiplier
#
# Results:
#       Returns the parse tree for the result.
#
# Performs the following peephole optimizations.
# (1) If either operand is a unary minus, it is hoisted out of the
#     expression.
# (2) If either operand is the constant 0, the result is the constant 0
# (3) If either operand is the constant 1, the result is the other operand.
# (4) If either operand is the constant -1, the result is unary minus
#     applied to the other operand
# (5) If both operands are constant, the result is a constant containing
#     their product.

proc math::calculus::symdiff::MakeProd {left right} {
    if {[IsUnaryMinus $left]} {
        return [MakeUnaryMinus [MakeProd [UnaryMinusArg $left] $right]]
    }
    if {[IsUnaryMinus $right]} {
        return [MakeUnaryMinus [MakeProd $left [UnaryMinusArg $right]]]
    }
    if {[IsConstant $left]} {
        set v [ConstantValue $left]
        if {$v == 0} {
            return [MakeConstant 0.0]
        } elseif {$v == 1} {
            return $right
        } elseif {$v == -1} {
            return [MakeUnaryMinus $right]
        } elseif {[IsConstant $right]} {
            return [MakeConstant [expr {[ConstantValue $left]
                                        * [ConstantValue $right]}]]
        }
    } elseif {[IsConstant $right]} {
        set v [ConstantValue $right]
        if {$v == 0} {
            return [MakeConstant 0.0]
        } elseif {$v == 1} {
            return $left
        } elseif {$v == -1} {
            return [MakeUnaryMinus $left]
        }
    }
    set result [MakeOperator *]
    lappend result $left $right
    return $result
}

# math::calculus::symdiff::MakeQuotient --
#
#       Makes a parse tree for a quotient, n/d
#
# Parameters:
#       n, d - Parse trees for numerator and denominator
#
# Results:
#       Returns the parse tree for the quotient.
#
# Performs peephole optimizations:
# (1) If either operand is a unary minus, it is hoisted out.
# (2) If the numerator is the constant 0, the result is the constant 0.
# (3) If the demominator is the constant 1, the result is the numerator
# (4) If the denominator is the constant -1, the result is the unary
#     negation of the numerator.
# (5) If both numerator and denominator are constant, the result is
#     a constant representing their quotient.

proc math::calculus::symdiff::MakeQuotient {n d} {
    if {[IsUnaryMinus $n]} {
        return [MakeUnaryMinus [MakeQuotient [UnaryMinusArg $n] $d]]
    }
    if {[IsUnaryMinus $d]} {
        return [MakeUnaryMinus [MakeQuotient $n [UnaryMinusArg $d]]]
    }
    if {[IsConstant $n]} {
        set v [ConstantValue $n]
        if {$v == 0} {
            return [MakeConstant 0.0]
        } elseif {[IsConstant $d]} {
            return [MakeConstant [expr {[ConstantValue $n]
                                        * [ConstantValue $d]}]]
        }
    } elseif {[IsConstant $d]} {
        set v [ConstantValue $d]
        if {$v == 0} {
            return -code error "requested expression will result in division by zero at run time"
        } elseif {$v == 1} {
            return $n
        } elseif {$v == -1} {
            return [MakeUnaryMinus $n]
        }
    }
    set result [MakeOperator /]
    lappend result $n $d
    return $result
}

# math::calculus::symdiff::MakePower --
#
#       Make a parse tree for an exponentiation operation
#
# Parameters:
#       a -- Base, expressed as a parse tree
#       b -- Exponent, expressed as a parse tree
#
# Results:
#       Returns a parse tree for the expression
#
# Performs peephole optimizations:
# (1) The constant zero raised to any non-zero power is 0
# (2) The constant 1 raised to any power is 1
# (3) Any non-zero quantity raised to the zero power is 1
# (4) Any non-zero quantity raised to the first power is the base itself.
# (5) MakeFunCall will optimize any other case of a constant raised
#     to a constant power.

proc math::calculus::symdiff::MakePower {a b} {
    if {[IsConstant $a]} {
        if {[ConstantValue $a] == 0} {
            if {[IsConstant $b] && [ConstantValue $b] == 0} {
                error "requested expression will result in zero to zero power at run time"
            }
            return [MakeConstant 0.0]
        } elseif {[ConstantValue $a] == 1} {
            return [MakeConstant 1.0]
        }
    }
    if {[IsConstant $b]} {
        if {[ConstantValue $b] == 0} {
            return [MakeConstant 1.0]
        } elseif {[ConstantValue $b] == 1} {
            return $a
        }
    }
    return [MakeFunCall pow $a $b]
}

# math::calculus::symdiff::MakeUnaryMinus --
#
#       Makes the parse tree for a unary negation.
#
# Parameters:
#       operand -- Parse tree for the operand
#
# Results:
#       Returns the parse tree for the expression
#
# Performs the following peephole optimizations:
# (1) -(-$a) = $a
# (2) The unary negation of a constant is another constant

proc math::calculus::symdiff::MakeUnaryMinus {operand} {
    if {[IsUnaryMinus $operand]} {
        return [UnaryMinusArg $operand]
    }
    if {[IsConstant $operand]} {
        return [MakeConstant [expr {-[ConstantValue $operand]}]]
    } else {
        return [list [list operator -] $operand]
    }
}

# math::calculus::symdiff::IsUnaryMinus --
#
#       Determines whether a parse tree represents a unary negation
#
# Parameters:
#       x - Parse tree to examine
#
# Results:
#       Returns 1 if the parse tree represents a unary minus, 0 otherwise

proc math::calculus::symdiff::IsUnaryMinus {x} {
    return [expr {[llength $x] == 2
                  && [lindex $x 0] eq [list operator -]}]
}

# math::calculus::symdiff::UnaryMinusArg --
#
#       Extracts the argument from a unary negation.
#
# Parameters:
#       x - Parse tree to examine, known to represent a unary negation
#
# Results:
#       Returns a parse tree representing the operand.

proc math::calculus::symdiff::UnaryMinusArg {x} {
    return [lindex $x 1]
}

# math::calculus::symdiff::MakeOperator --
#
#       Makes a partial parse tree for an operator
#
# Parameters:
#       op -- Name of the operator
#
# Results:
#       Returns the resulting parse tree.
#
# The caller may use [lappend] to place any needed operands

proc math::calculus::symdiff::MakeOperator {op} {
    if {$op eq {?}} {
        return -code error "symdiff can't differentiate the ternary ?: operator"
    } elseif {[namespace which [list differentiate::operator $op]] ne {}} {
        return [list [list operator $op]]
    } elseif {[string is alnum $op] && ($op ni {eq ne in ni})} {
        return -code error "symdiff can't differentiate the \"$op\" function"
    } else {
        return -code error "symdiff can't differentiate the \"$op\" operator"
    }
}

# math::calculus::symdiff::MakeVariable --
#
#       Makes a partial parse tree for a single variable
#
# Parameters:
#       name -- Name of the variable
#
# Results:
#       Returns a partial parse tree giving the variable

proc math::calculus::symdiff::MakeVariable {name} {
    return [list var $name]
}

# math::calculus::symdiff::MakeConstant --
#
#       Make the parse tree for a constant.
#
# Parameters:
#       value -- The constant's value
#
# Results:
#       Returns a parse tree.

proc math::calculus::symdiff::MakeConstant {value} {
    return [list constant $value]
}

# math::calculus::symdiff::IsConstant --
#
#       Test if an expression represented by a parse tree is a constant.
#
# Parameters:
#       Item - Parse tree to test
#
# Results:
#       Returns 1 for a constant, 0 for anything else

proc math::calculus::symdiff::IsConstant {item} {
    return [expr {[lindex $item 0] eq {constant}}]
}

# math::calculus::symdiff::ConstantValue --
#
#       Recovers a constant value from the parse tree representing a constant
#       expression.
#
# Parameters:
#       item -- Parse tree known to be a constant.
#
# Results:
#       Returns the constant value.

proc math::calculus::symdiff::ConstantValue {item} {
    return [lindex $item 1]
}

# Define the parse tree fabrication routines in the 'differentiate'
# namespace as well as the 'symdiff' namespace, without exporting them
# from the package.

interp alias {} math::calculus::symdiff::differentiate::IsConstant \
    {} math::calculus::symdiff::IsConstant
interp alias {} math::calculus::symdiff::differentiate::ConstantValue \
    {} math::calculus::symdiff::ConstantValue
interp alias {} math::calculus::symdiff::differentiate::MakeConstant \
    {} math::calculus::symdiff::MakeConstant
interp alias {} math::calculus::symdiff::differentiate::MakeDifference \
    {} math::calculus::symdiff::MakeDifference
interp alias {} math::calculus::symdiff::differentiate::MakeFunCall \
    {} math::calculus::symdiff::MakeFunCall
interp alias {} math::calculus::symdiff::differentiate::MakePower \
    {} math::calculus::symdiff::MakePower
interp alias {} math::calculus::symdiff::differentiate::MakeProd \
    {} math::calculus::symdiff::MakeProd
interp alias {} math::calculus::symdiff::differentiate::MakeQuotient \
    {} math::calculus::symdiff::MakeQuotient
interp alias {} math::calculus::symdiff::differentiate::MakeSum \
    {} math::calculus::symdiff::MakeSum
interp alias {} math::calculus::symdiff::differentiate::MakeUnaryMinus \
    {} math::calculus::symdiff::MakeUnaryMinus
interp alias {} math::calculus::symdiff::differentiate::MakeVariable \
    {} math::calculus::symdiff::MakeVariable
interp alias {} math::calculus::symdiff::differentiate::ExtractExpression \
    {} math::calculus::symdiff::ExtractExpression
