1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427
|
# probopt_pso.tcl --
# Straightforward implementation of particle swarm optimisation
#
# Options:
# - The "best" is taken to be the globally best particle (global)
# - The "best" is taken to be the best of the neighbourhood of
# each particle
#
# For more information see: https://en.wikipedia.org/wiki/Particle_swarm_optimization
#
# namespace --
#
namespace eval ::math::probopt {}
# pso --
# Public interface to the PSO implementations
#
# Arguments:
# func Function to be minimized
# bounds List of pairs of minimum and maximum per dimension
# args Key-value pairs:
# -swarmsize number Number of particles to consider (default: 50)
# -vweight value Weight for the current "velocity" (0-1, default: 0.5)
# -pweight value Weight for the individual particle's best position (0-1, default: 0.3)
# -gweight value Weight for the "best" overall position as per particle (0-1, default: 0.3)
# -type local/global Type of optimisation
# -neighbours number Size of the neighbourhood (default: 5, used if "local")
# -iterations number Maximum number of iterations
# -tolerance value Absolute minimal improvement for minimum value
#
# Result:
# Dictionary with the coordinates of the "best" position and the value as well as
# list of best values per iteration and the number of function evaluations.
#
proc ::math::probopt::pso {func bounds args} {
set func [uplevel 1 [list namespace which -command $func]]
set options [dict create -swarmsize 50 -vweight 0.5 -pweight 0.3 -gweight 0.3 \
-type global -neighbours 5 -iterations 50 -tolerance 0.0]
foreach {key value} $args {
if { [dict key $options $key] != "" } {
dict set options $key $value
} else {
return -code error "Unknown option: $key"
}
}
set xmin {}
set xmax {}
foreach bound $bounds {
lappend xmin [lindex $bound 0]
lappend xmax [lindex $bound 1]
}
set type [dict get $options -type]
if { $type == "global" } {
return [Pso_global $func $xmin $xmax $options]
}
if { $type == "local" } {
return [Pso_local $func $xmin $xmax $options]
}
return -code error "Unknown type: $type"
}
# Pso_global --
# Use the "global" PSO algorithm
#
# Arguments:
# func Function to be minimized
# xmin Minimum values for the coordinates
# xmax Maximum values for the coordinates
# options Dictionary of options
#
proc ::math::probopt::Pso_global {func xmin xmax options} {
#
# Set up the initial positions
#
set swarmsize [dict get $options -swarmsize]
set maxiters [dict get $options -iterations]
set particle_bests {}
set positions {}
set velocities {}
set global_best -1
set global_best_value {}
set evaluations 0
set best_values {}
for {set i 0} {$i < $swarmsize} {incr i} {
set coords [Pso_position $xmin $xmax]
set fvalue [$func $coords]
incr evaluations
lappend positions $coords
lappend velocities [lrepeat [llength $coords] 0.0]
lappend particle_bests [list $fvalue $coords]
if { $global_best == -1 || $global_best_value > $fvalue } {
set global_best $i
set global_best_value $fvalue
}
}
set vweight [dict get $options -vweight]
set pweight [dict get $options -pweight]
set gweight [dict get $options -gweight]
set tolerance [dict get $options -tolerance]
for {set iteration 0} {$iteration < $maxiters} {incr iteration} {
set new_positions {}
set new_velocities {}
set new_particle_bests {}
#
# Determine the new positions for all particles
#
for {set i 0} {$i < $swarmsize} {incr i} {
set old_velocity [lindex $velocities $i]
set old_position [lindex $positions $i]
set old_bestvalue [lindex $particle_bests $i 0]
set old_bestpos [lindex $particle_bests $i 1]
set velocity [Pso_update_vel $vweight $pweight $gweight $old_velocity $old_position $old_bestpos [lindex $positions $global_best]]
set position [Pso_new_position $old_position $velocity]
set fvalue [$func $position]
incr evaluations
lappend new_positions $position
lappend new_velocities $velocity
if { $fvalue < $old_bestvalue } {
lappend new_particle_bests [list $fvalue $position]
} else {
lappend new_particle_bests [list $old_bestvalue $old_position]
}
}
set positions $new_positions
set velocities $new_velocities
set particle_bests $new_particle_bests
#
# Determine the globally best position
#
for {set i 0} {$i < $swarmsize} {incr i} {
set fvalue [lindex $particle_bests $i 0]
if { $fvalue < $global_best_value } {
set global_best_value $fvalue
set global_best $i
set global_best_pos [lindex $particle_bests $i 1]
}
}
#
# Have we reached the tolerance yet?
#
if { $iteration > 0 } {
if { abs($prev_best_value - $global_best_value) < $tolerance &&
$prev_best_value > $global_best_value } {
break
}
}
set prev_best_value $global_best_value
lappend best_values $global_best_value
}
return [dict create optimum-coordinates $global_best_pos optimum-value $global_best_value evaluations $evaluations best-values $best_values]
}
# Pso_local --
# Use the "local" PSO algorithm, i.e. look only at the "neighbours"
#
# Arguments:
# func Function to be minimized
# xmin Minimum values for the coordinates
# xmax Maximum values for the coordinates
# options Dictionary of options
#
proc ::math::probopt::Pso_local {func xmin xmax options} {
#
# Set up the initial positions
#
set swarmsize [dict get $options -swarmsize]
set maxiters [dict get $options -iterations]
set neighbours [dict get $options -neighbours]
set particle_bests {}
set positions {}
set velocities {}
set global_best -1
set global_best_value {}
set evaluations 0
set best_values {}
for {set i 0} {$i < $swarmsize+$neighbours} {incr i} {
lappend neighbours_idx [expr {$i % $swarmsize}]
}
for {set i 0} {$i < $swarmsize} {incr i} {
set coords [Pso_position $xmin $xmax]
set fvalue [$func $coords]
incr evaluations
lappend positions $coords
lappend velocities [lrepeat [llength $coords] 0.0]
lappend particle_bests [list $fvalue $coords]
lappend local_best $i
lappend local_best_value $fvalue
lappend local_best_pos $coords
lappend prev_best_value $fvalue
}
#
# Initial estimates:
# Examine the neighbouring particles and determine which holds the best result
#
for {set i 0} {$i < $swarmsize} {incr i} {
set current_best [lindex $local_best $i]
set current_best_value [lindex $particle_bests $current_best 0]
set current_best_pos [lindex $particle_bests $current_best 1]
for {set n 0} {$n < $neighbours} {incr n} {
set nth [lindex $neighbours_idx [expr {$i+$n}]]
set fvalue [lindex $particle_bests $nth 0]
if { $current_best_value > $fvalue } {
set current_best $nth
set current_best_value $fvalue
set current_best_pos [lindex $particle_bests $nth 1]
}
}
lset local_best $i $current_best
lset local_best_value $i $current_best_value
lset local_best_pos $i $current_best_pos
}
#
# Actual loop
#
set vweight [dict get $options -vweight]
set pweight [dict get $options -pweight]
set gweight [dict get $options -gweight]
set tolerance [dict get $options -tolerance]
set stop 0
for {set iteration 0} {$iteration < $maxiters && $stop == 0} {incr iteration} {
set new_positions {}
set new_velocities {}
set new_particle_bests {}
#
# Determine the new positions for all particles
#
for {set i 0} {$i < $swarmsize} {incr i} {
set old_velocity [lindex $velocities $i]
set old_position [lindex $positions $i]
set old_bestvalue [lindex $particle_bests $i 0]
set old_bestpos [lindex $particle_bests $i 1]
set idx [lindex $local_best $i]
set velocity [Pso_update_vel $vweight $pweight $gweight $old_velocity $old_position $old_bestpos [lindex $positions $idx]]
set position [Pso_new_position $old_position $velocity]
set fvalue [$func $position]
incr evaluations
lappend new_positions $position
lappend new_velocities $velocity
if { $fvalue < $old_bestvalue } {
lappend new_particle_bests [list $fvalue $position]
} else {
lappend new_particle_bests [list $old_bestvalue $old_position]
}
}
set positions $new_positions
set velocities $new_velocities
set particle_bests $new_particle_bests
#
# Examine the neighbouring particles and determine which holds the best result
#
for {set i 0} {$i < $swarmsize} {incr i} {
set current_best [lindex $local_best $i]
set current_best_value [lindex $local_best_value $i]
set current_best_pos [lindex $local_best_pos $i]
for {set n 0} {$n < $neighbours} {incr n} {
set nth [lindex $neighbours_idx [expr {$i+$n}]]
set fvalue [lindex $particle_bests $nth 0]
if { $current_best_value > $fvalue } {
set current_best $nth
set current_best_value $fvalue
set current_best_pos [lindex $particle_bests $nth 1]
}
}
lset local_best $i $current_best
lset local_best_value $i $current_best_value
lset local_best_pos $i $current_best_pos
#
# Have we reached the tolerance yet?
# Note: local citerium - one group reaching a minimum? Then stop
#
if { $iteration > 0 } {
if { abs([lindex $prev_best_value $i] - $current_best_value) < $tolerance &&
[lindex $prev_best_value $i] > $current_best_value } {
set stop 1
break
}
}
lset prev_best_value $i $current_best_value
}
#
# Now determine the overall best position - within this iteration
# (to have a history)
#
set global_best_value [lindex $local_best_value 0]
set global_best_pos [lindex $local_best_pos 0]
for {set i 1} {$i <$swarmsize} {incr i} {
set particle_best_value [lindex $local_best_value $i]
if { $global_best_value > $particle_best_value } {
set global_best_value $particle_best_value
set global_best_pos [lindex $local_best_pos $i]
}
}
lappend best_values $global_best_value
}
return [dict create optimum-coordinates $global_best_pos optimum-value $global_best_value evaluations $evaluations best-values $best_values]
}
# Pso_position --
# Determine the initial position
#
# Arguments:
# xmin Minimum values for the coordinates
# xmax Maximum values for the coordinates
#
# Result:
# Vector of coordinates
#
proc ::math::probopt::Pso_position {xmin xmax} {
set new_position {}
foreach min $xmin max $xmax {
lappend new_position [expr {$min + ($max - $min) * rand()}]
}
return $new_position
}
# Pso_new_position --
# Update the position
#
# Arguments:
# position Position vector
# velocity Velocity vector
#
# Result:
# Vector of new coordinates
#
proc ::math::probopt::Pso_new_position {position velocity} {
set new_position {}
foreach p $position v $velocity {
lappend new_position [expr {$p + $v}]
}
return $new_position
}
# Pso_update_vel --
# Update the velocity
#
# Arguments:
# vweight Weight for the old vector
# pweight Weight for the particle's best position
# gweight Weight for the globally (locally) best position
# old_velocity Old velocity vector
# old_position Old position vector
# old_particle_best Old best vector for the particle
# old_global_best Old globally (locally) best vector
#
# Result:
# Vector of new coordinates
#
proc ::math::probopt::Pso_update_vel {vweight pweight gweight old_velocity old_position old_particle_best old_global_best} {
set new_velocity {}
set pw [expr {$pweight * rand()}]
set gw [expr {$gweight * rand()}]
foreach v $old_velocity p $old_position b $old_particle_best g $old_global_best {
lappend new_velocity [expr {$vweight * $v + $pw * ($b - $p) + $gw * ($g - $p)}]
}
return $new_velocity
}
|