From f6c5dbe342e9cdeb6f9765134cfdfcc156937b5f Mon Sep 17 00:00:00 2001
From: Massny
Date: Fri, 16 Oct 2026 18:38:50 +0200
Subject: [PATCH 1/6] feat: small docs and seo improvements
---
README.md | 23 +++++++++++++++++++----
docs/api/discrete.md | 4 ++++
docs/api/lon.md | 4 ++++
docs/api/sampling.md | 4 ++++
docs/api/step_size.md | 4 ++++
docs/api/visualization.md | 4 ++++
docs/contributing.md | 4 ++++
docs/index.md | 3 ++-
docs/overrides/main.html | 37 +++++++++++++++++++++++++++++++++----
docs/user-guide/discrete.md | 4 ++++
mkdocs.yml | 10 ++++++++--
11 files changed, 90 insertions(+), 11 deletions(-)
diff --git a/README.md b/README.md
index b54ba21..7cd0e7d 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,18 @@
-

+
+lonkit: Local Optima Networks in Python
[](https://pypi.org/project/lonkit/)
[](https://www.python.org/downloads/)
+[](https://helix-agh.github.io/lonkit/)
+[](LICENSE.txt)
[](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
@@ -175,7 +178,19 @@ You can define your own discrete problem by subclassing `DiscreteProblem`. For b
## 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..25a5f68 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,4 +1,5 @@
---
+title: Local Optima Networks in Python
description: lonkit - Python library for constructing, analyzing, and visualizing Local Optima Networks (LONs) for continuous and discrete optimization.
---
@@ -6,7 +7,7 @@ description: lonkit - Python library for constructing, analyzing, and visualizin
**Local Optima Networks**
-{ width="100%" }
+{ 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..a91c87b 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
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
From 9cb2171b071bb0c2d646de2e5016a53ee296aeac Mon Sep 17 00:00:00 2001
From: Massny
Date: Fri, 16 Oct 2026 18:39:56 +0200
Subject: [PATCH 2/6] docs: update title for clarity in index.md
---
docs/index.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/index.md b/docs/index.md
index 25a5f68..579ce36 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -5,7 +5,7 @@ description: lonkit - Python library for constructing, analyzing, and visualizin
# lonkit
-**Local Optima Networks**
+**Local Optima Networks in Python**
{ width="100%" }
From a9535a378d63d067fdaa4ddbfa1aa1239e13ff15 Mon Sep 17 00:00:00 2001
From: Massny
Date: Fri, 16 Oct 2026 19:00:13 +0200
Subject: [PATCH 3/6] feat: minor readme improvements
---
README.md | 18 +++++++-----------
1 file changed, 7 insertions(+), 11 deletions(-)
diff --git a/README.md b/README.md
index 7cd0e7d..5fba24a 100644
--- a/README.md
+++ b/README.md
@@ -16,12 +16,11 @@
## 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 with support for Compressed Monotonic LONs (CMLONs) representation.
+- **Continuous landscape exploration**: Configurable Basin-Hopping algorithm for data sampling, with automatic step-size estimation and pararellization.
+- **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
@@ -139,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
@@ -170,11 +168,9 @@ viz.plot_2d(cmlon, output_path="npp_cmlon.png")
-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 it provides `random_solution()`, `local_search()`, `perturb()`, and `solution_id()` out of the box, so you only need to implement `evaluate()`.
## Documentation
From 5473c59294efad0d6eff949f2075e1f9ad208299 Mon Sep 17 00:00:00 2001
From: Massny
Date: Fri, 16 Oct 2026 19:14:23 +0200
Subject: [PATCH 4/6] feat: improve docs for parallel sampling
---
docs/user-guide/discrete.md | 7 +--
docs/user-guide/sampling.md | 86 ++++++++++++++++++++++---------------
2 files changed, 56 insertions(+), 37 deletions(-)
diff --git a/docs/user-guide/discrete.md b/docs/user-guide/discrete.md
index a91c87b..c0bf50b 100644
--- a/docs/user-guide/discrete.md
+++ b/docs/user-guide/discrete.md
@@ -233,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
From 1195296dd7d18e4527085ba7fd24035402909418 Mon Sep 17 00:00:00 2001
From: Massny
Date: Fri, 16 Oct 2026 19:39:08 +0200
Subject: [PATCH 5/6] feat: remove remarks about "ealier behaviours" which were
not defined from documentation
---
src/lonkit/continuous/sampling.py | 2 +-
src/lonkit/discrete/sampling.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
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
From 0dfc15dca0bcbb5f275b6836062a673d2fafcbe2 Mon Sep 17 00:00:00 2001
From: Massny
Date: Sat, 17 Oct 2026 20:08:46 +0200
Subject: [PATCH 6/6] fix: grammar and typos in readme
---
README.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/README.md b/README.md
index 5fba24a..a39dbf1 100644
--- a/README.md
+++ b/README.md
@@ -16,11 +16,11 @@
## Features
-- **LON Construction**: Local Optima Networks as `igraph` graphs generated from sampled data with support for Compressed Monotonic LONs (CMLONs) representation.
-- **Continuous landscape exploration**: Configurable Basin-Hopping algorithm for data sampling, with automatic step-size estimation and pararellization.
+- **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.
+- **Visualization**: Configurable 2D and 3D LON plots, and animated rotating GIFs.
## Installation
@@ -170,7 +170,7 @@ viz.plot_2d(cmlon, output_path="npp_cmlon.png")
### Custom Discrete Problems
-You can define your own discrete problem by subclassing `DiscreteProblem`. You can also extend built-in problems - for bitstring problems, extend `BitstringProblem`, which it provides `random_solution()`, `local_search()`, `perturb()`, and `solution_id()` out of the box, so 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