Reference

Configuration Reference

Every key a GET configuration accepts: what it does, what else it can be, what rejects it. This is the page to look something up in. The route pages show a whole document being written and run.

One schema, two spellings. A TOML document and the Python config objects are the same thing: the objects serialize to TOML and that TOML is what the Rust side parses, so neither route accepts a configuration the other would reject. Where the Python name differs it is noted in the row.

Top Level

KeyTypeDefaultWhat it does
population_sizeintegerrequired Individuals in the population.
network_sizeintegerrequired Nodes in every expressed graph. Fixed for the run; nothing adds or removes nodes.
max_edge_multiplicityinteger1 Edge-weight cap. 1 is an unweighted simple graph; above 1 allows parallel edges. Must be 1–255. Also sets the SDA alphabet, which is derived as cap + 1, so every character the automaton can emit is a legal weight.
crossover_ratefloatrequired Probability a selected pair is recombined. Validated in [0, 1].
mutation_ratefloatrequired Probability a child is mutated at all, not a per-gene rate. Validated in [0, 1].
max_mutationsinteger1 A mutating child takes a count drawn uniformly from 1..=max_mutations. Must be at least 1. Read this and mutation_rate as one knob: whether a child mutates, then how hard.

[evolution]

Required. type selects the strategy, and the other keys belong to that strategy.

typeKeyDefaultWhat it does
"generational" num_generationsrequired Whole-population replacements to run.
elite_count1 Best individuals carried forward untouched each generation. Must be less than population_size; equal would mean nothing ever changes.
"steady_state" num_mating_eventsrequired Single breeding events to run. One event touches one scope, not the whole population.
replacement{ type = "worst" } Who the children overwrite. See Add a Replacement Policy for the axis. worst is what makes steady-state self-elitist; random draws uniformly and gives that up.

replacement lives inside [evolution] rather than in a section of its own, because it belongs to whichever strategy displaces individuals, the same way elite_count belongs to generational. Generational never asks who to overwrite.

[scope]

Required in every configuration, generational included. It names which slice of the population one breeding event may touch.

typeKeyWhat it does
"global"none Every individual is a candidate. Consumes no randomness.
"random_subset"size size distinct individuals, drawn uniformly without replacement. At least 1 and at most population_size, and at least 4 under steady-state, which needs two parents and two distinct individuals for them to replace. Generational has no such floor.
size lives here, not under [selection]. It is the scope's own number; [selection] carries only the tournament's. A tournament may exceed the population, because it samples with replacement, and the steady-state floor of four applies to size rather than to tournament_size.

[selection]

Required. Who, within the scope, becomes a parent.

typeKeyWhat it does
"best"none Takes the scope's fittest, in rank order. Consumes no randomness; the scope did the drawing.
"tournament"tournament_size Draws tournament_size members of the scope with replacement and takes the best, once per parent. At least 1.

[crossover]

Optional. Omitted means two-point, which is what every representation did before the operator became selectable, so an existing configuration keeps its behaviour by leaving this out.

typeWhat it does
"two_point" Two cut points, the middle segment exchanged. The only operator that ships.

Crossover is a shared section because both genomes' recombination is the same operation over a linear genome. Mutation is not; see below.

[genome]

Required. type selects the representation, and the keys under it are that representation's.

type = "edge_edit"

KeyDefaultWhat it does
gene_lengthrequired Number of edit operations in the genome. The graph is what replaying them produces.
base_graphthe empty graph An edge-list file the edits are applied to. A relative path from the location of the .toml file, not from your working directory. The file's # nodes = N header must equal network_size. Read as 0-indexed.
mutation{ type = "reroll_gene" } Which mutation operator applies. See Add a Mutation Operator.
operation_weightsall 1.0 Sub-table, below.

[genome.operation_weights]

Nine relative weights, one per edit operation: toggle, add, delete, hop, swap, local_toggle, local_add, local_delete, null. The Nine Operations says what each one does to the graph.

They are weights, not probabilities: they are normalized, so they need not sum to anything. Each must be finite and non-negative, at least one must be positive, and 0.0 disables its operation outright. Omit the table entirely, or any field in it, and that operation weighs 1.0. Like [genome] itself, this sub-table rejects a key it does not recognize.

type = "sda"

KeyDefaultWhat it does
num_statesrequired States in the automaton. Must be in 1..=65536.
max_resp_lenrequired Longest response string a transition may emit; must be at least 1. Responses are drawn from 1..=max_resp_len, never empty, which is what guarantees the automaton terminates.
init_state0 State the automaton starts in. Must be less than num_states.
init_char_mutation_rate0.04 Chance a mutation targets the initial character rather than the tables. In [0, 1].
transition_vs_response_rate0.5 Given the tables are targeted, the split between transitions and responses. In [0, 1].
mutation{ type = "redraw_one" } Which mutation operator applies.
There is no num_chars. The alphabet is derived as max_edge_multiplicity + 1, so every character the automaton can emit is a legal edge weight and none is ever clamped away.

Mutation is per-genome; crossover is shared. mutation sits inside each genome's own block rather than in a section of its own, because what counts as "one mutation" differs entirely between a list of edit operations and an automaton, and a shared enum would let a configuration name an operator meaningless for the genome actually selected.

[fitness]

Required. type selects the objective.

typeDirectionWhat it scores
"epi_spread"maximize Total ever-infected, averaged over epidemics.
"epi_length"maximize Timesteps for the outbreak to burn out.
"epi_prof_match"minimize RMSE against a target infection profile.
"struct_match"minimize Structural distance from a folder of reference graphs. Reads data from disk; requires max_edge_multiplicity = 1.
"python"declared at registration A Python callable registered on the evolver before the run.

The SIR block: the three epi_* objectives

These keys sit directly under [fitness], not in a sub-table.

KeyDefaultWhat it does
infection_raterequired Per-contact transmission probability, in [0, 1].
num_epidemicsrequired Epidemics averaged per evaluation. At least 1, since fitness is their mean.
patient_zerorandom each epidemic Fix the seed node instead. When set, it must be less than network_size.
min_epidemic_length3 Outbreaks shorter than this are re-simulated. Set to 1 to disable the re-roll.
max_epidemic_retries5 How many re-simulations before accepting a short one. At least 1.

epi_prof_match

target_profile is required, a non-empty array of finite numbers. Comparison covers the target array's length: a shorter epidemic is padded with zeros, and a longer epidemic's tail is ignored. Nothing is prepended or rescaled. Supplying it under another objective is rejected.

struct_match

KeyDefaultWhat it does
reference_folderrequired Folder of reference graphs, one edge-list file each. Not checked when the config is parsed: validation does no I/O, so a missing or empty folder is reported when the run starts.
degree_bins · clustering_bins · spectral_bins 50 Histogram bins per statistic family. More bins resolve finer differences and need more reference graphs to fill them. Each bin count must be at least 1.
degree_gamma · clustering_gamma · spectral_gamma 1.0 RBF bandwidths. Must be finite and greater than zero, because the kernel divides by them.
degree_weight · clustering_weight · spectral_weight 1.0 How much each family counts. Finite and non-negative; they cannot all be zero, which would score every candidate identically.
density_weight1.0 How much distance from the reference set's mean density counts. Must be finite and non-negative; zero switches the penalty off.
Too large a gamma is the dangerous direction. exp(-gamma * d²) collapses to zero for every candidate, the whole population scores about the same, and evolution stalls while appearing to run normally. And a weight is only as live as the reference set makes it: rings and paths have clustering coefficient 0 at every node, so a reference set drawn only from those leaves clustering_weight set and the family it weights inert, with nothing reporting it.

What Is Not a Config Value

ThingWhere it actually goes
The seed One master seed is passed to the run call; the population, evolution, epidemics and replicates all derive from it. Writing seed under [fitness] is rejected by name rather than ignored, so a document carrying one fails loudly instead of running with a seed nobody supplied.
A base graph in your own node numbering, or one supplied without a config file [genome] base_graph covers the ordinary case, but it is 0-indexed and it resolves against the config file, so a file numbered from 1, or a config assembled as objects in Python, still needs set_base_graph_from_file and its min_node_index. See Python: Config Objects.
Replicate count, output folder Arguments to the run, not the configuration. See the route pages.
num_chars for SDA Derived from max_edge_multiplicity, above.

Typos: Which Tables Catch Them

[genome] and [genome.operation_weights] reject any key they do not recognize, under either type. Most other tables ignore one.

[fitness] cannot reject typos, and this is structural rather than an oversight. Its variants flatten the shared SIR block into themselves, and flattening consumes unrecognized keys into the flattened field, so the check that rejects them cannot fire there. Two names are caught explicitly, because both are mistakes a reader makes rather than typos: seed, which belongs to the run call, and target_profile under an objective that never reads one. Anything else misspelled under [fitness] is silently ignored, and your run uses the default.

When a Value Is Rejected

Most validation runs once, up front, so common bad configurations fail before evolution. Some SDA dimension constraints and filesystem-dependent checks are reached only when a run starts rather than when the file loads: max_resp_len = 0 is one. The error names the field and the constraint. From Python it arrives as a ValueError naming the attribute path you would have written (config.scope.size), not the TOML key, so the message matches the code in front of you.

Validation deliberately does not touch the filesystem. A path-valued setting, today only reference_folder, is checked when the run starts; the folder must exist and contain at least one usable reference graph.