Class: MHL::ParticleSwarmOptimizationSolver
- Inherits:
-
Object
- Object
- MHL::ParticleSwarmOptimizationSolver
- Defined in:
- lib/mhl/particle_swarm_optimization_solver.rb
Overview
This solver implements the “canonical” variant of PSO called Constrained PSO. For more information, refer to equation 4.30 of [SUN11].
Constant Summary collapse
- DEFAULT_SWARM_SIZE =
This is the default swarm size recommended by SPSO 2011 [CLERC12].
40
Instance Method Summary collapse
-
#initialize(opts = {}) ⇒ ParticleSwarmOptimizationSolver
constructor
A new instance of ParticleSwarmOptimizationSolver.
-
#solve(func, params = {}) ⇒ Object
This is the method that solves the optimization problem.
Constructor Details
#initialize(opts = {}) ⇒ ParticleSwarmOptimizationSolver
Returns a new instance of ParticleSwarmOptimizationSolver.
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 |
# File 'lib/mhl/particle_swarm_optimization_solver.rb', line 16 def initialize(opts={}) @swarm_size = opts[:swarm_size].try(:to_i) || DEFAULT_SWARM_SIZE @constraints = opts[:constraints] @random_position_func = opts[:random_position_func] @random_velocity_func = opts[:random_velocity_func] @start_positions = opts[:start_positions] @start_velocities = opts[:start_velocities] @exit_condition = opts[:exit_condition] case opts[:logger] when :stdout @logger = Logger.new(STDOUT) when :stderr @logger = Logger.new(STDERR) else @logger = opts[:logger] end @quiet = opts[:quiet] if @logger @logger.level = (opts[:log_level] or :warn) end end |
Instance Method Details
#solve(func, params = {}) ⇒ Object
This is the method that solves the optimization problem
Parameter func is supposed to be a method (or a Proc, a lambda, or any callable object) that accepts the genotype as argument (that is, the set of parameters) and returns the phenotype (that is, the function result)
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 |
# File 'lib/mhl/particle_swarm_optimization_solver.rb', line 50 def solve(func, params={}) # initialize particle positions init_pos = if @start_positions # start positions have the highest priority @start_positions elsif @random_position_func # random_position_func has the second highest priority Array.new(@swarm_size) { @random_position_func.call } elsif @constraints # constraints were given, so we use them to initialize particle # positions. to this end, we adopt the SPSO 2006-2011 random position # initialization algorithm [CLERC12]. Array.new(@swarm_size) do min = @constraints[:min] max = @constraints[:max] # randomization is independent along each dimension min.zip(max).map do |min_i,max_i| min_i + SecureRandom.random_number * (max_i - min_i) end end else raise ArgumentError, "Not enough information to initialize particle positions!" end # initialize particle velocities init_vel = if @start_velocities # start velocities have the highest priority @start_velocities elsif @random_velocity_func # random_velocity_func has the second highest priority Array.new(@swarm_size) { @random_velocity_func.call } elsif @constraints # constraints were given, so we use them to initialize particle # velocities. to this end, we adopt the SPSO 2011 random velocity # initialization algorithm [CLERC12]. init_pos.map do |p| min = @constraints[:min] max = @constraints[:max] # randomization is independent along each dimension p.zip(min,max).map do |p_i,min_i,max_i| min_vel = min_i - p_i max_vel = max_i - p_i min_vel + SecureRandom.random_number * (max_vel - min_vel) end end else raise ArgumentError, "Not enough information to initialize particle velocities!" end # setup particles swarm = PSOSwarm.new(size: @swarm_size, initial_positions: init_pos, initial_velocities: init_vel, constraints: @constraints, logger: @logger) # initialize variables iter = 0 overall_best = nil # default behavior is to loop forever begin iter += 1 @logger.info("PSO - Starting iteration #{iter}") if @logger # assess height for every particle if params[:concurrent] # the function to optimize is thread safe: call it multiple times in # a concurrent fashion # to this end, we use the high level promise-based construct # recommended by the authors of ruby's (fantastic) concurrent gem promises = swarm.map do |particle| Concurrent::Promise.execute do # evaluate target function particle.evaluate(func) end end # wait for all the spawned threads to finish promises.map(&:wait) else # the function to optimize is not thread safe: call it multiple times # in a sequential fashion swarm.each do |particle| # evaluate target function particle.evaluate(func) end end # get swarm attractor (the highest particle) swarm_attractor = swarm.update_attractor # print results if @logger and !@quiet @logger.info "> iter #{iter}, best: #{swarm_attractor[:position]}, #{swarm_attractor[:height]}" end # calculate overall best (that plays the role of swarm attractor) if overall_best.nil? overall_best = swarm_attractor else overall_best = [ overall_best, swarm_attractor ].max_by {|x| x[:height] } end # mutate swarm swarm.mutate end while @exit_condition.nil? or !@exit_condition.call(iter, overall_best) overall_best end |