ngspice/examples/tclspice/tcl-testbench3/differentiate.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
}