|
import numpy as np |
|
import math |
|
import matplotlib.pyplot as plt |
|
from matplotlib.colors import LogNorm |
|
from io import BytesIO |
|
from PIL import Image |
|
import gradio as gr |
|
from mpl_toolkits.mplot3d import Axes3D |
|
|
|
class Objective: |
|
def Evaluate(self, p): |
|
return -5.0*np.exp(-0.5*((p[0]+2.2)**2/0.4+(p[1]-4.3)**2/0.4)) + -2.0*np.exp(-0.5*((p[0]-2.2)**2/0.4+(p[1]+4.3)**2/0.4)) |
|
|
|
|
|
obj = Objective() |
|
|
|
|
|
position = np.array([-2.2, 4.3]) |
|
fitness = obj.Evaluate(position) |
|
|
|
print(f"The fitness of the position {position} is {fitness}") |
|
|
|
class Bounds: |
|
def __init__(self, lower, upper, enforce="clip"): |
|
self.lower = np.array(lower) |
|
self.upper = np.array(upper) |
|
self.enforce = enforce.lower() |
|
|
|
def Upper(self): |
|
return self.upper |
|
|
|
def Lower(self): |
|
return self.lower |
|
|
|
def Limits(self, pos): |
|
npart, ndim = pos.shape |
|
for i in range(npart): |
|
for j in range(ndim): |
|
if pos[i, j] < self.lower[j]: |
|
if self.enforce == "clip": |
|
pos[i, j] = self.lower[j] |
|
elif self.enforce == "resample": |
|
pos[i, j] = self.lower[j] + np.random.random() * (self.upper[j] - self.lower[j]) |
|
elif pos[i, j] > self.upper[j]: |
|
if self.enforce == "clip": |
|
pos[i, j] = self.upper[j] |
|
elif self.enforce == "resample": |
|
pos[i, j] = self.lower[j] + np.random.random() * (self.upper[j] - self.lower[j]) |
|
pos[i] = self.Validate(pos[i]) |
|
return pos |
|
|
|
def Validate(self, pos): |
|
return pos |
|
|
|
|
|
lower_bounds = [-6, -6] |
|
upper_bounds = [6, 6] |
|
|
|
|
|
bounds = Bounds(lower_bounds, upper_bounds, enforce="clip") |
|
|
|
|
|
positions = np.array([[15, 15], [-15, -15], [5, 15], [15, 5]]) |
|
|
|
|
|
valid_positions = bounds.Limits(positions) |
|
|
|
print(f"Valid positions: {valid_positions}") |
|
|
|
|
|
lower_bounds = [-6, -6] |
|
upper_bounds = [6, 6] |
|
|
|
|
|
bounds = Bounds(lower_bounds, upper_bounds, enforce="resample") |
|
|
|
|
|
positions = np.array([[15, 15], [-15, -15], [5, 15], [15, 5]]) |
|
|
|
|
|
valid_positions = bounds.Limits(positions) |
|
|
|
print(f"Valid positions: {valid_positions}") |
|
|
|
class QuasiRandomInitializer: |
|
def __init__(self, npart=10, ndim=2, bounds=None, k=1, jitter=0.0): |
|
self.npart = npart |
|
self.ndim = ndim |
|
self.bounds = bounds |
|
self.k = k |
|
self.jitter = jitter |
|
self.primes = [ |
|
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, |
|
101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, |
|
199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, |
|
317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, |
|
443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, |
|
577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659 |
|
] |
|
|
|
def Halton(self, i, b): |
|
f = 1.0 |
|
r = 0 |
|
while i > 0: |
|
f = f / b |
|
r = r + f * (i % b) |
|
i = math.floor(i / b) |
|
return r |
|
|
|
def InitializeSwarm(self): |
|
self.swarm = np.zeros((self.npart, self.ndim)) |
|
lo = np.zeros(self.ndim) |
|
hi = np.ones(self.ndim) |
|
if self.bounds is not None: |
|
lo = self.bounds.Lower() |
|
hi = self.bounds.Upper() |
|
|
|
for i in range(self.npart): |
|
for j in range(self.ndim): |
|
h = self.Halton(i + self.k, self.primes[j % len(self.primes)]) |
|
q = self.jitter * (np.random.random() - 0.5) |
|
self.swarm[i, j] = lo[j] + (hi[j] - lo[j]) * h + q |
|
|
|
if self.bounds is not None: |
|
self.swarm = self.bounds.Limits(self.swarm) |
|
|
|
return self.swarm |
|
|
|
|
|
lower_bounds = [-6, -6] |
|
upper_bounds = [6, 6] |
|
bounds = Bounds(lower_bounds, upper_bounds, enforce="clip") |
|
|
|
|
|
init = QuasiRandomInitializer(npart=50, ndim=2, bounds=bounds) |
|
|
|
|
|
swarm_positions = init.InitializeSwarm() |
|
|
|
print(f"Initial swarm positions: {swarm_positions}") |
|
|
|
|
|
lower_bounds = [-6, -6] |
|
upper_bounds = [6, 6] |
|
bounds = Bounds(lower_bounds, upper_bounds, enforce="resample") |
|
|
|
|
|
init = QuasiRandomInitializer(npart=50, ndim=2, bounds=bounds) |
|
|
|
|
|
swarm_positions = init.InitializeSwarm() |
|
|
|
print(f"Initial swarm positions: {swarm_positions}") |
|
|
|
class GWO: |
|
def __init__(self, obj, eta=2.0, npart=10, ndim=2, max_iter=200,tol=None,init=None,done=None,bounds=None): |
|
self.obj = obj |
|
self.npart = npart |
|
self.ndim = ndim |
|
self.max_iter = max_iter |
|
self.init = init |
|
self.done = done |
|
self.bounds = bounds |
|
self.tol = tol |
|
self.eta = eta |
|
self.initialized = False |
|
|
|
def Initialize(self): |
|
"""Set up the swarm""" |
|
|
|
self.initialized = True |
|
self.iterations = 0 |
|
|
|
self.pos = self.init.InitializeSwarm() |
|
self.vpos= np.zeros(self.npart) |
|
for i in range(self.npart): |
|
self.vpos[i] = self.obj.Evaluate(self.pos[i]) |
|
|
|
|
|
self.all_positions = [] |
|
self.all_positions.append(self.pos.copy()) |
|
|
|
|
|
self.gidx = [] |
|
self.gbest = [] |
|
self.gpos = [] |
|
self.giter = [] |
|
idx = np.argmin(self.vpos) |
|
self.gidx.append(idx) |
|
self.gbest.append(self.vpos[idx]) |
|
self.gpos.append(self.pos[idx].copy()) |
|
self.giter.append(0) |
|
|
|
|
|
idx = np.argsort(self.vpos) |
|
self.alpha = self.pos[idx[0]].copy() |
|
self.valpha= self.vpos[idx[0]] |
|
self.beta = self.pos[idx[1]].copy() |
|
self.vbeta = self.vpos[idx[1]] |
|
self.delta = self.pos[idx[2]].copy() |
|
self.vdelta= self.vpos[idx[2]] |
|
|
|
|
|
def optimize(self): |
|
""" |
|
Run a full optimization and return the best positions and fitness values. |
|
This method is designed to be used with Gradio. |
|
""" |
|
|
|
self.Initialize() |
|
|
|
|
|
best_positions = [] |
|
best_fitness = [] |
|
|
|
|
|
while not self.Done(): |
|
self.Step() |
|
|
|
best_positions.append(self.gbest[-1]) |
|
best_fitness.append(self.gpos[-1]) |
|
|
|
|
|
best_positions_array = np.array(best_positions) |
|
np.save('best_positions_array', best_positions_array) |
|
|
|
|
|
|
|
|
|
print("Best Positions:", best_positions) |
|
print("Best Fitness:", best_fitness) |
|
|
|
|
|
return best_positions, best_fitness |
|
|
|
def Step(self): |
|
"""Do one swarm step""" |
|
|
|
|
|
a = self.eta - self.eta*(self.iterations/self.max_iter) |
|
|
|
|
|
for i in range(self.npart): |
|
A = 2*a*np.random.random(self.ndim) - a |
|
C = 2*np.random.random(self.ndim) |
|
Dalpha = np.abs(C*self.alpha - self.pos[i]) |
|
X1 = self.alpha - A*Dalpha |
|
|
|
A = 2*a*np.random.random(self.ndim) - a |
|
C = 2*np.random.random(self.ndim) |
|
Dbeta = np.abs(C*self.beta - self.pos[i]) |
|
X2 = self.beta - A*Dbeta |
|
|
|
A = 2*a*np.random.random(self.ndim) - a |
|
C = 2*np.random.random(self.ndim) |
|
Ddelta = np.abs(C*self.delta - self.pos[i]) |
|
X3 = self.delta - A*Ddelta |
|
|
|
self.pos[i,:] = (X1+X2+X3) / 3.0 |
|
|
|
|
|
if (self.bounds != None): |
|
self.pos = self.bounds.Limits(self.pos) |
|
|
|
|
|
for i in range(self.npart): |
|
self.vpos[i] = self.obj.Evaluate(self.pos[i]) |
|
|
|
|
|
if (self.vpos[i] < self.valpha): |
|
self.vdelta = self.vbeta |
|
self.delta = self.beta.copy() |
|
self.vbeta = self.valpha |
|
self.beta = self.alpha.copy() |
|
self.valpha = self.vpos[i] |
|
self.alpha = self.pos[i].copy() |
|
|
|
|
|
if (self.vpos[i] > self.valpha) and (self.vpos[i] < self.vbeta): |
|
self.vdelta = self.vbeta |
|
self.delta = self.beta.copy() |
|
self.vbeta = self.vpos[i] |
|
self.beta = self.pos[i].copy() |
|
|
|
|
|
if (self.vpos[i] > self.valpha) and (self.vpos[i] < self.vbeta) and (self.vpos[i] < self.vdelta): |
|
self.vdelta = self.vpos[i] |
|
self.delta = self.pos[i].copy() |
|
|
|
|
|
if (self.valpha < self.gbest[-1]): |
|
self.gidx.append(i) |
|
self.gbest.append(self.valpha) |
|
np.save('best_fitness.npy', np.array(self.gbest)) |
|
self.gpos.append(self.alpha.copy()) |
|
np.save('best_positions.npy', np.array(self.gpos)) |
|
|
|
self.all_positions.append(self.pos.copy()) |
|
np.save('all_positions.npy', np.array(self.all_positions), allow_pickle=True) |
|
self.giter.append(self.iterations) |
|
|
|
self.iterations += 1 |
|
|
|
def Done(self): |
|
"""Check if we are done""" |
|
|
|
if (self.done == None): |
|
if (self.tol == None): |
|
return (self.iterations == self.max_iter) |
|
else: |
|
return (self.gbest[-1] < self.tol) or (self.iterations == self.max_iter) |
|
else: |
|
return self.done.Done(self.gbest, |
|
gpos=self.gpos, |
|
pos=self.pos, |
|
max_iter=self.max_iter, |
|
iteration=self.iterations) |
|
|
|
def Evaluate(self, pos): |
|
p = np.zeros(self.npart) |
|
for i in range(self.npart): |
|
p[i] = self.obj.Evaluate(pos[i]) |
|
return p |
|
|
|
def Results(self): |
|
if (not self.initialized): |
|
return None |
|
return { |
|
"npart": self.npart, |
|
"ndim": self.ndim, |
|
"max_iter": self.max_iter, |
|
"iterations": self.iterations, |
|
"tol": self.tol, |
|
"eta": self.eta, |
|
"gbest": self.gbest, |
|
"giter": self.giter, |
|
"gpos": self.gpos, |
|
"gidx": self.gidx, |
|
"pos": self.pos, |
|
"vpos": self.vpos |
|
} |
|
def plot_contour_and_wolves(self, wolf_positions): |
|
|
|
best_positions_1D = np.load('best_positions.npy') |
|
|
|
wolf_positions = best_positions_1D |
|
|
|
|
|
def objective_function(x, y): |
|
return -5.0*np.exp(-0.5*((x+2.2)**2/0.4+(y-4.3)**2/0.4)) + -2.0*np.exp(-0.5*((x-2.2)**2/0.4+(y+4.3)**2/0.4)) |
|
|
|
|
|
x_min, x_max = wolf_positions[:, 0].min() - 1, wolf_positions[:, 0].max() + 1 |
|
y_min, y_max = wolf_positions[:, 1].min() - 1, wolf_positions[:, 1].max() + 1 |
|
|
|
|
|
x = np.linspace(x_min, x_max, 100) |
|
y = np.linspace(y_min, y_max, 100) |
|
X, Y = np.meshgrid(x, y) |
|
|
|
|
|
Z = objective_function(X, Y) |
|
|
|
|
|
plt.figure(figsize=(10, 8)) |
|
contour = plt.contour(X, Y, Z, levels=20, cmap='magma') |
|
plt.colorbar(contour) |
|
|
|
|
|
plt.plot(wolf_positions[:, 0], wolf_positions[:, 1], 'ro', markersize=5, label='Wolves') |
|
|
|
|
|
plt.title('Contour Map of Wolves Oscillating Over Search Space') |
|
plt.xlabel('x') |
|
plt.ylabel('y') |
|
plt.legend() |
|
|
|
|
|
plt.xlim(x_min, x_max) |
|
plt.ylim(y_min, y_max) |
|
|
|
|
|
buf = BytesIO() |
|
plt.savefig(buf, format='png') |
|
buf.seek(0) |
|
plt.close() |
|
return Image.open(buf) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def Dispersion(self): |
|
"""Calculate the dispersion of the swarm""" |
|
|
|
if not isinstance(self.gpos, np.ndarray): |
|
self.gpos = np.array(self.gpos) |
|
|
|
|
|
x, y = self.gpos[:, 0], self.gpos[:, 1] |
|
dx = x.max() - x.min() |
|
dy = y.max() - y.min() |
|
return (dx + dy) / 2.0 |
|
|
|
def plot_dispersion_heatmap(self, x_range, y_range, resolution=100): |
|
|
|
x = np.linspace(*x_range, resolution) |
|
y = np.linspace(*y_range, resolution) |
|
X, Y = np.meshgrid(x, y) |
|
positions = np.vstack([X.ravel(), Y.ravel()]).T |
|
|
|
|
|
dispersion_values = np.array([self.Dispersion() for _ in positions]) |
|
Z = dispersion_values.reshape(X.shape) |
|
|
|
|
|
plt.figure(figsize=(10, 8)) |
|
plt.pcolormesh(X, Y, Z, cmap='viridis') |
|
plt.colorbar(label='Dispersion') |
|
|
|
|
|
plt.title('Dispersion Heatmap') |
|
plt.xlabel('x') |
|
plt.ylabel('y') |
|
|
|
|
|
buf = BytesIO() |
|
plt.savefig(buf, format='png') |
|
buf.seek(0) |
|
plt.close() |
|
return Image.open(buf) |
|
|
|
def plot_dispersion(self): |
|
"""Plot the dispersion over time""" |
|
|
|
|
|
dispersion_values = [self.Dispersion() for _ in range(self.max_iter)] |
|
|
|
plt.figure(figsize=(10, 6)) |
|
plt.plot(range(self.max_iter), dispersion_values, label='Dispersion') |
|
plt.xlabel('Iteration') |
|
plt.ylabel('Dispersion') |
|
plt.title('Evolution of Dispersion') |
|
plt.legend() |
|
plt.show() |
|
|
|
|
|
buf = BytesIO() |
|
plt.savefig(buf, format='png') |
|
buf.seek(0) |
|
plt.close() |
|
return Image.open(buf) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def plot_positions(self, iterations=[0, 2, 8, 10]): |
|
"""Plot the positions of the particles over the specified iterations""" |
|
|
|
all_positions = np.load('all_positions.npy', allow_pickle=True) |
|
|
|
|
|
num_iterations = len(iterations) |
|
fig, axs = plt.subplots(num_iterations, figsize=(6, 4 * num_iterations)) |
|
|
|
|
|
if num_iterations == 1: |
|
axs = [axs] |
|
|
|
|
|
for i, ax in enumerate(axs): |
|
|
|
iteration = iterations[i] |
|
if iteration < len(all_positions): |
|
positions = all_positions[iteration] |
|
ax.scatter(positions[:, 0], positions[:, 1], c='r', alpha=0.5) |
|
|
|
|
|
ax.set_title(f'Iteration {iteration}') |
|
ax.set_xlabel('X') |
|
ax.set_ylabel('Y') |
|
else: |
|
ax.axis('off') |
|
|
|
plt.tight_layout() |
|
|
|
|
|
buf = BytesIO() |
|
plt.savefig(buf, format='png') |
|
buf.seek(0) |
|
image = Image.open(buf) |
|
plt.close() |
|
|
|
return image |
|
|
|
def plot_3d_meshgrid_contour(self, obj): |
|
"""Plot the 3D meshgrid with a 2D contour on the base""" |
|
|
|
x_range = np.linspace(self.bounds.Lower()[0], self.bounds.Upper()[0], 100) |
|
y_range = np.linspace(self.bounds.Lower()[1], self.bounds.Upper()[1], 100) |
|
|
|
|
|
|
|
X, Y = np.meshgrid(x_range, y_range) |
|
Z = np.zeros_like(X) |
|
|
|
|
|
for i in range(X.shape[0]): |
|
for j in range(X.shape[1]): |
|
Z[i, j] = obj.Evaluate(np.array([X[i, j], Y[i, j]])) |
|
|
|
|
|
fig, axs = plt.subplots(2, 2, figsize=(14, 10), subplot_kw={'projection': '3d'}) |
|
plt.subplots_adjust(wspace=0.1, hspace=0.1) |
|
|
|
|
|
rotations = [(30, 45), (30, 135), (30, -45), (30, -135)] |
|
|
|
for i, ax in enumerate(axs.flatten()): |
|
|
|
ax.plot_surface(X, Y, Z, cmap='viridis', alpha=0.5) |
|
|
|
|
|
ax.contour(X, Y, Z, levels=20, colors='k', alpha=0.5) |
|
|
|
|
|
best_positions = np.array(self.gpos) |
|
ax.plot(best_positions[:, 0], best_positions[:, 1], self.gbest, 'g*', markersize=4) |
|
|
|
|
|
ax.set_xlabel('X Position') |
|
ax.set_ylabel('Y Position') |
|
ax.set_zlabel('Z Position') |
|
ax.set_title(f'GWO Optimization Process in 3D - Rotation {i+1}') |
|
|
|
|
|
ax.set_xlim(self.bounds.Lower()[0], self.bounds.Upper()[0]) |
|
ax.set_ylim(self.bounds.Lower()[1], self.bounds.Upper()[1]) |
|
ax.set_zlim(np.min(Z), np.max(Z)) |
|
|
|
|
|
ax.set_facecolor('lightblue') |
|
|
|
|
|
ax.view_init(*rotations[i]) |
|
|
|
|
|
plt.show() |
|
|
|
|
|
buf = BytesIO() |
|
plt.savefig(buf, format='png') |
|
buf.seek(0) |
|
image = Image.open(buf) |
|
plt.close() |
|
|
|
return image |
|
|
|
|
|
|
|
def optimize(npart, ndim, max_iter): |
|
|
|
gwo = GWO(obj=obj, npart=npart, ndim=ndim, max_iter=max_iter, init=init, bounds=bounds) |
|
|
|
best_positions, best_fitness= gwo.optimize() |
|
|
|
|
|
best_fitness_npy = np.array(best_fitness) |
|
best_positions_npy = np.array(best_positions) |
|
|
|
|
|
dispersion = gwo.Dispersion() |
|
dispersion_text = f"Dispersion: {dispersion}" |
|
|
|
|
|
best_positions_array = np.load('best_positions_array.npy') |
|
|
|
|
|
plot_contour_and_wolves = gwo.plot_contour_and_wolves(best_positions_array) |
|
|
|
|
|
dispersion_plot = gwo.plot_dispersion() |
|
|
|
|
|
dispersion_heatmap_plot = gwo.plot_dispersion_heatmap(x_range=(-6,6), y_range=(-6,6)) |
|
|
|
|
|
plot_positions = gwo.plot_positions(iterations=[0, 2, 8, 10]) |
|
|
|
|
|
plot_3d_meshgrid_contour = gwo.plot_3d_meshgrid_contour(obj=obj) |
|
|
|
|
|
best_fitness_text = f"Best Fitness: {best_fitness_npy}" |
|
best_positions_text = f"Best Positions: {best_positions_npy}" |
|
|
|
|
|
return plot_contour_and_wolves, dispersion_plot, dispersion_heatmap_plot, plot_positions, plot_3d_meshgrid_contour, best_fitness_text, best_positions_text, best_fitness_npy, best_positions_npy, dispersion_text |
|
|
|
|
|
|
|
|
|
|
|
iface = gr.Interface( |
|
fn=optimize, |
|
inputs=[ |
|
gr.components.Slider(10, 50, 50, step=1, label="Number of Wolves [Default = 50 Wolves]"), |
|
gr.components.Slider(2, 2, 2, step=1, label="Two-Dimensional Search Space"), |
|
gr.components.Slider(100, 200, 200, step=1, label="Maximum Iterations [Default = 200 Epochs]"), |
|
], |
|
outputs=[ |
|
gr.components.Image(type="pil", label="Contour Map of Wolves Oscillating Over Search Space"), |
|
gr.components.Image(type="pil", label="Dispersion Plot"), |
|
gr.components.Image(type="pil", label="Dispersion Heatmap Plot"), |
|
gr.components.Image(type="pil", label="Best Positions Plot"), |
|
gr.components.Image(type="pil", label="Plot 3D Meshgrid Contour"), |
|
gr.components.Textbox(label="Dispersion Values"), |
|
gr.components.Textbox(label="Best Positions"), |
|
gr.components.Textbox(label="Best Fitness") |
|
|
|
], |
|
title="Grey Wolf Optimizer", |
|
description=r""" |
|
## Grey Wolf Optimizer |
|
|
|
The Grey Wolf Optimizer (GWO) is a population-based metaheuristic optimization algorithm inspired by the social behavior of grey wolves in nature. |
|
|
|
The objective function to be optimized is given by the following formula: |
|
|
|
```math |
|
f(p) = -5.0 \cdot \exp \left( -0.5 \cdot \left( \frac{(x+2.2)^2}{0.4} + \frac{(y-4.3)^2}{0.4} \right) \right) + -2.0 \cdot \exp \left( -0.5 \cdot \left( \frac{(x-2.2)^2}{0.4} + \frac{(y+4.3)^2}{0.4} \right) \right) |
|
``` |
|
Or in a more readable form: |
|
|
|
$$ |
|
f(p) = -5.0 \cdot \exp \left( -0.5 \cdot \left( \frac{(x+2.2)^2}{0.4} + \frac{(y-4.3)^2}{0.4} \right) \right) + -2.0 \cdot \exp \left( -0.5 \cdot \left( \frac{(x-2.2)^2}{0.4} + \frac{(y+4.3)^2}{0.4} \right) \right) |
|
$$ |
|
""", |
|
article="## Grey Wolf Optimizer\nThe Grey Wolf Optimizer (GWO) is a population-based metaheuristic optimization algorithm inspired by the social behavior of grey wolves in nature." |
|
) |
|
|
|
|
|
iface.launch() |
|
|