|
|
import sys |
|
|
import numpy as np |
|
|
import random |
|
|
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, |
|
|
QHBoxLayout, QPushButton, QTextEdit, QLabel, |
|
|
QTabWidget, QTableWidget, QTableWidgetItem, |
|
|
QHeaderView, QGroupBox, QSpinBox, QDoubleSpinBox, |
|
|
QFormLayout) |
|
|
from PyQt5.QtCore import Qt, QThread, pyqtSignal |
|
|
import matplotlib.pyplot as plt |
|
|
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas |
|
|
from matplotlib.figure import Figure |
|
|
|
|
|
class Particle: |
|
|
def __init__(self, dim, bounds): |
|
|
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.value = float('inf') |
|
|
|
|
|
def update_velocity(self, global_best_position, w=0.5, c1=1.5, c2=1.5): |
|
|
r1, r2 = random.random(), random.random() |
|
|
cognitive = c1 * r1 * (self.best_position - self.position) |
|
|
social = c2 * r2 * (global_best_position - self.position) |
|
|
self.velocity = w * self.velocity + cognitive + social |
|
|
|
|
|
def update_position(self, bounds): |
|
|
self.position += self.velocity |
|
|
|
|
|
for i in range(len(self.position)): |
|
|
if self.position[i] < bounds[i][0]: |
|
|
self.position[i] = bounds[i][0] |
|
|
elif self.position[i] > bounds[i][1]: |
|
|
self.position[i] = bounds[i][1] |
|
|
|
|
|
def evaluate(self, cost_function): |
|
|
self.value = cost_function(self.position) |
|
|
if self.value < self.best_value: |
|
|
self.best_value = self.value |
|
|
self.best_position = self.position.copy() |
|
|
|
|
|
class PSOThread(QThread): |
|
|
update_signal = pyqtSignal(str, int, float, list) |
|
|
finished_signal = pyqtSignal(list, list) |
|
|
|
|
|
def __init__(self, cost_function, bounds, num_particles=30, max_iter=100): |
|
|
super().__init__() |
|
|
self.cost_function = cost_function |
|
|
self.bounds = bounds |
|
|
self.num_particles = num_particles |
|
|
self.max_iter = max_iter |
|
|
self.dim = len(bounds) |
|
|
self.running = True |
|
|
|
|
|
def run(self): |
|
|
|
|
|
particles = [Particle(self.dim, self.bounds) for _ in range(self.num_particles)] |
|
|
global_best_position = particles[0].position.copy() |
|
|
global_best_value = float('inf') |
|
|
|
|
|
|
|
|
for particle in particles: |
|
|
particle.evaluate(self.cost_function) |
|
|
if particle.best_value < global_best_value: |
|
|
global_best_value = particle.best_value |
|
|
global_best_position = particle.best_position.copy() |
|
|
|
|
|
|
|
|
iteration_data = [] |
|
|
position_data = [] |
|
|
|
|
|
for iteration in range(self.max_iter): |
|
|
if not self.running: |
|
|
break |
|
|
|
|
|
for particle in particles: |
|
|
particle.update_velocity(global_best_position) |
|
|
particle.update_position(self.bounds) |
|
|
particle.evaluate(self.cost_function) |
|
|
|
|
|
if particle.best_value < global_best_value: |
|
|
global_best_value = particle.best_value |
|
|
global_best_position = particle.best_position.copy() |
|
|
|
|
|
|
|
|
iteration_data.append(iteration + 1) |
|
|
position_data.append(global_best_position.copy()) |
|
|
|
|
|
|
|
|
self.update_signal.emit( |
|
|
f"Iteration {iteration+1}/{self.max_iter}", |
|
|
iteration+1, |
|
|
global_best_value, |
|
|
global_best_position.tolist() |
|
|
) |
|
|
|
|
|
self.finished_signal.emit(iteration_data, position_data) |
|
|
|
|
|
def stop(self): |
|
|
self.running = False |
|
|
|
|
|
class CircuitExample: |
|
|
def __init__(self, example_num): |
|
|
self.example_num = example_num |
|
|
self.R1 = example_num |
|
|
self.V_out = example_num |
|
|
self.C1 = self.C2 = 1/(example_num + 1) |
|
|
self.L1 = self.L2 = 0.5 + 0.1 * example_num |
|
|
self.V1 = self.V2 = example_num |
|
|
self.alpha = self.R1 |
|
|
|
|
|
def get_description(self): |
|
|
return f""" |
|
|
Example {self.example_num}: |
|
|
- R1 = {self.R1} Ω |
|
|
- V_out(s) = {self.V_out} Ω |
|
|
- C1 = C2 = {self.C1:.3f}/s Ω |
|
|
- L1 = L2 = {self.L2:.3f}s Ω |
|
|
- V1 = V2 = {self.V1} V |
|
|
- α = {self.alpha} |
|
|
""" |
|
|
|
|
|
def theoretical_impedance(self, s): |
|
|
|
|
|
return self.alpha * s |
|
|
|
|
|
def cost_function(self, x): |
|
|
|
|
|
|
|
|
s_values = [0.1, 0.5, 1.0, 2.0, 5.0] |
|
|
error = 0 |
|
|
for s in s_values: |
|
|
theoretical = self.theoretical_impedance(s) |
|
|
estimated = x[0] * s |
|
|
error += (theoretical - estimated) ** 2 |
|
|
return error |
|
|
|
|
|
class MplCanvas(FigureCanvas): |
|
|
def __init__(self, parent=None, width=5, height=4, dpi=100): |
|
|
self.fig = Figure(figsize=(width, height), dpi=dpi) |
|
|
super().__init__(self.fig) |
|
|
self.setParent(parent) |
|
|
|
|
|
def plot_convergence(self, iterations, best_values): |
|
|
self.fig.clear() |
|
|
ax = self.fig.add_subplot(111) |
|
|
ax.plot(iterations, best_values, 'b-', linewidth=2) |
|
|
ax.set_xlabel('Iteration') |
|
|
ax.set_ylabel('Best Cost Value') |
|
|
ax.set_title('PSO Convergence') |
|
|
ax.grid(True) |
|
|
self.draw() |
|
|
|
|
|
def plot_parameter_evolution(self, iterations, parameters): |
|
|
self.fig.clear() |
|
|
ax = self.fig.add_subplot(111) |
|
|
for i in range(len(parameters[0])): |
|
|
param_values = [p[i] for p in parameters] |
|
|
ax.plot(iterations, param_values, label=f'Parameter {i+1}') |
|
|
ax.set_xlabel('Iteration') |
|
|
ax.set_ylabel('Parameter Value') |
|
|
ax.set_title('Parameter Evolution') |
|
|
ax.legend() |
|
|
ax.grid(True) |
|
|
self.draw() |
|
|
|
|
|
class PSOCircuitApp(QMainWindow): |
|
|
def __init__(self): |
|
|
super().__init__() |
|
|
self.examples = [CircuitExample(i+1) for i in range(10)] |
|
|
self.current_example = 0 |
|
|
self.pso_thread = None |
|
|
self.init_ui() |
|
|
|
|
|
def init_ui(self): |
|
|
self.setWindowTitle("PSO Circuit Analysis") |
|
|
self.setGeometry(100, 100, 1200, 800) |
|
|
|
|
|
|
|
|
central_widget = QWidget() |
|
|
self.setCentralWidget(central_widget) |
|
|
main_layout = QHBoxLayout(central_widget) |
|
|
|
|
|
|
|
|
left_panel = QVBoxLayout() |
|
|
|
|
|
|
|
|
example_group = QGroupBox("Circuit Examples") |
|
|
example_layout = QVBoxLayout() |
|
|
|
|
|
self.example_combo = QSpinBox() |
|
|
self.example_combo.setMinimum(1) |
|
|
self.example_combo.setMaximum(10) |
|
|
self.example_combo.valueChanged.connect(self.change_example) |
|
|
|
|
|
self.example_info = QTextEdit() |
|
|
self.example_info.setMaximumHeight(200) |
|
|
self.example_info.setReadOnly(True) |
|
|
|
|
|
example_layout.addWidget(QLabel("Select Example:")) |
|
|
example_layout.addWidget(self.example_combo) |
|
|
example_layout.addWidget(QLabel("Circuit Parameters:")) |
|
|
example_layout.addWidget(self.example_info) |
|
|
example_group.setLayout(example_layout) |
|
|
left_panel.addWidget(example_group) |
|
|
|
|
|
|
|
|
pso_group = QGroupBox("PSO Parameters") |
|
|
pso_layout = QFormLayout() |
|
|
|
|
|
self.num_particles_spin = QSpinBox() |
|
|
self.num_particles_spin.setMinimum(10) |
|
|
self.num_particles_spin.setMaximum(100) |
|
|
self.num_particles_spin.setValue(30) |
|
|
|
|
|
self.max_iter_spin = QSpinBox() |
|
|
self.max_iter_spin.setMinimum(10) |
|
|
self.max_iter_spin.setMaximum(500) |
|
|
self.max_iter_spin.setValue(100) |
|
|
|
|
|
self.w_spin = QDoubleSpinBox() |
|
|
self.w_spin.setMinimum(0.1) |
|
|
self.w_spin.setMaximum(2.0) |
|
|
self.w_spin.setValue(0.5) |
|
|
self.w_spin.setSingleStep(0.1) |
|
|
|
|
|
self.c1_spin = QDoubleSpinBox() |
|
|
self.c1_spin.setMinimum(0.1) |
|
|
self.c1_spin.setMaximum(3.0) |
|
|
self.c1_spin.setValue(1.5) |
|
|
self.c1_spin.setSingleStep(0.1) |
|
|
|
|
|
self.c2_spin = QDoubleSpinBox() |
|
|
self.c2_spin.setMinimum(0.1) |
|
|
self.c2_spin.setMaximum(3.0) |
|
|
self.c2_spin.setValue(1.5) |
|
|
self.c2_spin.setSingleStep(0.1) |
|
|
|
|
|
pso_layout.addRow("Number of Particles:", self.num_particles_spin) |
|
|
pso_layout.addRow("Maximum Iterations:", self.max_iter_spin) |
|
|
pso_layout.addRow("Inertia Weight (w):", self.w_spin) |
|
|
pso_layout.addRow("Cognitive Parameter (c1):", self.c1_spin) |
|
|
pso_layout.addRow("Social Parameter (c2):", self.c2_spin) |
|
|
|
|
|
pso_group.setLayout(pso_layout) |
|
|
left_panel.addWidget(pso_group) |
|
|
|
|
|
|
|
|
self.run_button = QPushButton("Run PSO") |
|
|
self.run_button.clicked.connect(self.run_pso) |
|
|
|
|
|
self.stop_button = QPushButton("Stop PSO") |
|
|
self.stop_button.clicked.connect(self.stop_pso) |
|
|
self.stop_button.setEnabled(False) |
|
|
|
|
|
left_panel.addWidget(self.run_button) |
|
|
left_panel.addWidget(self.stop_button) |
|
|
|
|
|
|
|
|
results_group = QGroupBox("Results") |
|
|
results_layout = QVBoxLayout() |
|
|
|
|
|
self.results_text = QTextEdit() |
|
|
self.results_text.setMaximumHeight(150) |
|
|
self.results_text.setReadOnly(True) |
|
|
|
|
|
results_layout.addWidget(self.results_text) |
|
|
results_group.setLayout(results_layout) |
|
|
left_panel.addWidget(results_group) |
|
|
|
|
|
|
|
|
main_layout.addLayout(left_panel, 1) |
|
|
|
|
|
|
|
|
right_panel = QVBoxLayout() |
|
|
|
|
|
|
|
|
self.plot_tabs = QTabWidget() |
|
|
|
|
|
|
|
|
self.convergence_canvas = MplCanvas(self, width=5, height=4, dpi=100) |
|
|
self.plot_tabs.addTab(self.convergence_canvas, "Convergence") |
|
|
|
|
|
|
|
|
self.param_canvas = MplCanvas(self, width=5, height=4, dpi=100) |
|
|
self.plot_tabs.addTab(self.param_canvas, "Parameter Evolution") |
|
|
|
|
|
right_panel.addWidget(self.plot_tabs) |
|
|
|
|
|
|
|
|
main_layout.addLayout(right_panel, 2) |
|
|
|
|
|
|
|
|
self.change_example(1) |
|
|
|
|
|
def change_example(self, value): |
|
|
self.current_example = value - 1 |
|
|
example = self.examples[self.current_example] |
|
|
self.example_info.setText(example.get_description()) |
|
|
self.results_text.clear() |
|
|
|
|
|
def run_pso(self): |
|
|
example = self.examples[self.current_example] |
|
|
|
|
|
|
|
|
bounds = [(0.1, 2 * example.alpha)] |
|
|
|
|
|
|
|
|
self.pso_thread = PSOThread( |
|
|
example.cost_function, |
|
|
bounds, |
|
|
self.num_particles_spin.value(), |
|
|
self.max_iter_spin.value() |
|
|
) |
|
|
|
|
|
|
|
|
self.pso_thread.update_signal.connect(self.update_progress) |
|
|
self.pso_thread.finished_signal.connect(self.pso_finished) |
|
|
|
|
|
|
|
|
self.run_button.setEnabled(False) |
|
|
self.stop_button.setEnabled(True) |
|
|
self.results_text.clear() |
|
|
self.results_text.append("Running PSO...") |
|
|
|
|
|
|
|
|
self.pso_thread.start() |
|
|
|
|
|
def stop_pso(self): |
|
|
if self.pso_thread and self.pso_thread.isRunning(): |
|
|
self.pso_thread.stop() |
|
|
self.pso_thread.wait() |
|
|
self.results_text.append("PSO stopped by user.") |
|
|
self.run_button.setEnabled(True) |
|
|
self.stop_button.setEnabled(False) |
|
|
|
|
|
def update_progress(self, status, iteration, best_value, best_position): |
|
|
example = self.examples[self.current_example] |
|
|
self.results_text.clear() |
|
|
self.results_text.append(f"Status: {status}") |
|
|
self.results_text.append(f"Best Cost: {best_value:.6f}") |
|
|
self.results_text.append(f"Estimated α: {best_position[0]:.4f}") |
|
|
self.results_text.append(f"Theoretical α: {example.alpha}") |
|
|
self.results_text.append(f"Error: {abs(best_position[0] - example.alpha):.4f}") |
|
|
|
|
|
def pso_finished(self, iterations, positions): |
|
|
example = self.examples[self.current_example] |
|
|
best_alpha = positions[-1][0] |
|
|
|
|
|
self.results_text.append("\n--- PSO Completed ---") |
|
|
self.results_text.append(f"Final Estimated α: {best_alpha:.4f}") |
|
|
self.results_text.append(f"Theoretical α: {example.alpha}") |
|
|
self.results_text.append(f"Absolute Error: {abs(best_alpha - example.alpha):.4f}") |
|
|
self.results_text.append(f"Relative Error: {abs(best_alpha - example.alpha)/example.alpha*100:.2f}%") |
|
|
|
|
|
|
|
|
best_values = [example.cost_function(p) for p in positions] |
|
|
self.convergence_canvas.plot_convergence(iterations, best_values) |
|
|
|
|
|
|
|
|
self.param_canvas.plot_parameter_evolution(iterations, positions) |
|
|
|
|
|
|
|
|
self.run_button.setEnabled(True) |
|
|
self.stop_button.setEnabled(False) |
|
|
|
|
|
if __name__ == "__main__": |
|
|
app = QApplication(sys.argv) |
|
|
window = PSOCircuitApp() |
|
|
window.show() |
|
|
sys.exit(app.exec_()) |