Class: MHL::ParticleSwarmOptimizationSolver

Inherits:
Object
  • Object
show all
Defined in:
lib/mhl/particle_swarm_optimization_solver.rb

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}) ⇒ ParticleSwarmOptimizationSolver

Returns a new instance of ParticleSwarmOptimizationSolver.



8
9
10
11
12
13
14
15
16
17
18
19
# File 'lib/mhl/particle_swarm_optimization_solver.rb', line 8

def initialize(opts={})
  @swarm_size = opts[:swarm_size].to_i
  unless @swarm_size
    raise ArgumentError, 'Swarm size is a required parameter!'
  end

  @random_position_func = opts[:random_position_func]
  @random_velocity_func = opts[:random_velocity_func]

  @start_positions = opts[:start_positions]
  @exit_condition  = opts[:exit_condition]
end

Instance Method Details

#solve(func) ⇒ 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)



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

def solve(func)
  # setup particles
  if @start_positions.nil?
    particles = Array.new(@swarm_size) do
      { position: Vector[*@random_position_func.call], velocity: Vector[*@random_velocity_func.call] }
    end
  else
    particles = @start_positions.each_slice(2).map do |pos,vel|
      { position: Vector[*pos], velocity: Vector[*vel] }
    end
  end

  # initialize variables
  gen = 0
  overall_best = nil

  # completely made up values
  alpha   = 0.5
  beta    = 0.3
  gamma   = 0.7
  delta   = 0.5
  epsilon = 0.6

  # default behavior is to loop forever
  begin
    gen += 1
    puts "Starting generation #{gen} at #{Time.now}"

    # assess height for every particle
    particles.each do |p|
      p[:task] = Concurrent::Future.new { func.call(p[:position]) }
    end

    # wait for all the evaluations to end
    particles.each_with_index do |p,i|
      p[:height] = p[:task].value
      if p[:highest_value].nil? or p[:height] > p[:highest_value]
        p[:highest_value]    = p[:height]
        p[:highest_position] = p[:position]
      end
    end

    # find highest particle
    highest_particle = particles.max_by {|x| x[:height] }

    # calculate overall best
    if overall_best.nil?
      overall_best = highest_particle
    else
      overall_best = [ overall_best, highest_particle ].max_by {|x| x[:height] }
    end

    # mutate swarm
    particles.each do |p|
      # randomly sample particles and use them as informants
      informants = random_portion(particles)

      # make sure that p is included among the informants
      informants << p unless informants.include? p

      # get fittest informant
      fittest_informant = informants.max_by {|x| x[:height] }

      # update velocity
      p[:velocity] =
        alpha * p[:velocity] +
        beta  * (p[:highest_position] - p[:position]) +
        gamma * (fittest_informant[:highest_position] - p[:position]) +
        delta * (overall_best[:highest_position] - p[:position])

      # update position
      p[:position] = p[:position] + epsilon * p[:velocity]
    end

  end while @exit_condition.nil? or !@exit_condition.call(gen, overall_best)
end