# symdiff.test --
#
#    Test cases for the 'symdiff' package
#
# This file contains a collection of tests for one or more of the Tcllib
# procedures.  Sourcing this file into Tcl runs the tests and
# generates output for errors.  No output means no errors were found.
#
# Copyright (c) 2005 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.test,v 1.2 2011/01/13 02:49:53 andreas_kupries Exp $

# -------------------------------------------------------------------------

source [file join \
	[file dirname [file dirname [file join [pwd] [info script]]]] \
	devtools testutilities.tcl]

testsNeedTcl     8.5
testsNeedTcltest 2.1

support {
    use     grammar_aycock/aycock-runtime.tcl grammar::aycock::runtime grammar::aycock
    useKeep grammar_aycock/aycock-debug.tcl   grammar::aycock::debug   grammar::aycock
    useKeep grammar_aycock/aycock-build.tcl   grammar::aycock          grammar::aycock
}
testing {
    useLocal symdiff.tcl math::calculus::symdiff
}

# -------------------------------------------------------------------------

namespace eval ::math::calculus::symdiff::test {

namespace import ::tcltest::test
namespace import ::tcltest::cleanupTests
namespace import ::math::calculus::symdiff::*

if {![package vsatisfies [package provide Tcl] 9]} {
    set prec $::tcl_precision
}
set ::tcl_precision 0

test symdiff-1.1 {derivative of a constant} {
    symdiff {1.0} a
} 0.0

test symdiff-2.1 {derivative of a variable} {
    symdiff {$a} a
} 1.0

test symdiff-2.2 {derivative of a variable} {
    symdiff {$b} a
} 0.0

test symdiff-3.1 {derivative of a sum, easy cases} {
    symdiff {1.0 + 1.0} a
} 0.0

test symdiff-3.2 {derivative of a sum, easy cases} {
    symdiff {1.0 + $a} a
} 1.0

test symdiff-3.3 {derivative of a sum, easy cases} {
    symdiff {$a + 1.0} a
} 1.0

test symdiff-3.4 {derivative of a sum, easy cases} {
    symdiff {$a + $a} a
} 2.0

test symdiff-3.5 {derivative of a sum, easy cases} {
    symdiff {$a + $b} a
} 1.0

test symdiff-3.6 {derivative of a sum, easy cases} {
    symdiff {$a + $a + $a} a
} 3.0

test symdiff-4.1 {derivative of a difference, easy cases} {
    -body {
	symdiff {1.0 - 1.0} a
    }
    -match regexp
    -result {[-+]?0.0}
}

test symdiff-4.2 {derivative of a difference, easy cases} {
    symdiff {1.0 - $a} a
} -1.0

test symdiff-4.3 {derivative of a difference, easy cases} {
    symdiff {$a - 1.0} a
} 1.0

test symdiff-4.4 {derivative of a difference, easy cases} {
    symdiff {$a - $a} a
} 0.0

test symdiff-4.5 {derivative of a difference, easy cases} {
    symdiff {$a + $b} a
} 1.0

test symdiff-4.6 {derivative of a difference, easy cases} {
    symdiff {$a + $a - $a} a
} 1.0

test symdiff-5.1 {derivative of a product, easy cases} {
    symdiff {1.0 * 1.0} a
} 0.0

test symdiff-5.2 {derivative of a product, easy cases} {
    symdiff {3.0 * $a} a
} 3.0

test symdiff-5.3 {derivative of a product, easy cases} {
    symdiff {$a * 3.0} a
} 3.0

test symdiff-5.4 {derivative of a product, easy cases} {
    symdiff {$a * $a} a
} {($a + $a)}

test symdiff-5.5 {derivative of a product, easy cases} {
    symdiff {$a * $b} a
} {$b}

test symdiff-5.6 {derivative of a product, easy cases} {
    symdiff {($a + $b) * ($a + $b)} a
} {(($a + $b) + ($a + $b))}

test symdiff-5.7 {derivative of a linear function} {
    symdiff {$a*$x + $b} x
} {$a}

test symdiff-6.1 {derivative of a sum} {
    symdiff {($a*$x+$b)+($c*$x+$d)} x
} {($a + $c)}

test symdiff-7.1 {derivative of a difference} {
    symdiff {($a*$x+$b)-($c*$x+$d)} x
} {($a - $c)}

test symdiff-8.1 {derivative of a product} {
    symdiff {($a*$x+$b)*($c*$x+$d)} x
} {(($c * (($a * $x) + $b)) + ($a * (($c * $x) + $d)))}

test symdiff-9.1 {derivative of a quotient} {
    symdiff {$x/1.0} x
} 1.0

test symdiff-9.2 {derivative of a quotient} {
    symdiff {$x/-1.0} x
} -1.0

test symdiff-9.3 {derivative of a quotient} {
    symdiff {1.0/$x} x
} {-(((1.0 / $x) / $x))}

test symdiff-9.4 {derivative of a quotient} {
    symdiff {($a*$x+$b)/($c*$x+$d)} x
} {(($a - (($c * (($a * $x) + $b)) / (($c * $x) + $d))) / (($c * $x) + $d))}

test symdiff-10.1 {derivative of an exponent} {
    symdiff {pow($a*$x+$b,3.5)} x
} {($a * (3.5 * pow((($a * $x) + $b), 2.5)))}

test symdiff-10.2 {derivative of an exponent, slightly harder case} {
    -body {
	symdiff {pow(10.0,$x)} x
    }
    -match regexp
    -result {\(pow\(10.0, \$x\) \* 2.30258509299404(?:59|6)\)}
}

test symdiff-10.3 {derivative of an exponent, awkward case} {
    symdiff {pow($a*$x+$b,$c*$x+$d)} x
} {(pow((($a * $x) + $b), (($c * $x) + $d)) * ((($a * (($c * $x) + $d)) / (($a * $x) + $b)) + ($c * log((($a * $x) + $b)))))}

test symdiff-11.1 {derivative of a unary negation} {
    symdiff {-($a*$x + $b)} x
} {-($a)}

test symdiff-11.2 {derivative of a unary plus} {
    symdiff {+($a*$x + $b)} x
} {$a}

test symdiff-12.1 {derivative of acos} {
    symdiff {acos($x)} x
} {(-1.0 / sqrt((1.0 - ($x * $x))))}

test symdiff-12.2 {derivative of acos} {
    symdiff {acos($a*$x+$b)} x
} {-(($a / sqrt((1.0 - ((($a * $x) + $b) * (($a * $x) + $b))))))}

test symdiff-13.1 {derivative of acos} {
    symdiff {asin($x)} x
} {(1.0 / sqrt((1.0 - ($x * $x))))}

test symdiff-13.2 {derivative of asin} {
    symdiff {asin($a*$x+$b)} x
} {($a / sqrt((1.0 - ((($a * $x) + $b) * (($a * $x) + $b)))))}

test symdiff-14.1 {derivative of atan} {
    symdiff {atan($x)} x
} {(1.0 / (1.0 + ($x * $x)))}

test symdiff-14.2 {derivative of atan} {
    symdiff {atan($a*$x+$b)} x
} {($a / (1.0 + ((($a * $x) + $b) * (($a * $x) + $b))))}

test symdiff-15.1 {derivative of atan2} {
    symdiff {atan2($x,1.0)} x
} {(1.0 / (($x * $x) + 1.0))}

test symdiff-15.2 {derivative of atan2} {
    symdiff {atan2(1.0,$x)} x
} {(-1.0 / (1.0 + ($x * $x)))}

test symdiff-15.3 {derivative of atan2} {
    symdiff {atan2($x,$y)} x
} {($y / (($x * $x) + ($y * $y)))}

test symdiff-15.4 {derivative of atan2} {
    symdiff {atan2($y,$x)} x
} {-(($y / (($y * $y) + ($x * $x))))}

test symdiff-15.5 {derivative of atan2} {
    symdiff {atan2($a*$x+$b,$c*$x+$d)} x
} {((($a * (($c * $x) + $d)) - ((($a * $x) + $b) * $c)) / (((($a * $x) + $b) * (($a * $x) + $b)) + ((($c * $x) + $d) * (($c * $x) + $d))))}

test symdiff-16.1 {derivative of cos} {
    symdiff {cos($x)} x
} {-(sin($x))}

test symdiff-16.2 {derivative of cos} {
    symdiff {cos($a*$x + $b)} x
} {-(($a * sin((($a * $x) + $b))))}

test symdiff-17.1 {derivative of cosh} {
    symdiff {cosh($x)} x
} {sinh($x)}

test symdiff-17.2 {derivative of cosh} {
    symdiff {cosh($a*$x + $b)} x
} {($a * sinh((($a * $x) + $b)))}

test symdiff-18.1 {derivative of exp} {
    symdiff {exp($x)} x
} {exp($x)}

test symdiff-18.2 {derivative of exp} {
    symdiff {exp($a*$x+$b)} x
} {($a * exp((($a * $x) + $b)))}

test symdiff-19.1 {derivative of hypot} {
    symdiff {hypot(0.0,$a)} a
} {($a / hypot(0.0, $a))}

test symdiff-19.2 {derivative of hypot} {
    symdiff {hypot($b,$a)} a
} {($a / hypot($b, $a))}

test symdiff-19.3 {derivative of hypot} {
    symdiff {hypot($a*$x+$b,$c*$x+$d)} x
} {((($a * (($a * $x) + $b)) + ($c * (($c * $x) + $d))) / hypot((($a * $x) + $b), (($c * $x) + $d)))}

test symdiff-20.1 {derivative of log} {
    symdiff {log($x)} x
} {(1.0 / $x)}

test symdiff-20.2 {derivative of log} {
    symdiff {log($a*$x+$b)} x
} {($a / (($a * $x) + $b))}

test symdiff-21.1 {derivative of log10} {
    -body {
	symdiff {log10($x)} x
    }
    -match regexp
    -result {\(1.0 / \(2.30258509299404(?:59|6) \* \$x\)\)}
}

test symdiff-21.2 {derivative of log10} {
    -body {
	symdiff {log10($a * $x + $b)} x
    }
    -match regexp
    -result {\(\$a / \(2.30258509299404(?:59|6) \* \(\(\$a \* \$x\) \+ \$b\)\)\)}
}

test symdiff-22.1 {derivative of sin} {
    symdiff {sin($x)} x
} {cos($x)}

test symdiff-22.2 {derivative of sin} {
    symdiff {sin($a*$x+$b)} x
} {($a * cos((($a * $x) + $b)))}

test symdiff-22.1 {derivative of sinh} {
    symdiff {sinh($x)} x
} {cosh($x)}

test symdiff-22.2 {derivative of sinh} {
    symdiff {sinh($a*$x+$b)} x
} {($a * cosh((($a * $x) + $b)))}

test symdiff-23.1 {derivative of sqrt} {
    symdiff {sqrt($x)} x
} {(1.0 / (2.0 * sqrt($x)))}

test symdiff-23.2 {derivative of sqrt} {
    symdiff {sqrt($a*$x+$b)} x
} {($a / (2.0 * sqrt((($a * $x) + $b))))}

test symdiff-24.1 {derivative of tan} {
    symdiff {tan($x)} x
} {(1.0 / (cos($x) * cos($x)))}

test symdiff-24.2 {derivative of tan} {
    symdiff {tan($a*$x+$b)} x
} {($a / (cos((($a * $x) + $b)) * cos((($a * $x) + $b))))}

test symdiff-24.1 {derivative of tanh} {
    symdiff {tanh($x)} x
} {(1.0 / (cosh($x) * cosh($x)))}

test symdiff-24.2 {derivative of tanh} {
    symdiff {tanh($a*$x+$b)} x
} {($a / (cosh((($a * $x) + $b)) * cosh((($a * $x) + $b))))}

test symdiff-25.1 {error handling} {
    -body {
	symdiff {[foo $x]} x
    }
    -match glob
    -returnCodes error
    -result {invalid character*}
}

test symdiff-25.2 {error handling} {
    -body {
	symdiff {$x(1)} x
    }
    -match glob
    -returnCodes error
    -result {syntax error*}
}

test symdiff-25.3 {error handling} {
    -body {
	symdiff {$a & $b} a
    }
    -match glob
    -returnCodes error
    -result {invalid character*}
}

test symdiff-25.4 {error handling} {
    list [catch {symdiff {int($a)} a} result] $result
} {1 {symdiff can't differentiate the "int" function}}

test symdiff-25.5 {error handling} {
    -body {
	symdiff {$a ? $b : $c} a
    }
    -returnCodes error
    -match glob
    -result {invalid character*}
}

test symdiff-26.1 {unary minus optimization} {
    symdiff {$a * $x + -$b * $x} x
} {($a - $b)}

test symdiff-26.2 {unary minus optimization} {
    symdiff {-$a * $x - $b * $x} x
} {-(($a + $b))}

test symdiff-26.3 {unary minus optimization} {
    symdiff {$a * $x - -$b * $x} x
} {($a + $b)}

test symdiff-26.4 {unary minus optimization} {
    symdiff {-$a * $x * $b} x
} {-(($a * $b))}

test symdiff-26.5 {unary minus optimization} {
    symdiff {$a * $x * -$b} x
} {-(($a * $b))}

test symdiff-26.6 {unary minus optimization} {
    symdiff {---($a*$x+$b)} x
} {-($a)}

test symdiff-26.7 {unary minus optimization} {
    symdiff {-$x * $x} x
} {-(($x + $x))}

test symdiff-27.1 {power optimizations} {
    symdiff {pow($x,1)} x
} 1.0

test symdiff-27.2 {power optimizations} {
    symdiff {pow($x,2.0)} x
} {(2.0 * $x)}

test symdiff-28.1 {quotient optimization} {
    symdiff {($x * $x) / 1.0} x
} {($x + $x)}

test symdiff-28.2 {quotient optimization} {
    symdiff {($x * $x) / -1.0} x
} {-(($x + $x))}

test symdiff-28.3 {quotient optimization - error case} {
    list [catch {symdiff {($x * $x) / 0.0} x} result] $result
} {1 {requested expression will result in division by zero at run time}}

test symdiff-29.1 {product optimization} {
    symdiff {(2. * $x) * 3.0} x
} 6.0

test symdiff-29.2 {product optimization} {
    symdiff {($a * $x) * -1.0} x
} {-($a)}

test symdiff-30.0 {illustration of Newton's method - find a root of sin(x)-0.5 near 0.5} {
    proc root {expr var guess} {
	upvar 1 $var v
	set deriv [symdiff $expr $var]
	set v $guess
	set updateExpr [list expr "\$$var - ($expr) / ($deriv)"]
	for { set i 0 } { $i < 4 } { incr i } {
	    set v [uplevel 1 $updateExpr]
	}
	return $v
    }
    set r [root {sin($x)-0.5} x 0.5]
    expr {sin($r)}
} 0.5

# End of test cases
if {![package vsatisfies [package provide Tcl] 9]} {
    set ::tcl_precision $prec
}
cleanupTests
}

namespace delete ::math::calculus::symdiff::test

# Local Variables:
# mode: tcl
# End:
