Source code for porcupy.utils.visualization

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from typing import List, Tuple, Optional, Union, Dict, Any, Callable
import matplotlib.cm as cm


[docs]def plot_convergence(cost_history: List[float], title: str = "Convergence Curve", xlabel: str = "Iterations", ylabel: str = "Cost", figsize: Tuple[int, int] = (10, 6), log_scale: bool = False, save_path: Optional[str] = None): """ Plot the convergence history of the optimization process. Parameters ---------- cost_history : list List of cost values at each iteration. title : str, optional Title of the plot (default: "Convergence Curve"). xlabel : str, optional Label for the x-axis (default: "Iterations"). ylabel : str, optional Label for the y-axis (default: "Cost"). figsize : tuple, optional Figure size as (width, height) in inches (default: (10, 6)). log_scale : bool, optional Whether to use logarithmic scale for the y-axis (default: False). save_path : str, optional Path to save the figure. If None, the figure is not saved (default: None). Returns ------- matplotlib.figure.Figure The created figure. """ plt.figure(figsize=figsize) iterations = np.arange(1, len(cost_history) + 1) plt.plot(iterations, cost_history, 'b-', linewidth=2) plt.title(title) plt.xlabel(xlabel) plt.ylabel(ylabel) plt.grid(True, linestyle='--', alpha=0.7) if log_scale and np.all(np.array(cost_history) > 0): plt.yscale('log') if save_path: plt.savefig(save_path, dpi=300, bbox_inches='tight') return plt.gcf()
[docs]def plot_population_size(pop_size_history: List[int], title: str = "Population Size History", xlabel: str = "Iterations", ylabel: str = "Population Size", figsize: Tuple[int, int] = (10, 6), save_path: Optional[str] = None): """ Plot the population size history of the optimization process. Parameters ---------- pop_size_history : list List of population sizes at each iteration. title : str, optional Title of the plot (default: "Population Size History"). xlabel : str, optional Label for the x-axis (default: "Iterations"). ylabel : str, optional Label for the y-axis (default: "Population Size"). figsize : tuple, optional Figure size as (width, height) in inches (default: (10, 6)). save_path : str, optional Path to save the figure. If None, the figure is not saved (default: None). Returns ------- matplotlib.figure.Figure The created figure. """ plt.figure(figsize=figsize) iterations = np.arange(1, len(pop_size_history) + 1) plt.plot(iterations, pop_size_history, 'r-', linewidth=2) plt.title(title) plt.xlabel(xlabel) plt.ylabel(ylabel) plt.grid(True, linestyle='--', alpha=0.7) if save_path: plt.savefig(save_path, dpi=300, bbox_inches='tight') return plt.gcf()
[docs]def plot_2d_search_space(func: Callable, bounds: Tuple[np.ndarray, np.ndarray], resolution: int = 100, positions: Optional[np.ndarray] = None, best_pos: Optional[np.ndarray] = None, title: str = "2D Search Space", figsize: Tuple[int, int] = (10, 8), cmap: str = 'viridis', contour_levels: int = 20, save_path: Optional[str] = None): """ Plot a 2D search space with positions of the porcupines. Parameters ---------- func : callable The objective function to visualize. bounds : tuple A tuple (lb, ub) containing the lower and upper bounds. resolution : int, optional Resolution of the grid for visualization (default: 100). positions : ndarray, optional Current positions of the porcupines, shape (pop_size, 2). best_pos : ndarray, optional Global best position, shape (2,). title : str, optional Title of the plot (default: "2D Search Space"). figsize : tuple, optional Figure size as (width, height) in inches (default: (10, 8)). cmap : str, optional Colormap for the contour plot (default: 'viridis'). contour_levels : int, optional Number of contour levels (default: 20). save_path : str, optional Path to save the figure. If None, the figure is not saved (default: None). Returns ------- matplotlib.figure.Figure The created figure. """ if len(bounds[0]) != 2 or len(bounds[1]) != 2: raise ValueError("This function only works for 2D search spaces") lb, ub = bounds # Create a grid of points x = np.linspace(lb[0], ub[0], resolution) y = np.linspace(lb[1], ub[1], resolution) X, Y = np.meshgrid(x, y) # Evaluate the function at each point Z = np.zeros_like(X) for i in range(resolution): for j in range(resolution): Z[i, j] = func([X[i, j], Y[i, j]]) # Create the plot plt.figure(figsize=figsize) # Plot the contour contour = plt.contourf(X, Y, Z, contour_levels, cmap=cmap, alpha=0.8) plt.colorbar(contour, label='Cost') # Plot the positions if provided if positions is not None: plt.scatter(positions[:, 0], positions[:, 1], c='white', edgecolors='black', s=50, label='Porcupines') # Plot the best position if provided if best_pos is not None: plt.scatter(best_pos[0], best_pos[1], c='red', s=100, marker='*', label='Best Position') plt.title(title) plt.xlabel('x1') plt.ylabel('x2') plt.grid(True, linestyle='--', alpha=0.3) plt.legend() if save_path: plt.savefig(save_path, dpi=300, bbox_inches='tight') return plt.gcf()
[docs]def animate_optimization_2d(position_history: List[np.ndarray], func: Callable, bounds: Tuple[np.ndarray, np.ndarray], best_pos_history: Optional[List[np.ndarray]] = None, interval: int = 200, figsize: Tuple[int, int] = (10, 8), cmap: str = 'viridis', contour_levels: int = 20, save_path: Optional[str] = None, dpi: int = 100): """ Create an animation of the optimization process in 2D. Parameters ---------- position_history : list List of position arrays at each iteration, each with shape (pop_size, 2). func : callable The objective function to visualize. bounds : tuple A tuple (lb, ub) containing the lower and upper bounds. best_pos_history : list, optional List of best positions at each iteration, each with shape (2,). interval : int, optional Interval between frames in milliseconds (default: 200). figsize : tuple, optional Figure size as (width, height) in inches (default: (10, 8)). cmap : str, optional Colormap for the contour plot (default: 'viridis'). contour_levels : int, optional Number of contour levels (default: 20). save_path : str, optional Path to save the animation. If None, the animation is not saved (default: None). dpi : int, optional DPI for the saved animation (default: 100). Returns ------- matplotlib.animation.FuncAnimation The created animation. """ if len(bounds[0]) != 2 or len(bounds[1]) != 2: raise ValueError("This function only works for 2D search spaces") lb, ub = bounds # Create a grid of points resolution = 100 x = np.linspace(lb[0], ub[0], resolution) y = np.linspace(lb[1], ub[1], resolution) X, Y = np.meshgrid(x, y) # Evaluate the function at each point Z = np.zeros_like(X) for i in range(resolution): for j in range(resolution): Z[i, j] = func([X[i, j], Y[i, j]]) # Create the figure and axes fig, ax = plt.subplots(figsize=figsize) # Plot the contour contour = ax.contourf(X, Y, Z, contour_levels, cmap=cmap, alpha=0.8) plt.colorbar(contour, ax=ax, label='Cost') # Initialize scatter plots scatter_porcupines = ax.scatter([], [], c='white', edgecolors='black', s=50, label='Porcupines') scatter_best = ax.scatter([], [], c='red', s=100, marker='*', label='Best Position') # Set labels and title ax.set_xlabel('x1') ax.set_ylabel('x2') ax.set_title('Optimization Process') ax.grid(True, linestyle='--', alpha=0.3) ax.legend() # Set axis limits ax.set_xlim(lb[0], ub[0]) ax.set_ylim(lb[1], ub[1]) # Text for iteration number iteration_text = ax.text(0.02, 0.98, '', transform=ax.transAxes, verticalalignment='top', fontsize=10) # Animation update function def update(frame): positions = position_history[frame] scatter_porcupines.set_offsets(positions) if best_pos_history is not None: best_pos = best_pos_history[frame] scatter_best.set_offsets([best_pos]) iteration_text.set_text(f'Iteration: {frame+1}/{len(position_history)}') return scatter_porcupines, scatter_best, iteration_text # Create the animation anim = FuncAnimation(fig, update, frames=len(position_history), interval=interval, blit=True) # Save the animation if a path is provided if save_path: anim.save(save_path, dpi=dpi, writer='pillow') return anim
[docs]def plot_multiple_runs(cost_histories: List[List[float]], labels: List[str] = None, title: str = "Comparison of Multiple Runs", xlabel: str = "Iterations", ylabel: str = "Cost", figsize: Tuple[int, int] = (10, 6), log_scale: bool = False, save_path: Optional[str] = None): """ Plot the convergence histories of multiple optimization runs. Parameters ---------- cost_histories : list List of cost history lists, each from a different run. labels : list, optional Labels for each run. If None, runs are labeled as "Run 1", "Run 2", etc. title : str, optional Title of the plot (default: "Comparison of Multiple Runs"). xlabel : str, optional Label for the x-axis (default: "Iterations"). ylabel : str, optional Label for the y-axis (default: "Cost"). figsize : tuple, optional Figure size as (width, height) in inches (default: (10, 6)). log_scale : bool, optional Whether to use logarithmic scale for the y-axis (default: False). save_path : str, optional Path to save the figure. If None, the figure is not saved (default: None). Returns ------- matplotlib.figure.Figure The created figure. """ plt.figure(figsize=figsize) if labels is None: labels = [f"Run {i+1}" for i in range(len(cost_histories))] for i, history in enumerate(cost_histories): iterations = np.arange(1, len(history) + 1) plt.plot(iterations, history, linewidth=2, label=labels[i]) plt.title(title) plt.xlabel(xlabel) plt.ylabel(ylabel) plt.grid(True, linestyle='--', alpha=0.7) plt.legend() if log_scale and all(np.all(np.array(history) > 0) for history in cost_histories): plt.yscale('log') if save_path: plt.savefig(save_path, dpi=300, bbox_inches='tight') return plt.gcf()
[docs]def plot_parameter_sensitivity(parameter_values: List[float], results: List[float], parameter_name: str, result_name: str = "Best Cost", title: str = None, figsize: Tuple[int, int] = (10, 6), save_path: Optional[str] = None): """ Plot the sensitivity of results to a parameter. Parameters ---------- parameter_values : list List of parameter values tested. results : list List of corresponding results (e.g., best costs). parameter_name : str Name of the parameter. result_name : str, optional Name of the result metric (default: "Best Cost"). title : str, optional Title of the plot. If None, a default title is generated. figsize : tuple, optional Figure size as (width, height) in inches (default: (10, 6)). save_path : str, optional Path to save the figure. If None, the figure is not saved (default: None). Returns ------- matplotlib.figure.Figure The created figure. """ if title is None: title = f"Sensitivity of {result_name} to {parameter_name}" plt.figure(figsize=figsize) plt.plot(parameter_values, results, 'bo-', linewidth=2, markersize=8) plt.title(title) plt.xlabel(parameter_name) plt.ylabel(result_name) plt.grid(True, linestyle='--', alpha=0.7) # Add data points for x, y in zip(parameter_values, results): plt.annotate(f"{y:.4g}", (x, y), textcoords="offset points", xytext=(0, 10), ha='center') if save_path: plt.savefig(save_path, dpi=300, bbox_inches='tight') return plt.gcf()