Local Optima Networks
lonkit is a Python library for constructing, analyzing, and visualizing Local Optima Networks (LONs) for both continuous and discrete optimization problems. LONs provide a powerful way to understand the structure of fitness landscapes, revealing how local optima are connected and how difficult it may be to find global optima.
- Basin-Hopping Sampling: Efficient exploration of continuous fitness landscapes using configurable Basin-Hopping
- Iterated Local Search: Discrete LON construction via ILS with built-in problems (Number Partitioning, OneMax)
- LON Construction: Automatic construction of Local Optima Networks from sampling data
- CMLON Support: Compressed Monotonic LONs for cleaner landscape analysis
- Rich Metrics: Compute landscape metrics including funnel analysis and neutrality
- Beautiful Visualizations: 2D and 3D plots with support for animated GIFs
pip install lonkitOr install from source:
git clone https://github.com/helix-agh/lonkit.git
cd lonkit
pip install -e .import numpy as np
from lonkit import compute_lon, LONVisualizer, BasinHoppingSamplerConfig
# Define an objective function
def rastrigin(x: np.ndarray) -> float:
return 10 * len(x) + np.sum(x**2 - 10 * np.cos(2 * np.pi * x))
# Construct the LON
config = BasinHoppingSamplerConfig(
n_runs=20,
n_iter_no_change=500,
seed=42
)
lon = compute_lon(
rastrigin,
dim=2,
lower_bound=-5.12,
upper_bound=5.12,
config=config
)
metrics = lon.compute_metrics()
print(f"Number of funnels: {metrics['n_funnels']}")
print(f"Global funnels: {metrics['n_global_funnels']}")
# Visualize
viz = LONVisualizer()
viz.plot_2d(lon, output_path="lon_2d.png")
viz.plot_3d(lon, output_path="lon_3d.png")CMLONs are a compressed representation where nodes with equal fitness that are connected get merged. This provides a cleaner view of the landscape's funnel structure.
# Convert LON to CMLON
cmlon = lon.to_cmlon()
# Analyze CMLON-specific metrics
cmlon_metrics = cmlon.compute_metrics()from lonkit import BasinHoppingSampler, BasinHoppingSamplerConfig
config = BasinHoppingSamplerConfig(
n_runs=50, # Number of independent runs
n_iter_no_change=1000, # Stop after this many consecutive non-improving perturbations
step_size=0.05, # Perturbation size
step_mode="percentage", # "percentage" or "fixed"
coordinate_precision=4, # Precision for identifying optima
fitness_precision=None, # Precision for fitness values (None = full double)
seed=42 # For reproducibility
)
sampler = BasinHoppingSampler(config)
# Define search domain
domain = [(-5.12, 5.12), (-5.12, 5.12)]
# Run sampling
result = sampler.sample(rastrigin, domain)
lon = sampler.sample_to_lon(result)lonkit also supports Local Optima Networks for discrete optimization problems using Iterated Local Search (ILS).
from lonkit import NumberPartitioning, ILSSampler, ILSSamplerConfig, LONVisualizer
# Define problem instance
problem = NumberPartitioning(n=20, k=0.5, instance_seed=1)
# Configure and run ILS sampling
config = ILSSamplerConfig(n_runs=50, n_iter_no_change=100, seed=42)
sampler = ILSSampler(config)
result = sampler.sample(problem)
# Build LON and CMLON
lon = sampler.sample_to_lon(result)
cmlon = lon.to_cmlon()
# Compute metrics
print(lon.compute_metrics())
print(cmlon.compute_metrics())
# Visualize
viz = LONVisualizer()
viz.plot_2d(cmlon, output_path="npp_cmlon.png")You can define your own discrete problem by subclassing DiscreteProblem. For bitstring problems, inherit from BitstringProblem instead it provides random_solution(), local_search(), perturb(), and solution_id() out of the box. You only need to implement evaluate().
For full documentation, visit: https://helix-agh.github.io/lonkit
Contributions are welcome! Please feel free to submit a Pull Request.