InΒ [Β ]:
# import warnings
# warnings.filterwarnings('ignore', category=DeprecationWarning)
# warnings.filterwarnings('ignore', message='Glyph.*missing from font')
InΒ [Β ]:
 
InΒ [1]:
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
from collections import defaultdict

INDEX=60

# ==================== 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

# ==================== ACO BASE CLASS ====================
class ACO:
    def __init__(self, dist_matrix, n_ants=10, n_iterations=100, alpha=1, beta=2, rho=0.5):
        self.dist_matrix = dist_matrix
        self.n_nodes = len(dist_matrix)
        self.n_ants = n_ants
        self.n_iterations = n_iterations
        self.alpha = alpha
        self.beta = beta
        self.rho = rho
        self.pheromone = np.ones((self.n_nodes, self.n_nodes))
        
    def _select_next_node(self, current, unvisited, max_degree=None, degrees=None):
        pheromone = self.pheromone[current, unvisited]
        heuristic = 1.0 / (self.dist_matrix[current, unvisited] + 1e-10)
        
        if max_degree is not None and degrees is not None:
            valid_mask = np.array([degrees[node] < max_degree for node in unvisited])
            if not np.any(valid_mask):
                return None
            pheromone = pheromone * valid_mask
            heuristic = heuristic * valid_mask
        
        probabilities = (pheromone ** self.alpha) * (heuristic ** self.beta)
        prob_sum = probabilities.sum()
        if prob_sum > 0:
            probabilities = probabilities / prob_sum
        else:
            return None
        
        return np.random.choice(unvisited, p=probabilities)
    
    def _update_pheromone(self, solutions, costs):
        self.pheromone *= (1 - self.rho)
        for solution, cost in zip(solutions, costs):
            for i, j in solution:
                self.pheromone[i][j] += 1.0 / cost
                self.pheromone[j][i] += 1.0 / cost

# ==================== ACO TSP PATH (FAIR - NO CYCLE) ====================
class ACO_TSP_Path(ACO):
    """TSP Hamiltonian Path - TIDAK kembali ke awal (n-1 edges) - FAIR"""
    
    def construct_solution(self, max_degree=None):
        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]
            next_node = self._select_next_node(current, unvisited, max_degree, degrees)
            
            if next_node is None:
                return None, float('inf')
            
            tour.append(next_node)
            unvisited.remove(next_node)
            
            if max_degree:
                degrees[current] += 1
                degrees[next_node] += 1
        
        # TIDAK kembali ke awal (PATH - FAIR COMPARISON)
        edges = [(tour[i], tour[i+1]) for i in range(len(tour)-1)]
        cost = sum(self.dist_matrix[i][j] for i, j in edges)
        
        return edges, cost
    
    def run(self, max_degree=None):
        best_cost = float('inf')
        best_solution = None
        
        for iteration in range(self.n_iterations):
            solutions = []
            costs = []
            
            for ant in range(self.n_ants):
                solution, cost = self.construct_solution(max_degree)
                if solution is not None:
                    solutions.append(solution)
                    costs.append(cost)
                    if cost < best_cost:
                        best_cost = cost
                        best_solution = solution
            
            if solutions:
                self._update_pheromone(solutions, costs)
        
        return best_cost, best_solution

# ==================== ACO MST ====================
class ACO_MST(ACO):
    def construct_solution(self, max_degree=None):
        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')
            
            probs = []
            for i, j in candidates:
                pheromone = self.pheromone[i][j]
                heuristic = 1.0 / (self.dist_matrix[i][j] + 1e-10)
                prob = (pheromone ** self.alpha) * (heuristic ** self.beta)
                probs.append(prob)
            
            probs = np.array(probs)
            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 = sum(self.dist_matrix[i][j] for i, j in edges)
        return edges, cost
    
    def run(self, max_degree=None):
        best_cost = float('inf')
        best_solution = None
        
        for iteration in range(self.n_iterations):
            solutions = []
            costs = []
            
            for ant in range(self.n_ants):
                solution, cost = self.construct_solution(max_degree)
                if solution is not None:
                    solutions.append(solution)
                    costs.append(cost)
                    if cost < best_cost:
                        best_cost = cost
                        best_solution = solution
            
            if solutions:
                self._update_pheromone(solutions, costs)
        
        return best_cost, best_solution

# ==================== VISUALIZATION ====================
def visualize_solution(dist_matrix, edges, node_labels, title, save_path=None):
    """Visualize graph solution"""
    G = nx.Graph()
    
    # Add all nodes
    for i, label in enumerate(node_labels):
        G.add_node(label)
    
    # Add solution edges
    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)
    
    # Draw nodes
    nx.draw_networkx_nodes(G, pos, node_color='lightblue', 
                           node_size=800, alpha=0.9)
    
    # Draw edges
    nx.draw_networkx_edges(G, pos, width=3, alpha=0.6, edge_color='darkblue')
    
    # Draw labels
    nx.draw_networkx_labels(G, pos, font_size=10, font_weight='bold')
    
    # Draw edge weights
    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.show()

