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()