K has more interdependencies, creating a more rugged surface with many local optima that agents can get trapped in.
The OptimizedNKLandscape class in examples/LandscapeConstruction_NK_Modular.py extends the classical NK model with a two-module structure and a parameter R that controls how insular each module’s dependencies are.
Parameters
| Parameter | Type | Description |
|---|---|---|
N | int | Number of bits in each agent’s state. Must be even. |
K | int | Number of epistatic dependencies per bit. Must satisfy 0 <= K < N. |
R | float | Fraction of intra-module dependencies for module 1 (0–1). R=0 means all cross-module; R=1 means fully self-contained. |
seed | int | Optional random seed for reproducibility. |
use_cache | bool | Enable LRU fitness caching (default True). |
max_cache_size | int | Maximum number of cached fitness values (default 100000). |
N must be even because the landscape is divided into exactly 2 modules of N/2 bits each. Passing an odd N raises a ValueError.Module structure
The landscape is split into two modules ofN/2 bits:
- Module 0 (bits
0toN/2 - 1): dependencies are chosen completely at random from allNbits. - Module 1 (bits
N/2toN - 1):Rcontrols the fraction of dependencies that stay within module 1. A fraction(1 - R)are drawn from module 0 (cross-module).
R landscape has two semi-independent search problems, while R=0 creates tight cross-module coupling.
The maximum valid
R is constrained by K and module size. If R is too high to satisfy K intra-module dependencies, the constructor raises a ValueError with the maximum permissible value.Fitness function
Fitness is calculated in two stages:- Raw fitness — for each bit
i, look upfitness_contributions[i]using the binary states ofiand itsKdependencies as an index. Average across allNbits. - Normalization and transformation — the raw value is linearly rescaled to
[0, 1]using the true min and max across all2^Nstates, then raised to the power of 4:
^4 transform concentrates the distribution near zero, making high-fitness states rare and meaningful.
Memory-efficient bounds calculation
Finding the true min and max requires evaluating all2^N states. OptimizedNKLandscape does this with a streaming pass at construction time — it never stores all fitness values at once, processing states in batches of 10,000:
O(2^N) in time but O(N) in memory.
Key methods
get_fitness(state)
get_fitness(state)
Compute the normalized, transformed fitness for a binary state array.Results are cached with LRU eviction. When the cache reaches
max_cache_size, the least-recently-used entry is evicted.random_state()
random_state()
Generate a random binary state vector of length Uses the landscape’s internal
N.RandomState, so behavior is reproducible when seed is set.get_neighbors(state)
get_neighbors(state)
Return all single-bit-flip neighbors of a state — a list of Useful for greedy hill-climbing: evaluate all neighbors and move to the best.
N arrays.analyze_modularity()
analyze_modularity()
Return a dict summarizing the dependency structure:
actual_R_module1 may differ slightly from target_R due to integer rounding when computing int(round(K * R)).visualize_interactions(save_path=None, figsize=(10, 8))
visualize_interactions(save_path=None, figsize=(10, 8))
Plot the Module 0 is outlined in green, module 1 in orange, and the boundary between modules is marked with red lines.
N × N binary interaction matrix as a heatmap with module boundaries highlighted. Requires matplotlib.Using with AgentModel
To simulate agents searching the NK landscape withAgentModel, store the landscape as a parameter and access it inside your timestep function.
Define the initial data function
Each node gets a random binary state and its fitness on the landscape.
Define the timestep function
Each agent either explores (random single-bit flip) or copies the fittest neighbor.
Cache management
For largeN or long simulations, the cache can grow large. Call clear_cache() periodically to release memory:
get_cache_stats():