class MHL::QuantumPSOSolver

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

Constants

DEFAULT_SWARM_SIZE

Public Class Methods

new(opts={}) click to toggle source
# 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

Public Instance Methods

solve(func, params={}) click to toggle source

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)

# 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