Jan 16, 2023 ▪ 12 min read (~1 pages) ▪ Computer Science

Particle swarm optimization (PSO) is a computational method used to find the optimal solution to problems. It is based on the behavior of large groups in nature, such as flocks of birds and swarms of insects, where individuals work together to find food or a new home. In PSO, each possible solution is represented by a particle that moves around in the solution space. The particles are guided by their own best solution (local best), as well as the best solution found by other particles (global best).

Install and import dependencies.

```
import random
```

The starting particles are randomly placed in the solution space.

```
def generate_swarm(x_0, n_par):
# dimensions (number of variables)
dimensions = len(x_0)
swarm = []
# generate particles
for i in range(0, n_par):
position = []
# best position
position_best = -1
# particle velocity
velocity = []
# particle error (cost)
error = -1
# best error (cost)
error_best = error
# position and velocity
for i in range(0, dimensions):
position.append(x_0[i])
velocity.append(random.uniform(-1, 1))
# append particle
swarm.append({
"dimensions": dimensions,
"position": position,
"position_best": position_best,
"velocity": velocity,
"error": error,
"error_best": error_best
})
return swarm
```

A swarm is a list of particles, where each particle is represented as a dictionary (parameters are key-value pairs), `n_par`

is number of particles, and `x_0`

is starting position (initial guess).

The velocity of each particle is updated based on its current position, local best position that the particle has encountered so far, and global best position that has been encountered by other particles. The `r_1`

and `r_2`

parameters are used to introduce randomness into the movement, which can be useful to prevent getting stuck in local optima.

```
def update_velocity(velocity, position, position_best, global_pos):
# random bias
r_1 = random.random()
r_2 = random.random()
# update velocity
velocity_cognative = c_1 * r_1 * (position_best - position)
velocity_social = c_2 * r_2 * (global_pos - position)
velocity = weight * velocity + velocity_cognative + velocity_social
return velocity
```

The configuration variables: constant inertia weight, cognitive constant, social constant, are used to control the movement of the particles in the solution space.

```
# constant inertia weight
weight = 0.5
# cognative constant
c_1 = 1
# social constant
c_2 = 2
```

The `weight`

constant is used to control balance between current velocity and previous velocity, `c_1`

is used to control influence of local best position on movement (cognative constant, higher value is more likely to move towards local best), `c_2`

is used to control influence of global best position on movement (social constant, higher value is more likely to move towards global best).

The position of each particle is updated based on its current velocity.

```
def update_position(position, velocity):
position = position + velocity
return position
```

The fitness of each particle is evaluated using the cost function, which is then used to update velocity and position.

```
def iterate_swarm(f, swarm, bounds=None, global_best=-1, global_pos=-1):
# iterate particles and evaluate cost function
for j in range(0, len(swarm)):
dimensions = swarm[j]["dimensions"]
position = swarm[j]["position"]
error_best = swarm[j]["error_best"]
# evaluate new error (cost)
error = swarm[j]["error"] = f(position)
# update local best position if current position gives better local error
if (error < error_best or error_best == -1):
swarm[j]["position_best"] = position
swarm[j]["error_best"] = error
position_best = swarm[j]["position_best"]
velocity = swarm[j]["velocity"]
# update global best if position of current particle gives best global error
if (error < global_best or global_best == -1):
global_pos = list(position)
global_best = float(error)
# update particle velocity and position
for i in range(0, dimensions):
velocity[i] = update_velocity(velocity[i], position[i], position_best[i], global_pos[i])
position[i] = update_position(position[i], velocity[i])
# check bounds
if bounds:
# max value for position
if (position[i] > bounds[i][1]):
position[i] = bounds[i][1]
# min value for position
if (position[i] < bounds[i][0]):
position[i] = bounds[i][0]
# return
return swarm, round(global_best, 2), [round(pos, 2) for pos in global_pos]
```

In the below examples, the maximum number of iterations is set to `50`

, which should be enough, and random seed is set to `1234`

, so that the algorithm will generate the same result given the same configuration variables.

```
MAX_ITERATIONS = 50
```

```
random.seed(1234)
```

In this example, the cost function is `x[0] ** 5 - 3 * x[0] ** 4 + 5`

, where x-range is `[0, 4]`

(note that `x: [x_1, x_2, ..., x_n]`

).

```
# minimize x^5 - 3x^4 + 5 over [0, 4]
def f(x):
return x[0] ** 5 - 3 * x[0] ** 4 + 5
```

```
# reset global
global_best = -1
global_pos = -1
# initial swarm
swarm = generate_swarm(x_0=[5], n_par=15)
# iterate swarm
for i in range(MAX_ITERATIONS):
swarm, global_best, global_pos = iterate_swarm(f, swarm, bounds=[(0, 4)], global_best=global_best, global_pos=global_pos)
print((global_best, global_pos))
# (-14.91, [2.4])
```

```
assert (global_best, global_pos) == (-14.91, [2.4])
```

In this example, the cost function is `-(5 + 3 * x[0] - 4 * x[1] - x[0] ** 2 + x[0] * x[1] - x[1] ** 2)`

with no bounds.

```
# minimize -(5 + 3x - 4y - x^2 + x y - y^2)
def f(x):
return -(5 + 3 * x[0] - 4 * x[1] - x[0] ** 2 + x[0] * x[1] - x[1] ** 2)
```

```
# reset global
global_best = -1
global_pos = -1
# initial swarm
swarm = generate_swarm(x_0=[5, 5], n_par=15)
# iterate swarm
for i in range(MAX_ITERATIONS):
swarm, global_best, global_pos = iterate_swarm(f, swarm, global_best=global_best, global_pos=global_pos)
print((global_best, global_pos))
# (-9.33, [0.67, -1.67])
```

```
assert (global_best, global_pos) == (-9.33, [0.67, -1.67])
```