Examples¶
Complete examples demonstrating lonpy's capabilities.
Basic LON Analysis¶
import numpy as np
from lonpy import compute_lon, LONVisualizer
# Define the Rastrigin function
def rastrigin(x):
"""Highly multimodal test function."""
return 10 * len(x) + np.sum(x**2 - 10 * np.cos(2 * np.pi * x))
# Build the LON
lon = compute_lon(
func=rastrigin,
dim=2,
lower_bound=-5.12,
upper_bound=5.12,
n_runs=30,
n_iterations=500,
seed=42
)
# Analyze
print("=== LON Analysis ===")
print(f"Local optima: {lon.n_vertices}")
print(f"Transitions: {lon.n_edges}")
print(f"Best fitness: {lon.best_fitness}")
metrics = lon.compute_metrics()
for key, value in metrics.items():
if isinstance(value, float):
print(f"{key}: {value:.4f}")
else:
print(f"{key}: {value}")
# Visualize
viz = LONVisualizer()
viz.plot_2d(lon, output_path="rastrigin_lon.png", seed=42)
viz.plot_3d(lon, output_path="rastrigin_3d.png", seed=42)
Comparing Multiple Functions¶
import numpy as np
import pandas as pd
from lonpy import compute_lon
# Test functions
def sphere(x):
return np.sum(x**2)
def rastrigin(x):
return 10 * len(x) + np.sum(x**2 - 10 * np.cos(2 * np.pi * x))
def ackley(x):
n = len(x)
sum1 = np.sum(x**2)
sum2 = np.sum(np.cos(2 * np.pi * x))
return -20 * np.exp(-0.2 * np.sqrt(sum1/n)) - np.exp(sum2/n) + 20 + np.e
def rosenbrock(x):
return sum(100*(x[i+1]-x[i]**2)**2 + (1-x[i])**2 for i in range(len(x)-1))
# Configuration
functions = {
"Sphere": (sphere, -5.0, 5.0, 0.0),
"Rastrigin": (rastrigin, -5.12, 5.12, 0.0),
"Ackley": (ackley, -5.0, 5.0, 0.0),
"Rosenbrock": (rosenbrock, -5.0, 10.0, 0.0),
}
# Analyze each
results = []
for name, (func, lb, ub, optimal) in functions.items():
print(f"Analyzing {name}...")
lon = compute_lon(
func=func,
dim=2,
lower_bound=lb,
upper_bound=ub,
n_runs=30,
n_iterations=500,
seed=42
)
metrics = lon.compute_metrics(known_best=optimal * 10**4) # scaled
cmlon = lon.to_cmlon()
cmlon_metrics = cmlon.compute_metrics(known_best=optimal * 10**4)
results.append({
"Function": name,
"Optima": lon.n_vertices,
"Funnels": metrics['n_funnels'],
"Global Funnels": metrics['n_global_funnels'],
"Strength": f"{metrics['strength']:.1%}",
"Global Funnel %": f"{cmlon_metrics['global_funnel_proportion']:.1%}",
})
# Display results
df = pd.DataFrame(results)
print("\n=== Comparison ===")
print(df.to_string(index=False))
Detailed CMLON Analysis¶
import numpy as np
from lonpy import compute_lon, LONVisualizer
def rastrigin(x):
return 10 * len(x) + np.sum(x**2 - 10 * np.cos(2 * np.pi * x))
# Build LON
lon = compute_lon(
rastrigin,
dim=2,
lower_bound=-5.12,
upper_bound=5.12,
n_runs=50,
seed=42
)
# Convert to CMLON
cmlon = lon.to_cmlon()
print("=== LON vs CMLON ===")
print(f"LON vertices: {lon.n_vertices}")
print(f"CMLON vertices: {cmlon.n_vertices}")
print(f"Compression: {1 - cmlon.n_vertices/lon.n_vertices:.1%}")
print("\n=== CMLON Structure ===")
print(f"Total sinks: {len(cmlon.get_sinks())}")
print(f"Global sinks: {len(cmlon.get_global_sinks())}")
print(f"Local sinks: {len(cmlon.get_local_sinks())}")
# Metrics
cmlon_metrics = cmlon.compute_metrics()
print("\n=== CMLON Metrics ===")
print(f"Global funnel proportion: {cmlon_metrics['global_funnel_proportion']:.1%}")
print(f"Strength to global: {cmlon_metrics['strength']:.1%}")
# Visualize
viz = LONVisualizer()
viz.plot_2d(cmlon, output_path="cmlon_2d.png", seed=42)
viz.plot_3d(cmlon, output_path="cmlon_3d.png", seed=42)
Custom Sampling Configuration¶
import numpy as np
from lonpy import BasinHoppingSampler, BasinHoppingSamplerConfig, LONVisualizer
def schwefel(x):
"""Schwefel function - deceptive with distant global optimum."""
n = len(x)
return 418.9829 * n - np.sum(x * np.sin(np.sqrt(np.abs(x))))
# Custom configuration for challenging function
config = BasinHoppingSamplerConfig(
n_runs=100, # More runs for coverage
n_iterations=300, # Moderate depth
step_mode="percentage",
step_size=0.15, # Larger steps for this landscape
hash_digits=3, # Coarser grouping
bounded=True,
minimizer_method="L-BFGS-B",
minimizer_options={
"ftol": 1e-08,
"gtol": 1e-06,
},
seed=42
)
# Sample
domain = [(-500.0, 500.0), (-500.0, 500.0)]
sampler = BasinHoppingSampler(config)
def progress(run, total):
if run % 10 == 0:
print(f"Progress: {run}/{total}")
lon = sampler.sample_to_lon(schwefel, domain, progress_callback=progress)
# Analyze
print(f"\nFound {lon.n_vertices} local optima")
print(f"Best fitness: {lon.best_fitness}")
# Known optimum at x = (420.9687, ...) with f(x) ≈ 0
metrics = lon.compute_metrics()
print(f"Funnels: {metrics['n_funnels']}")
print(f"Strength: {metrics['strength']:.1%}")
Accessing Raw Trace Data¶
import numpy as np
import pandas as pd
from lonpy import BasinHoppingSampler, BasinHoppingSamplerConfig
def sphere(x):
return np.sum(x**2)
config = BasinHoppingSamplerConfig(n_runs=5, n_iterations=100, seed=42)
sampler = BasinHoppingSampler(config)
domain = [(-5.0, 5.0), (-5.0, 5.0)]
trace_df, raw_records = sampler.sample(sphere, domain)
# Trace data for LON construction
print("=== Trace Data ===")
print(trace_df.head(10))
print(f"\nTotal transitions: {len(trace_df)}")
# Raw iteration data
print("\n=== Sample Raw Records ===")
for i, rec in enumerate(raw_records[:5]):
print(f"Run {rec['run']}, Iter {rec['iteration']}:")
print(f" From: {rec['current_f']:.6f} -> To: {rec['new_f']:.6f}")
print(f" Accepted: {rec['accepted']}")
# Analyze acceptance rate
accepted = sum(r['accepted'] for r in raw_records)
total = len(raw_records)
print(f"\nAcceptance rate: {accepted/total:.1%}")
Working with the Graph Directly¶
import numpy as np
from lonpy import compute_lon
def rastrigin(x):
return 10 * len(x) + np.sum(x**2 - 10 * np.cos(2 * np.pi * x))
lon = compute_lon(rastrigin, dim=2, lower_bound=-5.12, upper_bound=5.12, n_runs=30, seed=42)
# Access igraph object
g = lon.graph
print("=== Graph Properties ===")
print(f"Vertices: {g.vcount()}")
print(f"Edges: {g.ecount()}")
print(f"Density: {g.density():.4f}")
# Find most visited nodes
counts = g.vs["Count"]
top_indices = sorted(range(len(counts)), key=lambda i: counts[i], reverse=True)[:5]
print("\n=== Most Visited Optima ===")
for i, idx in enumerate(top_indices):
print(f"{i+1}. Node {g.vs[idx]['name']}: {counts[idx]} visits, fitness={g.vs[idx]['Fitness']}")
# Analyze edge weights
if g.ecount() > 0:
edge_weights = g.es["Count"]
print(f"\n=== Edge Statistics ===")
print(f"Total edges: {len(edge_weights)}")
print(f"Total transitions: {sum(edge_weights)}")
print(f"Max edge weight: {max(edge_weights)}")
print(f"Mean edge weight: {np.mean(edge_weights):.2f}")
Batch Analysis Script¶
import numpy as np
import json
from pathlib import Path
from lonpy import compute_lon, LONVisualizer
def analyze_function(name, func, bounds, output_dir):
"""Analyze a function and save results."""
output_dir = Path(output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
# Build LON
lon = compute_lon(
func=func,
dim=2,
lower_bound=bounds[0],
upper_bound=bounds[1],
n_runs=30,
n_iterations=500,
seed=42
)
# Compute metrics
lon_metrics = lon.compute_metrics()
cmlon = lon.to_cmlon()
cmlon_metrics = cmlon.compute_metrics()
# Save metrics
results = {
"function": name,
"lon": {
"n_vertices": lon.n_vertices,
"n_edges": lon.n_edges,
"best_fitness": lon.best_fitness,
**lon_metrics
},
"cmlon": {
"n_vertices": cmlon.n_vertices,
"n_edges": cmlon.n_edges,
**cmlon_metrics
}
}
with open(output_dir / f"{name}_metrics.json", "w") as f:
json.dump(results, f, indent=2)
# Generate visualizations
viz = LONVisualizer()
viz.visualize_all(lon, output_dir / name, seed=42)
return results
# Example usage
def rastrigin(x):
return 10 * len(x) + np.sum(x**2 - 10 * np.cos(2 * np.pi * x))
results = analyze_function("rastrigin", rastrigin, (-5.12, 5.12), "./analysis")
print(json.dumps(results, indent=2))
Next Steps¶
- API Reference - Complete API documentation
- Core Concepts - Understand LON theory