Skip to content
Merged
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
41 changes: 26 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
<div align="center">
<img src="https://raw.githubusercontent.com/helix-agh/lonkit/main/docs/assets/icon.jpg" alt="lonkit" width="800">
<img src="https://raw.githubusercontent.com/helix-agh/lonkit/main/docs/assets/icon.jpg" alt="lonkit: Local Optima Networks visualization in Python" width="800">
</div>

<h1 align="center">lonkit: Local Optima Networks in Python</h1>

[![PyPI version](https://badge.fury.io/py/lonkit.svg)](https://pypi.org/project/lonkit/)
[![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
[![Documentation](https://img.shields.io/badge/docs-helix--agh.github.io%2Flonkit-indigo.svg)](https://helix-agh.github.io/lonkit/)
[![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE.txt)
[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/drive/1Ujl48ffgHg9ck1Hueh59s65OR3Q3BG99?usp=sharing)

**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.

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.
**Documentation:** [helix-agh.github.io/lonkit](https://helix-agh.github.io/lonkit/)

## Features

- **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, NK Landscape, 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
- **LON Construction**: Local Optima Networks as `igraph` graphs generated from sampled data, including Compressed Monotonic LONs (CMLONs).
- **Continuous landscape exploration**: Configurable Basin-Hopping algorithm for data sampling, with automatic step-size estimation and parallelization.
- **Discrete landscape exploration**: Iterated Local Search sampler, with parallelization, built-in problems (OneMax, NK Landscape, Number Partitioning) and classes to define custom problem instances.
- **Landscape metrics**: Funnel analysis, neutrality, global and sink strength, plus search performance (success rate, deviation from the known optimum).
- **Visualization**: Configurable 2D and 3D LON plots, and animated rotating GIFs.

## Installation

Expand Down Expand Up @@ -136,8 +138,7 @@ lon = sampler.sample_to_lon(result)
## Discrete Optimization Problems

lonkit also supports Local Optima Networks for discrete optimization problems using Iterated Local Search (ILS).

### Quick Start (Discrete)
The library includes several built-in discrete problems for analysis: Number Partitioning, NK Landscape and OneMax.

```python
from lonkit import NumberPartitioning, ILSSampler, ILSSamplerConfig, LONVisualizer
Expand Down Expand Up @@ -167,15 +168,25 @@ viz.plot_2d(cmlon, output_path="npp_cmlon.png")
<img src="https://raw.githubusercontent.com/helix-agh/lonkit/main/docs/assets/readme/npp_cmlon_2d.png" alt="CMLON of a Number Partitioning instance with many global funnels and one local funnel" width="500">
</p>

Unlike Rastrigin, this Number Partitioning instance has many separate funnels: most of them end in a global optimum (red), and one ends in a worse local sink (blue).

### Custom Discrete Problems

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()`.
You can define your own discrete problem by subclassing `DiscreteProblem`. You can also extend built-in problems - for bitstring problems, extend `BitstringProblem`, which provides `random_solution()`, `local_search()`, `perturb()`, and `solution_id()` out of the box, so you only need to implement `evaluate()`.

## Documentation

For full documentation, visit: [https://helix-agh.github.io/lonkit](https://helix-agh.github.io/lonkit)
The full documentation is at [helix-agh.github.io/lonkit](https://helix-agh.github.io/lonkit/):

- [Installation](https://helix-agh.github.io/lonkit/getting-started/installation/)
- [Quick Start](https://helix-agh.github.io/lonkit/getting-started/quickstart/)
- [Core Concepts: LONs and fitness landscapes](https://helix-agh.github.io/lonkit/getting-started/concepts/)
- [Discrete Problems](https://helix-agh.github.io/lonkit/user-guide/discrete/)
- [API Reference](https://helix-agh.github.io/lonkit/api/)

## References

- G. Ochoa, M. Tomassini, S. Vérel, C. Darabos. *A Study of NK Landscapes' Basins and Local Optima Networks.* GECCO 2008.
- G. Ochoa, N. Veerapen, F. Daolio, M. Tomassini. *Understanding Phase Transitions with Local Optima Networks: Number Partitioning as a Case Study.* EvoCOP 2017.
- J. Adair, G. Ochoa, K. M. Malan. *Local Optima Networks for Continuous Fitness Landscapes.* GECCO 2019 Companion.

## Contributing

Expand Down
4 changes: 4 additions & 0 deletions docs/api/discrete.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
---
description: "API reference for lonkit discrete optimization: DiscreteProblem, BitstringProblem, NumberPartitioning, NKLandscape, OneMax, and the Iterated Local Search (ILS) sampler."
---

# Discrete Module

## Problems
Expand Down
4 changes: 4 additions & 0 deletions docs/api/lon.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
---
description: "API reference for the lonkit LON and CMLON classes: building Local Optima Networks from trace data, computing landscape metrics, and compressing to CMLONs."
---

# LON Module

::: lonkit.lon.LON
Expand Down
4 changes: 4 additions & 0 deletions docs/api/sampling.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
---
description: "API reference for lonkit continuous sampling: compute_lon, BasinHoppingSampler, and BasinHoppingSamplerConfig for building Local Optima Networks of continuous functions."
---

# Continuous Sampling Module

::: lonkit.continuous.sampling.compute_lon
Expand Down
4 changes: 4 additions & 0 deletions docs/api/step_size.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
---
description: "API reference for lonkit StepSizeEstimator, which tunes the Basin-Hopping perturbation step size for continuous Local Optima Network sampling."
---

# Step Size Module

::: lonkit.continuous.step_size.StepSizeEstimator
Expand Down
4 changes: 4 additions & 0 deletions docs/api/visualization.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
---
description: "API reference for LONVisualizer: 2D and 3D plots and animated GIFs of Local Optima Networks and CMLONs."
---

# Visualization Module

::: lonkit.visualization.LONVisualizer
Expand Down
4 changes: 4 additions & 0 deletions docs/contributing.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
---
description: "How to contribute to lonkit, the Python library for Local Optima Networks: development setup, tests, code style, and documentation."
---

# Contributing

Thank you for your interest in contributing to lonkit!
Expand Down
5 changes: 3 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
---
title: Local Optima Networks in Python
description: lonkit - Python library for constructing, analyzing, and visualizing Local Optima Networks (LONs) for continuous and discrete optimization.
---

# lonkit

**Local Optima Networks**
**Local Optima Networks in Python**

![lonkit](assets/icon.jpg){ width="100%" }
![lonkit: Local Optima Networks visualization in Python](assets/icon.jpg){ width="100%" }

lonkit is a Python library for constructing, analyzing, and visualizing Local Optima Networks (LONs) for both continuous and discrete optimization problems.

Expand Down
37 changes: 33 additions & 4 deletions docs/overrides/main.html
Original file line number Diff line number Diff line change
@@ -1,23 +1,52 @@
{% extends "base.html" %} {% block extrahead %}
{{ super() }}
{% if config.extra.google_site_verification %}
<meta name="google-site-verification" content="{{ config.extra.google_site_verification }}" />
{% endif %}
{% if page.meta and page.meta.title %}
{% set seo_title = page.meta.title ~ " - " ~ config.site_name %}
{% elif page.title and not page.is_homepage %}
{% set seo_title = page.title | striptags ~ " - " ~ config.site_name %}
{% else %}
{% set seo_title = config.site_name %}
{% endif %}
{% set seo_description = (page.meta.description if page.meta and page.meta.description else config.site_description) %}
<meta property="og:type" content="website" />
<meta property="og:site_name" content="{{ config.site_name }}" />
<meta property="og:title" content="{{ seo_title }}" />
<meta property="og:description" content="{{ seo_description }}" />
<meta property="og:url" content="{{ page.canonical_url }}" />
<meta property="og:image" content="https://helix-agh.github.io/lonkit/assets/icon.jpg" />
<meta property="og:image:alt" content="lonkit: Local Optima Networks visualization in Python" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="{{ seo_title }}" />
<meta name="twitter:description" content="{{ seo_description }}" />
<meta name="twitter:image" content="https://helix-agh.github.io/lonkit/assets/icon.jpg" />
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "lonkit",
"description": "Local Optima Networks (LONs) for continuous optimization - Python library for constructing, analyzing, and visualizing fitness landscapes.",
"url": "https://helix-agh.github.io/lonkit",
"alternateName": "lonkit: Local Optima Networks in Python",
"description": "Python library for constructing, analyzing, and visualizing Local Optima Networks (LONs) for continuous and discrete optimization and fitness landscape analysis.",
"url": "https://helix-agh.github.io/lonkit/",
"applicationCategory": "DeveloperApplication",
"operatingSystem": "Python 3.10+",
"programmingLanguage": "Python",
"license": "https://opensource.org/licenses/MIT",
"isAccessibleForFree": true,
"author": {
"@type": "Organization",
"name": "HELIX AGH",
"url": "https://github.com/helix-agh"
"url": "https://helix-agh.github.io/"
},
"downloadUrl": "https://pypi.org/project/lonkit/",
"keywords": "optimization, fitness landscape, local optima networks, basin hopping, evolutionary algorithms, visualization, multi-dimensional"
"installUrl": "https://pypi.org/project/lonkit/",
"sameAs": [
"https://github.com/helix-agh/lonkit",
"https://pypi.org/project/lonkit/"
],
"keywords": "local optima networks, LON, CMLON, fitness landscape analysis, basin hopping, iterated local search, continuous optimization, discrete optimization, metaheuristics, evolutionary computation, Python"
}
</script>
{% endblock %}
11 changes: 8 additions & 3 deletions docs/user-guide/discrete.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
---
description: "Build Local Optima Networks for discrete optimization problems with Iterated Local Search: Number Partitioning, NK Landscapes, OneMax, and custom bitstring problems."
---

# Discrete Problems

lonkit supports Local Optima Networks for discrete optimization problems using
Expand Down Expand Up @@ -229,12 +233,13 @@ config = ILSSamplerConfig(
| `max_iter` | None | Hard limit on total iterations per run. Set to `None` for no limit (must set `n_iter_no_change`) |
| `accept_equal` | True | If `True`, accept moves to equal-fitness optima (greedy with equal acceptance). If `False`, only strictly improving moves |
| `seed` | None | Random seed controlling all search randomness |
| `n_jobs` | 1 | Concurrent jobs using joblib semantics; `1` runs sequentially, `-1` uses all CPUs |
| `n_jobs` | 1 | Maximum number of runs executed in parallel; `1` runs sequentially, `-1` uses all CPUs. See [Parallel Execution](sampling.md#parallel-execution) |

At least one of `n_iter_no_change` or `max_iter` must be set.

See [parallel ILS sampling](sampling.md#discrete-sampling-with-ils) for an example,
reproducibility guarantees, and progress callback semantics.
See [parallel ILS sampling](sampling.md#discrete-sampling-with-ils) for an example and
[Parallel Execution](sampling.md#parallel-execution) for `n_jobs` values, reproducibility
guarantees, and progress callback semantics.

### Comparison with BasinHoppingSamplerConfig

Expand Down
86 changes: 52 additions & 34 deletions docs/user-guide/sampling.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,7 @@ lon = sampler.sample_to_lon(result)
| `n_iter_no_change` | 250 | Max consecutive non-improving perturbations before stopping each run. At least one of `n_iter_no_change` or `max_iter` must be set. |
| `max_iter` | None | Max total perturbation steps per run. Use together with `n_iter_no_change` or alone. |
| `seed` | None | Random seed for reproducibility |
| `n_jobs` | 1 | Number of parallel jobs. For interpretation, refer to the [joblib documentation](https://joblib.readthedocs.io/en/latest/generated/joblib.Parallel.html#:~:text=Parameters%3A,int%2C%20default%3DNone). |

**Parallelism and reproducibility:**

- The behaviour of `n_jobs` parameter is in line with the one in [joblib documentation](https://joblib.readthedocs.io/en/latest/generated/joblib.Parallel.html#:~:text=Parameters%3A,int%2C%20default%3DNone), that is:
- `1` runs sequentially,
- `-1` uses all available CPUs,
- `N > 1` uses maximum of `N` processes,
- `-N` uses maximum of `max(1, cpu_count - N + 1)` processes,
- `None` is treated as `1`.
- Setting `seed` guarantees **identical results** regardless of `n_jobs` value — each run receives a deterministic RNG seed derived from `SeedSequence(seed)`
| `n_jobs` | 1 | Maximum number of runs executed in parallel; `1` runs sequentially, `-1` uses all CPUs. See [Parallel Execution](#parallel-execution). |

**Choosing `n_runs` and stopping criteria:**

Expand Down Expand Up @@ -187,7 +177,8 @@ perturbations), the starting point, and the reason for failure.

## Discrete Sampling with ILS

Independent ILS runs can use multiple processes via `n_jobs`:
Independent ILS runs can use multiple processes via `n_jobs`, with the same
semantics as `BasinHoppingSampler` (see [Parallel Execution](#parallel-execution)):

```python
from lonkit import ILSSampler, ILSSamplerConfig, NumberPartitioning
Expand All @@ -198,28 +189,55 @@ result = sampler.sample(problem)
lon = sampler.sample_to_lon(result)
```

`n_jobs` follows joblib semantics, as in `BasinHoppingSampler`: `1` (the default)
runs sequentially, `-1` uses all available CPUs, and `-2` leaves one CPU unused.
`None` uses the joblib context setting, or `1` without a context; `0` is invalid.
When the effective number of jobs is one, ILS runs directly without workers.
Parallel execution prefers processes. Custom problems must be serializable by
joblib and must not hold mutable search state. All runs use the supplied problem's
data; workers do not regenerate instances. For short runs, process startup and
serialization overhead can outweigh parallelism's benefit. Worker errors propagate
to the caller.

Each run gets its own RNG from `SeedSequence(seed).spawn(n_runs)` in both modes.
With a fixed seed and the same problem data, `raw_records` and `trace_df` are
identical across `n_jobs` values, including run and record order. **Seeded ILS
results change compared with earlier versions**, which shared one RNG across
all runs. Custom problems must use the supplied RNG for all search randomness.

`sample(problem, progress_callback=callback)` calls
`callback(completed_runs, total_runs)` in the main process once after receiving
each completed run, with counts from `1` through `n_runs`. Results are received
in run order, so an earlier slow run can delay notifications for later runs.
This also changes sequential callbacks from before a run to after its completion.
The `ILSResult` format and `sample_to_lon()` usage remain unchanged.
When running in parallel, custom problems must be serializable by joblib and must
not hold mutable search state. All runs use the supplied problem's data; workers
do not regenerate instances. Custom problems must also use the supplied RNG for
all search randomness, otherwise results are not reproducible.

**Seeded ILS results change compared with earlier versions**, which shared one
RNG across all runs. Progress callbacks are now called after each run completes
rather than before it starts. The `ILSResult` format and `sample_to_lon()` usage
remain unchanged.

## Parallel Execution

Both samplers run their `n_runs` independent runs in parallel according to the
`n_jobs` config parameter, which follows
[joblib semantics](https://joblib.readthedocs.io/en/latest/generated/joblib.Parallel.html):

| `n_jobs` | Behaviour |
| ------------- | ------------------------------------------------------------------------------------------ |
| `1` (default) | Runs execute sequentially in the main process, without workers |
| `N > 1` | Up to `N` worker processes |
| `-1` | One worker process per CPU |
| `-N` | Up to `max(1, cpu_count - N + 1)` worker processes, e.g. `-2` leaves one CPU free |
| `None` | Value from an enclosing `joblib.parallel_config(n_jobs=...)` context, otherwise `1` |
| `0` | Invalid, raises `ValueError` |

Whenever the effective number of jobs is `1`, runs execute directly in the main
process. Otherwise joblib worker processes are used, so the objective function
(or discrete problem) must be serializable by joblib. For short runs, process
startup and serialization overhead can outweigh the benefit of parallelism.
Worker errors propagate to the caller. `BasinHoppingSampler` also re-emits
warnings raised in workers (such as [failed local searches](#failed-local-searches))
in the main process.

#### Reproducibility

Each run gets its own RNG from
`SeedSequence(seed).spawn(n_runs)`, whatever the value of `n_jobs`. With a fixed
`seed`, the resulting `trace_df` and LON are identical across `n_jobs` values.

#### Ordering and progress callbacks

`progress_callback(completed_runs, total_runs)`
is always called in the main process, once per completed run, with counts from `1`
through `n_runs`. The two samplers differ in how parallel results are collected:

| Sampler | Results collected | `raw_records` order across `n_jobs` | Callback timing |
| --------------------- | --------------------- | ----------------------------------- | --------------------------------------------------- |
| `BasinHoppingSampler` | As runs finish | May differ (`trace_df` is sorted) | As soon as any run finishes |
| `ILSSampler` | In run order | Identical | In run order; a slow early run delays later updates |

## LON Construction Configuration

Expand Down
Loading
Loading