summaryrefslogtreecommitdiffstats
path: root/tcllib/modules/math/optimize.tcl
diff options
context:
space:
mode:
Diffstat (limited to 'tcllib/modules/math/optimize.tcl')
-rwxr-xr-xtcllib/modules/math/optimize.tcl1319
1 files changed, 1319 insertions, 0 deletions
diff --git a/tcllib/modules/math/optimize.tcl b/tcllib/modules/math/optimize.tcl
new file mode 100755
index 0000000..b5ddafe
--- /dev/null
+++ b/tcllib/modules/math/optimize.tcl
@@ -0,0 +1,1319 @@
+#----------------------------------------------------------------------
+#
+# math/optimize.tcl --
+#
+# This file contains functions for optimization of a function
+# or expression.
+#
+# Copyright (c) 2004, by Arjen Markus.
+# Copyright (c) 2004, 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: optimize.tcl,v 1.12 2011/01/18 07:49:53 arjenmarkus Exp $
+#
+#----------------------------------------------------------------------
+
+package require Tcl 8.4
+
+# math::optimize --
+# Namespace for the commands
+#
+namespace eval ::math::optimize {
+ namespace export minimum maximum solveLinearProgram linearProgramMaximum
+ namespace export min_bound_1d min_unbound_1d
+
+ # Possible extension: minimumExpr, maximumExpr
+}
+
+# minimum --
+# Minimize a given function over a given interval
+#
+# Arguments:
+# begin Start of the interval
+# end End of the interval
+# func Name of the function to be minimized (takes one
+# argument)
+# maxerr Maximum relative error (defaults to 1.0e-4)
+# Return value:
+# Computed value for which the function is minimal
+# Notes:
+# The function needs not to be differentiable, but it is supposed
+# to be continuous. There is no provision for sub-intervals where
+# the function is constant (this might happen when the maximum
+# error is very small, < 1.0e-15)
+#
+# Warning:
+# This procedure is deprecated - use min_bound_1d instead
+#
+proc ::math::optimize::minimum { begin end func {maxerr 1.0e-4} } {
+
+ set nosteps [expr {3+int(-log($maxerr)/log(2.0))}]
+ set delta [expr {0.5*($end-$begin)*$maxerr}]
+
+ for { set step 0 } { $step < $nosteps } { incr step } {
+ set x1 [expr {($end+$begin)/2.0}]
+ set x2 [expr {$x1+$delta}]
+
+ set fx1 [uplevel 1 $func $x1]
+ set fx2 [uplevel 1 $func $x2]
+
+ if {$fx1 < $fx2} {
+ set end $x1
+ } else {
+ set begin $x1
+ }
+ }
+ return $x1
+}
+
+# maximum --
+# Maximize a given function over a given interval
+#
+# Arguments:
+# begin Start of the interval
+# end End of the interval
+# func Name of the function to be maximized (takes one
+# argument)
+# maxerr Maximum relative error (defaults to 1.0e-4)
+# Return value:
+# Computed value for which the function is maximal
+# Notes:
+# The function needs not to be differentiable, but it is supposed
+# to be continuous. There is no provision for sub-intervals where
+# the function is constant (this might happen when the maximum
+# error is very small, < 1.0e-15)
+#
+# Warning:
+# This procedure is deprecated - use max_bound_1d instead
+#
+proc ::math::optimize::maximum { begin end func {maxerr 1.0e-4} } {
+
+ set nosteps [expr {3+int(-log($maxerr)/log(2.0))}]
+ set delta [expr {0.5*($end-$begin)*$maxerr}]
+
+ for { set step 0 } { $step < $nosteps } { incr step } {
+ set x1 [expr {($end+$begin)/2.0}]
+ set x2 [expr {$x1+$delta}]
+
+ set fx1 [uplevel 1 $func $x1]
+ set fx2 [uplevel 1 $func $x2]
+
+ if {$fx1 > $fx2} {
+ set end $x1
+ } else {
+ set begin $x1
+ }
+ }
+ return $x1
+}
+
+#----------------------------------------------------------------------
+#
+# min_bound_1d --
+#
+# Find a local minimum of a function between two given
+# abscissae. Derivative of f is not required.
+#
+# Usage:
+# min_bound_1d f x1 x2 ?-option value?,,,
+#
+# Parameters:
+# f - Function to minimize. Must be expressed as a Tcl
+# command, to which will be appended the value at which
+# to evaluate the function.
+# x1 - Lower bound of the interval in which to search for a
+# minimum
+# x2 - Upper bound of the interval in which to search for a minimum
+#
+# Options:
+# -relerror value
+# Gives the tolerance desired for the returned
+# abscissa. Default is 1.0e-7. Should never be less
+# than the square root of the machine precision.
+# -maxiter n
+# Constrains minimize_bound_1d to evaluate the function
+# no more than n times. Default is 100. If convergence
+# is not achieved after the specified number of iterations,
+# an error is thrown.
+# -guess value
+# Gives a point between x1 and x2 that is an initial guess
+# for the minimum. f(guess) must be at most f(x1) or
+# f(x2).
+# -fguess value
+# Gives the value of the ordinate at the value of '-guess'
+# if known. Default is to evaluate the function
+# -abserror value
+# Gives the desired absolute error for the returned
+# abscissa. Default is 1.0e-10.
+# -trace boolean
+# A true value causes a trace to the standard output
+# of the function evaluations. Default is 0.
+#
+# Results:
+# Returns a two-element list comprising the abscissa at which
+# the function reaches a local minimum within the interval,
+# and the value of the function at that point.
+#
+# Side effects:
+# Whatever side effects arise from evaluating the given function.
+#
+#----------------------------------------------------------------------
+
+proc ::math::optimize::min_bound_1d { f x1 x2 args } {
+
+ set f [lreplace $f 0 0 [uplevel 1 [list namespace which [lindex $f 0]]]]
+
+ set phim1 0.6180339887498949
+ set twomphi 0.3819660112501051
+
+ array set params {
+ -relerror 1.0e-7
+ -abserror 1.0e-10
+ -maxiter 100
+ -trace 0
+ -fguess {}
+ }
+ set params(-guess) [expr { $phim1 * $x1 + $twomphi * $x2 }]
+
+ if { ( [llength $args] % 2 ) != 0 } {
+ return -code error -errorcode [list min_bound_1d wrongNumArgs] \
+ "wrong \# args, should be\
+ \"[lreplace [info level 0] 1 end f x1 x2 ?-option value?...]\""
+ }
+ foreach { key value } $args {
+ if { ![info exists params($key)] } {
+ return -code error -errorcode [list min_bound_1d badoption $key] \
+ "unknown option \"$key\",\
+ should be -abserror,\
+ -fguess, -guess, -initial, -maxiter, -relerror,\
+ or -trace"
+ }
+ set params($key) $value
+ }
+
+ # a and b presumably bracket the minimum of the function. Make sure
+ # they're in ascending order.
+
+ if { $x1 < $x2 } {
+ set a $x1; set b $x2
+ } else {
+ set b $x1; set a $x2
+ }
+
+ set x $params(-guess); # Best abscissa found so far
+ set w $x; # Second best abscissa found so far
+ set v $x; # Most recent earlier value of w
+
+ set e 0.0; # Distance moved on the step before
+ # last.
+
+ # Evaluate the function at the initial guess
+
+ if { $params(-fguess) ne {} } {
+ set fx $params(-fguess)
+ } else {
+ set s $f; lappend s $x; set fx [eval $s]
+ if { $params(-trace) } {
+ puts stdout "f($x) = $fx (initialisation)"
+ }
+ }
+ set fw $fx
+ set fv $fx
+
+ for { set iter 0 } { $iter < $params(-maxiter) } { incr iter } {
+
+ # Find the midpoint of the current interval
+
+ set xm [expr { 0.5 * ( $a + $b ) }]
+
+ # Compute the current tolerance for x, and twice its value
+
+ set tol [expr { $params(-relerror) * abs($x) + $params(-abserror) }]
+ set tol2 [expr { $tol + $tol }]
+ if { abs( $x - $xm ) <= $tol2 - 0.5 * ($b - $a) } {
+ return [list $x $fx]
+ }
+ set golden 1
+ if { abs($e) > $tol } {
+
+ # Use parabolic interpolation to find a minimum determined
+ # by the evaluations at x, v, and w. The size of the step
+ # to take will be $p/$q.
+
+ set r [expr { ( $x - $w ) * ( $fx - $fv ) }]
+ set q [expr { ( $x - $v ) * ( $fx - $fw ) }]
+ set p [expr { ( $x - $v ) * $q - ( $x - $w ) * $r }]
+ set q [expr { 2. * ( $q - $r ) }]
+ if { $q > 0 } {
+ set p [expr { - $p }]
+ } else {
+ set q [expr { - $q }]
+ }
+ set olde $e
+ set e $d
+
+ # Test if parabolic interpolation results in less than half
+ # the movement of the step two steps ago.
+
+ if { abs($p) < abs( .5 * $q * $olde )
+ && $p > $q * ( $a - $x )
+ && $p < $q * ( $b - $x ) } {
+
+ set d [expr { $p / $q }]
+ set u [expr { $x + $d }]
+ if { ( $u - $a ) < $tol2 || ( $b - $u ) < $tol2 } {
+ if { $xm-$x < 0 } {
+ set d [expr { - $tol }]
+ } else {
+ set d $tol
+ }
+ }
+ set golden 0
+ }
+ }
+
+ # If parabolic interpolation didn't come up with an acceptable
+ # result, use Golden Section instead.
+
+ if { $golden } {
+ if { $x >= $xm } {
+ set e [expr { $a - $x }]
+ } else {
+ set e [expr { $b - $x }]
+ }
+ set d [expr { $twomphi * $e }]
+ }
+
+ # At this point, d is the size of the step to take. Make sure
+ # that it's at least $tol.
+
+ if { abs($d) >= $tol } {
+ set u [expr { $x + $d }]
+ } elseif { $d < 0 } {
+ set u [expr { $x - $tol }]
+ } else {
+ set u [expr { $x + $tol }]
+ }
+
+ # Evaluate the function
+
+ set s $f; lappend s $u; set fu [eval $s]
+ if { $params(-trace) } {
+ if { $golden } {
+ puts stdout "f($u)=$fu (golden section)"
+ } else {
+ puts stdout "f($u)=$fu (parabolic interpolation)"
+ }
+ }
+
+ if { $fu <= $fx } {
+ # We've the best abscissa so far.
+
+ if { $u >= $x } {
+ set a $x
+ } else {
+ set b $x
+ }
+ set v $w
+ set fv $fw
+ set w $x
+ set fw $fx
+ set x $u
+ set fx $fu
+ } else {
+
+ if { $u < $x } {
+ set a $u
+ } else {
+ set b $u
+ }
+ if { $fu <= $fw || $w == $x } {
+ # We've the second-best abscissa so far
+ set v $w
+ set fv $fw
+ set w $u
+ set fw $fu
+ } elseif { $fu <= $fv || $v == $x || $v == $w } {
+ # We've the third-best so far
+ set v $u
+ set fv $fu
+ }
+ }
+ }
+
+ return -code error -errorcode [list min_bound_1d noconverge $iter] \
+ "[lindex [info level 0] 0] failed to converge after $iter steps."
+
+}
+
+#----------------------------------------------------------------------
+#
+# brackmin --
+#
+# Find a place along the number line where a given function has
+# a local minimum.
+#
+# Usage:
+# brackmin f x1 x2 ?trace?
+#
+# Parameters:
+# f - Function to minimize
+# x1 - Abscissa thought to be near the minimum
+# x2 - Additional abscissa thought to be near the minimum
+# trace - Boolean variable that, if true,
+# causes 'brackmin' to print a trace of its function
+# evaluations to the standard output. Default is 0.
+#
+# Results:
+# Returns a three element list {x1 y1 x2 y2 x3 y3} where
+# y1=f(x1), y2=f(x2), y3=f(x3). x2 lies between x1 and x3, and
+# y1>y2, y3>y2, proving that there is a local minimum somewhere
+# in the interval (x1,x3).
+#
+# Side effects:
+# Whatever effects the evaluation of f has.
+#
+#----------------------------------------------------------------------
+
+proc ::math::optimize::brackmin { f x1 x2 {trace 0} } {
+
+ set f [lreplace $f 0 0 [uplevel 1 [list namespace which [lindex $f 0]]]]
+
+ set phi 1.6180339887498949
+ set epsilon 1.0e-20
+ set limit 50.
+
+ # Choose a and b so that f(a) < f(b)
+
+ set cmd $f; lappend cmd $x1; set fx1 [eval $cmd]
+ if { $trace } {
+ puts "f($x1) = $fx1 (initialisation)"
+ }
+ set cmd $f; lappend cmd $x2; set fx2 [eval $cmd]
+ if { $trace } {
+ puts "f($x2) = $fx2 (initialisation)"
+ }
+ if { $fx1 > $fx2 } {
+ set a $x1; set fa $fx1
+ set b $x2; set fb $fx2
+ } else {
+ set a $x2; set fa $fx2
+ set b $x1; set fb $fx1
+ }
+
+ # Choose a c in the downhill direction
+
+ set c [expr { $b + $phi * ($b - $a) }]
+ set cmd $f; lappend cmd $c; set fc [eval $cmd]
+ if { $trace } {
+ puts "f($c) = $fc (initial dilatation by phi)"
+ }
+
+ while { $fb >= $fc } {
+
+ # Try to do parabolic extrapolation to the minimum
+
+ set r [expr { ($b - $a) * ($fb - $fc) }]
+ set q [expr { ($b - $c) * ($fb - $fa) }]
+ if { abs( $q - $r ) > $epsilon } {
+ set denom [expr { $q - $r }]
+ } elseif { $q > $r } {
+ set denom $epsilon
+ } else {
+ set denom -$epsilon
+ }
+ set u [expr { $b - ( (($b - $c) * $q - ($b - $a) * $r)
+ / (2. * $denom) ) }]
+ set ulimit [expr { $b + $limit * ( $c - $b ) }]
+
+ # Test the extrapolated abscissa
+
+ if { ($b - $u) * ($u - $c) > 0 } {
+
+ # u lies between b and c. Try to interpolate
+
+ set cmd $f; lappend cmd $u; set fu [eval $cmd]
+ if { $trace } {
+ puts "f($u) = $fu (parabolic interpolation)"
+ }
+
+ if { $fu < $fc } {
+
+ # fb > fu and fc > fu, so there is a minimum between b and c
+ # with u as a starting guess.
+
+ return [list $b $fb $u $fu $c $fc]
+
+ }
+
+ if { $fu > $fb } {
+
+ # fb < fu, fb < fa, and u cannot lie between a and b
+ # (because it lies between a and c). There is a minimum
+ # somewhere between a and u, with b a starting guess.
+
+ return [list $a $fa $b $fb $u $fu]
+
+ }
+
+ # Parabolic interpolation was useless. Expand the
+ # distance by a factor of phi and try again.
+
+ set u [expr { $c + $phi * ($c - $b) }]
+ set cmd $f; lappend cmd $u; set fu [eval $cmd]
+ if { $trace } {
+ puts "f($u) = $fu (parabolic interpolation failed)"
+ }
+
+
+ } elseif { ( $c - $u ) * ( $u - $ulimit ) > 0 } {
+
+ # u lies between $c and $ulimit.
+
+ set cmd $f; lappend cmd $u; set fu [eval $cmd]
+ if { $trace } {
+ puts "f($u) = $fu (parabolic extrapolation)"
+ }
+
+ if { $fu > $fc } {
+
+ # minimum lies between b and u, with c an initial guess.
+
+ return [list $b $fb $c $fc $u $fu]
+
+ }
+
+ # function is still decreasing fa > fb > fc > fu. Take
+ # another factor-of-phi step.
+
+ set b $c; set fb $fc
+ set c $u; set fc $fu
+ set u [expr { $c + $phi * ( $c - $b ) }]
+ set cmd $f; lappend cmd $u; set fu [eval $cmd]
+ if { $trace } {
+ puts "f($u) = $fu (parabolic extrapolation ok)"
+ }
+
+ } elseif { ($u - $ulimit) * ( $ulimit - $c ) >= 0 } {
+
+ # u went past ulimit. Pull in to ulimit and evaluate there.
+
+ set u $ulimit
+ set cmd $f; lappend cmd $u; set fu [eval $cmd]
+ if { $trace } {
+ puts "f($u) = $fu (limited step)"
+ }
+
+ } else {
+
+ # parabolic extrapolation gave a useless value.
+
+ set u [expr { $c + $phi * ( $c - $b ) }]
+ set cmd $f; lappend cmd $u; set fu [eval $cmd]
+ if { $trace } {
+ puts "f($u) = $fu (parabolic extrapolation failed)"
+ }
+
+ }
+
+ set a $b; set fa $fb
+ set b $c; set fb $fc
+ set c $u; set fc $fu
+ }
+
+ return [list $a $fa $b $fb $c $fc]
+}
+
+#----------------------------------------------------------------------
+#
+# min_unbound_1d --
+#
+# Minimize a function of one variable, unconstrained, derivatives
+# not required.
+#
+# Usage:
+# min_bound_1d f x1 x2 ?-option value?,,,
+#
+# Parameters:
+# f - Function to minimize. Must be expressed as a Tcl
+# command, to which will be appended the value at which
+# to evaluate the function.
+# x1 - Initial guess at the minimum
+# x2 - Second initial guess at the minimum, used to set the
+# initial length scale for the search.
+#
+# Options:
+# -relerror value
+# Gives the tolerance desired for the returned
+# abscissa. Default is 1.0e-7. Should never be less
+# than the square root of the machine precision.
+# -maxiter n
+# Constrains min_bound_1d to evaluate the function
+# no more than n times. Default is 100. If convergence
+# is not achieved after the specified number of iterations,
+# an error is thrown.
+# -abserror value
+# Gives the desired absolute error for the returned
+# abscissa. Default is 1.0e-10.
+# -trace boolean
+# A true value causes a trace to the standard output
+# of the function evaluations. Default is 0.
+#
+#----------------------------------------------------------------------
+
+proc ::math::optimize::min_unbound_1d { f x1 x2 args } {
+
+ set f [lreplace $f 0 0 [uplevel 1 [list namespace which [lindex $f 0]]]]
+
+ array set params {
+ -relerror 1.0e-7
+ -abserror 1.0e-10
+ -maxiter 100
+ -trace 0
+ }
+ if { ( [llength $args] % 2 ) != 0 } {
+ return -code error -errorcode [list min_unbound_1d wrongNumArgs] \
+ "wrong \# args, should be\
+ \"[lreplace [info level 0] 1 end \
+ f x1 x2 ?-option value?...]\""
+ }
+ foreach { key value } $args {
+ if { ![info exists params($key)] } {
+ return -code error -errorcode [list min_unbound_1d badoption $key] \
+ "unknown option \"$key\",\
+ should be -trace"
+ }
+ set params($key) $value
+ }
+ foreach { a fa b fb c fc } [brackmin $f $x1 $x2 $params(-trace)] {
+ break
+ }
+ return [eval [linsert [array get params] 0 \
+ min_bound_1d $f $a $c -guess $b -fguess $fb]]
+}
+
+#----------------------------------------------------------------------
+#
+# nelderMead --
+#
+# Attempt to minimize/maximize a function using the downhill
+# simplex method of Nelder and Mead.
+#
+# Usage:
+# nelderMead f x ?-keyword value?
+#
+# Parameters:
+# f - The function to minimize. The function must be an incomplete
+# Tcl command, to which will be appended N parameters.
+# x - The starting guess for the minimum; a vector of N parameters
+# to be passed to the function f.
+#
+# Options:
+# -scale xscale
+# Initial guess as to the problem scale. If '-scale' is
+# supplied, then the parameters will be varied by the
+# specified amounts. The '-scale' parameter must of the
+# same dimension as the 'x' vector, and all elements must
+# be nonzero. Default is 0.0001 times the 'x' vector,
+# or 0.0001 for zero elements in the 'x' vector.
+#
+# -ftol epsilon
+# Requested tolerance in the function value; nelderMead
+# returns if N+1 consecutive iterates all differ by less
+# than the -ftol value. Default is 1.0e-7
+#
+# -maxiter N
+# Maximum number of iterations to attempt. Default is
+# 500.
+#
+# -trace flag
+# If '-trace 1' is supplied, nelderMead writes a record
+# of function evaluations to the standard output as it
+# goes. Default is 0.
+#
+#----------------------------------------------------------------------
+
+proc ::math::optimize::nelderMead { f startx args } {
+ array set params {
+ -ftol 1.e-7
+ -maxiter 500
+ -scale {}
+ -trace 0
+ }
+
+ # Check arguments
+
+ if { ( [llength $args] % 2 ) != 0 } {
+ return -code error -errorcode [list nelderMead wrongNumArgs] \
+ "wrong \# args, should be\
+ \"[lreplace [info level 0] 1 end \
+ f x1 x2 ?-option value?...]\""
+ }
+ foreach { key value } $args {
+ if { ![info exists params($key)] } {
+ return -code error -errorcode [list nelderMead badoption $key] \
+ "unknown option \"$key\",\
+ should be -ftol, -maxiter, -scale or -trace"
+ }
+ set params($key) $value
+ }
+
+ # Construct the initial simplex
+
+ set vertices [list $startx]
+ if { [llength $params(-scale)] == 0 } {
+ set i 0
+ foreach x0 $startx {
+ if { $x0 == 0 } {
+ set x1 0.0001
+ } else {
+ set x1 [expr {1.0001 * $x0}]
+ }
+ lappend vertices [lreplace $startx $i $i $x1]
+ incr i
+ }
+ } elseif { [llength $params(-scale)] != [llength $startx] } {
+ return -code error -errorcode [list nelderMead badOption -scale] \
+ "-scale vector must be of same size as starting x vector"
+ } else {
+ set i 0
+ foreach x0 $startx s $params(-scale) {
+ lappend vertices [lreplace $startx $i $i [expr { $x0 + $s }]]
+ incr i
+ }
+ }
+
+ # Evaluate at the initial points
+
+ set n [llength $startx]
+ foreach x $vertices {
+ set cmd $f
+ foreach xx $x {
+ lappend cmd $xx
+ }
+ set y [uplevel 1 $cmd]
+ if {$params(-trace)} {
+ puts "nelderMead: evaluating initial point: x=[list $x] y=$y"
+ }
+ lappend yvec $y
+ }
+
+
+ # Loop adjusting the simplex in the 'vertices' array.
+
+ set nIter 0
+ while { 1 } {
+
+ # Find the highest, next highest, and lowest value in y,
+ # and save the indices.
+
+ set iBot 0
+ set yBot [lindex $yvec 0]
+ set iTop -1
+ set yTop [lindex $yvec 0]
+ set iNext -1
+ set i 0
+ foreach y $yvec {
+ if { $y <= $yBot } {
+ set yBot $y
+ set iBot $i
+ }
+ if { $iTop < 0 || $y >= $yTop } {
+ set iNext $iTop
+ set yNext $yTop
+ set iTop $i
+ set yTop $y
+ } elseif { $iNext < 0 || $y >= $yNext } {
+ set iNext $i
+ set yNext $y
+ }
+ incr i
+ }
+
+ # Return if the relative error is within an acceptable range
+
+ set rerror [expr { 2. * abs( $yTop - $yBot )
+ / ( abs( $yTop ) + abs( $yBot ) + $params(-ftol) ) }]
+ if { $rerror < $params(-ftol) } {
+ set status ok
+ break
+ }
+
+ # Count iterations
+
+ if { [incr nIter] > $params(-maxiter) } {
+ set status too-many-iterations
+ break
+ }
+ incr nIter
+
+ # Find the centroid of the face opposite the vertex that
+ # maximizes the function value.
+
+ set centroid {}
+ for { set i 0 } { $i < $n } { incr i } {
+ lappend centroid 0.0
+ }
+ set i 0
+ foreach v $vertices {
+ if { $i != $iTop } {
+ set newCentroid {}
+ foreach x0 $centroid x1 $v {
+ lappend newCentroid [expr { $x0 + $x1 }]
+ }
+ set centroid $newCentroid
+ }
+ incr i
+ }
+ set newCentroid {}
+ foreach x $centroid {
+ lappend newCentroid [expr { $x / $n }]
+ }
+ set centroid $newCentroid
+
+ # The first trial point is a reflection of the high point
+ # around the centroid
+
+ set trial {}
+ foreach x0 [lindex $vertices $iTop] x1 $centroid {
+ lappend trial [expr {$x1 + ($x1 - $x0)}]
+ }
+ set cmd $f
+ foreach xx $trial {
+ lappend cmd $xx
+ }
+ set yTrial [uplevel 1 $cmd]
+ if { $params(-trace) } {
+ puts "nelderMead: trying reflection: x=[list $trial] y=$yTrial"
+ }
+
+ # If that reflection yields a new minimum, replace the high point,
+ # and additionally try dilating in the same direction.
+
+ if { $yTrial < $yBot } {
+ set trial2 {}
+ foreach x0 $centroid x1 $trial {
+ lappend trial2 [expr { $x1 + ($x1 - $x0) }]
+ }
+ set cmd $f
+ foreach xx $trial2 {
+ lappend cmd $xx
+ }
+ set yTrial2 [uplevel 1 $cmd]
+ if { $params(-trace) } {
+ puts "nelderMead: trying dilated reflection:\
+ x=[list $trial2] y=$y"
+ }
+ if { $yTrial2 < $yBot } {
+
+ # Additional dilation yields a new minimum
+
+ lset vertices $iTop $trial2
+ lset yvec $iTop $yTrial2
+ } else {
+
+ # Additional dilation failed, but we can still use
+ # the first trial point.
+
+ lset vertices $iTop $trial
+ lset yvec $iTop $yTrial
+
+ }
+ } elseif { $yTrial < $yNext } {
+
+ # The reflected point isn't a new minimum, but it's
+ # better than the second-highest. Replace the old high
+ # point and try again.
+
+ lset vertices $iTop $trial
+ lset yvec $iTop $yTrial
+
+ } else {
+
+ # The reflected point is worse than the second-highest point.
+ # If it's better than the highest, keep it... but in any case,
+ # we want to try contracting the simplex, because a further
+ # reflection will simply bring us back to the starting point.
+
+ if { $yTrial < $yTop } {
+ lset vertices $iTop $trial
+ lset yvec $iTop $yTrial
+ set yTop $yTrial
+ }
+ set trial {}
+ foreach x0 [lindex $vertices $iTop] x1 $centroid {
+ lappend trial [expr { ( $x0 + $x1 ) / 2. }]
+ }
+ set cmd $f
+ foreach xx $trial {
+ lappend cmd $xx
+ }
+ set yTrial [uplevel 1 $cmd]
+ if { $params(-trace) } {
+ puts "nelderMead: contracting from high point:\
+ x=[list $trial] y=$y"
+ }
+ if { $yTrial < $yTop } {
+
+ # Contraction gave an improvement, so continue with
+ # the smaller simplex
+
+ lset vertices $iTop $trial
+ lset yvec $iTop $yTrial
+
+ } else {
+
+ # Contraction gave no improvement either; we seem to
+ # be in a valley of peculiar topology. Contract the
+ # simplex about the low point and try again.
+
+ set newVertices {}
+ set newYvec {}
+ set i 0
+ foreach v $vertices y $yvec {
+ if { $i == $iBot } {
+ lappend newVertices $v
+ lappend newYvec $y
+ } else {
+ set newv {}
+ foreach x0 $v x1 [lindex $vertices $iBot] {
+ lappend newv [expr { ($x0 + $x1) / 2. }]
+ }
+ lappend newVertices $newv
+ set cmd $f
+ foreach xx $newv {
+ lappend cmd $xx
+ }
+ lappend newYvec [uplevel 1 $cmd]
+ if { $params(-trace) } {
+ puts "nelderMead: contracting about low point:\
+ x=[list $newv] y=$y"
+ }
+ }
+ incr i
+ }
+ set vertices $newVertices
+ set yvec $newYvec
+ }
+
+ }
+
+ }
+ return [list y $yBot x [lindex $vertices $iBot] vertices $vertices yvec $yvec nIter $nIter status $status]
+
+}
+
+# solveLinearProgram
+# Solve a linear program in standard form
+#
+# Arguments:
+# objective Vector defining the objective function
+# constraints Matrix of constraints (as a list of lists)
+#
+# Return value:
+# Computed values for the coordinates or "unbounded" or "infeasible"
+#
+proc ::math::optimize::solveLinearProgram { objective constraints } {
+ #
+ # Check the arguments first and then put them in a more convenient
+ # form
+ #
+
+ foreach {nconst nvars matrix} \
+ [SimplexPrepareMatrix $objective $constraints] {break}
+
+ set solution [SimplexSolve $nconst nvars $matrix]
+
+ if { [llength $solution] > 1 } {
+ return [lrange $solution 0 [expr {$nvars-1}]]
+ } else {
+ return $solution
+ }
+}
+
+# linearProgramMaximum --
+# Compute the value attained at the optimum
+#
+# Arguments:
+# objective The coefficients of the objective function
+# result The coordinate values as obtained by solving the program
+#
+# Return value:
+# Value at the maximum point
+#
+proc ::math::optimize::linearProgramMaximum {objective result} {
+
+ set value 0.0
+
+ foreach coeff $objective coord $result {
+ set value [expr {$value+$coeff*$coord}]
+ }
+
+ return $value
+}
+
+# SimplexPrintMatrix
+# Debugging routine: print the matrix in easy to read form
+#
+# Arguments:
+# matrix Matrix to be printed
+#
+# Return value:
+# None
+#
+# Note:
+# The tableau should be transposed ...
+#
+proc ::math::optimize::SimplexPrintMatrix {matrix} {
+ puts "\nBasis:\t[join [lindex $matrix 0] \t]"
+ foreach col [lrange $matrix 1 end] {
+ puts " \t[join $col \t]"
+ }
+}
+
+# SimplexPrepareMatrix
+# Prepare the standard tableau from all program data
+#
+# Arguments:
+# objective Vector defining the objective function
+# constraints Matrix of constraints (as a list of lists)
+#
+# Return value:
+# List of values as a standard tableau and two values
+# for the sizes
+#
+proc ::math::optimize::SimplexPrepareMatrix {objective constraints} {
+
+ #
+ # Check the arguments first
+ #
+ set nconst [llength $constraints]
+ set ncols {}
+ foreach row $constraints {
+ if { $ncols == {} } {
+ set ncols [llength $row]
+ } else {
+ if { $ncols != [llength $row] } {
+ return -code error -errorcode ARGS "Incorrectly formed constraints matrix"
+ }
+ }
+ }
+
+ set nvars [expr {$ncols-1}]
+
+ if { [llength $objective] != $nvars } {
+ return -code error -errorcode ARGS "Incorrect length for objective vector"
+ }
+
+ #
+ # Set up the tableau:
+ # Easiest manipulations if we store the columns first
+ # So:
+ # - First column is the list of variable indices in the basis
+ # - Second column is the list of maximum values
+ # - "nvars" columns that follow: the coefficients for the actual
+ # variables
+ # - last "nconst" columns: the slack variables
+ #
+ set matrix [list]
+ set lastrow [concat $objective [list 0.0]]
+
+ set newcol [list]
+ for {set idx 0} {$idx < $nconst} {incr idx} {
+ lappend newcol [expr {$nvars+$idx}]
+ }
+ lappend newcol "?"
+ lappend matrix $newcol
+
+ set zvector [list]
+ foreach row $constraints {
+ lappend zvector [lindex $row end]
+ }
+ lappend zvector 0.0
+ lappend matrix $zvector
+
+ for {set idx 0} {$idx < $nvars} {incr idx} {
+ set newcol [list]
+ foreach row $constraints {
+ lappend newcol [expr {double([lindex $row $idx])}]
+ }
+ lappend newcol [expr {-double([lindex $lastrow $idx])}]
+ lappend matrix $newcol
+ }
+
+ #
+ # Add the columns for the slack variables
+ #
+ set zeros {}
+ for {set idx 0} {$idx <= $nconst} {incr idx} {
+ lappend zeros 0.0
+ }
+ for {set idx 0} {$idx < $nconst} {incr idx} {
+ lappend matrix [lreplace $zeros $idx $idx 1.0]
+ }
+
+ return [list $nconst $nvars $matrix]
+}
+
+# SimplexSolve --
+# Solve the given linear program using the simplex method
+#
+# Arguments:
+# nconst Number of constraints
+# nvars Number of actual variables
+# tableau Standard tableau (as a list of columns)
+#
+# Return value:
+# List of values for the actual variables
+#
+proc ::math::optimize::SimplexSolve {nconst nvars tableau} {
+ set end 0
+ while { !$end } {
+
+ #
+ # Find the new variable to put in the basis
+ #
+ set nextcol [SimplexFindNextColumn $tableau]
+ if { $nextcol == -1 } {
+ set end 1
+ continue
+ }
+
+ #
+ # Now determine which one should leave
+ # TODO: is a lack of a proper row indeed an
+ # indication of the infeasibility?
+ #
+ set nextrow [SimplexFindNextRow $tableau $nextcol]
+ if { $nextrow == -1 } {
+ return "unbounded"
+ }
+
+ #
+ # Make the vector for sweeping through the tableau
+ #
+ set vector [SimplexMakeVector $tableau $nextcol $nextrow]
+
+ #
+ # Sweep through the tableau
+ #
+ set tableau [SimplexNewTableau $tableau $nextcol $nextrow $vector]
+ }
+
+ #
+ # Now we can return the result
+ #
+ SimplexResult $tableau
+}
+
+# SimplexResult --
+# Reconstruct the result vector
+#
+# Arguments:
+# tableau Standard tableau (as a list of columns)
+#
+# Return value:
+# Vector of values representing the maximum point
+#
+proc ::math::optimize::SimplexResult {tableau} {
+ set result {}
+
+ set firstcol [lindex $tableau 0]
+ set secondcol [lindex $tableau 1]
+ set result {}
+
+ set nvars [expr {[llength $tableau]-2}]
+ for {set i 0} {$i < $nvars } { incr i } {
+ lappend result 0.0
+ }
+
+ set idx 0
+ foreach col [lrange $firstcol 0 end-1] {
+ set value [lindex $secondcol $idx]
+ if { $value >= 0.0 } {
+ set result [lreplace $result $col $col [lindex $secondcol $idx]]
+ incr idx
+ } else {
+ # If a negative component, then the problem was not feasible
+ return "infeasible"
+ }
+ }
+
+ return $result
+}
+
+# SimplexFindNextColumn --
+# Find the next column - the one with the largest negative
+# coefficient
+#
+# Arguments:
+# tableau Standard tableau (as a list of columns)
+#
+# Return value:
+# Index of the column
+#
+proc ::math::optimize::SimplexFindNextColumn {tableau} {
+ set idx 0
+ set minidx -1
+ set mincoeff 0.0
+
+ foreach col [lrange $tableau 2 end] {
+ set coeff [lindex $col end]
+ if { $coeff < 0.0 } {
+ if { $coeff < $mincoeff } {
+ set minidx $idx
+ set mincoeff $coeff
+ }
+ }
+ incr idx
+ }
+
+ return $minidx
+}
+
+# SimplexFindNextRow --
+# Find the next row - the one with the largest negative
+# coefficient
+#
+# Arguments:
+# tableau Standard tableau (as a list of columns)
+# nextcol Index of the variable that will replace this one
+#
+# Return value:
+# Index of the row
+#
+proc ::math::optimize::SimplexFindNextRow {tableau nextcol} {
+ set idx 0
+ set minidx -1
+ set mincoeff {}
+
+ set bvalues [lrange [lindex $tableau 1] 0 end-1]
+ set yvalues [lrange [lindex $tableau [expr {2+$nextcol}]] 0 end-1]
+
+ foreach rowcoeff $bvalues divcoeff $yvalues {
+ if { $divcoeff > 0.0 } {
+ set coeff [expr {$rowcoeff/$divcoeff}]
+
+ if { $mincoeff == {} || $coeff < $mincoeff } {
+ set minidx $idx
+ set mincoeff $coeff
+ }
+ }
+ incr idx
+ }
+
+ return $minidx
+}
+
+# SimplexMakeVector --
+# Make the "sweep" vector
+#
+# Arguments:
+# tableau Standard tableau (as a list of columns)
+# nextcol Index of the variable that will replace this one
+# nextrow Index of the variable in the base that will be replaced
+#
+# Return value:
+# Vector to be used to update the coefficients of the tableau
+#
+proc ::math::optimize::SimplexMakeVector {tableau nextcol nextrow} {
+
+ set idx 0
+ set vector {}
+ set column [lindex $tableau [expr {2+$nextcol}]]
+ set divcoeff [lindex $column $nextrow]
+
+ foreach colcoeff $column {
+ if { $idx != $nextrow } {
+ set coeff [expr {-$colcoeff/$divcoeff}]
+ } else {
+ set coeff [expr {1.0/$divcoeff-1.0}]
+ }
+ lappend vector $coeff
+ incr idx
+ }
+
+ return $vector
+}
+
+# SimplexNewTableau --
+# Sweep through the tableau and create the new one
+#
+# Arguments:
+# tableau Standard tableau (as a list of columns)
+# nextcol Index of the variable that will replace this one
+# nextrow Index of the variable in the base that will be replaced
+# vector Vector to sweep with
+#
+# Return value:
+# New tableau
+#
+proc ::math::optimize::SimplexNewTableau {tableau nextcol nextrow vector} {
+
+ #
+ # The first column: replace the nextrow-th element
+ # The second column: replace the value at the nextrow-th element
+ # For all the others: the same receipe
+ #
+ set firstcol [lreplace [lindex $tableau 0] $nextrow $nextrow $nextcol]
+ set newtableau [list $firstcol]
+
+ #
+ # The rest of the matrix
+ #
+ foreach column [lrange $tableau 1 end] {
+ set yval [lindex $column $nextrow]
+ set newcol {}
+ foreach c $column vcoeff $vector {
+ set newval [expr {$c+$yval*$vcoeff}]
+ lappend newcol $newval
+ }
+ lappend newtableau $newcol
+ }
+
+ return $newtableau
+}
+
+# Now we can announce our presence
+package provide math::optimize 1.0.1
+
+if { ![info exists ::argv0] || [string compare $::argv0 [info script]] } {
+ return
+}
+
+namespace import math::optimize::min_bound_1d
+namespace import math::optimize::maximum
+namespace import math::optimize::nelderMead
+
+proc f {x y} {
+ set xx [expr { $x - 3.1415926535897932 / 2. }]
+ set v1 [expr { 0.3 * exp( -$xx*$xx / 2. ) }]
+ set d [expr { 10. * $y - sin(9. * $x) }]
+ set v2 [expr { exp(-10.*$d*$d)}]
+ set rv [expr { -$v1 - $v2 }]
+ return $rv
+}
+
+proc g {a b} {
+ set x1 [expr {0.1 - $a + $b}]
+ set x2 [expr {$a + $b - 1.}]
+ set x3 [expr {3.-8.*$a+8.*$a*$a-8.*$b+8.*$b*$b}]
+ set x4 [expr {$a/10. + $b/10. + $x1*$x1/3. + $x2*$x2 - $x2 * exp(1-$x3*$x3)}]
+ return $x4
+}
+
+set prec $::tcl_precision
+if {![package vsatisfies [package provide Tcl] 8.5]} {
+ set ::tcl_precision 17
+} else {
+ set ::tcl_precision 0
+}
+
+puts "f"
+puts [math::optimize::nelderMead f {1. 0.} -scale {0.1 0.01} -trace 1]
+puts "g"
+puts [math::optimize::nelderMead g {0. 0.} -scale {1. 1.} -trace 1]
+
+set ::tcl_precision $prec