InΒ [Β ]:
InΒ [2]:
import numpy as np
import pandas as pd
import networkx as nx
import matplotlib.pyplot as plt
import seaborn as sns
import string
import os
import warnings
from collections import defaultdict
INDEX=20
# Suppress warnings for cleaner output
warnings.filterwarnings('ignore', category=DeprecationWarning)
warnings.filterwarnings('ignore', category=UserWarning)
# Set matplotlib style for better plots
try:
plt.style.use('seaborn-v0_8-darkgrid')
except:
plt.style.use('seaborn-darkgrid')
sns.set_palette("husl")
# ==================== HELPER FUNCTIONS ====================
def generate_two_letter_labels(n):
labels = []
for i in range(n):
first = i // 26
second = i % 26
label = string.ascii_uppercase[first] + string.ascii_uppercase[second]
labels.append(label)
return labels
# ==================== KRUSKAL ALGORITHM ====================
class UnionFind:
def __init__(self, n):
self.parent = list(range(n))
self.rank = [0] * n
def find(self, x):
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x])
return self.parent[x]
def union(self, x, y):
px, py = self.find(x), self.find(y)
if px == py:
return False
if self.rank[px] < self.rank[py]:
px, py = py, px
self.parent[py] = px
if self.rank[px] == self.rank[py]:
self.rank[px] += 1
return True
def kruskal_mst(dist_matrix):
n = len(dist_matrix)
edges = []
for i in range(n):
for j in range(i+1, n):
edges.append((dist_matrix[i][j], i, j))
edges.sort()
uf = UnionFind(n)
mst_edges = []
total_weight = 0
for weight, u, v in edges:
if uf.union(u, v):
mst_edges.append((u, v))
total_weight += weight
if len(mst_edges) == n - 1:
break
return total_weight, mst_edges
# ==================== PSO BASE CLASS ====================
"""
π PARTICLE SWARM OPTIMIZATION (PSO) - MAIN DIFFERENCES FROM ACO:
**ACO (Ant Colony Optimization):**
- Uses PHEROMONE trails (chemical signals)
- Ants build solutions following pheromone + heuristic
- Pheromone EVAPORATES over time
- Good solutions DEPOSIT more pheromone
- Probabilistic construction
**PSO (Particle Swarm Optimization):**
- Uses PARTICLE position & velocity (physics-inspired)
- Particles MOVE in search space
- Each particle has PERSONAL BEST (pbest)
- Swarm has GLOBAL BEST (gbest)
- Velocity updated by: INERTIA + COGNITIVE + SOCIAL
- Position updated by adding velocity
"""
class PSO:
def __init__(self, dist_matrix, n_particles=10, n_iterations=50, w=0.7, c1=1.5, c2=1.5):
"""
Initialize PSO Algorithm
**KEY PSO PARAMETERS (Different from ACO):**
- n_particles: Number of particles (like n_ants in ACO)
- n_iterations: Number of iterations
- **w (inertia weight)**: Momentum factor [0-1]
* High w = more exploration
* Low w = more exploitation
- **c1 (cognitive coefficient)**: Learning from own best [0-2]
* Trust in personal experience
- **c2 (social coefficient)**: Learning from swarm best [0-2]
* Trust in collective intelligence
**NO pheromone matrix like ACO!**
**NO alpha, beta, rho parameters!**
"""
self.dist_matrix = dist_matrix
self.n_nodes = len(dist_matrix)
self.n_particles = n_particles
self.n_iterations = n_iterations
# **PSO-SPECIFIC PARAMETERS (Not in ACO)**
self.w = w # Inertia weight
self.c1 = c1 # Cognitive coefficient
self.c2 = c2 # Social coefficient
# **PARTICLES instead of pheromone (Main difference!)**
self.particles = []
self.velocities = []
self.pbest_positions = [] # Personal best for each particle
self.pbest_costs = []
self.gbest_position = None # Global best
self.gbest_cost = float('inf')
def _initialize_particle(self, max_degree=None):
"""
Initialize a random particle (solution)
This is similar to ACO's ant construction but creates initial population
"""
raise NotImplementedError("Subclass must implement _initialize_particle")
def _calculate_cost(self, edges):
"""Calculate total cost of edges"""
return sum(self.dist_matrix[i][j] for i, j in edges)
def _apply_velocity_tsp(self, position, velocity, pbest, gbest):
"""
**CORE PSO MECHANISM for TSP Path (Different from ACO!)**
In PSO, we don't use pheromone probability like ACO.
Instead, we use VELOCITY to guide LOCAL SEARCH operations.
**PSO Update Rules:**
1. velocity = w*velocity + c1*r1*(pbest-position) + c2*r2*(gbest-position)
2. position = position + velocity
For discrete problems (TSP/MST), we interpret:
- velocity = intensity/number of local search operations
- position update = apply 2-opt or edge swap
**Guidance from pbest and gbest:**
- Extract good edges from pbest and gbest
- Bias local search to preserve these good edges
"""
# Convert velocity to number of local search operations
# Higher velocity = more operations
n_operations = min(5, max(1, int(abs(velocity) / 50) + 1))
# Reconstruct path from edges
path = self._edges_to_path(position)
if path is None:
return position
best_path = path.copy()
best_cost = self._calculate_cost(position)
# Extract good edges from pbest and gbest for guidance
good_edges = set()
if pbest:
good_edges.update(pbest)
if gbest:
good_edges.update(gbest)
# **Apply local search operations (guided by velocity)**
for _ in range(n_operations):
# 2-opt move
i = np.random.randint(1, len(path) - 1)
j = np.random.randint(i + 1, len(path))
# Reverse segment [i:j]
new_path = path[:i] + path[i:j][::-1] + path[j:]
new_edges = self._path_to_edges(new_path)
new_cost = self._calculate_cost(new_edges)
# **PSO guidance: prefer moves that include good edges**
edge_quality = sum(1 for e in new_edges if e in good_edges or (e[1], e[0]) in good_edges)
# Accept if improved or maintains good edges
if new_cost < best_cost:
best_path = new_path
best_cost = new_cost
return self._path_to_edges(best_path)
def _apply_velocity_mst(self, position, velocity, pbest, gbest):
"""
**CORE PSO MECHANISM for MST (Different from ACO!)**
Similar to TSP but uses edge swap instead of 2-opt
"""
n_operations = min(5, max(1, int(abs(velocity) / 50) + 1))
best_edges = position.copy()
best_cost = self._calculate_cost(position)
# Extract good edges from pbest and gbest
good_edges = set()
if pbest:
good_edges.update(pbest)
if gbest:
good_edges.update(gbest)
# **Apply edge swap operations (guided by velocity)**
for _ in range(n_operations):
# Remove random edge
if len(best_edges) < 2:
continue
edge_idx = np.random.randint(len(best_edges))
removed_edge = best_edges[edge_idx]
temp_edges = [e for i, e in enumerate(best_edges) if i != edge_idx]
# Check connectivity
G = nx.Graph()
G.add_edges_from(temp_edges)
if not nx.is_connected(G):
# Find components and reconnect
components = list(nx.connected_components(G))
if len(components) == 2:
comp1, comp2 = components
# Find best reconnection edge
best_reconnect = None
best_reconnect_cost = float('inf')
for u in comp1:
for v in comp2:
cost = self.dist_matrix[u][v]
# **PSO guidance: prefer edges from pbest/gbest**
if (u, v) in good_edges or (v, u) in good_edges:
cost *= 0.8 # Bias towards good edges
if cost < best_reconnect_cost:
best_reconnect_cost = cost
best_reconnect = (u, v)
if best_reconnect:
new_edges = temp_edges + [best_reconnect]
new_cost = self._calculate_cost(new_edges)
if new_cost < best_cost:
best_edges = new_edges
best_cost = new_cost
return best_edges
def _edges_to_path(self, edges):
"""Convert edges to path (for TSP)"""
if not edges:
return None
# Build adjacency
adj = defaultdict(list)
for i, j in edges:
adj[i].append(j)
adj[j].append(i)
# Start from any node
start = edges[0][0]
path = [start]
visited = {start}
current = start
while len(visited) < self.n_nodes:
next_nodes = [n for n in adj[current] if n not in visited]
if not next_nodes:
break
next_node = next_nodes[0]
path.append(next_node)
visited.add(next_node)
current = next_node
return path if len(path) == self.n_nodes else None
def _path_to_edges(self, path):
"""Convert path to edges"""
return [(path[i], path[i+1]) for i in range(len(path) - 1)]
def _update_velocity_and_position(self, particle_idx, apply_velocity_func):
"""
**MAIN PSO UPDATE MECHANISM (Completely different from ACO!)**
**ACO:** Uses pheromone trails
**PSO:** Uses velocity and personal/global best
**PSO Velocity Update Formula:**
v(t+1) = w*v(t) + c1*r1*(pbest - x) + c2*r2*(gbest - x)
Components:
1. **w*v(t)**: INERTIA - keep moving in same direction
2. **c1*r1*(pbest-x)**: COGNITIVE - pull towards personal best
3. **c2*r2*(gbest-x)**: SOCIAL - pull towards global best
Where:
- w = inertia weight
- c1 = cognitive coefficient (self-confidence)
- c2 = social coefficient (swarm confidence)
- r1, r2 = random numbers [0,1]
"""
position = self.particles[particle_idx]
velocity = self.velocities[particle_idx]
pbest = self.pbest_positions[particle_idx]
gbest = self.gbest_position
# **STEP 1: Calculate velocity components**
# Inertia component
inertia = self.w * velocity
# Cognitive component (learn from own best)
r1 = np.random.random()
cognitive_influence = self.c1 * r1 * (self.pbest_costs[particle_idx] -
self._calculate_cost(position))
# Social component (learn from swarm best)
r2 = np.random.random()
social_influence = self.c2 * r2 * (self.gbest_cost -
self._calculate_cost(position))
# **STEP 2: Update velocity**
# **This is the HEART of PSO (not in ACO!)**
new_velocity = inertia + cognitive_influence + social_influence
# **STEP 3: Update position using velocity**
# Apply velocity through local search
new_position = apply_velocity_func(position, new_velocity, pbest, gbest)
# **STEP 4: Update personal best if improved**
new_cost = self._calculate_cost(new_position)
if new_cost < self.pbest_costs[particle_idx]:
self.pbest_positions[particle_idx] = new_position
self.pbest_costs[particle_idx] = new_cost
# **STEP 5: Update global best if improved**
if new_cost < self.gbest_cost:
self.gbest_position = new_position
self.gbest_cost = new_cost
# Update particle state
self.particles[particle_idx] = new_position
self.velocities[particle_idx] = new_velocity
return new_cost
# ==================== PSO TSP PATH (FAIR - NO CYCLE) ====================
class PSO_TSP_Path(PSO):
"""
PSO for TSP Hamiltonian Path (n-1 edges, no cycle)
**Comparison with ACO_TSP_Path:**
- ACO: Builds path using pheromone probability
- PSO: Builds initial population, then moves particles using velocity
"""
def _initialize_particle(self, max_degree=None):
"""Initialize a random TSP path"""
start = np.random.randint(self.n_nodes)
tour = [start]
unvisited = list(range(self.n_nodes))
unvisited.remove(start)
degrees = defaultdict(int) if max_degree else None
if max_degree:
degrees[start] = 0
while unvisited:
current = tour[-1]
# Check degree constraint
if max_degree and degrees[current] >= max_degree:
return None, float('inf')
# Filter valid candidates
if max_degree:
candidates = [n for n in unvisited if degrees[n] < max_degree]
else:
candidates = unvisited
if not candidates:
return None, float('inf')
# **PSO: Random selection (no pheromone like ACO)**
# Initial population just needs diversity
distances = [self.dist_matrix[current][c] for c in candidates]
probs = 1.0 / (np.array(distances) + 1e-10)
probs = probs / probs.sum()
next_node = np.random.choice(candidates, p=probs)
tour.append(next_node)
unvisited.remove(next_node)
if max_degree:
degrees[current] += 1
degrees[next_node] += 1
# Convert to edges (PATH - no cycle back to start)
edges = [(tour[i], tour[i+1]) for i in range(len(tour)-1)]
cost = self._calculate_cost(edges)
return edges, cost
def run(self, max_degree=None):
"""
**PSO MAIN ALGORITHM (Different from ACO!)**
**ACO Algorithm:**
1. Initialize pheromone
2. For each iteration:
- Ants build solutions using pheromone probability
- Update pheromone (evaporate + deposit)
**PSO Algorithm:**
1. Initialize particle swarm (random solutions)
2. For each iteration:
- Update velocity (inertia + cognitive + social)
- Update position (apply velocity)
- Update pbest and gbest
"""
#print(f"[PSO] Initializing {self.n_particles} particles...")
# **INITIALIZATION PHASE**
for _ in range(self.n_particles):
position, cost = self._initialize_particle(max_degree)
if position is None:
# Retry if infeasible
for _ in range(5):
position, cost = self._initialize_particle(max_degree)
if position is not None:
break
if position is None:
position = []
cost = float('inf')
self.particles.append(position)
self.velocities.append(0.0) # Initial velocity = 0
self.pbest_positions.append(position)
self.pbest_costs.append(cost)
# Update global best
if cost < self.gbest_cost:
self.gbest_cost = cost
self.gbest_position = position
#print(f"[PSO] Initial best cost: {self.gbest_cost:.2f}")
# **MAIN PSO LOOP**
for iteration in range(self.n_iterations):
# **Update each particle**
for p in range(self.n_particles):
self._update_velocity_and_position(p, self._apply_velocity_tsp)
#if (iteration + 1) % 10 == 0:
#print(f"[PSO] Iteration {iteration+1}/{self.n_iterations}, "
#f"Best: {self.gbest_cost:.2f}")
return self.gbest_cost, self.gbest_position
# ==================== PSO MST ====================
class PSO_MST(PSO):
"""
PSO for Minimum Spanning Tree
**Comparison with ACO_MST:**
- ACO: Builds MST using pheromone on edges
- PSO: Builds initial MST population, then optimizes using velocity
"""
def _initialize_particle(self, max_degree=None):
"""Initialize a random MST"""
start = np.random.randint(self.n_nodes)
in_tree = {start}
edges = []
degrees = defaultdict(int) if max_degree else None
while len(in_tree) < self.n_nodes:
candidates = []
for node in in_tree:
if max_degree and degrees[node] >= max_degree:
continue
for next_node in range(self.n_nodes):
if next_node not in in_tree:
if max_degree is None or degrees[next_node] < max_degree:
candidates.append((node, next_node))
if not candidates:
return None, float('inf')
# **PSO: Select based on distance (no pheromone like ACO)**
distances = [self.dist_matrix[i][j] for i, j in candidates]
probs = 1.0 / (np.array(distances) + 1e-10)
probs = probs / probs.sum()
selected_idx = np.random.choice(len(candidates), p=probs)
selected_edge = candidates[selected_idx]
edges.append(selected_edge)
in_tree.add(selected_edge[1])
if max_degree:
degrees[selected_edge[0]] += 1
degrees[selected_edge[1]] += 1
cost = self._calculate_cost(edges)
return edges, cost
def run(self, max_degree=None):
"""Run PSO for MST"""
#print(f"[PSO] Initializing {self.n_particles} particles...")
# Initialize swarm
for _ in range(self.n_particles):
position, cost = self._initialize_particle(max_degree)
if position is None:
for _ in range(5):
position, cost = self._initialize_particle(max_degree)
if position is not None:
break
if position is None:
position = []
cost = float('inf')
self.particles.append(position)
self.velocities.append(0.0)
self.pbest_positions.append(position)
self.pbest_costs.append(cost)
if cost < self.gbest_cost:
self.gbest_cost = cost
self.gbest_position = position
#print(f"[PSO] Initial best cost: {self.gbest_cost:.2f}")
# Main PSO loop
for iteration in range(self.n_iterations):
for p in range(self.n_particles):
self._update_velocity_and_position(p, self._apply_velocity_mst)
#if (iteration + 1) % 10 == 0:
#print(f"[PSO] Iteration {iteration+1}/{self.n_iterations}, "
# f"Best: {self.gbest_cost:.2f}")
return self.gbest_cost, self.gbest_position
# ==================== VISUALIZATION ====================
def visualize_solution(dist_matrix, edges, node_labels, title, save_path=None):
"""Visualize graph solution"""
G = nx.Graph()
for i, label in enumerate(node_labels):
G.add_node(label)
for i, j in edges:
u = node_labels[i]
v = node_labels[j]
weight = dist_matrix[i][j]
G.add_edge(u, v, weight=weight)
plt.figure(figsize=(12, 10))
pos = nx.spring_layout(G, seed=42, k=2)
nx.draw_networkx_nodes(G, pos, node_color='lightblue',
node_size=800, alpha=0.9)
nx.draw_networkx_edges(G, pos, width=3, alpha=0.6, edge_color='darkblue')
nx.draw_networkx_labels(G, pos, font_size=10, font_weight='bold')
edge_labels = {(u, v): f'{data["weight"]:.0f}' for u, v, data in G.edges(data=True)}
nx.draw_networkx_edge_labels(G, pos, edge_labels, font_size=8)
plt.title(title, fontsize=16, fontweight='bold', pad=20)
plt.axis('off')
plt.tight_layout()
if save_path:
plt.savefig(save_path, dpi=150, bbox_inches='tight')
plt.close()
# ==================== LOAD DATASET ====================
def load_datasets_from_csv(num_vertices=10, datasetKolomMax=31, folder_name='results_pso'):
if not os.path.exists(folder_name):
os.makedirs(folder_name)
NF = f'dataset/dataset{num_vertices}.csv'
try:
df = pd.read_csv(NF)
expected_edges = num_vertices * (num_vertices - 1) // 2
datasets = []
for ii in range(1, datasetKolomMax):
COL = f'NamaFile{ii}'
if COL not in df.columns:
break
weights = df[COL].tolist()
if len(weights) < expected_edges:
break
G = nx.complete_graph(num_vertices)
for i, (u, v) in enumerate(G.edges()):
G[u][v]['weight'] = weights[i]
if num_vertices <= 20:
node_labels = [letter for letter in string.ascii_uppercase[:num_vertices]]
else:
node_labels = generate_two_letter_labels(num_vertices)
G = nx.relabel_nodes(G, {i: node_labels[i] for i in range(num_vertices)})
nodes = sorted(G.nodes())
n = len(nodes)
node_to_idx = {node: idx for idx, node in enumerate(nodes)}
dist_matrix = np.zeros((n, n))
for u, v, data in G.edges(data=True):
i = node_to_idx[u]
j = node_to_idx[v]
weight = data['weight']
dist_matrix[i][j] = weight
dist_matrix[j][i] = weight
datasets.append((dist_matrix, node_labels, G))
return datasets
except FileNotFoundError:
print(f"Creating sample dataset...")
os.makedirs('dataset', exist_ok=True)
n_edges = expected_edges
data = {f'NamaFile{i}': np.random.randint(10, 100, size=n_edges)
for i in range(1, min(datasetKolomMax, 11))}
df_new = pd.DataFrame(data)
df_new.insert(0, 'Nomor', range(1, n_edges + 1))
df_new.to_csv(NF, index=False)
return load_datasets_from_csv(num_vertices, datasetKolomMax, folder_name)
# ==================== MAIN EXECUTION ====================
if __name__ == "__main__":
print("="*80)
print("PSO COMPARISON: TSP PATH vs MST (SAME n-1 EDGES) "+str(INDEX)+" Vertex")
print("="*80)
# Configuration
num_vertices = INDEX
datasetKolomMax = 31
folder_name = 'results_pso'
# Load datasets
print("\nLoading datasets from CSV...")
datasets = load_datasets_from_csv(num_vertices, datasetKolomMax, folder_name)
if not datasets:
print("Error loading datasets!")
exit(1)
print(f"Loaded {len(datasets)} datasets with {num_vertices} vertices each")
# Initialize results
results = {
'Dataset': [],
'Kruskal': [],
'PSO_TSP_Path_Same': [],
'PSO_TSP_Path_Tuned': [],
'PSO_MST_Same': [],
'PSO_MST_Tuned': [],
'PSO_TSP_Path_Deg3_Same': [],
'PSO_TSP_Path_Deg3_Tuned': [],
'PSO_MST_Deg3_Same': [],
'PSO_MST_Deg3_Tuned': []
}
# Store first dataset solutions for visualization
first_solutions = {}
print("\n" + "="*80)
print("RUNNING EXPERIMENTS...")
print("="*80)
for idx, (dist_matrix, node_labels, G_complete) in enumerate(datasets):
print(f"\nDataset {idx+1}:")
results['Dataset'].append(f"D{idx+1}")
# Kruskal
kruskal_cost, kruskal_edges = kruskal_mst(dist_matrix)
results['Kruskal'].append(kruskal_cost)
print(f" Kruskal MST: {kruskal_cost:.2f} ({len(kruskal_edges)} edges)")
if idx == 0:
first_solutions['kruskal'] = (kruskal_edges, kruskal_cost)
# PSO TSP Path Same (w=0.7, c1=1.5, c2=1.5)
#print(f" Running PSO TSP Path Same...")
pso_tsp_same = PSO_TSP_Path(dist_matrix, n_particles=10, n_iterations=50,
w=0.7, c1=1.5, c2=1.5)
cost, edges = pso_tsp_same.run()
results['PSO_TSP_Path_Same'].append(cost)
print(f" PSO TSP Path Same: {cost:.2f} ({len(edges) if edges else 0} edges)")
if idx == 0 and edges:
first_solutions['tsp_same'] = (edges, cost)
# PSO TSP Path Tuned (w=0.6, c1=2.0, c2=1.0)
#print(f" Running PSO TSP Path Tuned...")
pso_tsp_tuned = PSO_TSP_Path(dist_matrix, n_particles=15, n_iterations=50,
w=0.6, c1=2.0, c2=1.0)
cost, edges = pso_tsp_tuned.run()
results['PSO_TSP_Path_Tuned'].append(cost)
print(f" PSO TSP Path Tuned: {cost:.2f} ({len(edges) if edges else 0} edges)")
# PSO MST Same
#print(f" Running PSO MST Same...")
pso_mst_same = PSO_MST(dist_matrix, n_particles=10, n_iterations=50,
w=0.7, c1=1.5, c2=1.5)
cost, edges = pso_mst_same.run()
results['PSO_MST_Same'].append(cost)
print(f" PSO MST Same: {cost:.2f} ({len(edges) if edges else 0} edges)")
if idx == 0 and edges:
first_solutions['mst_same'] = (edges, cost)
# PSO MST Tuned (w=0.8, c1=1.2, c2=1.8)
#print(f" Running PSO MST Tuned...")
pso_mst_tuned = PSO_MST(dist_matrix, n_particles=12, n_iterations=50,
w=0.8, c1=1.2, c2=1.8)
cost, edges = pso_mst_tuned.run()
results['PSO_MST_Tuned'].append(cost)
print(f" PSO MST Tuned: {cost:.2f} ({len(edges) if edges else 0} edges)")
# With Degree=3 constraints
for name, pso_class, params in [
('TSP_Path_Deg3_Same', PSO_TSP_Path, {'w': 0.7, 'c1': 1.5, 'c2': 1.5, 'n_particles': 10}),
('TSP_Path_Deg3_Tuned', PSO_TSP_Path, {'w': 0.6, 'c1': 2.0, 'c2': 1.0, 'n_particles': 15}),
('MST_Deg3_Same', PSO_MST, {'w': 0.7, 'c1': 1.5, 'c2': 1.5, 'n_particles': 10}),
('MST_Deg3_Tuned', PSO_MST, {'w': 0.8, 'c1': 1.2, 'c2': 1.8, 'n_particles': 12})
]:
#print(f" Running PSO {name}...")
pso = pso_class(dist_matrix, n_iterations=50, **params)
cost, edges = pso.run(max_degree=3)
if cost == float('inf'):
for _ in range(5):
cost, edges = pso.run(max_degree=3)
if cost != float('inf'):
break
results[f'PSO_{name}'].append(cost if cost != float('inf') else None)
edge_count = len(edges) if edges and cost != float('inf') else 0
print(f" PSO {name}: {cost:.2f} ({edge_count} edges)" if cost != float('inf')
else f" PSO {name}: No solution")
# Create DataFrame
df = pd.DataFrame(results)
print("\n" + "="*80)
print("RESULTS TABLE")
print("="*80)
print(df.to_string(index=False))
# Summary statistics
print("\n" + "="*80)
print("SUMMARY STATISTICS")
print("="*80)
def calc_stats(values):
clean_values = [v for v in values if v is not None and v != float('inf')]
if not clean_values:
return None, None, None, None
return (np.mean(clean_values), np.std(clean_values),
min(clean_values), max(clean_values))
summary_data = []
for col in df.columns:
if col != 'Dataset':
mean, std, min_val, max_val = calc_stats(df[col])
summary_data.append({
'Algorithm': col,
'Mean': mean,
'Std': std,
'Min': min_val,
'Max': max_val
})
summary = pd.DataFrame(summary_data)
print(summary.to_string(index=False))
# COMPARISON ANALYSIS
print("\n" + "="*80)
print("β
PSO COMPARISON ANALYSIS (n-1 edges)")
print("="*80)
kruskal_mean = summary.iloc[0]['Mean']
pso_tsp_path_same_mean = summary.iloc[1]['Mean']
pso_mst_same_mean = summary.iloc[3]['Mean']
print(f"\nEdge Count: n-1 = {num_vertices - 1} edges")
print(f"\n1. Kruskal (Optimal): {kruskal_mean:.2f}")
print(f"2. PSO TSP Path: {pso_tsp_path_same_mean:.2f}")
print(f"3. PSO MST: {pso_mst_same_mean:.2f}")
tsp_gap = ((pso_tsp_path_same_mean / kruskal_mean) - 1) * 100
mst_gap = ((pso_mst_same_mean / kruskal_mean) - 1) * 100
print(f"\nGap from Optimal:")
print(f" PSO TSP Path vs Kruskal: {tsp_gap:+.2f}%")
print(f" PSO MST vs Kruskal: {mst_gap:+.2f}%")
print(f"\nβ
Fair comparison karena:")
print(f" β’ Same edge count: {num_vertices - 1} edges")
print(f" β’ No cycle: TSP Path & MST both are paths/trees")
print(f" β’ Apple-to-apple: Both connect all nodes without cycle")
# VISUALIZE FIRST DATASET
print("\n" + "="*80)
print("VISUALIZING FIRST DATASET RESULTS")
print("="*80)
dist_matrix_1, node_labels_1, G_complete_1 = datasets[0]
# 1. Visualize Complete Graph
print("\n1. Visualizing Complete Graph...")
plt.figure(figsize=(12, 10))
pos = nx.spring_layout(G_complete_1, seed=42, k=2)
nx.draw_networkx_nodes(G_complete_1, pos, node_color='lightgray',
node_size=800, alpha=0.6)
nx.draw_networkx_edges(G_complete_1, pos, width=1, alpha=0.2,
edge_color='gray')
nx.draw_networkx_labels(G_complete_1, pos, font_size=10, font_weight='bold')
edge_labels = {(u, v): f'{data["weight"]:.0f}'
for u, v, data in G_complete_1.edges(data=True)
if data['weight'] < 50}
nx.draw_networkx_edge_labels(G_complete_1, pos, edge_labels, font_size=7)
plt.title('Complete Graph - Dataset 1 (All Edges)',
fontsize=16, fontweight='bold', pad=20)
plt.axis('off')
plt.tight_layout()
plt.savefig(f'{folder_name}/complete_graph.png', dpi=150, bbox_inches='tight')
print(" β Saved: complete_graph.png")
print(" π Displaying graph...")
plt.show() # β DISPLAY GRAPH
plt.close()
# 2. Visualize Kruskal MST
if 'kruskal' in first_solutions:
print("\n2. Visualizing Kruskal MST...")
edges, cost = first_solutions['kruskal']
visualize_solution(dist_matrix_1, edges, node_labels_1,
f'Kruskal MST - Dataset 1 (Weight={cost:.0f}, {len(edges)} edges)',
f'{folder_name}/kruskal_mst.png')
print(" β Saved: kruskal_mst.png")
print(" π Displaying graph...")
# Display the saved graph
img = plt.imread(f'{folder_name}/kruskal_mst.png')
plt.figure(figsize=(12, 10))
plt.imshow(img)
plt.axis('off')
plt.tight_layout()
plt.show()
plt.close()
# 3. Visualize PSO TSP Path
if 'tsp_same' in first_solutions:
print("\n3. Visualizing PSO TSP Path...")
edges, cost = first_solutions['tsp_same']
visualize_solution(dist_matrix_1, edges, node_labels_1,
f'PSO TSP Path - Dataset 1 (Weight={cost:.0f}, {len(edges)} edges)',
f'{folder_name}/pso_tsp_path.png')
print(" β Saved: pso_tsp_path.png")
print(" π Displaying graph...")
img = plt.imread(f'{folder_name}/pso_tsp_path.png')
plt.figure(figsize=(12, 10))
plt.imshow(img)
plt.axis('off')
plt.tight_layout()
plt.show()
plt.close()
# 4. Visualize PSO MST
if 'mst_same' in first_solutions:
print("\n4. Visualizing PSO MST...")
edges, cost = first_solutions['mst_same']
visualize_solution(dist_matrix_1, edges, node_labels_1,
f'PSO MST - Dataset 1 (Weight={cost:.0f}, {len(edges)} edges)',
f'{folder_name}/pso_mst.png')
print(" β Saved: pso_mst.png")
print(" π Displaying graph...")
img = plt.imread(f'{folder_name}/pso_mst.png')
plt.figure(figsize=(12, 10))
plt.imshow(img)
plt.axis('off')
plt.tight_layout()
plt.show()
plt.close()
# 5. Creating comparison plots
print("\n5. Creating comparison plots...")
fig, axes = plt.subplots(2, 2, figsize=(16, 12))
# Plot 1: Fair Comparison (n-1 edges)
ax1 = axes[0, 0]
fair_data = [
[v for v in df['Kruskal'].values if v is not None],
[v for v in df['PSO_TSP_Path_Same'].values if v is not None],
[v for v in df['PSO_MST_Same'].values if v is not None]
]
bp1 = ax1.boxplot(fair_data, labels=['Kruskal\n(Optimal)', 'PSO TSP Path\n(Same)', 'PSO MST\n(Same)'],
patch_artist=True)
colors = ['lightgreen', 'lightblue', 'lightcoral']
for patch, color in zip(bp1['boxes'], colors):
patch.set_facecolor(color)
ax1.set_title('β
Fair Comparison: PSO (All n-1 edges)',
fontsize=14, fontweight='bold')
ax1.set_ylabel('Total Weight', fontsize=12)
ax1.grid(True, alpha=0.3)
# Plot 2: With Degree Constraint
ax2 = axes[0, 1]
deg_data = [
[v for v in df['PSO_TSP_Path_Deg3_Same'].values
if v is not None and v != float('inf')],
[v for v in df['PSO_MST_Deg3_Same'].values
if v is not None and v != float('inf')]
]
if all(len(d) > 0 for d in deg_data):
bp2 = ax2.boxplot(deg_data, labels=['PSO TSP Path\nDeg3', 'PSO MST\nDeg3'],
patch_artist=True)
for patch, color in zip(bp2['boxes'], ['lightblue', 'lightcoral']):
patch.set_facecolor(color)
ax2.set_title('PSO with Degree Constraint = 3',
fontsize=14, fontweight='bold')
ax2.set_ylabel('Total Weight', fontsize=12)
ax2.grid(True, alpha=0.3)
else:
ax2.text(0.5, 0.5, 'Insufficient data for\nDegree=3 constraint',
ha='center', va='center', fontsize=12)
ax2.set_title('PSO with Degree Constraint = 3',
fontsize=14, fontweight='bold')
# Plot 3: Bar comparison
ax3 = axes[1, 0]
means = [m if m is not None else 0 for m in summary['Mean'].values[:5]]
colors_bar = ['green', 'blue', 'lightblue', 'purple', 'orchid']
bars = ax3.bar(range(len(means)), means, color=colors_bar, alpha=0.7)
ax3.set_xticks(range(len(means)))
ax3.set_xticklabels(['Kruskal', 'PSO TSP\nSame', 'PSO TSP\nTuned',
'PSO MST\nSame', 'PSO MST\nTuned'], fontsize=9)
ax3.set_title('PSO Mean Performance (n-1 edges)',
fontsize=14, fontweight='bold')
ax3.set_ylabel('Mean Weight', fontsize=12)
ax3.grid(True, alpha=0.3, axis='y')
for bar, val in zip(bars, means):
if val > 0:
ax3.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 5,
f'{val:.0f}', ha='center', va='bottom', fontsize=9)
# Plot 4: Edge count verification
ax4 = axes[1, 1]
edge_counts = [num_vertices - 1, num_vertices - 1, num_vertices - 1]
labels_edge = ['Kruskal', 'PSO TSP Path', 'PSO MST']
bars4 = ax4.bar(labels_edge, edge_counts,
color=['green', 'blue', 'purple'], alpha=0.7)
ax4.set_title('β
Edge Count Verification (Fair!)',
fontsize=14, fontweight='bold')
ax4.set_ylabel('Number of Edges', fontsize=12)
ax4.axhline(y=num_vertices - 1, color='r', linestyle='--',
label=f'n-1 = {num_vertices - 1}')
ax4.legend()
ax4.grid(True, alpha=0.3, axis='y')
for i, (label, count) in enumerate(zip(labels_edge, edge_counts)):
ax4.text(i, count + 0.1, str(count), ha='center', va='bottom',
fontsize=12, fontweight='bold')
plt.tight_layout()
plt.savefig(f'{folder_name}/pso_comparison.png', dpi=300, bbox_inches='tight')
print(" β Saved: pso_comparison.png")
print(" π Displaying comparison plots...")
plt.show() # β DISPLAY COMPARISON PLOTS
plt.close()
# Additional: PSO vs ACO Comparison Plot
print("\n6. Creating PSO vs ACO comparison...")
# Try to load ACO results if available
try:
df_aco = pd.read_csv('results/fair_aco_results.csv')
fig, axes = plt.subplots(1, 2, figsize=(16, 6))
# MST Comparison
ax1 = axes[0]
mst_comparison = [
[v for v in df['Kruskal'].values if v is not None],
[v for v in df_aco['ACO_MST_Same'].values if v is not None],
[v for v in df['PSO_MST_Same'].values if v is not None]
]
bp1 = ax1.boxplot(mst_comparison,
labels=['Kruskal\n(Optimal)', 'ACO MST', 'PSO MST'],
patch_artist=True)
colors = ['lightgreen', 'lightyellow', 'lightblue']
for patch, color in zip(bp1['boxes'], colors):
patch.set_facecolor(color)
ax1.set_title('MST Performance: Kruskal vs ACO vs PSO',
fontsize=14, fontweight='bold')
ax1.set_ylabel('Total Weight', fontsize=12)
ax1.grid(True, alpha=0.3)
# Calculate means for annotation
aco_mst_mean = np.mean([v for v in df_aco['ACO_MST_Same'].values
if v is not None])
pso_mst_mean = np.mean([v for v in df['PSO_MST_Same'].values
if v is not None])
kruskal_mean_val = np.mean([v for v in df['Kruskal'].values
if v is not None])
aco_gap = ((aco_mst_mean / kruskal_mean_val) - 1) * 100
pso_gap = ((pso_mst_mean / kruskal_mean_val) - 1) * 100
ax1.text(0.5, 0.98, f'ACO Gap: +{aco_gap:.2f}% | PSO Gap: +{pso_gap:.2f}%',
transform=ax1.transAxes, ha='center', va='top',
bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5))
# TSP Comparison
ax2 = axes[1]
tsp_comparison = [
[v for v in df_aco['ACO_TSP_Path_Same'].values if v is not None],
[v for v in df['PSO_TSP_Path_Same'].values if v is not None]
]
bp2 = ax2.boxplot(tsp_comparison, labels=['ACO TSP Path', 'PSO TSP Path'],
patch_artist=True)
colors = ['lightyellow', 'lightblue']
for patch, color in zip(bp2['boxes'], colors):
patch.set_facecolor(color)
ax2.set_title('TSP Path Performance: ACO vs PSO',
fontsize=14, fontweight='bold')
ax2.set_ylabel('Total Weight', fontsize=12)
ax2.grid(True, alpha=0.3)
# Calculate TSP gaps
aco_tsp_mean = np.mean([v for v in df_aco['ACO_TSP_Path_Same'].values
if v is not None])
pso_tsp_mean = np.mean([v for v in df['PSO_TSP_Path_Same'].values
if v is not None])
aco_tsp_gap = ((aco_tsp_mean / kruskal_mean_val) - 1) * 100
pso_tsp_gap = ((pso_tsp_mean / kruskal_mean_val) - 1) * 100
ax2.text(0.5, 0.98,
f'ACO Gap: +{aco_tsp_gap:.2f}% | PSO Gap: +{pso_tsp_gap:.2f}%',
transform=ax2.transAxes, ha='center', va='top',
bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5))
plt.tight_layout()
plt.savefig(f'{folder_name}/pso_vs_aco_comparison.png',
dpi=300, bbox_inches='tight')
print(" β Saved: pso_vs_aco_comparison.png")
print(" π Displaying PSO vs ACO comparison...")
plt.show() # β DISPLAY PSO vs ACO
plt.close()
except FileNotFoundError:
print(" β ACO results not found, skipping ACO vs PSO comparison")
# Save results
df.to_csv(f'{folder_name}/{str(INDEX)}pso_results.csv', index=False)
print("\n β Saved: pso_results.csv")
print("\n" + "="*80)
print("β
COMPLETED!")
print("="*80)
print(f"\nFiles saved to '{folder_name}/' folder:")
print(" π pso_results.csv - Numerical results")
print(" π pso_comparison.png - PSO comparison plots")
print(" π complete_graph.png - Full graph visualization")
print(" π³ kruskal_mst.png - Kruskal MST result")
print(" π€οΈ pso_tsp_path.png - PSO TSP Path result")
print(" π² pso_mst.png - PSO MST result")
if os.path.exists(f'{folder_name}/pso_vs_aco_comparison.png'):
print(" π pso_vs_aco_comparison.png - PSO vs ACO comparison")
print("\nπ‘ KEY FINDINGS:")
print(f" β’ All methods use n-1 = {num_vertices - 1} edges (FAIR!)")
print(f" β’ PSO TSP Path: NO cycle (like MST)")
print(f" β’ Kruskal: {kruskal_mean:.2f} (optimal)")
print(f" β’ PSO TSP Path: {pso_tsp_path_same_mean:.2f} ({tsp_gap:+.2f}%)")
print(f" β’ PSO MST: {pso_mst_same_mean:.2f} ({mst_gap:+.2f}%)")
print("\n㪠PSO CHARACTERISTICS:")
print(" β’ Uses velocity and position updates")
print(" β’ Personal best (pbest) + Global best (gbest)")
print(" β’ NO pheromone matrix (unlike ACO)")
print(" β’ Direct communication via gbest")
print(" β’ Iterative improvement approach")
================================================================================
PSO COMPARISON: TSP PATH vs MST (SAME n-1 EDGES) 20 Vertex
================================================================================
Loading datasets from CSV...
Loaded 30 datasets with 20 vertices each
================================================================================
RUNNING EXPERIMENTS...
================================================================================
Dataset 1:
Kruskal MST: 982.00 (19 edges)
PSO TSP Path Same: 1973.00 (19 edges)
PSO TSP Path Tuned: 2083.00 (19 edges)
PSO MST Same: 992.00 (19 edges)
PSO MST Tuned: 1003.00 (19 edges)
PSO TSP_Path_Deg3_Same: 2231.00 (19 edges)
PSO TSP_Path_Deg3_Tuned: 1913.00 (19 edges)
PSO MST_Deg3_Same: 1512.00 (19 edges)
PSO MST_Deg3_Tuned: 1001.00 (19 edges)
Dataset 2:
Kruskal MST: 903.00 (19 edges)
PSO TSP Path Same: 2531.00 (19 edges)
PSO TSP Path Tuned: 1870.00 (19 edges)
PSO MST Same: 948.00 (19 edges)
PSO MST Tuned: 912.00 (19 edges)
PSO TSP_Path_Deg3_Same: 2082.00 (19 edges)
PSO TSP_Path_Deg3_Tuned: 1950.00 (19 edges)
PSO MST_Deg3_Same: 914.00 (19 edges)
PSO MST_Deg3_Tuned: 1157.00 (19 edges)
Dataset 3:
Kruskal MST: 1682.00 (19 edges)
PSO TSP Path Same: 3220.00 (19 edges)
PSO TSP Path Tuned: 2901.00 (19 edges)
PSO MST Same: 1754.00 (19 edges)
PSO MST Tuned: 1855.00 (19 edges)
PSO TSP_Path_Deg3_Same: 3091.00 (19 edges)
PSO TSP_Path_Deg3_Tuned: 2832.00 (19 edges)
PSO MST_Deg3_Same: 1707.00 (19 edges)
PSO MST_Deg3_Tuned: 1948.00 (19 edges)
Dataset 4:
Kruskal MST: 1020.00 (19 edges)
PSO TSP Path Same: 2593.00 (19 edges)
PSO TSP Path Tuned: 2774.00 (19 edges)
PSO MST Same: 2028.00 (19 edges)
PSO MST Tuned: 1647.00 (19 edges)
PSO TSP_Path_Deg3_Same: 2612.00 (19 edges)
PSO TSP_Path_Deg3_Tuned: 2496.00 (19 edges)
PSO MST_Deg3_Same: 1780.00 (19 edges)
PSO MST_Deg3_Tuned: 1771.00 (19 edges)
Dataset 5:
Kruskal MST: 923.00 (19 edges)
PSO TSP Path Same: 2259.00 (19 edges)
PSO TSP Path Tuned: 2456.00 (19 edges)
PSO MST Same: 1593.00 (19 edges)
PSO MST Tuned: 1096.00 (19 edges)
PSO TSP_Path_Deg3_Same: 2023.00 (19 edges)
PSO TSP_Path_Deg3_Tuned: 1960.00 (19 edges)
PSO MST_Deg3_Same: 1259.00 (19 edges)
PSO MST_Deg3_Tuned: 1113.00 (19 edges)
Dataset 6:
Kruskal MST: 1440.00 (19 edges)
PSO TSP Path Same: 2513.00 (19 edges)
PSO TSP Path Tuned: 2334.00 (19 edges)
PSO MST Same: 1466.00 (19 edges)
PSO MST Tuned: 1440.00 (19 edges)
PSO TSP_Path_Deg3_Same: 2599.00 (19 edges)
PSO TSP_Path_Deg3_Tuned: 2614.00 (19 edges)
PSO MST_Deg3_Same: 1988.00 (19 edges)
PSO MST_Deg3_Tuned: 1586.00 (19 edges)
Dataset 7:
Kruskal MST: 1162.00 (19 edges)
PSO TSP Path Same: 2230.00 (19 edges)
PSO TSP Path Tuned: 2332.00 (19 edges)
PSO MST Same: 1314.00 (19 edges)
PSO MST Tuned: 1312.00 (19 edges)
PSO TSP_Path_Deg3_Same: 2195.00 (19 edges)
PSO TSP_Path_Deg3_Tuned: 2426.00 (19 edges)
PSO MST_Deg3_Same: 1393.00 (19 edges)
PSO MST_Deg3_Tuned: 1188.00 (19 edges)
Dataset 8:
Kruskal MST: 1230.00 (19 edges)
PSO TSP Path Same: 2254.00 (19 edges)
PSO TSP Path Tuned: 2456.00 (19 edges)
PSO MST Same: 1670.00 (19 edges)
PSO MST Tuned: 1230.00 (19 edges)
PSO TSP_Path_Deg3_Same: 2095.00 (19 edges)
PSO TSP_Path_Deg3_Tuned: 2369.00 (19 edges)
PSO MST_Deg3_Same: 1457.00 (19 edges)
PSO MST_Deg3_Tuned: 1233.00 (19 edges)
Dataset 9:
Kruskal MST: 1544.00 (19 edges)
PSO TSP Path Same: 2934.00 (19 edges)
PSO TSP Path Tuned: 2690.00 (19 edges)
PSO MST Same: 1822.00 (19 edges)
PSO MST Tuned: 1754.00 (19 edges)
PSO TSP_Path_Deg3_Same: 2716.00 (19 edges)
PSO TSP_Path_Deg3_Tuned: 2718.00 (19 edges)
PSO MST_Deg3_Same: 2359.00 (19 edges)
PSO MST_Deg3_Tuned: 1722.00 (19 edges)
Dataset 10:
Kruskal MST: 1634.00 (19 edges)
PSO TSP Path Same: 2965.00 (19 edges)
PSO TSP Path Tuned: 2596.00 (19 edges)
PSO MST Same: 1870.00 (19 edges)
PSO MST Tuned: 2117.00 (19 edges)
PSO TSP_Path_Deg3_Same: 2807.00 (19 edges)
PSO TSP_Path_Deg3_Tuned: 2724.00 (19 edges)
PSO MST_Deg3_Same: 1852.00 (19 edges)
PSO MST_Deg3_Tuned: 1677.00 (19 edges)
Dataset 11:
Kruskal MST: 987.00 (19 edges)
PSO TSP Path Same: 2327.00 (19 edges)
PSO TSP Path Tuned: 2038.00 (19 edges)
PSO MST Same: 1389.00 (19 edges)
PSO MST Tuned: 1289.00 (19 edges)
PSO TSP_Path_Deg3_Same: 2367.00 (19 edges)
PSO TSP_Path_Deg3_Tuned: 2074.00 (19 edges)
PSO MST_Deg3_Same: 1433.00 (19 edges)
PSO MST_Deg3_Tuned: 998.00 (19 edges)
Dataset 12:
Kruskal MST: 1192.00 (19 edges)
PSO TSP Path Same: 2266.00 (19 edges)
PSO TSP Path Tuned: 2355.00 (19 edges)
PSO MST Same: 1766.00 (19 edges)
PSO MST Tuned: 2301.00 (19 edges)
PSO TSP_Path_Deg3_Same: 2251.00 (19 edges)
PSO TSP_Path_Deg3_Tuned: 2209.00 (19 edges)
PSO MST_Deg3_Same: 1916.00 (19 edges)
PSO MST_Deg3_Tuned: 1678.00 (19 edges)
Dataset 13:
Kruskal MST: 949.00 (19 edges)
PSO TSP Path Same: 2040.00 (19 edges)
PSO TSP Path Tuned: 1879.00 (19 edges)
PSO MST Same: 1228.00 (19 edges)
PSO MST Tuned: 1041.00 (19 edges)
PSO TSP_Path_Deg3_Same: 1758.00 (19 edges)
PSO TSP_Path_Deg3_Tuned: 1991.00 (19 edges)
PSO MST_Deg3_Same: 949.00 (19 edges)
PSO MST_Deg3_Tuned: 1154.00 (19 edges)
Dataset 14:
Kruskal MST: 1298.00 (19 edges)
PSO TSP Path Same: 2626.00 (19 edges)
PSO TSP Path Tuned: 2480.00 (19 edges)
PSO MST Same: 1390.00 (19 edges)
PSO MST Tuned: 1780.00 (19 edges)
PSO TSP_Path_Deg3_Same: 2802.00 (19 edges)
PSO TSP_Path_Deg3_Tuned: 2624.00 (19 edges)
PSO MST_Deg3_Same: 1444.00 (19 edges)
PSO MST_Deg3_Tuned: 1844.00 (19 edges)
Dataset 15:
Kruskal MST: 1088.00 (19 edges)
PSO TSP Path Same: 1881.00 (19 edges)
PSO TSP Path Tuned: 2513.00 (19 edges)
PSO MST Same: 1088.00 (19 edges)
PSO MST Tuned: 1294.00 (19 edges)
PSO TSP_Path_Deg3_Same: 2389.00 (19 edges)
PSO TSP_Path_Deg3_Tuned: 2368.00 (19 edges)
PSO MST_Deg3_Same: 1424.00 (19 edges)
PSO MST_Deg3_Tuned: 1146.00 (19 edges)
Dataset 16:
Kruskal MST: 1087.00 (19 edges)
PSO TSP Path Same: 2769.00 (19 edges)
PSO TSP Path Tuned: 2561.00 (19 edges)
PSO MST Same: 1087.00 (19 edges)
PSO MST Tuned: 1103.00 (19 edges)
PSO TSP_Path_Deg3_Same: 2424.00 (19 edges)
PSO TSP_Path_Deg3_Tuned: 2383.00 (19 edges)
PSO MST_Deg3_Same: 1116.00 (19 edges)
PSO MST_Deg3_Tuned: 1103.00 (19 edges)
Dataset 17:
Kruskal MST: 1421.00 (19 edges)
PSO TSP Path Same: 2845.00 (19 edges)
PSO TSP Path Tuned: 2926.00 (19 edges)
PSO MST Same: 1844.00 (19 edges)
PSO MST Tuned: 1811.00 (19 edges)
PSO TSP_Path_Deg3_Same: 3095.00 (19 edges)
PSO TSP_Path_Deg3_Tuned: 2777.00 (19 edges)
PSO MST_Deg3_Same: 1507.00 (19 edges)
PSO MST_Deg3_Tuned: 1537.00 (19 edges)
Dataset 18:
Kruskal MST: 1259.00 (19 edges)
PSO TSP Path Same: 2636.00 (19 edges)
PSO TSP Path Tuned: 2667.00 (19 edges)
PSO MST Same: 1564.00 (19 edges)
PSO MST Tuned: 1750.00 (19 edges)
PSO TSP_Path_Deg3_Same: 2294.00 (19 edges)
PSO TSP_Path_Deg3_Tuned: 2429.00 (19 edges)
PSO MST_Deg3_Same: 1437.00 (19 edges)
PSO MST_Deg3_Tuned: 1259.00 (19 edges)
Dataset 19:
Kruskal MST: 1226.00 (19 edges)
PSO TSP Path Same: 2338.00 (19 edges)
PSO TSP Path Tuned: 2443.00 (19 edges)
PSO MST Same: 1470.00 (19 edges)
PSO MST Tuned: 1521.00 (19 edges)
PSO TSP_Path_Deg3_Same: 2602.00 (19 edges)
PSO TSP_Path_Deg3_Tuned: 2283.00 (19 edges)
PSO MST_Deg3_Same: 1501.00 (19 edges)
PSO MST_Deg3_Tuned: 1279.00 (19 edges)
Dataset 20:
Kruskal MST: 1124.00 (19 edges)
PSO TSP Path Same: 2226.00 (19 edges)
PSO TSP Path Tuned: 2314.00 (19 edges)
PSO MST Same: 1220.00 (19 edges)
PSO MST Tuned: 1124.00 (19 edges)
PSO TSP_Path_Deg3_Same: 1793.00 (19 edges)
PSO TSP_Path_Deg3_Tuned: 2401.00 (19 edges)
PSO MST_Deg3_Same: 1133.00 (19 edges)
PSO MST_Deg3_Tuned: 1124.00 (19 edges)
Dataset 21:
Kruskal MST: 1265.00 (19 edges)
PSO TSP Path Same: 2550.00 (19 edges)
PSO TSP Path Tuned: 2386.00 (19 edges)
PSO MST Same: 1981.00 (19 edges)
PSO MST Tuned: 2361.00 (19 edges)
PSO TSP_Path_Deg3_Same: 2502.00 (19 edges)
PSO TSP_Path_Deg3_Tuned: 2519.00 (19 edges)
PSO MST_Deg3_Same: 2025.00 (19 edges)
PSO MST_Deg3_Tuned: 1774.00 (19 edges)
Dataset 22:
Kruskal MST: 811.00 (19 edges)
PSO TSP Path Same: 2207.00 (19 edges)
PSO TSP Path Tuned: 2053.00 (19 edges)
PSO MST Same: 841.00 (19 edges)
PSO MST Tuned: 811.00 (19 edges)
PSO TSP_Path_Deg3_Same: 2122.00 (19 edges)
PSO TSP_Path_Deg3_Tuned: 1900.00 (19 edges)
PSO MST_Deg3_Same: 896.00 (19 edges)
PSO MST_Deg3_Tuned: 911.00 (19 edges)
Dataset 23:
Kruskal MST: 851.00 (19 edges)
PSO TSP Path Same: 2146.00 (19 edges)
PSO TSP Path Tuned: 1789.00 (19 edges)
PSO MST Same: 854.00 (19 edges)
PSO MST Tuned: 851.00 (19 edges)
PSO TSP_Path_Deg3_Same: 1812.00 (19 edges)
PSO TSP_Path_Deg3_Tuned: 1631.00 (19 edges)
PSO MST_Deg3_Same: 851.00 (19 edges)
PSO MST_Deg3_Tuned: 851.00 (19 edges)
Dataset 24:
Kruskal MST: 1233.00 (19 edges)
PSO TSP Path Same: 2733.00 (19 edges)
PSO TSP Path Tuned: 2680.00 (19 edges)
PSO MST Same: 1531.00 (19 edges)
PSO MST Tuned: 2266.00 (19 edges)
PSO TSP_Path_Deg3_Same: 3012.00 (19 edges)
PSO TSP_Path_Deg3_Tuned: 2621.00 (19 edges)
PSO MST_Deg3_Same: 1757.00 (19 edges)
PSO MST_Deg3_Tuned: 2117.00 (19 edges)
Dataset 25:
Kruskal MST: 1244.00 (19 edges)
PSO TSP Path Same: 2173.00 (19 edges)
PSO TSP Path Tuned: 2423.00 (19 edges)
PSO MST Same: 1438.00 (19 edges)
PSO MST Tuned: 1533.00 (19 edges)
PSO TSP_Path_Deg3_Same: 2138.00 (19 edges)
PSO TSP_Path_Deg3_Tuned: 2399.00 (19 edges)
PSO MST_Deg3_Same: 1429.00 (19 edges)
PSO MST_Deg3_Tuned: 1649.00 (19 edges)
Dataset 26:
Kruskal MST: 1331.00 (19 edges)
PSO TSP Path Same: 2253.00 (19 edges)
PSO TSP Path Tuned: 2404.00 (19 edges)
PSO MST Same: 1691.00 (19 edges)
PSO MST Tuned: 1514.00 (19 edges)
PSO TSP_Path_Deg3_Same: 2473.00 (19 edges)
PSO TSP_Path_Deg3_Tuned: 2536.00 (19 edges)
PSO MST_Deg3_Same: 1625.00 (19 edges)
PSO MST_Deg3_Tuned: 1556.00 (19 edges)
Dataset 27:
Kruskal MST: 1012.00 (19 edges)
PSO TSP Path Same: 2205.00 (19 edges)
PSO TSP Path Tuned: 2200.00 (19 edges)
PSO MST Same: 1223.00 (19 edges)
PSO MST Tuned: 1034.00 (19 edges)
PSO TSP_Path_Deg3_Same: 2208.00 (19 edges)
PSO TSP_Path_Deg3_Tuned: 2190.00 (19 edges)
PSO MST_Deg3_Same: 1077.00 (19 edges)
PSO MST_Deg3_Tuned: 1179.00 (19 edges)
Dataset 28:
Kruskal MST: 1389.00 (19 edges)
PSO TSP Path Same: 2851.00 (19 edges)
PSO TSP Path Tuned: 2521.00 (19 edges)
PSO MST Same: 1782.00 (19 edges)
PSO MST Tuned: 1803.00 (19 edges)
PSO TSP_Path_Deg3_Same: 2469.00 (19 edges)
PSO TSP_Path_Deg3_Tuned: 2601.00 (19 edges)
PSO MST_Deg3_Same: 1743.00 (19 edges)
PSO MST_Deg3_Tuned: 1988.00 (19 edges)
Dataset 29:
Kruskal MST: 1552.00 (19 edges)
PSO TSP Path Same: 3122.00 (19 edges)
PSO TSP Path Tuned: 2763.00 (19 edges)
PSO MST Same: 2411.00 (19 edges)
PSO MST Tuned: 1884.00 (19 edges)
PSO TSP_Path_Deg3_Same: 2650.00 (19 edges)
PSO TSP_Path_Deg3_Tuned: 3104.00 (19 edges)
PSO MST_Deg3_Same: 1907.00 (19 edges)
PSO MST_Deg3_Tuned: 2499.00 (19 edges)
Dataset 30:
Kruskal MST: 1044.00 (19 edges)
PSO TSP Path Same: 2386.00 (19 edges)
PSO TSP Path Tuned: 2065.00 (19 edges)
PSO MST Same: 1494.00 (19 edges)
PSO MST Tuned: 1235.00 (19 edges)
PSO TSP_Path_Deg3_Same: 2071.00 (19 edges)
PSO TSP_Path_Deg3_Tuned: 2358.00 (19 edges)
PSO MST_Deg3_Same: 1779.00 (19 edges)
PSO MST_Deg3_Tuned: 1179.00 (19 edges)
================================================================================
RESULTS TABLE
================================================================================
Dataset Kruskal PSO_TSP_Path_Same PSO_TSP_Path_Tuned PSO_MST_Same PSO_MST_Tuned PSO_TSP_Path_Deg3_Same PSO_TSP_Path_Deg3_Tuned PSO_MST_Deg3_Same PSO_MST_Deg3_Tuned
D1 982.0 1973.0 2083.0 992.0 1003.0 2231.0 1913.0 1512.0 1001.0
D2 903.0 2531.0 1870.0 948.0 912.0 2082.0 1950.0 914.0 1157.0
D3 1682.0 3220.0 2901.0 1754.0 1855.0 3091.0 2832.0 1707.0 1948.0
D4 1020.0 2593.0 2774.0 2028.0 1647.0 2612.0 2496.0 1780.0 1771.0
D5 923.0 2259.0 2456.0 1593.0 1096.0 2023.0 1960.0 1259.0 1113.0
D6 1440.0 2513.0 2334.0 1466.0 1440.0 2599.0 2614.0 1988.0 1586.0
D7 1162.0 2230.0 2332.0 1314.0 1312.0 2195.0 2426.0 1393.0 1188.0
D8 1230.0 2254.0 2456.0 1670.0 1230.0 2095.0 2369.0 1457.0 1233.0
D9 1544.0 2934.0 2690.0 1822.0 1754.0 2716.0 2718.0 2359.0 1722.0
D10 1634.0 2965.0 2596.0 1870.0 2117.0 2807.0 2724.0 1852.0 1677.0
D11 987.0 2327.0 2038.0 1389.0 1289.0 2367.0 2074.0 1433.0 998.0
D12 1192.0 2266.0 2355.0 1766.0 2301.0 2251.0 2209.0 1916.0 1678.0
D13 949.0 2040.0 1879.0 1228.0 1041.0 1758.0 1991.0 949.0 1154.0
D14 1298.0 2626.0 2480.0 1390.0 1780.0 2802.0 2624.0 1444.0 1844.0
D15 1088.0 1881.0 2513.0 1088.0 1294.0 2389.0 2368.0 1424.0 1146.0
D16 1087.0 2769.0 2561.0 1087.0 1103.0 2424.0 2383.0 1116.0 1103.0
D17 1421.0 2845.0 2926.0 1844.0 1811.0 3095.0 2777.0 1507.0 1537.0
D18 1259.0 2636.0 2667.0 1564.0 1750.0 2294.0 2429.0 1437.0 1259.0
D19 1226.0 2338.0 2443.0 1470.0 1521.0 2602.0 2283.0 1501.0 1279.0
D20 1124.0 2226.0 2314.0 1220.0 1124.0 1793.0 2401.0 1133.0 1124.0
D21 1265.0 2550.0 2386.0 1981.0 2361.0 2502.0 2519.0 2025.0 1774.0
D22 811.0 2207.0 2053.0 841.0 811.0 2122.0 1900.0 896.0 911.0
D23 851.0 2146.0 1789.0 854.0 851.0 1812.0 1631.0 851.0 851.0
D24 1233.0 2733.0 2680.0 1531.0 2266.0 3012.0 2621.0 1757.0 2117.0
D25 1244.0 2173.0 2423.0 1438.0 1533.0 2138.0 2399.0 1429.0 1649.0
D26 1331.0 2253.0 2404.0 1691.0 1514.0 2473.0 2536.0 1625.0 1556.0
D27 1012.0 2205.0 2200.0 1223.0 1034.0 2208.0 2190.0 1077.0 1179.0
D28 1389.0 2851.0 2521.0 1782.0 1803.0 2469.0 2601.0 1743.0 1988.0
D29 1552.0 3122.0 2763.0 2411.0 1884.0 2650.0 3104.0 1907.0 2499.0
D30 1044.0 2386.0 2065.0 1494.0 1235.0 2071.0 2358.0 1779.0 1179.0
================================================================================
SUMMARY STATISTICS
================================================================================
Algorithm Mean Std Min Max
Kruskal 1196.100000 228.205222 811.0 1682.0
PSO_TSP_Path_Same 2468.400000 337.863444 1881.0 3220.0
PSO_TSP_Path_Tuned 2398.400000 293.727379 1789.0 2926.0
PSO_MST_Same 1491.633333 366.947452 841.0 2411.0
PSO_MST_Tuned 1489.066667 435.326769 811.0 2361.0
PSO_TSP_Path_Deg3_Same 2389.433333 357.147466 1758.0 3095.0
PSO_TSP_Path_Deg3_Tuned 2380.000000 321.121472 1631.0 3104.0
PSO_MST_Deg3_Same 1505.666667 367.023190 851.0 2359.0
PSO_MST_Deg3_Tuned 1440.700000 395.284467 851.0 2499.0
================================================================================
β
PSO COMPARISON ANALYSIS (n-1 edges)
================================================================================
Edge Count: n-1 = 19 edges
1. Kruskal (Optimal): 1196.10
2. PSO TSP Path: 2468.40
3. PSO MST: 1491.63
Gap from Optimal:
PSO TSP Path vs Kruskal: +106.37%
PSO MST vs Kruskal: +24.71%
β
Fair comparison karena:
β’ Same edge count: 19 edges
β’ No cycle: TSP Path & MST both are paths/trees
β’ Apple-to-apple: Both connect all nodes without cycle
================================================================================
VISUALIZING FIRST DATASET RESULTS
================================================================================
1. Visualizing Complete Graph...
β Saved: complete_graph.png
π Displaying graph...
2. Visualizing Kruskal MST... β Saved: kruskal_mst.png π Displaying graph...
3. Visualizing PSO TSP Path... β Saved: pso_tsp_path.png π Displaying graph...
4. Visualizing PSO MST... β Saved: pso_mst.png π Displaying graph...
5. Creating comparison plots... β Saved: pso_comparison.png π Displaying comparison plots...
6. Creating PSO vs ACO comparison... β ACO results not found, skipping ACO vs PSO comparison β Saved: pso_results.csv ================================================================================ β COMPLETED! ================================================================================ Files saved to 'results_pso/' folder: π pso_results.csv - Numerical results π pso_comparison.png - PSO comparison plots π complete_graph.png - Full graph visualization π³ kruskal_mst.png - Kruskal MST result π€οΈ pso_tsp_path.png - PSO TSP Path result π² pso_mst.png - PSO MST result π‘ KEY FINDINGS: β’ All methods use n-1 = 19 edges (FAIR!) β’ PSO TSP Path: NO cycle (like MST) β’ Kruskal: 1196.10 (optimal) β’ PSO TSP Path: 2468.40 (+106.37%) β’ PSO MST: 1491.63 (+24.71%) π¬ PSO CHARACTERISTICS: β’ Uses velocity and position updates β’ Personal best (pbest) + Global best (gbest) β’ NO pheromone matrix (unlike ACO) β’ Direct communication via gbest β’ Iterative improvement approach
InΒ [Β ]: