202 lines
5.3 KiB
Tcl
202 lines
5.3 KiB
Tcl
# differentiate.tcl --
|
|
# Numerical differentiation
|
|
#
|
|
|
|
namespace eval ::math::calculus {
|
|
}
|
|
namespace eval ::math::optimize {
|
|
}
|
|
|
|
# deriv --
|
|
# Return the derivative of a function at a given point
|
|
# Arguments:
|
|
# func Name of a procedure implementing the function
|
|
# point Coordinates of the point
|
|
# scale (Optional) the scale of the coordinates
|
|
# Result:
|
|
# List representing the gradient vector at the given point
|
|
# Note:
|
|
# The scale is necessary to create a proper step in the
|
|
# coordinates. The derivative is estimated using central
|
|
# differences.
|
|
# The function may have an arbitrary number of arguments,
|
|
# for each the derivative is determined - this results
|
|
# in a list of derivatives rather than a single value.
|
|
# (No provision is made for the function to be a
|
|
# vector function! So, second derivatives are not
|
|
# possible)
|
|
#
|
|
proc ::math::calculus::deriv {func point {scale {}} } {
|
|
|
|
set epsilon 1.0e-12
|
|
set eps2 [expr {sqrt($epsilon)}]
|
|
|
|
#
|
|
# Determine a scale
|
|
#
|
|
foreach c $point {
|
|
if { $scale == {} } {
|
|
set scale [expr {abs($c)}]
|
|
} else {
|
|
if { $scale < abs($c) } {
|
|
set scale [expr {abs($c)}]
|
|
}
|
|
}
|
|
}
|
|
if { $scale == 0.0 } {
|
|
set scale 1.0
|
|
}
|
|
|
|
#
|
|
# Check the number of coordinates
|
|
#
|
|
if { [llength $point] == 1 } {
|
|
set v1 [$func [expr {$point+$eps2*$scale}]]
|
|
set v2 [$func [expr {$point-$eps2*$scale}]]
|
|
return [expr {($v1-$v2)/(2.0*$eps2*$scale)}]
|
|
} else {
|
|
set result {}
|
|
set idx 0
|
|
foreach c $point {
|
|
set c1 [expr {$c+$eps2*$scale}]
|
|
set c2 [expr {$c-$eps2*$scale}]
|
|
|
|
set v1 [eval $func [lreplace $point $idx $idx $c1]]
|
|
set v2 [eval $func [lreplace $point $idx $idx $c2]]
|
|
|
|
lappend result [expr {($v1-$v2)/(2.0*$eps2*$scale)}]
|
|
incr idx
|
|
}
|
|
return $result
|
|
}
|
|
}
|
|
|
|
# auxiliary functions --
|
|
#
|
|
proc ::math::optimize::unitVector {vector} {
|
|
set length 0.0
|
|
foreach c $vector {
|
|
set length [expr {$length+$c*$c}]
|
|
}
|
|
scaleVector $vector [expr {1.0/sqrt($length)}]
|
|
}
|
|
proc ::math::optimize::scaleVector {vector scale} {
|
|
set result {}
|
|
foreach c $vector {
|
|
lappend result [expr {$c*$scale}]
|
|
}
|
|
return $result
|
|
}
|
|
proc ::math::optimize::addVector {vector1 vector2} {
|
|
set result {}
|
|
foreach c1 $vector1 c2 $vector2 {
|
|
lappend result [expr {$c1+$c2}]
|
|
}
|
|
return $result
|
|
}
|
|
|
|
# minimumSteepestDescent --
|
|
# Find the minimum of a function via steepest descent
|
|
# (unconstrained!)
|
|
# Arguments:
|
|
# func Name of a procedure implementing the function
|
|
# point Coordinates of the starting point
|
|
# eps (Optional) measure for the accuracy
|
|
# maxsteps (Optional) maximum number of steps
|
|
# Result:
|
|
# Coordinates of a point near the minimum
|
|
#
|
|
proc ::math::optimize::minimumSteepestDescent {func point {eps 1.0e-5} {maxsteps 100} } {
|
|
|
|
set factor 100
|
|
set nosteps 0
|
|
if { [llength $point] == 1 } {
|
|
while { $nosteps < $maxsteps } {
|
|
set fvalue [$func $point]
|
|
set gradient [::math::calculus::deriv $func $point]
|
|
if { $gradient < 0.0 } {
|
|
set gradient -1.0
|
|
} else {
|
|
set gradient 1.0
|
|
}
|
|
set found 0
|
|
set substeps 0
|
|
while { $found == 0 && $substeps < 3 } {
|
|
set newpoint [expr {$point-$factor*$gradient}]
|
|
set newfval [$func $newpoint]
|
|
|
|
#puts "factor: $factor - point: $point"
|
|
#
|
|
# Check that the new point has a lower value for the
|
|
# function. Can we increase the factor?
|
|
#
|
|
#
|
|
if { $newfval < $fvalue } {
|
|
set point $newpoint
|
|
|
|
#
|
|
# This failed with sin(x), x0 = 1.0
|
|
# set newpoint2 [expr {$newpoint-$factor*$gradient}]
|
|
# set newfval2 [$func $newpoint2]
|
|
# if { $newfval2 < $newfval } {
|
|
# set factor [expr {2.0*$factor}]
|
|
# set point $newpoint2
|
|
# }
|
|
set found 1
|
|
} else {
|
|
set factor [expr {$factor/2.0}]
|
|
}
|
|
|
|
incr substeps
|
|
}
|
|
|
|
#
|
|
# Have we reached convergence?
|
|
#
|
|
if { abs($factor*$gradient) < $eps } {
|
|
break
|
|
}
|
|
incr nosteps
|
|
}
|
|
} else {
|
|
while { $nosteps < $maxsteps } {
|
|
set fvalue [eval $func $point]
|
|
set gradient [::math::calculus::deriv $func $point]
|
|
set gradient [unitVector $gradient]
|
|
|
|
set found 0
|
|
set substeps 0
|
|
while { $found == 0 && $nosteps < $maxsteps } {
|
|
set newpoint [addVector $point [scaleVector $gradient -$factor]]
|
|
set newfval [eval $func $newpoint]
|
|
|
|
#puts "factor: $factor - point: $point"
|
|
#
|
|
# Check that the new point has a lower value for the
|
|
# function. Can we increase the factor?
|
|
#
|
|
#
|
|
if { $newfval < $fvalue } {
|
|
set point $newpoint
|
|
set found 1
|
|
} else {
|
|
set factor [expr {$factor/2.0}]
|
|
}
|
|
|
|
incr nosteps
|
|
}
|
|
|
|
#
|
|
# Have we reached convergence?
|
|
#
|
|
if { abs($factor) < $eps } {
|
|
break
|
|
}
|
|
incr nosteps
|
|
}
|
|
}
|
|
|
|
return $point
|
|
}
|
|
|