Particle Swarm Optimization
I got bored because I moved into a new apartment and had no internet or television access to do any work, so I decided to write a Particle Swarm Optimization implementation in Ruby. It ain’t fast, but it seems to get the job done.
# Alter the array class to add a method that allows us to use our comparator # to find not only the max value, but also it's index class Array public def comparator_with_index(comparator) return nil unless self.size > 0 m = self[0] mi = 0 self[1..self.size-1].each_with_index { |e, i| if send(comparator,e,m); m = e; mi = (i+1); end } return [m, mi] end end #end Array definition class ParticleSwarmOptimization private # A private vector implementation # Basically just adds a + function to Arrays class Vector < Array def +(other) raise "Vectors must have same dimensions" unless self.size == other.size ret = [] 0.upto(self.size-1) { |i| ret[i] = self[i] + other[i] } return ret end end #end Vector definition # Our particles class Fly attr_accessor :position, :velocity, :current_fitness # Define the feature dimensions, feature limits, et cetera def initialize(feature_dimension, feature_limits, inertia, cognitive, social, comparator) @position = Vector.new(feature_dimension) @velocity = Vector.new(feature_dimension, 0) feature_limits.each_index { |i| @position[i] = feature_limits[i][0] + (feature_limits[i][1] - feature_limits[i][0]) * rand } @current_fitness = nil @fitness_max = nil @position_max = nil @inertia = inertia @cognitive = cognitive @social = social @comparator = comparator end #end initialize # update the best local fitness def update_fitness! if @fitness_max.nil? @fitness_max = @current_fitness @position_max = @position else if send(@comparator, @current_fitness, @fitness_max) @fitness_max = @current_fitness @position_max = @position end end end #end min_max_compare! # update the position based on local and global best, as well as momentum factors def update!(global_best_features) @velocity.each_index { |i| @velocity[i] = @inertia * @velocity[i] + @cognitive * rand * (@position_max[i] - @position[i]) + @social * rand * (global_best_features[i] - @position[i]) } @position = @position + @velocity end #end update! end #end Fly definition public # The big function # Inputs: # population_size: Number of particles you want in the swarm # feature_dimension: How many features are in the problem # feature_limits: What range are the features defined in (for initialization only) # max_iterations: Number of iterations to perform # inertia: How fast the particle will move in its current direction # cognitive: How fast the particle moves towards its own personal max # social: How fast the particle moves towards the global max # comparator: A function that takes the fitness of two particles and determines which is # 'better'. Takes the form of comparator(x,y), and returns true if x is # 'better' than y. # # Returns the global best fitness and feature set def self.optimize_over(population_size, feature_dimension, feature_limits, max_iterations, inertia, cognitive, social, comparator) raise "Feature limit definition must be equal to feature size definition" unless feature_dimension == feature_limits.size raise "Block must be provided to optimize features" unless block_given? raise "Comparator must not have identical name of Array methods" unless !Array.instance_methods.include?(comparator.to_s) # initialize our population @population = Array.new(population_size) @population.map! { Fly.new(feature_dimension, feature_limits, inertia, cognitive, social, comparator) } global_best = nil global_best_features = nil iteration = 0 begin # find the fitness of each fly and see if it is a local best @population.each { |fly| fly.current_fitness = yield fly.position.clone fly.update_fitness! } # find the best fitness of the current swarm best_fitness, best_fitness_index = @population.map { |fly| fly.current_fitness }.comparator_with_index(comparator) # check to see if it is a global best if global_best.nil? global_best = best_fitness global_best_features = @population[best_fitness_index].position elsif send(comparator, best_fitness, global_best) global_best = best_fitness global_best_features = @population[best_fitness_index].position end # update each fly with the global best feature set @population.each { |fly| fly.update!(global_best_features) } iteration = iteration + 1 end while iteration < max_iterations return [global_best, global_best_features] end #end optimize_over end #end ParticleSwarmOptimization definition
And some example code on how to use it…
def maximize(x,y) return (x > y ? true : false) end def minimize(x,y) return (x < y ? true : false) end fitness, value = ParticleSwarmOptimization.optimize_over(50, 1, [[-10, 10]], 4000, 1, 2, 2, :minimize) { |x| Math::cos(x[0]) * Math::exp(Math::sin(x[0])) * Math::sin(x[0]) / 1.5 } puts "x: #{value} y: #{fitness}"
I found a cool paper which discusses using multiple swarms, and using Darwinism to evolve swarms. A cool concept.