1. Introduction
In this article, we’ll take a look at a Multi-swarm optimization algorithm. Like other algorithms of the same class, its purpose is to find the best solution to a problem by maximizing or minimizing a specific function, called a fitness function.
Let’s start with some theory.
2. How Multi-Swarm Optimization Works
The Multi-swarm is a variation of the Swarm algorithm. As the name suggests, the Swarm algorithm solves a problem by simulating the movement of a group of objects in the space of possible solutions. In the multi-swarm version, there are multiple swarms instead of just one.
The basic component of a swarm is called a particle. The particle is defined by its actual position, which is also a possible solution to our problem, and its speed, which is used to calculate the next position.
The speed of the particle constantly changes, leaning towards the best position found among all the particles in all the swarms with a certain degree of randomness to increase the amount of space covered.
This ultimately leads most particles to a finite set of points which are local minima or maxima in the fitness function, depending on whether we’re trying to minimize or maximize it.
Although the point found is always a local minimum or maximum of the function, it’s not necessarily a global one since there’s no guarantee that the algorithm has completely explored the space of solutions.
For this reason, the multi-swarm is said to be a metaheuristic – the solutions it finds are among the best, but they may not be the absolute best.
3. Implementation
Now that we know what a multi-swarm is and how it works let’s take a look at how to implement it.
For our example, we’ll try to address this real-life optimization problem posted on StackExchange:
In League of Legends, a player’s Effective Health when defending against physical damage is given by E=H(100+A)/100, where H is health and A is armor.
Health costs 2.5 gold per unit, and Armor costs 18 gold per unit. You have 3600 gold, and you need to optimize the effectiveness E of your health and armor to survive as long as possible against the enemy team’s attacks. How much of each should you buy?
3.1. Particle
We start off by modeling our base construct, a particle. The state of a particle includes its current position, which is a pair of health and armor values that solve the problem, the speed of the particle on both axes and the particle fitness score.
We’ll also store the best position and fitness score we find since we’ll need them to update the particle speed:
public class Particle { private long[] position; private long[] speed; private double fitness; private long[] bestPosition; private double bestFitness = Double.NEGATIVE_INFINITY; // constructors and other methods }
We choose to use long arrays to represent both speed and position because we can deduce from the problem statement that we can’t buy fractions of armor or health, hence the solution must be in the integer domain.
We don’t want to use int because that can cause overflow problems during calculations.
3.2. Swarm
Next up, let’s define a swarm as a collection of particles. Once again we’ll also store the historical best position and score for later computation.
The swarm will also need to take care of its particles’ initialization by assigning a random initial position and speed to each one.
We can roughly estimate a boundary for the solution, so we add this limit to the random number generator.
This will reduce the computational power and time needed to run the algorithm:
public class Swarm { private Particle[] particles; private long[] bestPosition; private double bestFitness = Double.NEGATIVE_INFINITY; public Swarm(int numParticles) { particles = new Particle[numParticles]; for (int i = 0; i < numParticles; i++) { long[] initialParticlePosition = { random.nextInt(Constants.PARTICLE_UPPER_BOUND), random.nextInt(Constants.PARTICLE_UPPER_BOUND) }; long[] initialParticleSpeed = { random.nextInt(Constants.PARTICLE_UPPER_BOUND), random.nextInt(Constants.PARTICLE_UPPER_BOUND) }; particles[i] = new Particle( initialParticlePosition, initialParticleSpeed); } } // methods omitted }
3.3. Multiswarm
Finally, let’s conclude our model by creating a Multiswarm class.
Similarly to the swarm, we’ll keep track of a collection of swarms and the best particle position and fitness found among all the swarms.
We’ll also store a reference to the fitness function for later use:
public class Multiswarm { private Swarm[] swarms; private long[] bestPosition; private double bestFitness = Double.NEGATIVE_INFINITY; private FitnessFunction fitnessFunction; public Multiswarm( int numSwarms, int particlesPerSwarm, FitnessFunction fitnessFunction) { this.fitnessFunction = fitnessFunction; this.swarms = new Swarm[numSwarms]; for (int i = 0; i < numSwarms; i++) { swarms[i] = new Swarm(particlesPerSwarm); } } // methods omitted }
3.4. Fitness Function
Let’s now implement the fitness function.
To decouple the algorithm logic from this specific problem, we’ll introduce an interface with a single method.
This method takes a particle position as an argument and returns a value indicating how good it is:
public interface FitnessFunction { public double getFitness(long[] particlePosition); }
Provided that the found result is valid according to the problem constraints, measuring the fitness is just a matter of returning the computed effective health which we want to maximize.
For our problem, we have the following specific validation constraints:
- solutions must only be positive integers
- solutions must be feasible with the provided amount of gold
When one of these constraints is violated, we return a negative number that tells how far away we’re from the validity boundary.
This is either the number found in the former case or the amount of unavailable gold in the latter:
public class LolFitnessFunction implements FitnessFunction { @Override public double getFitness(long[] particlePosition) { long health = particlePosition[0]; long armor = particlePosition[1]; if (health < 0 && armor < 0) { return -(health * armor); } else if (health < 0) { return health; } else if (armor < 0) { return armor; } double cost = (health * 2.5) + (armor * 18); if (cost > 3600) { return 3600 - cost; } else { long fitness = (health * (100 + armor)) / 100; return fitness; } } }
3.5. Main Loop
The main program will iterate between all particles in all swarms and do the following:
- compute the particle fitness
- if a new best position has been found, update the particle, swarm and multiswarm history
- compute the new particle position by adding the current speed to each dimension
- compute the new particle speed
For the moment, we’ll leave the speed updating to the next section by creating a dedicated method:
public void mainLoop() { for (Swarm swarm : swarms) { for (Particle particle : swarm.getParticles()) { long[] particleOldPosition = particle.getPosition().clone(); particle.setFitness(fitnessFunction.getFitness(particleOldPosition)); if (particle.getFitness() > particle.getBestFitness()) { particle.setBestFitness(particle.getFitness()); particle.setBestPosition(particleOldPosition); if (particle.getFitness() > swarm.getBestFitness()) { swarm.setBestFitness(particle.getFitness()); swarm.setBestPosition(particleOldPosition); if (swarm.getBestFitness() > bestFitness) { bestFitness = swarm.getBestFitness(); bestPosition = swarm.getBestPosition().clone(); } } } long[] position = particle.getPosition(); long[] speed = particle.getSpeed(); position[0] += speed[0]; position[1] += speed[1]; speed[0] = getNewParticleSpeedForIndex(particle, swarm, 0); speed[1] = getNewParticleSpeedForIndex(particle, swarm, 1); } } }
3.6. Speed Update
It’s essential for the particle to change its speed since that’s how it manages to explore different possible solutions.
The speed of the particle will need to make the particle move towards the best position found by itself, by its swarm and by all the swarms, assigning a certain weight to each of these. We’ll call these weights, cognitive weight, social weight and global weight, respectively.
To add some variation, we’ll multiply each of these weights with a random number between 0 and 1. We’ll also add an inertia factor to the formula which incentivizes the particle not to slow down too much:
private int getNewParticleSpeedForIndex( Particle particle, Swarm swarm, int index) { return (int) ((Constants.INERTIA_FACTOR * particle.getSpeed()[index]) + (randomizePercentage(Constants.COGNITIVE_WEIGHT) * (particle.getBestPosition()[index] - particle.getPosition()[index])) + (randomizePercentage(Constants.SOCIAL_WEIGHT) * (swarm.getBestPosition()[index] - particle.getPosition()[index])) + (randomizePercentage(Constants.GLOBAL_WEIGHT) * (bestPosition[index] - particle.getPosition()[index]))); }
Accepted values for inertia, cognitive, social and global weights are 0.729, 1.49445, 1.49445 and 0.3645, respectively.
4. Conclusion
In this tutorial, we went through the theory and the implementation of a swarm algorithm. We also saw how to design a fitness function according to a specific problem.
If you want to read more about this topic, have a look at this book and this article which were also used as information sources for this article.
As always, all the code of the example is available over on the GitHub project.