Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ paper_results/
.venv/
.ruff_cache/
.mypy_cache/
.pytest_cache/
htmlcov/
paper_reproduction.py
bbob_visualizations/
site/
.coverage
CLAUDE.md
examples/
47 changes: 30 additions & 17 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,28 +1,41 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
rev: v6.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-toml
- id: check-added-large-files
- id: check-merge-conflict
- id: debug-statements
args: [--maxkb=1000]
- id: check-json
- id: check-toml
- id: check-yaml
- id: detect-private-key
- id: end-of-file-fixer
- id: mixed-line-ending
- id: no-commit-to-branch
args: [--branch=main, --branch=master, --branch=production]
- id: trailing-whitespace

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.2
rev: v0.14.10
hooks:
- id: ruff
- id: ruff-check
args: [--fix]
- id: ruff-format

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.13.0
- repo: https://github.com/astral-sh/uv-pre-commit
rev: 0.9.22
hooks:
- id: uv-lock

- repo: https://github.com/gitleaks/gitleaks
rev: v8.30.0
hooks:
- id: gitleaks

- repo: local
hooks:
- id: mypy
additional_dependencies:
- numpy
- pandas-stubs
- types-Pillow
args: [--ignore-missing-imports]
- id: ty
name: ty (type checker)
entry: uvx ty check
language: system
types: [python]
pass_filenames: false
42 changes: 42 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
.PHONY: help install test test-cov check docs docs-serve clean

help:
@echo "Available commands:"
@echo " make install - Install dependencies with uv"
@echo " make test - Run tests with pytest"
@echo " make test-cov - Run tests with coverage report"
@echo " make check - Run all pre-commit hooks"
@echo " make docs - Build documentation"
@echo " make docs-serve - Serve documentation locally"
@echo " make clean - Remove build artifacts"

install:
uv sync --all-extras

test:
uv run python -m pytest tests/ -v

test-cov:
uv run python -m pytest tests/ -v --cov=src/lonpy --cov-report=term-missing --cov-report=html

check:
pre-commit run --all-files

docs:
uv run mkdocs build

docs-serve:
uv run mkdocs serve

