TroglodyteDerivations's picture
Upload 7 files
b8440f4 verified
import sys
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from mpl_toolkits.mplot3d import Axes3D
import random
from PyQt5.QtWidgets import (QApplication, QMainWindow, QVBoxLayout, QHBoxLayout,
QWidget, QComboBox, QPushButton, QLabel, QSpinBox,
QDoubleSpinBox, QGroupBox, QGridLayout, QTextEdit,
QSplitter, QProgressBar)
from PyQt5.QtCore import QTimer, Qt
from PyQt5.QtGui import QFont
class Particle:
def __init__(self, dim, bounds):
self.dim = dim
self.position = np.array([random.uniform(bounds[i][0], bounds[i][1]) for i in range(dim)])
self.velocity = np.array([random.uniform(-1, 1) for _ in range(dim)])
self.best_position = self.position.copy()
self.best_value = float('inf')
self.bounds = bounds
def update_velocity(self, global_best_position, w, c1, c2):
for i in range(self.dim):
r1, r2 = random.random(), random.random()
cognitive = c1 * r1 * (self.best_position[i] - self.position[i])
social = c2 * r2 * (global_best_position[i] - self.position[i])
self.velocity[i] = w * self.velocity[i] + cognitive + social
def update_position(self):
self.position += self.velocity
# Apply bounds
for i in range(self.dim):
if self.position[i] < self.bounds[i][0]:
self.position[i] = self.bounds[i][0]
self.velocity[i] *= -0.5
elif self.position[i] > self.bounds[i][1]:
self.position[i] = self.bounds[i][1]
self.velocity[i] *= -0.5
class PSO:
def __init__(self, objective_func, dim, bounds, num_particles=30, w=0.7, c1=1.4, c2=1.4):
self.objective_func = objective_func
self.dim = dim
self.bounds = bounds
self.num_particles = num_particles
self.w = w
self.c1 = c1
self.c2 = c2
self.particles = [Particle(dim, bounds) for _ in range(num_particles)]
self.global_best_position = np.array([random.uniform(bounds[i][0], bounds[i][1]) for i in range(dim)])
self.global_best_value = float('inf')
self.history = []
def optimize(self, max_iterations):
for iteration in range(max_iterations):
for particle in self.particles:
# Evaluate fitness
value = self.objective_func(particle.position)
# Update personal best
if value < particle.best_value:
particle.best_value = value
particle.best_position = particle.position.copy()
# Update global best
if value < self.global_best_value:
self.global_best_value = value
self.global_best_position = particle.position.copy()
# Update velocities and positions
for particle in self.particles:
particle.update_velocity(self.global_best_position, self.w, self.c1, self.c2)
particle.update_position()
# Save history for visualization
self.history.append({
'positions': [p.position.copy() for p in self.particles],
'global_best': self.global_best_position.copy(),
'global_best_value': self.global_best_value,
'iteration': iteration
})
return self.global_best_position, self.global_best_value
class EquationDefinitions:
@staticmethod
def get_equations():
equations = {
# 2D Equations
"Sphere Function": {
"func": lambda x: sum(xi**2 for xi in x),
"dim": 2,
"bounds": [(-5.12, 5.12), (-5.12, 5.12)],
"description": "f(x,y) = x² + y²\nMinimum at (0,0)"
},
"Rosenbrock Function": {
"func": lambda x: 100*(x[1]-x[0]**2)**2 + (1-x[0])**2,
"dim": 2,
"bounds": [(-2, 2), (-1, 3)],
"description": "f(x,y) = 100(y-x²)² + (1-x)²\nMinimum at (1,1)"
},
"Rastrigin Function": {
"func": lambda x: 20 + sum(xi**2 - 10*np.cos(2*np.pi*xi) for xi in x),
"dim": 2,
"bounds": [(-5.12, 5.12), (-5.12, 5.12)],
"description": "f(x,y) = 20 + x²+y² -10(cos(2πx)+cos(2πy))\nMinimum at (0,0)"
},
"Ackley Function": {
"func": lambda x: -20*np.exp(-0.2*np.sqrt(0.5*sum(xi**2 for xi in x))) -
np.exp(0.5*sum(np.cos(2*np.pi*xi) for xi in x)) + 20 + np.e,
"dim": 2,
"bounds": [(-5, 5), (-5, 5)],
"description": "Complex function with many local minima\nMinimum at (0,0)"
},
"Matyas Function": {
"func": lambda x: 0.26*(x[0]**2 + x[1]**2) - 0.48*x[0]*x[1],
"dim": 2,
"bounds": [(-10, 10), (-10, 10)],
"description": "f(x,y) = 0.26(x²+y²) - 0.48xy\nMinimum at (0,0)"
},
"Himmelblau's Function": {
"func": lambda x: (x[0]**2 + x[1] - 11)**2 + (x[0] + x[1]**2 - 7)**2,
"dim": 2,
"bounds": [(-5, 5), (-5, 5)],
"description": "f(x,y) = (x²+y-11)² + (x+y²-7)²\n4 minima at (3,2), (-2.8,3.1), (-3.8,-3.3), (3.6,-1.8)"
},
"Three-Hump Camel": {
"func": lambda x: 2*x[0]**2 - 1.05*x[0]**4 + x[0]**6/6 + x[0]*x[1] + x[1]**2,
"dim": 2,
"bounds": [(-5, 5), (-5, 5)],
"description": "f(x,y) = 2x² -1.05x⁴ + x⁶/6 + xy + y²\nMinimum at (0,0)"
},
"Easom Function": {
"func": lambda x: -np.cos(x[0])*np.cos(x[1])*np.exp(-((x[0]-np.pi)**2 + (x[1]-np.pi)**2)),
"dim": 2,
"bounds": [(-10, 10), (-10, 10)],
"description": "f(x,y) = -cos(x)cos(y)exp(-((x-π)²+(y-π)²))\nMinimum at (π,π)"
},
"Cross-in-Tray": {
"func": lambda x: -0.0001*(abs(np.sin(x[0])*np.sin(x[1])*np.exp(abs(100-np.sqrt(x[0]**2+x[1]**2)/np.pi))) + 1)**0.1,
"dim": 2,
"bounds": [(-10, 10), (-10, 10)],
"description": "Multiple global minima in cross pattern"
},
"Holder Table": {
"func": lambda x: -abs(np.sin(x[0])*np.cos(x[1])*np.exp(abs(1-np.sqrt(x[0]**2+x[1]**2)/np.pi))),
"dim": 2,
"bounds": [(-10, 10), (-10, 10)],
"description": "Multiple minima in table-like pattern"
},
# 3D Equations
"Sphere 3D": {
"func": lambda x: sum(xi**2 for xi in x),
"dim": 3,
"bounds": [(-5.12, 5.12), (-5.12, 5.12), (-5.12, 5.12)],
"description": "f(x,y,z) = x² + y² + z²\nMinimum at (0,0,0)"
},
"Rosenbrock 3D": {
"func": lambda x: sum(100*(x[i+1]-x[i]**2)**2 + (1-x[i])**2 for i in range(len(x)-1)),
"dim": 3,
"bounds": [(-2, 2), (-2, 2), (-2, 2)],
"description": "3D extension of Rosenbrock\nMinimum at (1,1,1)"
},
"Rastrigin 3D": {
"func": lambda x: 30 + sum(xi**2 - 10*np.cos(2*np.pi*xi) for xi in x),
"dim": 3,
"bounds": [(-5.12, 5.12), (-5.12, 5.12), (-5.12, 5.12)],
"description": "3D Rastrigin function\nMinimum at (0,0,0)"
},
"Ackley 3D": {
"func": lambda x: -20*np.exp(-0.2*np.sqrt(1/3*sum(xi**2 for xi in x))) -
np.exp(1/3*sum(np.cos(2*np.pi*xi) for xi in x)) + 20 + np.e,
"dim": 3,
"bounds": [(-5, 5), (-5, 5), (-5, 5)],
"description": "3D Ackley function\nMinimum at (0,0,0)"
},
"Sum of Different Powers": {
"func": lambda x: sum(abs(xi)**(i+2) for i, xi in enumerate(x)),
"dim": 3,
"bounds": [(-1, 1), (-1, 1), (-1, 1)],
"description": "f(x,y,z) = |x|² + |y|³ + |z|⁴\nMinimum at (0,0,0)"
},
"Rotated Hyper-Ellipsoid": {
"func": lambda x: sum(sum(x[j]**2 for j in range(i+1)) for i in range(len(x))),
"dim": 3,
"bounds": [(-5.12, 5.12), (-5.12, 5.12), (-5.12, 5.12)],
"description": "f(x,y,z) = x² + (x²+y²) + (x²+y²+z²)\nMinimum at (0,0,0)"
},
"Zakharov 3D": {
"func": lambda x: sum(xi**2 for xi in x) + (0.5*sum(i*xi for i, xi in enumerate(x, 1)))**2 + (0.5*sum(i*xi for i, xi in enumerate(x, 1)))**4,
"dim": 3,
"bounds": [(-5, 10), (-5, 10), (-5, 10)],
"description": "Zakharov function in 3D\nMinimum at (0,0,0)"
},
"Dixon-Price": {
"func": lambda x: (x[0]-1)**2 + sum(i*(2*x[i]**2 - x[i-1])**2 for i in range(1, len(x))),
"dim": 3,
"bounds": [(-10, 10), (-10, 10), (-10, 10)],
"description": "Dixon-Price function\nMinimum depends on dimension"
},
"Levy 3D": {
"func": lambda x: (
np.sin(np.pi * (1 + (x[0] - 1) / 4))**2 +
sum(
((1 + (x[i] - 1) / 4 - 1)**2 *
(1 + 10 * np.sin(np.pi * (1 + (x[i] - 1) / 4) + 1)**2))
for i in range(len(x) - 1)
) +
((1 + (x[-1] - 1) / 4 - 1)**2 *
(1 + np.sin(2 * np.pi * (1 + (x[-1] - 1) / 4))**2))
),
"dim": 3,
"bounds": [(-10, 10), (-10, 10), (-10, 10)],
"description": "Levy function in 3D\nMinimum at (1,1,1)"
},
"Michalewicz 3D": {
"func": lambda x: -sum(np.sin(x[i]) * np.sin((i+1)*x[i]**2/np.pi)**20 for i in range(len(x))),
"dim": 3,
"bounds": [(0, np.pi), (0, np.pi), (0, np.pi)],
"description": "Michalewicz function\nMany local minima, hard global optimization"
}
}
return equations
class PlotCanvas(FigureCanvas):
def __init__(self, parent=None, width=5, height=4, dpi=100, is_3d=False):
self.fig = Figure(figsize=(width, height), dpi=dpi)
super().__init__(self.fig)
self.setParent(parent)
self.is_3d = is_3d
if is_3d:
self.ax = self.fig.add_subplot(111, projection='3d')
else:
self.ax = self.fig.add_subplot(111)
self.ax.grid(True, alpha=0.3)
def plot_optimization(self, equation_info, particles_history, current_iteration):
self.ax.clear()
if current_iteration >= len(particles_history):
return
current_data = particles_history[current_iteration]
positions = current_data['positions']
if equation_info['dim'] == 2:
self._plot_2d(equation_info, positions, current_data)
else:
if self.is_3d:
self._plot_3d(equation_info, positions, current_data)
else:
self._plot_3d_projection(equation_info, positions, current_data)
self.ax.set_title(f'Iteration {current_iteration + 1}\nBest Value: {current_data["global_best_value"]:.6f}')
self.draw()
def _plot_2d(self, equation_info, positions, current_data):
# Create contour plot of the function
bounds = equation_info['bounds']
x = np.linspace(bounds[0][0], bounds[0][1], 100)
y = np.linspace(bounds[1][0], bounds[1][1], 100)
X, Y = np.meshgrid(x, y)
Z = np.array([[equation_info['func']([xi, yi]) for xi in x] for yi in y])
# Plot contour
contour = self.ax.contour(X, Y, Z, levels=20, alpha=0.6)
self.ax.clabel(contour, inline=True, fontsize=8)
# Plot particles
particle_x = [p[0] for p in positions]
particle_y = [p[1] for p in positions]
self.ax.scatter(particle_x, particle_y, c='red', s=30, alpha=0.7, label='Particles')
# Plot global best
self.ax.scatter(current_data['global_best'][0], current_data['global_best'][1],
c='green', s=100, marker='*', label='Global Best')
self.ax.set_xlabel('X')
self.ax.set_ylabel('Y')
self.ax.legend()
def _plot_3d(self, equation_info, positions, current_data):
bounds = equation_info['bounds']
x = np.linspace(bounds[0][0], bounds[0][1], 30)
y = np.linspace(bounds[1][0], bounds[1][1], 30)
X, Y = np.meshgrid(x, y)
# For 3D functions, we'll fix the third dimension for visualization
if len(positions[0]) == 3:
fixed_z = current_data['global_best'][2] # Use best z value
Z = np.array([[equation_info['func']([xi, yi, fixed_z]) for xi in x] for yi in y])
# Plot surface
self.ax.plot_surface(X, Y, Z, cmap='viridis', alpha=0.6)
# Plot particles
particle_x = [p[0] for p in positions]
particle_y = [p[1] for p in positions]
particle_z = [equation_info['func']([p[0], p[1], fixed_z]) for p in positions]
self.ax.scatter(particle_x, particle_y, particle_z, c='red', s=30, alpha=0.7, label='Particles')
# Plot global best
best_x, best_y = current_data['global_best'][0], current_data['global_best'][1]
best_z = equation_info['func']([best_x, best_y, fixed_z])
self.ax.scatter([best_x], [best_y], [best_z], c='green', s=100, marker='*', label='Global Best')
self.ax.set_xlabel('X')
self.ax.set_ylabel('Y')
self.ax.set_zlabel('f(X,Y)')
self.ax.legend()
def _plot_3d_projection(self, equation_info, positions, current_data):
"""2D projection of 3D function by fixing one dimension"""
bounds = equation_info['bounds']
# Use the best position to determine which dimensions to fix
best_pos = current_data['global_best']
# Create a 2D projection by fixing one dimension
x = np.linspace(bounds[0][0], bounds[0][1], 100)
y = np.linspace(bounds[1][0], bounds[1][1], 100)
X, Y = np.meshgrid(x, y)
# Fix the third dimension at the best value
fixed_z = best_pos[2] if len(best_pos) > 2 else 0
Z = np.array([[equation_info['func']([xi, yi, fixed_z]) for xi in x] for yi in y])
# Plot contour
contour = self.ax.contour(X, Y, Z, levels=20, alpha=0.6)
self.ax.clabel(contour, inline=True, fontsize=8)
# Plot particles (only first two dimensions)
particle_x = [p[0] for p in positions]
particle_y = [p[1] for p in positions]
self.ax.scatter(particle_x, particle_y, c='red', s=30, alpha=0.7, label='Particles')
# Plot global best
self.ax.scatter(best_pos[0], best_pos[1], c='green', s=100, marker='*', label='Global Best')
self.ax.set_xlabel('X')
self.ax.set_ylabel('Y')
self.ax.set_title(f'3D Function Projection (Z fixed at {fixed_z:.3f})')
self.ax.legend()
class PSOApp(QMainWindow):
def __init__(self):
super().__init__()
self.equations = EquationDefinitions.get_equations()
self.current_pso = None
self.current_iteration = 0
self.timer = QTimer()
self.timer.timeout.connect(self.update_visualization)
self.init_ui()
def init_ui(self):
self.setWindowTitle("Particle Swarm Optimization - 20 Equations Solver")
self.setGeometry(100, 100, 1600, 1000)
# Central widget
central_widget = QWidget()
self.setCentralWidget(central_widget)
# Main layout
main_layout = QHBoxLayout(central_widget)
# Left panel for controls
left_panel = QWidget()
left_panel.setMaximumWidth(400)
left_layout = QVBoxLayout(left_panel)
# Equation selection
equation_group = QGroupBox("Equation Selection")
equation_layout = QVBoxLayout(equation_group)
self.equation_combo = QComboBox()
self.equation_combo.addItems(self.equations.keys())
self.equation_combo.currentTextChanged.connect(self.on_equation_changed)
equation_layout.addWidget(QLabel("Select Equation:"))
equation_layout.addWidget(self.equation_combo)
self.equation_desc = QTextEdit()
self.equation_desc.setMaximumHeight(100)
self.equation_desc.setReadOnly(True)
equation_layout.addWidget(QLabel("Description:"))
equation_layout.addWidget(self.equation_desc)
left_layout.addWidget(equation_group)
# PSO Parameters
params_group = QGroupBox("PSO Parameters")
params_layout = QGridLayout(params_group)
params_layout.addWidget(QLabel("Particles:"), 0, 0)
self.particles_spin = QSpinBox()
self.particles_spin.setRange(10, 100)
self.particles_spin.setValue(30)
params_layout.addWidget(self.particles_spin, 0, 1)
params_layout.addWidget(QLabel("Iterations:"), 1, 0)
self.iterations_spin = QSpinBox()
self.iterations_spin.setRange(10, 500)
self.iterations_spin.setValue(100)
params_layout.addWidget(self.iterations_spin, 1, 1)
params_layout.addWidget(QLabel("Inertia (w):"), 2, 0)
self.w_spin = QDoubleSpinBox()
self.w_spin.setRange(0.1, 1.0)
self.w_spin.setSingleStep(0.1)
self.w_spin.setValue(0.7)
params_layout.addWidget(self.w_spin, 2, 1)
params_layout.addWidget(QLabel("Cognitive (c1):"), 3, 0)
self.c1_spin = QDoubleSpinBox()
self.c1_spin.setRange(0.1, 2.0)
self.c1_spin.setSingleStep(0.1)
self.c1_spin.setValue(1.4)
params_layout.addWidget(self.c1_spin, 3, 1)
params_layout.addWidget(QLabel("Social (c2):"), 4, 0)
self.c2_spin = QDoubleSpinBox()
self.c2_spin.setRange(0.1, 2.0)
self.c2_spin.setSingleStep(0.1)
self.c2_spin.setValue(1.4)
params_layout.addWidget(self.c2_spin, 4, 1)
left_layout.addWidget(params_group)
# Control buttons
control_group = QGroupBox("Controls")
control_layout = QVBoxLayout(control_group)
self.run_button = QPushButton("Run PSO")
self.run_button.clicked.connect(self.run_pso)
control_layout.addWidget(self.run_button)
self.pause_button = QPushButton("Pause")
self.pause_button.clicked.connect(self.toggle_pause)
self.pause_button.setEnabled(False)
control_layout.addWidget(self.pause_button)
self.step_button = QPushButton("Step")
self.step_button.clicked.connect(self.step_forward)
self.step_button.setEnabled(False)
control_layout.addWidget(self.step_button)
self.reset_button = QPushButton("Reset")
self.reset_button.clicked.connect(self.reset)
control_layout.addWidget(self.reset_button)
left_layout.addWidget(control_group)
# Progress
progress_group = QGroupBox("Progress")
progress_layout = QVBoxLayout(progress_group)
self.progress_bar = QProgressBar()
self.progress_bar.setValue(0)
progress_layout.addWidget(self.progress_bar)
self.status_label = QLabel("Ready to optimize")
progress_layout.addWidget(self.status_label)
self.results_text = QTextEdit()
self.results_text.setMaximumHeight(150)
self.results_text.setReadOnly(True)
progress_layout.addWidget(self.results_text)
left_layout.addWidget(progress_group)
left_layout.addStretch()
# Right panel for visualizations
right_panel = QWidget()
right_layout = QVBoxLayout(right_panel)
# Create splitter for 2D and 3D plots
splitter = QSplitter(Qt.Vertical)
self.plot_2d = PlotCanvas(self, width=8, height=6, dpi=100, is_3d=False)
self.plot_3d = PlotCanvas(self, width=8, height=6, dpi=100, is_3d=True)
splitter.addWidget(self.plot_2d)
splitter.addWidget(self.plot_3d)
splitter.setSizes([500, 500])
right_layout.addWidget(splitter)
# Add panels to main layout
main_layout.addWidget(left_panel)
main_layout.addWidget(right_panel)
# Initialize with first equation
self.on_equation_changed(self.equation_combo.currentText())
def on_equation_changed(self, equation_name):
equation_info = self.equations[equation_name]
self.equation_desc.setText(equation_info['description'])
def run_pso(self):
try:
equation_name = self.equation_combo.currentText()
equation_info = self.equations[equation_name]
# Get PSO parameters
num_particles = self.particles_spin.value()
max_iterations = self.iterations_spin.value()
w = self.w_spin.value()
c1 = self.c1_spin.value()
c2 = self.c2_spin.value()
# Run PSO
self.current_pso = PSO(
objective_func=equation_info['func'],
dim=equation_info['dim'],
bounds=equation_info['bounds'],
num_particles=num_particles,
w=w, c1=c1, c2=c2
)
# Run optimization
best_position, best_value = self.current_pso.optimize(max_iterations)
# Display results
self.results_text.setText(
f"Optimization Complete!\n"
f"Best Position: {[f'{x:.6f}' for x in best_position]}\n"
f"Best Value: {best_value:.10f}\n"
f"Equation: {equation_name}"
)
# Setup visualization
self.current_iteration = 0
self.progress_bar.setMaximum(max_iterations - 1)
self.update_visualization()
# Enable controls
self.pause_button.setEnabled(True)
self.step_button.setEnabled(True)
self.run_button.setEnabled(False)
# Start animation timer
self.timer.start(100) # Update every 100ms
except Exception as e:
self.results_text.setText(f"Error during optimization: {str(e)}")
def toggle_pause(self):
if self.timer.isActive():
self.timer.stop()
self.pause_button.setText("Resume")
else:
self.timer.start(100)
self.pause_button.setText("Pause")
def step_forward(self):
if self.current_pso and self.current_iteration < len(self.current_pso.history) - 1:
self.current_iteration += 1
self.update_visualization()
def reset(self):
self.timer.stop()
self.current_pso = None
self.current_iteration = 0
self.progress_bar.setValue(0)
self.status_label.setText("Ready to optimize")
self.results_text.clear()
self.pause_button.setEnabled(False)
self.step_button.setEnabled(False)
self.run_button.setEnabled(True)
self.pause_button.setText("Pause")
# Clear plots
self.plot_2d.ax.clear()
self.plot_3d.ax.clear()
self.plot_2d.draw()
self.plot_3d.draw()
def update_visualization(self):
if not self.current_pso or self.current_iteration >= len(self.current_pso.history):
self.timer.stop()
self.status_label.setText("Optimization Complete!")
return
equation_name = self.equation_combo.currentText()
equation_info = self.equations[equation_name]
try:
# Update 2D plot
self.plot_2d.plot_optimization(equation_info, self.current_pso.history, self.current_iteration)
# Update 3D plot
self.plot_3d.plot_optimization(equation_info, self.current_pso.history, self.current_iteration)
# Update progress
self.progress_bar.setValue(self.current_iteration)
self.status_label.setText(f"Iteration {self.current_iteration + 1}/{len(self.current_pso.history)}")
self.current_iteration += 1
if self.current_iteration >= len(self.current_pso.history):
self.timer.stop()
self.status_label.setText("Optimization Complete!")
except Exception as e:
self.status_label.setText(f"Visualization error: {str(e)}")
self.timer.stop()
def main():
app = QApplication(sys.argv)
app.setStyle('Fusion') # Modern style
# Set application font
font = QFont("Arial", 10)
app.setFont(font)
window = PSOApp()
window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()