Discrete Module¶
Problems¶
DiscreteProblem ¶
Bases: ABC, Generic[SolutionT]
Abstract base class for discrete optimization problems.
A DiscreteProblem defines the search space, objective function,
neighborhood structure, and perturbation operator. It is stateless:
all randomness is injected via an rng parameter owned by the caller
(typically the ILS sampler).
Subclasses must implement all abstract methods to define: - Solution generation and identity - Fitness evaluation (pure function) - Local search (hill climbing with problem-specific neighborhood) - Perturbation (escape operator for ILS)
The ILS sampler calls these methods without knowing the solution representation, neighborhood structure, or move operators.
Type parameter
SolutionT: The solution representation type (e.g., list[int] for bitstrings, list[int] for permutations).
Source code in src/lonkit/discrete/problems/problem.py
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 | |
is_better ¶
is_better_or_equal ¶
compare ¶
Compare two fitness values.
Returns:
| Type | Description |
|---|---|
int
|
1 if a is better, -1 if b is better, 0 if equal. |
Source code in src/lonkit/discrete/problems/problem.py
random_solution
abstractmethod
¶
Generate a random initial solution.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rng
|
Generator
|
Random number generator (owned by the caller). |
required |
Returns:
| Type | Description |
|---|---|
SolutionT
|
A solution object (representation is problem-specific). |
SolutionT
|
The returned solution does NOT need to be a local optimum. |
Source code in src/lonkit/discrete/problems/problem.py
evaluate
abstractmethod
¶
Evaluate and return the fitness of a solution.
This must be a pure function: same solution always returns the same fitness, no side effects.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
solution
|
SolutionT
|
A solution object. |
required |
Returns:
| Type | Description |
|---|---|
float
|
Scalar fitness value. |
Source code in src/lonkit/discrete/problems/problem.py
local_search
abstractmethod
¶
Run local search from a starting solution to find a local optimum.
This encapsulates the full hill-climbing loop: neighborhood definition, scanning strategy (first/best improvement), and termination.
Stochastic local search note: When using first-improvement with randomized scan order, the local optimum reached from a given starting solution depends on the RNG state. This means basin identity is sampler-state dependent -- the same starting solution may reach different local optima across runs. This is an intentional and well-understood property of stochastic local search in LON construction.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
solution
|
SolutionT
|
Starting solution (not necessarily a local optimum). |
required |
rng
|
Generator
|
Random number generator (owned by the caller). |
required |
Returns:
| Type | Description |
|---|---|
tuple[SolutionT, float]
|
Tuple of (local_optimum_solution, fitness). |
Source code in src/lonkit/discrete/problems/problem.py
perturb
abstractmethod
¶
Apply a perturbation to escape the current basin of attraction.
The perturbation should be strong enough to potentially reach a different basin but not so strong as to be random restart.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
solution
|
SolutionT
|
A local optimum solution. |
required |
rng
|
Generator
|
Random number generator (owned by the caller). |
required |
Returns:
| Type | Description |
|---|---|
SolutionT
|
A perturbed solution (NOT necessarily a local optimum). |
Source code in src/lonkit/discrete/problems/problem.py
solution_id
abstractmethod
¶
Return a unique string identifier for this solution.
Two solutions that represent the same point in the search space MUST return the same ID. Two different solutions MUST return different IDs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
solution
|
SolutionT
|
A solution object. |
required |
Returns:
| Type | Description |
|---|---|
str
|
String identifier (used as node ID in the LON). |
Source code in src/lonkit/discrete/problems/problem.py
BitstringProblem ¶
Bases: DiscreteProblem[list[int]]
Base class for problems with binary string representation.
Provides common bitstring operations: - Random solution generation (random bitstring) - Perturbation (k random bit flips) - Solution identity (join bits as string) - Hill climbing with first/best-improvement flip neighborhood
Subclasses must implement evaluate() and may override
minimize or local_search().
The problem is stateless: n, n_perturbation_flips, and
first_improvement are immutable configuration. All randomness
comes from the rng parameter passed by the caller.
Source code in src/lonkit/discrete/problems/bitstring.py
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 | |
__init__ ¶
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n
|
int
|
Length of the bitstring. Must be > 0. |
required |
n_perturbation_flips
|
int
|
Number of random bit flips per perturbation. Must be in [1, n]. |
2
|
first_improvement
|
bool
|
If True, local search uses first-improvement hill climbing (stochastic -- scan order randomized each pass). If False, uses best-improvement (deterministic). |
True
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If n <= 0 or n_perturbation_flips is out of [1, n]. |
Source code in src/lonkit/discrete/problems/bitstring.py
perturb ¶
Flip n_perturbation_flips random distinct bits.
Source code in src/lonkit/discrete/problems/bitstring.py
delta_evaluate ¶
Optional delta evaluation hook for flipping bit at index.
Returns the fitness change (delta) if efficient delta evaluation is supported, or None to fall back to full evaluation.
The default implementation returns None. Subclasses like OneMax can override this for O(1) evaluation instead of O(n).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
solution
|
list[int]
|
Current solution (not modified). |
required |
index
|
int
|
Bit index that would be flipped. |
required |
Returns:
| Type | Description |
|---|---|
float | None
|
Fitness delta (new_fitness - current_fitness), or None. |
Source code in src/lonkit/discrete/problems/bitstring.py
local_search ¶
First/best improvement hill climbing with 1-bit-flip neighborhood.
Scans all N bit positions. For first-improvement, the scan order is randomized each pass (stochastic). For best-improvement, the scan is deterministic. Stops when no improving neighbor exists.
Uses delta_evaluate() when available for O(1) neighbor evaluation;
falls back to full evaluate() otherwise.
Source code in src/lonkit/discrete/problems/bitstring.py
NumberPartitioning ¶
Bases: BitstringProblem
Number Partitioning Problem (NPP).
Given a set of N positive integers, partition them into two subsets such that the absolute difference of their sums is minimized.
A solution is a bitstring of length N. Bit i=0 means item i goes to subset A, bit i=1 means subset B.
Fitness = |sum(A) - sum(B)| (minimization, optimal = 0).
Construction: provide either explicit weights or both k and
instance_seed for random instance generation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n
|
int
|
Number of items. Must be > 0. |
required |
k
|
float | None
|
Hardness parameter. Items drawn uniformly from [1, 2^(n*k)].
Higher k = harder instances (phase transition around k ~ 1.0).
Required if |
None
|
instance_seed
|
int | None
|
Seed for generating item weights.
Required if |
None
|
weights
|
list[int] | None
|
Explicit item weights. If provided, |
None
|
n_perturbation_flips
|
int
|
Number of random flips per perturbation (default: 2). |
2
|
first_improvement
|
bool
|
If True, local search uses first-improvement hill climbing (stochastic -- scan order randomized each pass). If False, uses best-improvement (deterministic). Default: True. |
True
|
Source code in src/lonkit/discrete/problems/bitstring.py
OneMax ¶
Bases: BitstringProblem
OneMax problem: maximize the number of 1-bits.
Fitness = sum(bits). Single global optimum at all-ones.
Supports O(1) delta evaluation: flipping bit i changes fitness by -1 (if 1->0) or +1 (if 0->1).
Source code in src/lonkit/discrete/problems/bitstring.py
delta_evaluate ¶
Sampling¶
ILSSamplerConfig
dataclass
¶
Configuration for Iterated Local Search sampling.
Attributes:
| Name | Type | Description |
|---|---|---|
n_runs |
int
|
Number of independent ILS runs. Default: 100. |
n_iter_no_change |
int | None
|
Maximum consecutive non-improving iterations before stopping each run. Use None for no limit. Setting both n_iter_no_change and max_iter to None will result in an error. Default: 100. |
max_iter |
int | None
|
Maximum total iterations per run. Use None for no limit. Setting both n_iter_no_change and max_iter to None will result in an error. Default: None. |
accept_equal |
bool
|
If True, accept moves to equal-fitness optima (greedy with equal acceptance). If False, only accept strictly improving moves. Default: True. |
seed |
int | None
|
Random seed for reproducibility. Controls ALL search randomness: initial solution generation, local search scan order, and perturbation. The problem instance is stateless and receives the sampler's RNG. Default: None. |
Source code in src/lonkit/discrete/sampling.py
ILSSampler ¶
Iterated Local Search sampler for constructing Local Optima Networks from discrete optimization problems.
ILS alternates between perturbation and local search to explore the space of local optima. Transitions between local optima are recorded in the same trace format as BasinHoppingSampler, enabling direct use with LON.from_trace_data().
The sampler owns all search randomness via a single numpy.random.Generator
instance created from ILSSamplerConfig.seed. The problem instance
is stateless — the sampler passes its RNG into every problem method
call (random_solution, local_search, perturb).
Example
from lonkit.discrete import NumberPartitioning, ILSSampler, ILSSamplerConfig problem = NumberPartitioning(n=20, k=0.5, instance_seed=1) config = ILSSamplerConfig(n_runs=10, n_iter_no_change=100, seed=42) sampler = ILSSampler(config) result = sampler.sample(problem) lon = sampler.sample_to_lon(result)
Source code in src/lonkit/discrete/sampling.py
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 | |
sample ¶
sample(
problem: DiscreteProblem[Any],
progress_callback: Callable[[int, int], None]
| None = None,
) -> ILSResult
Run ILS sampling and construct trace data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
problem
|
DiscreteProblem[Any]
|
A DiscreteProblem instance defining the optimization problem, local search, and perturbation operators. The problem must be stateless — the sampler provides the RNG. |
required |
progress_callback
|
Callable[[int, int], None] | None
|
Optional callback(run, total_runs) for progress reporting. Default: None. |
None
|
Returns:
| Type | Description |
|---|---|
ILSResult
|
ILSResult with trace DataFrame and raw records. |
Source code in src/lonkit/discrete/sampling.py
sample_to_lon ¶
Construct a LON from an ILSResult.
Convenience wrapper that passes the trace data to LON.from_trace_data(). Equivalent to calling LON.from_trace_data(sampler_result.trace_df, config=lon_config).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sampler_result
|
ILSResult
|
Result returned by |
required |
lon_config
|
LONConfig | None
|
LON construction configuration. If |
None
|
Returns:
| Type | Description |
|---|---|
LON
|
|
Source code in src/lonkit/discrete/sampling.py
ILSResult
dataclass
¶
Result of ILS sampling.
Attributes:
| Name | Type | Description |
|---|---|---|
trace_df |
DataFrame
|
DataFrame with columns [run, fit1, node1, fit2, node2] representing transitions between local optima. |
raw_records |
list[dict]
|
List of dicts with per-iteration data. Each dict has keys: run, iteration, current_id, current_fitness, new_id, new_fitness, accepted. |
minimize |
bool
|
Whether the problem is a minimization problem. Default: True. |