clean:
rm -rf build/
rm -rf dist/
rm -rf *.egg-info/
rm -rf .pytest_cache/
rm -rf .mypy_cache/
rm -rf .ruff_cache/
rm -rf htmlcov/
rm -rf .coverage
rm -rf site/
find . -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true
find . -type f -name "*.pyc" -delete 2>/dev/null || true
64 changes: 58 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@
[![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/drive/1Ujl48ffgHg9ck1Hueh59s65OR3Q3BG99?usp=sharing)

**Local Optima Networks for Continuous Optimization**
**Local Optima Networks**

lonpy is a Python library for constructing, analyzing, and visualizing Local Optima Networks (LONs) for continuous 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.
lonpy 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.

## Features

- **Basin-Hopping Sampling**: Efficient exploration of fitness landscapes using configurable Basin-Hopping
- **Continuous Optimization**: Basin-Hopping sampling for continuous fitness landscapes
- **Discrete Optimization**: Iterated Local Search (ILS) sampling for combinatorial problems
- **Built-in Problems**: OneMax, Knapsack, Number Partitioning, and custom problem support
- **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
Expand All @@ -34,6 +36,8 @@ pip install -e .

## Quick Start

### Continuous Optimization

```python
import numpy as np
from lonpy import compute_lon, LONVisualizer
Expand All @@ -54,6 +58,7 @@ lon = compute_lon(
)

metrics = lon.compute_metrics()
print(f"Number of optima: {metrics['n_optima']}")
print(f"Number of funnels: {metrics['n_funnels']}")
print(f"Global funnels: {metrics['n_global_funnels']}")

Expand All @@ -63,7 +68,33 @@ viz.plot_2d(lon, output_path="lon_2d.png")
viz.plot_3d(lon, output_path="lon_3d.png")
```

### Compressed Monotonic LONs (CMLONs)
### Discrete Optimization

```python
from lonpy import compute_discrete_lon, OneMax, Knapsack, NumberPartitioning

# OneMax problem (maximize number of 1s in a bitstring)
problem = OneMax(n=20)
lon = compute_discrete_lon(problem, n_runs=100, seed=42)

metrics = lon.compute_metrics()
print(f"Number of optima: {metrics['n_optima']}")
print(f"Number of funnels: {metrics['n_funnels']}")

# Knapsack problem
knapsack = Knapsack(
values=[60, 100, 120, 80, 90],
weights=[10, 20, 30, 15, 25],
capacity=50
)
lon = compute_discrete_lon(knapsack, n_runs=100, seed=42)

# Number Partitioning problem
npp = NumberPartitioning(n=15, k=0.5, seed=42)
lon = compute_discrete_lon(npp, n_runs=100, seed=42)
```

## Compressed Monotonic LONs (CMLONs)

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.

Expand All @@ -73,9 +104,12 @@ cmlon = lon.to_cmlon()

# Analyze CMLON-specific metrics
cmlon_metrics = cmlon.compute_metrics()
print(f"Global funnel proportion: {cmlon_metrics['global_funnel_proportion']}")
```

### Custom Sampling Configuration
## Advanced Configuration

### Continuous Sampling (Basin-Hopping)

```python
from lonpy import BasinHoppingSampler, BasinHoppingSamplerConfig
Expand All @@ -84,7 +118,7 @@ config = BasinHoppingSamplerConfig(
n_runs=50, # Number of independent runs
n_iterations=1000, # Iterations per run
step_size=0.05, # Perturbation size
step_mode="per", # "per" (percentage) or "fix" (fixed)
step_mode="percentage", # "percentage" (of domain) or "fixed"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The change of step_mode's value from "per" to "percentage" is a good improvement for clarity. However, this is a breaking change for users of the previous version. It would be beneficial to mention this in the pull request description or in a release notes/changelog section to ensure users are aware they need to update their configurations.

hash_digits=4, # Precision for identifying optima
seed=42 # For reproducibility
)
Expand All @@ -98,6 +132,24 @@ domain = [(-5.12, 5.12), (-5.12, 5.12)]
lon = sampler.sample_to_lon(rastrigin, domain)
```

### Discrete Sampling (Iterated Local Search)

```python
from lonpy import ILSSampler, ILSSamplerConfig, OneMax

config = ILSSamplerConfig(
n_runs=100, # Number of independent ILS runs
non_improvement_iterations=100, # Stop after no improvement
perturbation_strength=2, # Number of random moves per perturbation
first_improvement=True, # Use first improvement hill climbing
seed=42 # For reproducibility
)

sampler = ILSSampler(config)
problem = OneMax(n=20)
lon = sampler.sample_to_lon(problem)
```

## Documentation

For full documentation, visit: [https://agh-a2s.github.io/lonpy](https://agh-a2s.github.io/lonpy)
Expand Down
38 changes: 30 additions & 8 deletions docs/api/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Complete API documentation for lonpy.

## Modules

lonpy is organized into three main modules:
lonpy is organized into modules for continuous and discrete optimization:

### [LON Module](lon.md)

Expand All @@ -15,11 +15,25 @@ Data structures for Local Optima Networks.

### [Sampling Module](sampling.md)

Basin-Hopping sampling for LON construction.
Sampling algorithms for LON construction.

- [`compute_lon()`](sampling.md#lonpy.sampling.compute_lon) - High-level convenience function
- [`BasinHoppingSampler`](sampling.md#lonpy.sampling.BasinHoppingSampler) - Sampling class
- [`BasinHoppingSamplerConfig`](sampling.md#lonpy.sampling.BasinHoppingSamplerConfig) - Configuration
**Continuous Optimization:**

- [`compute_lon()`](sampling.md#lonpy.continuous.sampling.compute_lon) - High-level convenience function
- [`BasinHoppingSampler`](sampling.md#lonpy.continuous.sampling.BasinHoppingSampler) - Basin-Hopping sampler
- [`BasinHoppingSamplerConfig`](sampling.md#lonpy.continuous.sampling.BasinHoppingSamplerConfig) - Configuration

**Discrete Optimization:**

- [`compute_discrete_lon()`](sampling.md#lonpy.discrete.sampling.compute_discrete_lon) - High-level convenience function
- [`ILSSampler`](sampling.md#lonpy.discrete.sampling.ILSSampler) - Iterated Local Search sampler
- [`ILSSamplerConfig`](sampling.md#lonpy.discrete.sampling.ILSSamplerConfig) - Configuration

**Built-in Problems:**

- [`OneMax`](sampling.md#lonpy.problems.discrete.OneMax) - Maximize 1s in bitstring
- [`Knapsack`](sampling.md#lonpy.problems.discrete.Knapsack) - 0/1 Knapsack problem
- [`NumberPartitioning`](sampling.md#lonpy.problems.discrete.NumberPartitioning) - Number partitioning problem

### [Visualization Module](visualization.md)

Expand All @@ -29,12 +43,11 @@ Plotting and animation tools.

## Quick Reference

### Creating a LON
### Creating a LON (Continuous)

```python
from lonpy import compute_lon

# Simple usage
lon = compute_lon(
func=objective_function,
dim=2,
Expand All @@ -45,6 +58,15 @@ lon = compute_lon(
)
```

### Creating a LON (Discrete)

```python
from lonpy import compute_discrete_lon, OneMax

problem = OneMax(n=20)
lon = compute_discrete_lon(problem, n_runs=100, seed=42)
```

### Analyzing a LON

```python
Expand Down Expand Up @@ -86,7 +108,7 @@ viz.visualize_all(lon, output_folder="./output")
lonpy depends on:

- `numpy` - Numerical computations
- `scipy` - Optimization
- `scipy` - Optimization (continuous)
- `pandas` - Data handling
- `igraph` - Graph operations
- `matplotlib` - 2D plotting
Expand Down
67 changes: 60 additions & 7 deletions docs/api/sampling.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,76 @@
# Sampling Module

::: lonpy.sampling.compute_lon
## Continuous Optimization

### compute_lon

::: lonpy.continuous.sampling.compute_lon
options:
show_root_heading: true
show_source: true

::: lonpy.sampling.BasinHoppingSamplerConfig
### BasinHoppingSamplerConfig

::: lonpy.continuous.sampling.BasinHoppingSamplerConfig
options:
show_root_heading: true
show_source: true

::: lonpy.sampling.BasinHoppingSampler
### BasinHoppingSampler

::: lonpy.continuous.sampling.BasinHoppingSampler
options:
show_root_heading: true
show_source: true
members:
- sample
- sample_to_lon
- hash_solution
- fitness_to_int
- bounded_perturbation
- unbounded_perturbation

## Discrete Optimization

### compute_discrete_lon

::: lonpy.discrete.sampling.compute_discrete_lon
options:
show_root_heading: true
show_source: true

### ILSSamplerConfig

::: lonpy.discrete.sampling.ILSSamplerConfig
options:
show_root_heading: true
show_source: true

### ILSSampler

::: lonpy.discrete.sampling.ILSSampler
options:
show_root_heading: true
show_source: true
members:
- sample
- sample_to_lon

## Built-in Problems

### OneMax

::: lonpy.problems.discrete.OneMax
options:
show_root_heading: true
show_source: true

### Knapsack

::: lonpy.problems.discrete.Knapsack
options:
show_root_heading: true
show_source: true

### NumberPartitioning

::: lonpy.problems.discrete.NumberPartitioning
options:
show_root_heading: true
show_source: true
Loading