# ==================== LOAD DATASET ====================
def load_datasets_from_csv(num_vertices=10, datasetKolomMax=31, folder_name='results'):
    if not os.path.exists(folder_name):
        os.makedirs(folder_name)
    
    NF = f'datasetNorm/dataset{num_vertices}_ideal.csv'
    print(f"Loading file: {NF}")
    
    try:
        # PERBAIKAN: Gunakan delimiter semicolon dan strip whitespace dari header
        df = pd.read_csv(NF, delimiter=';')
        df.columns = df.columns.str.strip()  # Hapus whitespace dari nama kolom
        
        print(f"Loaded CSV with {len(df)} rows and {len(df.columns)} columns")
        print(f"Columns: {df.columns.tolist()}")
        
        expected_edges = num_vertices * (num_vertices - 1) // 2
        print(f"Expected edges for {num_vertices} vertices: {expected_edges}")
        
        datasets = []
        
        for ii in range(1, datasetKolomMax):
            COL = f'NamaFile{ii}'
            
            if COL not in df.columns:
                print(f"Column {COL} not found, stopping at {ii-1} datasets")
                break
            
            weights = df[COL].tolist()
            
            if len(weights) < expected_edges:
                print(f"Not enough weights in {COL}: {len(weights)} < {expected_edges}")
                break
            
            # Create complete graph
            G = nx.complete_graph(num_vertices)
            
            for i, (u, v) in enumerate(G.edges()):
                if i < len(weights):  # Safety check
                    G[u][v]['weight'] = weights[i]
            
            # Generate node labels
            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)})
            
            # Convert to distance matrix
            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))
        
        print(f"Successfully loaded {len(datasets)} datasets")
        return datasets
    
    except FileNotFoundError:
        print(f"File not found: {NF}")
        print(f"Creating sample dataset...")
        os.makedirs('datasetNorm', exist_ok=True)
        
        n_edges = num_vertices * (num_vertices - 1) // 2
        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))
        
        # PERBAIKAN: Simpan dengan delimiter semicolon
        df_new.to_csv(NF, index=False, sep=';')
        
        return load_datasets_from_csv(num_vertices, datasetKolomMax, folder_name)
    
    except Exception as e:
        print(f"Error loading datasets: {e}")
        import traceback
        traceback.print_exc()
        return []

# ==================== MAIN EXECUTION ====================
if __name__ == "__main__":
    print("="*80)
    print("FAIR ACO COMPARISON: TSP PATH vs MST (SAME n-1 EDGES)")
    print("="*80)
    
    # Configuration
    num_vertices = INDEX
    datasetKolomMax = 31
    folder_name = 'results'
    
    # Load datasets
    print("\nLoading datasets from CSV...")
    datasets = load_datasets_from_csv(num_vertices, datasetKolomMax, folder_name)
    
    if not datasets:
        print("Error loading datasetsx!")
        exit(1)
    
    print(f"Loaded {len(datasets)} datasets with {num_vertices} vertices each")
    
    # Initialize results
    results = {
        'Dataset': [],
        'Kruskal': [],
        'ACO_TSP_Path_Same': [],
        'ACO_TSP_Path_Tuned': [],
        'ACO_MST_Same': [],
        'ACO_MST_Tuned': [],
        'ACO_TSP_Path_Deg3_Same': [],
        'ACO_TSP_Path_Deg3_Tuned': [],
        'ACO_MST_Deg3_Same': [],
        'ACO_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)
        
        # ACO TSP Path Same (β=2, ρ=0.5)
        aco_tsp_same = ACO_TSP_Path(dist_matrix, beta=2, rho=0.5)
        cost, edges = aco_tsp_same.run()
        results['ACO_TSP_Path_Same'].append(cost)
        print(f"  ACO TSP Path Same: {cost:.2f} ({len(edges) if edges else 0} edges)")
        
        if idx == 0 and edges:
            first_solutions['tsp_same'] = (edges, cost)
        
        # ACO TSP Path Tuned (β=2, ρ=0.3)
        aco_tsp_tuned = ACO_TSP_Path(dist_matrix, beta=2, rho=0.3)
        cost, edges = aco_tsp_tuned.run()
        results['ACO_TSP_Path_Tuned'].append(cost)
        print(f"  ACO TSP Path Tuned: {cost:.2f} ({len(edges) if edges else 0} edges)")
        
        # ACO MST Same
        aco_mst_same = ACO_MST(dist_matrix, beta=2, rho=0.5)
        cost, edges = aco_mst_same.run()
        results['ACO_MST_Same'].append(cost)
        print(f"  ACO MST Same: {cost:.2f} ({len(edges) if edges else 0} edges)")
        
        if idx == 0 and edges:
            first_solutions['mst_same'] = (edges, cost)
        
        # ACO MST Tuned
        aco_mst_tuned = ACO_MST(dist_matrix, beta=1, rho=0.1)
        cost, edges = aco_mst_tuned.run()
        results['ACO_MST_Tuned'].append(cost)
        print(f"  ACO MST Tuned: {cost:.2f} ({len(edges) if edges else 0} edges)")
        
        # With Degree=3 constraints
        for name, aco_class, params in [
            ('TSP_Path_Deg3_Same', ACO_TSP_Path, {'beta': 2, 'rho': 0.5}),
            ('TSP_Path_Deg3_Tuned', ACO_TSP_Path, {'beta': 2, 'rho': 0.3}),
            ('MST_Deg3_Same', ACO_MST, {'beta': 2, 'rho': 0.5}),
            ('MST_Deg3_Tuned', ACO_MST, {'beta': 1, 'rho': 0.1})
        ]:
            aco = aco_class(dist_matrix, **params)
            cost, edges = aco.run(max_degree=3)
            
            if cost == float('inf'):
                for _ in range(5):
                    cost, edges = aco.run(max_degree=3)
                    if cost != float('inf'):
                        break
            
            results[f'ACO_{name}'].append(cost if cost != float('inf') else None)
            edge_count = len(edges) if edges and cost != float('inf') else 0
            print(f"  ACO {name}: {cost:.2f} ({edge_count} edges)" if cost != float('inf') else f"  ACO {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))
    
    # FAIR COMPARISON ANALYSIS
    print("\n" + "="*80)
    print("βœ… FAIR COMPARISON ANALYSIS (n-1 edges)")
    print("="*80)
    
    kruskal_mean = summary.iloc[0]['Mean']
    tsp_path_same_mean = summary.iloc[1]['Mean']
    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. ACO TSP Path:      {tsp_path_same_mean:.2f}")
    print(f"3. ACO MST:           {mst_same_mean:.2f}")
    
    tsp_gap = ((tsp_path_same_mean / kruskal_mean) - 1) * 100
    mst_gap = ((mst_same_mean / kruskal_mean) - 1) * 100
    
    print(f"\nGap from Optimal:")
    print(f"  TSP Path vs Kruskal: {tsp_gap:+.2f}%")
    print(f"  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]
    
    # 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')
    plt.show()
    
    # 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')
    
    # Visualize ACO TSP Path
    if 'tsp_same' in first_solutions:
        print("\n3. Visualizing ACO TSP Path...")
        edges, cost = first_solutions['tsp_same']
        visualize_solution(dist_matrix_1, edges, node_labels_1,
                         f'ACO TSP Path - Dataset 1 (Weight={cost:.0f}, {len(edges)} edges)',
                         f'{folder_name}/aco_tsp_path.png')
    
    # Visualize ACO MST
    if 'mst_same' in first_solutions:
        print("\n4. Visualizing ACO MST...")
        edges, cost = first_solutions['mst_same']
        visualize_solution(dist_matrix_1, edges, node_labels_1,
                         f'ACO MST - Dataset 1 (Weight={cost:.0f}, {len(edges)} edges)',
                         f'{folder_name}/aco_mst.png')
    
    # 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['ACO_TSP_Path_Same'].values if v is not None],
        [v for v in df['ACO_MST_Same'].values if v is not None]
    ]
    ax1.boxplot(fair_data, tick_labels=['Kruskal\n(Optimal)', 'TSP Path\n(Same)', 'MST\n(Same)'])
    ax1.set_title('βœ“ Fair Comparison (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['ACO_TSP_Path_Deg3_Same'].values if v is not None and v != float('inf')],
        [v for v in df['ACO_MST_Deg3_Same'].values if v is not None and v != float('inf')]
    ]
    if all(len(d) > 0 for d in deg_data):
        ax2.boxplot(deg_data, tick_labels=['TSP Path\nDeg3', 'MST\nDeg3'])
        ax2.set_title('Degree Constraint = 3', fontsize=14, fontweight='bold')
        ax2.set_ylabel('Total Weight', fontsize=12)
        ax2.grid(True, alpha=0.3)
    
    # 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 = ['green', 'blue', 'lightblue', 'purple', 'orchid']
    bars = ax3.bar(range(len(means)), means, color=colors, alpha=0.7)
    ax3.set_xticks(range(len(means)))
    ax3.set_xticklabels(['Kruskal', 'TSP Path\nSame', 'TSP Path\nTuned', 'MST\nSame', 'MST\nTuned'], fontsize=9)
    ax3.set_title('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', 'TSP Path', 'MST']
    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}/fair_comparison.png', dpi=300, bbox_inches='tight')
    plt.show()
    
    # Save results
    df.to_csv(f'{folder_name}/fair_aco_results.csv', index=False)
    
    print("\n" + "="*80)
    print("βœ… COMPLETED!")
    print("="*80)
    print(f"\nFiles saved to '{folder_name}/' folder:")
    print("  πŸ“Š fair_aco_results.csv - Numerical results")
    print("  πŸ“ˆ fair_comparison.png - Comparison plots")
    print("  🌐 complete_graph.png - Full graph visualization")
    print("  🌳 kruskal_mst.png - Kruskal MST result")
    print("  πŸ›€οΈ  aco_tsp_path.png - ACO TSP Path result")
    print("  🌲 aco_mst.png - ACO MST result")
    
    print("\nπŸ’‘ KEY FINDINGS:")
    print(f"  β€’ All methods use n-1 = {num_vertices - 1} edges (FAIR!)")
    print(f"  β€’ TSP Path: NO cycle (like MST)")
    print(f"  β€’ Kruskal: {kruskal_mean:.2f} (optimal)")
    print(f"  β€’ ACO TSP Path: {tsp_path_same_mean:.2f} ({tsp_gap:+.2f}%)")
    print(f"  β€’ ACO MST: {mst_same_mean:.2f} ({mst_gap:+.2f}%)")
================================================================================
FAIR ACO COMPARISON: TSP PATH vs MST (SAME n-1 EDGES)
================================================================================

Loading datasets from CSV...
Loading file: datasetNorm/dataset60_ideal.csv
Loaded CSV with 1770 rows and 31 columns
Columns: ['Nomor', 'NamaFile1', 'NamaFile2', 'NamaFile3', 'NamaFile4', 'NamaFile5', 'NamaFile6', 'NamaFile7', 'NamaFile8', 'NamaFile9', 'NamaFile10', 'NamaFile11', 'NamaFile12', 'NamaFile13', 'NamaFile14', 'NamaFile15', 'NamaFile16', 'NamaFile17', 'NamaFile18', 'NamaFile19', 'NamaFile20', 'NamaFile21', 'NamaFile22', 'NamaFile23', 'NamaFile24', 'NamaFile25', 'NamaFile26', 'NamaFile27', 'NamaFile28', 'NamaFile29', 'NamaFile30']
Expected edges for 60 vertices: 1770
Successfully loaded 30 datasets
Loaded 30 datasets with 60 vertices each

================================================================================
RUNNING EXPERIMENTS...
================================================================================

Dataset 1:
  Kruskal MST: 986.00 (59 edges)
  ACO TSP Path Same: 1155.00 (59 edges)
  ACO TSP Path Tuned: 1161.00 (59 edges)
  ACO MST Same: 1006.00 (59 edges)
  ACO MST Tuned: 1281.00 (59 edges)
  ACO TSP_Path_Deg3_Same: 1121.00 (59 edges)
  ACO TSP_Path_Deg3_Tuned: 1173.00 (59 edges)
  ACO MST_Deg3_Same: 1037.00 (59 edges)
  ACO MST_Deg3_Tuned: 1286.00 (59 edges)

Dataset 2:
  Kruskal MST: 1051.00 (59 edges)
  ACO TSP Path Same: 1241.00 (59 edges)
  ACO TSP Path Tuned: 1247.00 (59 edges)
  ACO MST Same: 1076.00 (59 edges)
  ACO MST Tuned: 1341.00 (59 edges)
  ACO TSP_Path_Deg3_Same: 1226.00 (59 edges)
  ACO TSP_Path_Deg3_Tuned: 1230.00 (59 edges)
  ACO MST_Deg3_Same: 1083.00 (59 edges)
  ACO MST_Deg3_Tuned: 1406.00 (59 edges)

Dataset 3:
  Kruskal MST: 949.00 (59 edges)
  ACO TSP Path Same: 1205.00 (59 edges)
  ACO TSP Path Tuned: 1201.00 (59 edges)
  ACO MST Same: 959.00 (59 edges)
  ACO MST Tuned: 1201.00 (59 edges)
  ACO TSP_Path_Deg3_Same: 1186.00 (59 edges)
  ACO TSP_Path_Deg3_Tuned: 1203.00 (59 edges)
  ACO MST_Deg3_Same: 992.00 (59 edges)
  ACO MST_Deg3_Tuned: 1288.00 (59 edges)

Dataset 4:
  Kruskal MST: 939.00 (59 edges)
  ACO TSP Path Same: 1149.00 (59 edges)
  ACO TSP Path Tuned: 1176.00 (59 edges)
  ACO MST Same: 949.00 (59 edges)
  ACO MST Tuned: 1258.00 (59 edges)
  ACO TSP_Path_Deg3_Same: 1159.00 (59 edges)
  ACO TSP_Path_Deg3_Tuned: 1181.00 (59 edges)
  ACO MST_Deg3_Same: 976.00 (59 edges)
  ACO MST_Deg3_Tuned: 1258.00 (59 edges)

Dataset 5:
  Kruskal MST: 1022.00 (59 edges)
  ACO TSP Path Same: 1216.00 (59 edges)
  ACO TSP Path Tuned: 1214.00 (59 edges)
  ACO MST Same: 1029.00 (59 edges)
  ACO MST Tuned: 1379.00 (59 edges)
  ACO TSP_Path_Deg3_Same: 1197.00 (59 edges)
  ACO TSP_Path_Deg3_Tuned: 1195.00 (59 edges)
  ACO MST_Deg3_Same: 1060.00 (59 edges)
  ACO MST_Deg3_Tuned: 1368.00 (59 edges)

Dataset 6:
  Kruskal MST: 1042.00 (59 edges)
  ACO TSP Path Same: 1184.00 (59 edges)
  ACO TSP Path Tuned: 1179.00 (59 edges)
  ACO MST Same: 1056.00 (59 edges)
  ACO MST Tuned: 1370.00 (59 edges)
  ACO TSP_Path_Deg3_Same: 1200.00 (59 edges)
  ACO TSP_Path_Deg3_Tuned: 1171.00 (59 edges)
  ACO MST_Deg3_Same: 1083.00 (59 edges)
  ACO MST_Deg3_Tuned: 1338.00 (59 edges)

Dataset 7:
  Kruskal MST: 884.00 (59 edges)
  ACO TSP Path Same: 1138.00 (59 edges)
  ACO TSP Path Tuned: 1144.00 (59 edges)
  ACO MST Same: 896.00 (59 edges)
  ACO MST Tuned: 1170.00 (59 edges)
  ACO TSP_Path_Deg3_Same: 1143.00 (59 edges)
  ACO TSP_Path_Deg3_Tuned: 1146.00 (59 edges)
  ACO MST_Deg3_Same: 940.00 (59 edges)
  ACO MST_Deg3_Tuned: 1154.00 (59 edges)

Dataset 8:
  Kruskal MST: 985.00 (59 edges)
  ACO TSP Path Same: 1169.00 (59 edges)
  ACO TSP Path Tuned: 1184.00 (59 edges)
  ACO MST Same: 1004.00 (59 edges)
  ACO MST Tuned: 1269.00 (59 edges)
  ACO TSP_Path_Deg3_Same: 1180.00 (59 edges)
  ACO TSP_Path_Deg3_Tuned: 1195.00 (59 edges)
  ACO MST_Deg3_Same: 1014.00 (59 edges)
  ACO MST_Deg3_Tuned: 1261.00 (59 edges)

Dataset 9:
  Kruskal MST: 985.00 (59 edges)
  ACO TSP Path Same: 1203.00 (59 edges)
  ACO TSP Path Tuned: 1181.00 (59 edges)
  ACO MST Same: 1010.00 (59 edges)
  ACO MST Tuned: 1339.00 (59 edges)
  ACO TSP_Path_Deg3_Same: 1186.00 (59 edges)
  ACO TSP_Path_Deg3_Tuned: 1193.00 (59 edges)
  ACO MST_Deg3_Same: 1010.00 (59 edges)
  ACO MST_Deg3_Tuned: 1199.00 (59 edges)

Dataset 10:
  Kruskal MST: 967.00 (59 edges)
  ACO TSP Path Same: 1187.00 (59 edges)
  ACO TSP Path Tuned: 1198.00 (59 edges)
  ACO MST Same: 981.00 (59 edges)
  ACO MST Tuned: 1205.00 (59 edges)
  ACO TSP_Path_Deg3_Same: 1189.00 (59 edges)
  ACO TSP_Path_Deg3_Tuned: 1172.00 (59 edges)
  ACO MST_Deg3_Same: 1016.00 (59 edges)
  ACO MST_Deg3_Tuned: 1276.00 (59 edges)

Dataset 11:
  Kruskal MST: 1043.00 (59 edges)
  ACO TSP Path Same: 1189.00 (59 edges)
  ACO TSP Path Tuned: 1219.00 (59 edges)
  ACO MST Same: 1064.00 (59 edges)
  ACO MST Tuned: 1364.00 (59 edges)
  ACO TSP_Path_Deg3_Same: 1194.00 (59 edges)
  ACO TSP_Path_Deg3_Tuned: 1198.00 (59 edges)
  ACO MST_Deg3_Same: 1080.00 (59 edges)
  ACO MST_Deg3_Tuned: 1374.00 (59 edges)

Dataset 12:
  Kruskal MST: 1059.00 (59 edges)
  ACO TSP Path Same: 1240.00 (59 edges)
  ACO TSP Path Tuned: 1217.00 (59 edges)
  ACO MST Same: 1078.00 (59 edges)
  ACO MST Tuned: 1378.00 (59 edges)
  ACO TSP_Path_Deg3_Same: 1222.00 (59 edges)
  ACO TSP_Path_Deg3_Tuned: 1230.00 (59 edges)
  ACO MST_Deg3_Same: 1104.00 (59 edges)
  ACO MST_Deg3_Tuned: 1340.00 (59 edges)

Dataset 13:
  Kruskal MST: 1059.00 (59 edges)
  ACO TSP Path Same: 1230.00 (59 edges)
  ACO TSP Path Tuned: 1243.00 (59 edges)
  ACO MST Same: 1082.00 (59 edges)
  ACO MST Tuned: 1394.00 (59 edges)
  ACO TSP_Path_Deg3_Same: 1206.00 (59 edges)
  ACO TSP_Path_Deg3_Tuned: 1230.00 (59 edges)
  ACO MST_Deg3_Same: 1110.00 (59 edges)
  ACO MST_Deg3_Tuned: 1363.00 (59 edges)

Dataset 14:
  Kruskal MST: 943.00 (59 edges)
  ACO TSP Path Same: 1178.00 (59 edges)
  ACO TSP Path Tuned: 1162.00 (59 edges)
  ACO MST Same: 948.00 (59 edges)
  ACO MST Tuned: 1205.00 (59 edges)
  ACO TSP_Path_Deg3_Same: 1203.00 (59 edges)
  ACO TSP_Path_Deg3_Tuned: 1155.00 (59 edges)
  ACO MST_Deg3_Same: 988.00 (59 edges)
  ACO MST_Deg3_Tuned: 1267.00 (59 edges)

Dataset 15:
  Kruskal MST: 943.00 (59 edges)
  ACO TSP Path Same: 1176.00 (59 edges)
  ACO TSP Path Tuned: 1171.00 (59 edges)
  ACO MST Same: 949.00 (59 edges)
  ACO MST Tuned: 1285.00 (59 edges)
  ACO TSP_Path_Deg3_Same: 1200.00 (59 edges)
  ACO TSP_Path_Deg3_Tuned: 1181.00 (59 edges)
  ACO MST_Deg3_Same: 991.00 (59 edges)
  ACO MST_Deg3_Tuned: 1284.00 (59 edges)

Dataset 16:
  Kruskal MST: 943.00 (59 edges)
  ACO TSP Path Same: 1164.00 (59 edges)
  ACO TSP Path Tuned: 1145.00 (59 edges)
  ACO MST Same: 954.00 (59 edges)
  ACO MST Tuned: 1223.00 (59 edges)
  ACO TSP_Path_Deg3_Same: 1180.00 (59 edges)
  ACO TSP_Path_Deg3_Tuned: 1185.00 (59 edges)
  ACO MST_Deg3_Same: 993.00 (59 edges)
  ACO MST_Deg3_Tuned: 1289.00 (59 edges)

Dataset 17:
  Kruskal MST: 1051.00 (59 edges)
  ACO TSP Path Same: 1170.00 (59 edges)
  ACO TSP Path Tuned: 1195.00 (59 edges)
  ACO MST Same: 1067.00 (59 edges)
  ACO MST Tuned: 1322.00 (59 edges)
  ACO TSP_Path_Deg3_Same: 1209.00 (59 edges)
  ACO TSP_Path_Deg3_Tuned: 1190.00 (59 edges)
  ACO MST_Deg3_Same: 1090.00 (59 edges)
  ACO MST_Deg3_Tuned: 1398.00 (59 edges)

Dataset 18:
  Kruskal MST: 1051.00 (59 edges)
  ACO TSP Path Same: 1203.00 (59 edges)
  ACO TSP Path Tuned: 1191.00 (59 edges)
  ACO MST Same: 1073.00 (59 edges)
  ACO MST Tuned: 1368.00 (59 edges)
  ACO TSP_Path_Deg3_Same: 1211.00 (59 edges)
  ACO TSP_Path_Deg3_Tuned: 1211.00 (59 edges)
  ACO MST_Deg3_Same: 1099.00 (59 edges)
  ACO MST_Deg3_Tuned: 1410.00 (59 edges)

Dataset 19:
  Kruskal MST: 1052.00 (59 edges)
  ACO TSP Path Same: 1291.00 (59 edges)
  ACO TSP Path Tuned: 1286.00 (59 edges)
  ACO MST Same: 1079.00 (59 edges)
  ACO MST Tuned: 1371.00 (59 edges)
  ACO TSP_Path_Deg3_Same: 1240.00 (59 edges)
  ACO TSP_Path_Deg3_Tuned: 1292.00 (59 edges)
  ACO MST_Deg3_Same: 1107.00 (59 edges)
  ACO MST_Deg3_Tuned: 1420.00 (59 edges)

Dataset 20:
  Kruskal MST: 1052.00 (59 edges)
  ACO TSP Path Same: 1289.00 (59 edges)
  ACO TSP Path Tuned: 1234.00 (59 edges)
  ACO MST Same: 1063.00 (59 edges)
  ACO MST Tuned: 1370.00 (59 edges)
  ACO TSP_Path_Deg3_Same: 1265.00 (59 edges)
  ACO TSP_Path_Deg3_Tuned: 1278.00 (59 edges)
  ACO MST_Deg3_Same: 1103.00 (59 edges)
  ACO MST_Deg3_Tuned: 1402.00 (59 edges)

Dataset 21:
  Kruskal MST: 1020.00 (59 edges)
  ACO TSP Path Same: 1206.00 (59 edges)
  ACO TSP Path Tuned: 1209.00 (59 edges)
  ACO MST Same: 1028.00 (59 edges)
  ACO MST Tuned: 1368.00 (59 edges)
  ACO TSP_Path_Deg3_Same: 1195.00 (59 edges)
  ACO TSP_Path_Deg3_Tuned: 1199.00 (59 edges)
  ACO MST_Deg3_Same: 1057.00 (59 edges)
  ACO MST_Deg3_Tuned: 1398.00 (59 edges)

Dataset 22:
  Kruskal MST: 943.00 (59 edges)
  ACO TSP Path Same: 1126.00 (59 edges)
  ACO TSP Path Tuned: 1095.00 (59 edges)
  ACO MST Same: 963.00 (59 edges)
  ACO MST Tuned: 1237.00 (59 edges)
  ACO TSP_Path_Deg3_Same: 1066.00 (59 edges)
  ACO TSP_Path_Deg3_Tuned: 1122.00 (59 edges)
  ACO MST_Deg3_Same: 966.00 (59 edges)
  ACO MST_Deg3_Tuned: 1205.00 (59 edges)

Dataset 23:
  Kruskal MST: 1136.00 (59 edges)
  ACO TSP Path Same: 1329.00 (59 edges)
  ACO TSP Path Tuned: 1358.00 (59 edges)
  ACO MST Same: 1165.00 (59 edges)
  ACO MST Tuned: 1502.00 (59 edges)
  ACO TSP_Path_Deg3_Same: 1350.00 (59 edges)
  ACO TSP_Path_Deg3_Tuned: 1352.00 (59 edges)
  ACO MST_Deg3_Same: 1193.00 (59 edges)
  ACO MST_Deg3_Tuned: 1566.00 (59 edges)

Dataset 24:
  Kruskal MST: 1136.00 (59 edges)
  ACO TSP Path Same: 1358.00 (59 edges)
  ACO TSP Path Tuned: 1350.00 (59 edges)
  ACO MST Same: 1159.00 (59 edges)
  ACO MST Tuned: 1506.00 (59 edges)
  ACO TSP_Path_Deg3_Same: 1359.00 (59 edges)
  ACO TSP_Path_Deg3_Tuned: 1357.00 (59 edges)
  ACO MST_Deg3_Same: 1183.00 (59 edges)
  ACO MST_Deg3_Tuned: 1547.00 (59 edges)

Dataset 25:
  Kruskal MST: 1075.00 (59 edges)
  ACO TSP Path Same: 1287.00 (59 edges)
  ACO TSP Path Tuned: 1262.00 (59 edges)
  ACO MST Same: 1090.00 (59 edges)
  ACO MST Tuned: 1432.00 (59 edges)
  ACO TSP_Path_Deg3_Same: 1253.00 (59 edges)
  ACO TSP_Path_Deg3_Tuned: 1239.00 (59 edges)
  ACO MST_Deg3_Same: 1110.00 (59 edges)
  ACO MST_Deg3_Tuned: 1440.00 (59 edges)

Dataset 26:
  Kruskal MST: 1018.00 (59 edges)
  ACO TSP Path Same: 1251.00 (59 edges)
  ACO TSP Path Tuned: 1254.00 (59 edges)
  ACO MST Same: 1031.00 (59 edges)
  ACO MST Tuned: 1324.00 (59 edges)
  ACO TSP_Path_Deg3_Same: 1238.00 (59 edges)
  ACO TSP_Path_Deg3_Tuned: 1254.00 (59 edges)
  ACO MST_Deg3_Same: 1061.00 (59 edges)
  ACO MST_Deg3_Tuned: 1310.00 (59 edges)

Dataset 27:
  Kruskal MST: 1018.00 (59 edges)
  ACO TSP Path Same: 1239.00 (59 edges)
  ACO TSP Path Tuned: 1272.00 (59 edges)
  ACO MST Same: 1027.00 (59 edges)
  ACO MST Tuned: 1343.00 (59 edges)
  ACO TSP_Path_Deg3_Same: 1245.00 (59 edges)
  ACO TSP_Path_Deg3_Tuned: 1237.00 (59 edges)
  ACO MST_Deg3_Same: 1070.00 (59 edges)
  ACO MST_Deg3_Tuned: 1364.00 (59 edges)

Dataset 28:
  Kruskal MST: 1035.00 (59 edges)
  ACO TSP Path Same: 1202.00 (59 edges)
  ACO TSP Path Tuned: 1204.00 (59 edges)
  ACO MST Same: 1047.00 (59 edges)
  ACO MST Tuned: 1356.00 (59 edges)
  ACO TSP_Path_Deg3_Same: 1184.00 (59 edges)
  ACO TSP_Path_Deg3_Tuned: 1210.00 (59 edges)
  ACO MST_Deg3_Same: 1065.00 (59 edges)
  ACO MST_Deg3_Tuned: 1297.00 (59 edges)

Dataset 29:
  Kruskal MST: 1035.00 (59 edges)
  ACO TSP Path Same: 1192.00 (59 edges)
  ACO TSP Path Tuned: 1196.00 (59 edges)
  ACO MST Same: 1059.00 (59 edges)
  ACO MST Tuned: 1401.00 (59 edges)
  ACO TSP_Path_Deg3_Same: 1192.00 (59 edges)
  ACO TSP_Path_Deg3_Tuned: 1195.00 (59 edges)
  ACO MST_Deg3_Same: 1062.00 (59 edges)
  ACO MST_Deg3_Tuned: 1281.00 (59 edges)

Dataset 30:
  Kruskal MST: 973.00 (59 edges)
  ACO TSP Path Same: 1173.00 (59 edges)
  ACO TSP Path Tuned: 1163.00 (59 edges)
  ACO MST Same: 988.00 (59 edges)
  ACO MST Tuned: 1225.00 (59 edges)
  ACO TSP_Path_Deg3_Same: 1204.00 (59 edges)
  ACO TSP_Path_Deg3_Tuned: 1196.00 (59 edges)
  ACO MST_Deg3_Same: 1012.00 (59 edges)
  ACO MST_Deg3_Tuned: 1249.00 (59 edges)

================================================================================
RESULTS TABLE
================================================================================
Dataset  Kruskal  ACO_TSP_Path_Same  ACO_TSP_Path_Tuned  ACO_MST_Same  ACO_MST_Tuned  ACO_TSP_Path_Deg3_Same  ACO_TSP_Path_Deg3_Tuned  ACO_MST_Deg3_Same  ACO_MST_Deg3_Tuned
     D1    986.0             1155.0              1161.0        1006.0         1281.0                  1121.0                   1173.0             1037.0              1286.0
     D2   1051.0             1241.0              1247.0        1076.0         1341.0                  1226.0                   1230.0             1083.0              1406.0
     D3    949.0             1205.0              1201.0         959.0         1201.0                  1186.0                   1203.0              992.0              1288.0
     D4    939.0             1149.0              1176.0         949.0         1258.0                  1159.0                   1181.0              976.0              1258.0
     D5   1022.0             1216.0              1214.0        1029.0         1379.0                  1197.0                   1195.0             1060.0              1368.0
     D6   1042.0             1184.0              1179.0        1056.0         1370.0                  1200.0                   1171.0             1083.0              1338.0
     D7    884.0             1138.0              1144.0         896.0         1170.0                  1143.0                   1146.0              940.0              1154.0
     D8    985.0             1169.0              1184.0        1004.0         1269.0                  1180.0                   1195.0             1014.0              1261.0
     D9    985.0             1203.0              1181.0        1010.0         1339.0                  1186.0                   1193.0             1010.0              1199.0
    D10    967.0             1187.0              1198.0         981.0         1205.0                  1189.0                   1172.0             1016.0              1276.0
    D11   1043.0             1189.0              1219.0        1064.0         1364.0                  1194.0                   1198.0             1080.0              1374.0
    D12   1059.0             1240.0              1217.0        1078.0         1378.0                  1222.0                   1230.0             1104.0              1340.0
    D13   1059.0             1230.0              1243.0        1082.0         1394.0                  1206.0                   1230.0             1110.0              1363.0
    D14    943.0             1178.0              1162.0         948.0         1205.0                  1203.0                   1155.0              988.0              1267.0
    D15    943.0             1176.0              1171.0         949.0         1285.0                  1200.0                   1181.0              991.0              1284.0
    D16    943.0             1164.0              1145.0         954.0         1223.0                  1180.0                   1185.0              993.0              1289.0
    D17   1051.0             1170.0              1195.0        1067.0         1322.0                  1209.0                   1190.0             1090.0              1398.0
    D18   1051.0             1203.0              1191.0        1073.0         1368.0                  1211.0                   1211.0             1099.0              1410.0
    D19   1052.0             1291.0              1286.0        1079.0         1371.0                  1240.0                   1292.0             1107.0              1420.0
    D20   1052.0             1289.0              1234.0        1063.0         1370.0                  1265.0                   1278.0             1103.0              1402.0
    D21   1020.0             1206.0              1209.0        1028.0         1368.0                  1195.0                   1199.0             1057.0              1398.0
    D22    943.0             1126.0              1095.0         963.0         1237.0                  1066.0                   1122.0              966.0              1205.0
    D23   1136.0             1329.0              1358.0        1165.0         1502.0                  1350.0                   1352.0             1193.0              1566.0
    D24   1136.0             1358.0              1350.0        1159.0         1506.0                  1359.0                   1357.0             1183.0              1547.0
    D25   1075.0             1287.0              1262.0        1090.0         1432.0                  1253.0                   1239.0             1110.0              1440.0
    D26   1018.0             1251.0              1254.0        1031.0         1324.0                  1238.0                   1254.0             1061.0              1310.0
    D27   1018.0             1239.0              1272.0        1027.0         1343.0                  1245.0                   1237.0             1070.0              1364.0
    D28   1035.0             1202.0              1204.0        1047.0         1356.0                  1184.0                   1210.0             1065.0              1297.0
    D29   1035.0             1192.0              1196.0        1059.0         1401.0                  1192.0                   1195.0             1062.0              1281.0
    D30    973.0             1173.0              1163.0         988.0         1225.0                  1204.0                   1196.0             1012.0              1249.0

================================================================================
SUMMARY STATISTICS
================================================================================
              Algorithm        Mean       Std    Min    Max
                Kruskal 1013.166667 57.856768  884.0 1136.0
      ACO_TSP_Path_Same 1211.333333 54.670732 1126.0 1358.0
     ACO_TSP_Path_Tuned 1210.366667 55.934178 1095.0 1358.0
           ACO_MST_Same 1029.333333 61.694588  896.0 1165.0
          ACO_MST_Tuned 1326.233333 84.142214 1170.0 1506.0
 ACO_TSP_Path_Deg3_Same 1206.766667 55.004050 1066.0 1359.0
ACO_TSP_Path_Deg3_Tuned 1212.333333 51.975849 1122.0 1357.0
      ACO_MST_Deg3_Same 1055.166667 59.403750  940.0 1193.0
     ACO_MST_Deg3_Tuned 1334.600000 91.885291 1154.0 1566.0

================================================================================
βœ… FAIR COMPARISON ANALYSIS (n-1 edges)
================================================================================

Edge Count: n-1 = 59 edges

1. Kruskal (Optimal): 1013.17
2. ACO TSP Path:      1211.33
3. ACO MST:           1029.33

Gap from Optimal:
  TSP Path vs Kruskal: +19.56%
  MST vs Kruskal:      +1.60%

βœ… Fair comparison karena:
  β€’ Same edge count: 59 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...
No description has been provided for this image
2. Visualizing Kruskal MST...
No description has been provided for this image
3. Visualizing ACO TSP Path...
No description has been provided for this image
4. Visualizing ACO MST...
No description has been provided for this image
5. Creating comparison plots...
No description has been provided for this image
================================================================================
βœ… COMPLETED!
================================================================================

Files saved to 'results/' folder:
  πŸ“Š fair_aco_results.csv - Numerical results
  πŸ“ˆ fair_comparison.png - Comparison plots
  🌐 complete_graph.png - Full graph visualization
  🌳 kruskal_mst.png - Kruskal MST result
  πŸ›€οΈ  aco_tsp_path.png - ACO TSP Path result
  🌲 aco_mst.png - ACO MST result

πŸ’‘ KEY FINDINGS:
  β€’ All methods use n-1 = 59 edges (FAIR!)
  β€’ TSP Path: NO cycle (like MST)
  β€’ Kruskal: 1013.17 (optimal)
  β€’ ACO TSP Path: 1211.33 (+19.56%)
  β€’ ACO MST: 1029.33 (+1.60%)
InΒ [Β ]: