Class: MHL::ParticleSwarmOptimizationSolver

Inherits:
Object
  • Object
show all
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

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
44
45
# 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]

  @pool = Concurrent::FixedThreadPool.new(Concurrent::processor_count * 4)

  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 Logger::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)



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
# File 'lib/mhl/particle_swarm_optimization_solver.rb', line 52

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(@swarm_size, init_pos, init_vel,
                       params.merge(constraints: @constraints))

  # initialize variables
  iter = 0
  overall_best = nil

  # default behavior is to loop forever
  begin
    iter += 1
    @logger.info("PSO - Starting iteration #{iter}") if @logger

    # create latch to control program termination
    latch = Concurrent::CountDownLatch.new(@swarm_size)

    # assess height for every particle
    swarm.each do |particle|
      @pool.post do
        # evaluate target function
        particle.evaluate(func)
        # update latch
        latch.count_down
      end
    end

    # wait for all the threads to terminate
    latch.wait

    # get swarm attractor (the highest particle)
    swarm_attractor = swarm.update_attractor

    # print results
    puts "> iter #{iter}, best: #{swarm_attractor[:position]}, #{swarm_attractor[:height]}" unless @quiet

    # 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