Skip to content

Sampling Guide

This guide covers how to configure Basin-Hopping sampling for LON construction.

Quick Start

The simplest way to create a LON:

from lonpy import compute_lon

lon = compute_lon(
    func=my_objective,
    dim=2,
    lower_bound=-5.0,
    upper_bound=5.0,
    n_runs=20,
    seed=42
)

Configuration Options

For more control, use BasinHoppingSamplerConfig:

from lonpy import BasinHoppingSampler, BasinHoppingSamplerConfig

config = BasinHoppingSamplerConfig(
    n_runs=30,              # Number of independent runs
    n_iterations=500,       # Iterations per run
    step_mode="percentage", # "percentage" or "fixed"
    step_size=0.1,          # Perturbation magnitude
    hash_digits=4,          # Precision for node identification
    opt_digits=-1,          # Precision for optimization (-1 = full)
    bounded=True,           # Enforce domain bounds
    minimizer_method="L-BFGS-B",
    seed=42
)

sampler = BasinHoppingSampler(config)
lon = sampler.sample_to_lon(my_objective, domain)

Parameters Explained

Sampling Parameters

Parameter Default Description
n_runs 10 Number of independent Basin-Hopping runs
n_iterations 1000 Iterations per run
seed None Random seed for reproducibility

Choosing n_runs and n_iterations:

  • More runs = better coverage of the landscape
  • More iterations = deeper exploitation from each starting point
  • Trade-off: n_runs × n_iterations determines total evaluations
# Wide coverage (recommended for unknown landscapes)
config = BasinHoppingSamplerConfig(n_runs=50, n_iterations=200)

# Deep exploration (for complex local structure)
config = BasinHoppingSamplerConfig(n_runs=10, n_iterations=1000)

Perturbation Settings

Parameter Default Description
step_mode "fixed" How to interpret step_size
step_size 0.01 Perturbation magnitude
bounded True Keep perturbations within domain

Step modes:

# Fixed: step_size is absolute distance
config = BasinHoppingSamplerConfig(
    step_mode="fixed",
    step_size=0.5  # Always perturb by ±0.5 in each dimension
)

# Percentage: step_size is fraction of domain range
config = BasinHoppingSamplerConfig(
    step_mode="percentage",
    step_size=0.1  # Perturb by ±10% of (upper - lower)
)

Choosing step size:

  • Too small: Stays in same basin, misses transitions
  • Too large: Jumps randomly, misses local structure
  • Good starting point: 5-10% of domain range

Precision Settings

Parameter Default Description
hash_digits 4 Decimal places for node identification
opt_digits -1 Decimal places for optimization (-1 = full)

hash_digits determines when two solutions are considered the same optimum:

# High precision: More distinct nodes
config = BasinHoppingSamplerConfig(hash_digits=6)

# Low precision: More merging, fewer nodes
config = BasinHoppingSamplerConfig(hash_digits=2)

Local Minimizer Settings

Parameter Default Description
minimizer_method "L-BFGS-B" Scipy minimizer algorithm
minimizer_options {"ftol": 1e-07, "gtol": 1e-05} Minimizer options
# Custom minimizer settings
config = BasinHoppingSamplerConfig(
    minimizer_method="L-BFGS-B",
    minimizer_options={
        "ftol": 1e-10,  # Tighter function tolerance
        "gtol": 1e-08,  # Tighter gradient tolerance
        "maxiter": 1000 # More iterations allowed
    }
)

Domain Specification

The domain is specified as a list of (lower, upper) tuples:

# Same bounds for all dimensions
lon = compute_lon(func, dim=5, lower_bound=-5.0, upper_bound=5.0)

# Different bounds per dimension
domain = [
    (-5.0, 5.0),    # x1
    (0.0, 10.0),    # x2
    (-1.0, 1.0)     # x3
]
sampler = BasinHoppingSampler()
lon = sampler.sample_to_lon(func, domain)

Accessing Raw Data

For custom analysis, access the raw trace data:

sampler = BasinHoppingSampler(config)
trace_df, raw_records = sampler.sample(func, domain)

# trace_df columns: [run, fit1, node1, fit2, node2]
print(trace_df.head())

# raw_records contains detailed iteration data
for record in raw_records[:5]:
    print(f"Run {record['run']}, Iter {record['iteration']}")
    print(f"  Current: {record['current_f']:.4f}")
    print(f"  New: {record['new_f']:.4f}")
    print(f"  Accepted: {record['accepted']}")

Progress Monitoring

Track sampling progress with a callback:

def progress(run, total):
    print(f"Run {run}/{total}")

sampler = BasinHoppingSampler(config)
lon = sampler.sample_to_lon(func, domain, progress_callback=progress)

Best Practices

For Standard Test Functions

# Rastrigin, Ackley, etc. with known bounds
config = BasinHoppingSamplerConfig(
    n_runs=30,
    n_iterations=500,
    step_mode="percentage",
    step_size=0.1,
    hash_digits=4,
    seed=42
)

For Unknown Functions

# Start with wider exploration
config = BasinHoppingSamplerConfig(
    n_runs=50,
    n_iterations=200,
    step_mode="percentage",
    step_size=0.15,  # Larger steps initially
    hash_digits=3,   # Coarser grouping
)

# Refine based on initial results

For High-Dimensional Problems

# More runs needed for coverage
config = BasinHoppingSamplerConfig(
    n_runs=100,
    n_iterations=500,
    step_mode="percentage",
    step_size=0.05,  # Smaller relative steps
)

Next Steps