diff --git a/README.md b/README.md index b54ba21..a39dbf1 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,26 @@
- lonkit + lonkit: Local Optima Networks visualization in Python
+

lonkit: Local Optima Networks in Python

[![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 @@ -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 @@ -167,15 +168,25 @@ viz.plot_2d(cmlon, output_path="npp_cmlon.png") CMLON of a Number Partitioning instance with many global funnels and one local funnel

-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 diff --git a/docs/api/discrete.md b/docs/api/discrete.md index 367e918..0c319a7 100644 --- a/docs/api/discrete.md +++ b/docs/api/discrete.md @@ -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 diff --git a/docs/api/lon.md b/docs/api/lon.md index 035dde1..255e86e 100644 --- a/docs/api/lon.md +++ b/docs/api/lon.md @@ -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 diff --git a/docs/api/sampling.md b/docs/api/sampling.md index 8aa7ccb..292ae62 100644 --- a/docs/api/sampling.md +++ b/docs/api/sampling.md @@ -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 diff --git a/docs/api/step_size.md b/docs/api/step_size.md index f9eaf6d..b552752 100644 --- a/docs/api/step_size.md +++ b/docs/api/step_size.md @@ -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 diff --git a/docs/api/visualization.md b/docs/api/visualization.md index 62fecda..ca5029f 100644 --- a/docs/api/visualization.md +++ b/docs/api/visualization.md @@ -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 diff --git a/docs/contributing.md b/docs/contributing.md index 5323f17..406c197 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -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! diff --git a/docs/index.md b/docs/index.md index 4e6ce0b..579ce36 100644 --- a/docs/index.md +++ b/docs/index.md @@ -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. diff --git a/docs/overrides/main.html b/docs/overrides/main.html index 0649b29..dde1a13 100644 --- a/docs/overrides/main.html +++ b/docs/overrides/main.html @@ -1,23 +1,52 @@ {% extends "base.html" %} {% block extrahead %} {{ super() }} +{% if 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) %} + + + + + + + + + + + {% endblock %} diff --git a/docs/user-guide/discrete.md b/docs/user-guide/discrete.md index 7885425..c0bf50b 100644 --- a/docs/user-guide/discrete.md +++ b/docs/user-guide/discrete.md @@ -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 @@ -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 diff --git a/docs/user-guide/sampling.md b/docs/user-guide/sampling.md index c1652b8..aa6bf25 100644 --- a/docs/user-guide/sampling.md +++ b/docs/user-guide/sampling.md @@ -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:** @@ -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 @@ -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 diff --git a/mkdocs.yml b/mkdocs.yml index 6704b5d..d466144 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,5 +1,5 @@ site_name: lonkit -site_description: Local Optima Networks for Continuous Optimization +site_description: lonkit is a Python library for constructing, analyzing, and visualizing Local Optima Networks (LONs) for continuous and discrete optimization and fitness landscape analysis. site_author: HELIX AGH site_url: https://helix-agh.github.io/lonkit @@ -85,19 +85,25 @@ nav: - Sampling: user-guide/sampling.md - Analysis: user-guide/analysis.md - Visualization: user-guide/visualization.md + - Discrete Problems: user-guide/discrete.md - Examples: user-guide/examples.md - API Reference: - Overview: api/index.md - LON: api/lon.md - Sampling: api/sampling.md + - Step Size: api/step_size.md + - Discrete: api/discrete.md - Visualization: api/visualization.md - Contributing: contributing.md extra: + # Google Search Console verification token (HTML tag method), e.g. "abc123...". + # Rendered as when set. + google_site_verification: "" social: - icon: fontawesome/brands/github link: https://github.com/helix-agh/lonkit - icon: fontawesome/brands/python link: https://pypi.org/project/lonkit/ -copyright: Copyright © 2025 AGH University of Kraków +copyright: Copyright © 2025-2026 AGH University of Kraków diff --git a/src/lonkit/continuous/sampling.py b/src/lonkit/continuous/sampling.py index ec828b4..0f8f627 100644 --- a/src/lonkit/continuous/sampling.py +++ b/src/lonkit/continuous/sampling.py @@ -52,7 +52,7 @@ class BasinHoppingSamplerConfig: Reproducibility is guaranteed across different ``n_jobs`` values when ``seed`` is set. Default: ``1``. preserve_runs_without_transitions: Keep runs with no accepted moves as self-loop trace rows. If False, omit them from the trace, LON, and - performance metrics, matching the earlier behavior. Default: True. + performance metrics. Default: True. A local search fails if ``minimize`` raises ``ValueError`` or returns a non-finite ``fun``. A failed initial search skips the run; a failed perturbation is rejected diff --git a/src/lonkit/discrete/sampling.py b/src/lonkit/discrete/sampling.py index ff4f51b..2895004 100644 --- a/src/lonkit/discrete/sampling.py +++ b/src/lonkit/discrete/sampling.py @@ -39,7 +39,7 @@ class ILSSamplerConfig: a context. 0 is invalid. Processes are preferred. Default: 1. preserve_runs_without_transitions: Keep runs with no accepted moves as self-loop trace rows. If False, omit them from the trace, LON, and - performance metrics, matching the earlier behavior. Default: True. + performance metrics. Default: True. """ n_runs: int = 100