Class: MHL::QuantumPSOSolver

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

Overview

This solver implements the QPSO Type 2 algorithm.

For more information, refer to equation 4.82 of:

SUN11

Jun Sun, Choi-Hong Lai, Xiao-Jun Wu, “Particle Swarm Optimisation:

Classical and Quantum Perspectives“, CRC Press, 2011

Constant Summary collapse

DEFAULT_SWARM_SIZE =
40

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}) ⇒ QuantumPSOSolver

Returns a new instance of QuantumPSOSolver.



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

def initialize(opts={})
  @swarm_size = opts[:swarm_size].try(:to_i) || DEFAULT_SWARM_SIZE

  @constraints = opts[:constraints]

  @random_position_func = opts[:random_position_func]

  @start_positions = opts[:start_positions]

  @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)



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

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

  swarm = QPSOSwarm.new(size: @swarm_size, initial_positions: init_pos,
                        constraints: @constraints, logger: @logger)

  # initialize variables
  iter = 0
  overall_best = nil

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

    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