Class: MHL::MultiSwarmQPSOSolver
- Inherits:
-
Object
- Object
- MHL::MultiSwarmQPSOSolver
- Defined in:
- lib/mhl/multiswarm_qpso_solver.rb
Overview
This solver implements the multiswarm QPSO algorithm, based on a number of charged (QPSO Type 2) and neutral (PSO) swarms.
For more information, refer to:
- BLACKWELLBRANKE04
-
Tim Blackwell, Jürgen Branke, “Multi-swarm Optimization
in Dynamic Environments“, Applications of Evolutionary Computing, pp. 489-500, Springer, 2004. DOI: 10.1007/978-3-540-24653-4_50
Constant Summary collapse
- DEFAULT_SWARM_SIZE =
20
Instance Method Summary collapse
-
#initialize(opts = {}) ⇒ MultiSwarmQPSOSolver
constructor
A new instance of MultiSwarmQPSOSolver.
-
#solve(func, params = {}) ⇒ Object
This is the method that solves the optimization problem.
Constructor Details
#initialize(opts = {}) ⇒ MultiSwarmQPSOSolver
Returns a new instance of MultiSwarmQPSOSolver.
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 |
# File 'lib/mhl/multiswarm_qpso_solver.rb', line 19 def initialize(opts={}) @swarm_size = opts[:swarm_size].try(:to_i) || DEFAULT_SWARM_SIZE @num_swarms = opts[:num_swarms].to_i unless @num_swarms raise ArgumentError, 'Number of swarms is a required parameter!' end @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)
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 |
# File 'lib/mhl/multiswarm_qpso_solver.rb', line 58 def solve(func, params={}) swarms = Array.new(@num_swarms) do |index| # initialize particle positions init_pos = if @start_positions # start positions have the highest priority @start_positions[index * @swarm_size, @swarm_size] 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[index * @swarm_size / 2, @swarm_size / 2] elsif @random_velocity_func # random_velocity_func has the second highest priority Array.new(@swarm_size / 2) { @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 ChargedSwarm.new(size: @swarm_size, initial_positions: init_pos, initial_velocities: init_vel, constraints: @constraints, logger: @logger) end # initialize variables iter = 0 overall_best = nil # default behavior is to loop forever begin iter += 1 @logger.info "MultiSwarm QPSO - 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 = swarms.map do |swarm| swarm.map do |particle| Concurrent::Promise.execute do # evaluate target function particle.evaluate(func) end end end.flatten! # 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 swarms.each do |swarm| swarm.each do |particle| # evaluate target function particle.evaluate(func) end end end # update attractors (the highest particle in each swarm) swarm_attractors = swarms.map {|s| s.update_attractor } best_attractor = swarm_attractors.max_by {|x| x[:height] } # print results if @logger and !@quiet @logger.info "> iter #{iter}, best: #{best_attractor[:position]}, #{best_attractor[:height]}" end # calculate overall best if overall_best.nil? overall_best = best_attractor else overall_best = [ overall_best, best_attractor ].max_by {|x| x[:height] } end # exclusion phase # this phase is necessary to preserve diversity between swarms. we need # to ensure that swarm attractors are distant at least r_{excl} units # from each other. if the attractors of two swarms are closer than # r_{excl}, we randomly reinitialize the worst of those swarms. # TODO: IMPLEMENT # anti-convergence phase # this phase is necessary to ensure that a swarm is "spread" enough to # effectively follow the movements of a "peak" in the solution space. # TODO: IMPLEMENT # mutate swarms swarms.each {|s| s.mutate } end while @exit_condition.nil? or !@exit_condition.call(iter, overall_best) overall_best end |