graph-evolution-tool 0.9.0__cp38-abi3-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
get/__init__.py ADDED
@@ -0,0 +1,12 @@
1
+ # Re-exports the compiled extension module so `import get` gives the classes
2
+ # directly rather than `get.get.GraphEvolver`.
3
+ #
4
+ # maturin generates exactly this file when the package has no Python source of
5
+ # its own. It is written out here because the package now *does* have Python
6
+ # source — `py.typed` and `__init__.pyi`, which an editor needs beside the
7
+ # module — and declaring `python-source` means maturin stops generating it.
8
+ from .get import *
9
+
10
+ __doc__ = get.__doc__
11
+ if hasattr(get, "__all__"):
12
+ __all__ = get.__all__
get/__init__.pyi ADDED
@@ -0,0 +1,680 @@
1
+ """Type stubs for `get`, generated by `tools/generate_stubs.py`.
2
+
3
+ Regenerate with `maturin develop --release && python3 tools/generate_stubs.py`.
4
+ Type annotations here are written by hand and survive regeneration; everything
5
+ else comes from the built module.
6
+ """
7
+
8
+ from typing import Callable
9
+
10
+ __all__ = ['GraphEvolver', 'Config', 'EvolutionConfig', 'ReplacementConfig', 'ScopeConfig', 'SelectionConfig', 'CrossoverConfig', 'EdgeEditMutationConfig', 'SdaMutationConfig', 'GenomeConfig', 'FitnessConfig', 'SirParams', 'OperationWeights', 'RunResult', 'GenerationStats']
11
+
12
+ class GraphEvolver:
13
+ """Python-facing entry point to the graph-evolution engine.
14
+
15
+ Constructed from a `config.toml` path; `run` dispatches on the configured
16
+ evolution strategy, genome representation and fitness objective.
17
+
18
+ **It holds no results.** A run's state lives in the `RunResult` that `run`
19
+ returns, so one evolver drives repeated runs with nothing stale from the
20
+ previous one hanging off it.
21
+ """
22
+ def __init__(self, config_path: str) -> None:
23
+ ...
24
+ @staticmethod
25
+ def from_config(config: Config) -> GraphEvolver:
26
+ """Build from configuration assembled in Python, rather than from a file.
27
+
28
+ Accepts exactly what a `config.toml` would, and reports a rejection in
29
+ the same words. Every block is its own object — `EvolutionConfig`,
30
+ `ScopeConfig`, `SelectionConfig`, `GenomeConfig`, `FitnessConfig` — and
31
+ all of them are required.
32
+
33
+ A field too large for a TOML integer is rejected here, which a
34
+ `config.toml` writer never meets.
35
+ """
36
+ ...
37
+ def load_reference_graphs(self, folder: str, min_node_index: int=0) -> list[tuple[str, int, list[tuple[int, int, int]]]]:
38
+ """Read a folder of graphs, one file per graph, and hand them back.
39
+
40
+ The bulk counterpart to `set_base_graph_from_file`, for reference data an
41
+ objective matches against. Each file is one edge per line,
42
+ `start,end,weight`, and every file in the folder shares this run's node
43
+ numbering.
44
+
45
+ **A reference graph may be larger than the network being evolved**, and
46
+ usually is — the only ceiling is a sanity bound against a file indexed
47
+ the wrong way.
48
+
49
+ Each graph comes back as `(name, num_nodes, edges)`, **sorted by file
50
+ name**, because a reference set is consumed positionally and filesystem
51
+ order would let a run's numbers depend on how its data was written to
52
+ disk. The node count is stated rather than derived: an isolated node
53
+ appears in no edge, so each file declares its own in a `# nodes = N`
54
+ header. Sub-directories are skipped; every other file is read.
55
+
56
+ **This call declares the run's numbering** as the base-graph setters do.
57
+ The reference graphs themselves come back exactly as supplied.
58
+
59
+ A rejection names the file and the line; the warnings are the base-graph
60
+ loader's, each naming the file it came from.
61
+ """
62
+ ...
63
+ def run(self, seed: int, n_runs: int=1, max_cores: int | None=None) -> list[RunResult]:
64
+ """Evolve a population `n_runs` times and return what every run produced.
65
+
66
+ **Always a list, one `RunResult` per replicate, in run order** — even at
67
+ the default `n_runs = 1`, so the return type does not change shape with
68
+ an argument.
69
+
70
+ **One master seed, not `n` of them**, so a run's seed does not depend on
71
+ how many were requested: asking for 50 reproduces the first 30 of a
72
+ 30-run request exactly.
73
+
74
+ **Whether replicates run concurrently is the engine's call, not yours.**
75
+ A native Rust objective runs them in parallel; `fitness = "python"` runs
76
+ them one at a time, since concurrent runs would contend for a single GIL.
77
+ `max_cores` caps the concurrency; unset means all available.
78
+
79
+ A replicate that fails abandons the remaining runs rather than returning
80
+ half-complete.
81
+
82
+ # Memory: the sizes multiply, they do not add
83
+
84
+ The whole population is materialized before scoring, and a graph is a
85
+ **dense** matrix however sparse it actually is, so peak memory is roughly
86
+ `network_size² × 4 bytes × population_size × min(max_cores, replicates)`.
87
+ Raising any one of them scales the whole product.
88
+ """
89
+ ...
90
+ def set_base_graph(self, num_nodes: int, edges: list[tuple[int, int, int]], min_node_index: int=0) -> None:
91
+ """Seed an edge-edit run from a graph the caller already has.
92
+
93
+ `edges` is `(u, v, multiplicity)` — the same shape `run` hands back as
94
+ `best_edges`, so one run's output feeds the next without reshaping.
95
+
96
+ **One run has one numbering.** `min_node_index` is where the caller's
97
+ own numbering starts — pass `1` for 1-indexed edges. Every loader on this
98
+ evolver must declare the same one, and it is what results are shifted
99
+ back into, so they return in the numbering the data arrived in.
100
+
101
+ **Left unset, every run starts from an empty graph.** Several edit
102
+ opcodes need existing structure to walk, so early generations do little
103
+ until `Add` and `Toggle` have built some. That is self-correcting.
104
+
105
+ A rejected edge raises `ValueError` naming the index as the caller wrote
106
+ it, not as it would be after shifting. A pair given more than once is a
107
+ `UserWarning` and the **last** occurrence wins, compared canonically, so
108
+ `(2, 5)` and `(5, 2)` are one undirected edge.
109
+ """
110
+ ...
111
+ def set_base_graph_from_file(self, path: str, min_node_index: int=0) -> None:
112
+ """Seed an edge-edit run from an edge-list file, rather than from a list
113
+ built in Python.
114
+
115
+ One edge per line, `start,end,weight`, comma-delimited, any line ending.
116
+ `min_node_index` is where the caller's own node numbering starts — pass
117
+ `1` for 1-indexed data, which is the common case in graph files.
118
+ 1-indexed in is 1-indexed out.
119
+
120
+ A `# nodes = N` header is mandatory and must agree with `network_size`;
121
+ a file with no header is rejected rather than assumed to match it.
122
+
123
+ **Nothing is stored unless the whole file survives**, and a rejection
124
+ names the line it came from. A repeated edge, a zero-weight edge and an
125
+ empty file are each a `UserWarning` rather than an error.
126
+ """
127
+ ...
128
+ def set_fitness_function(self, callable: Callable[..., float], direction: str) -> None:
129
+ """Register a Python callable as the objective, with the direction it is
130
+ meant to be optimized in.
131
+
132
+ `config.toml` only *selects* Python — `[fitness] type = "python"`. The
133
+ callable itself arrives here, and so does its direction: nothing can
134
+ infer whether a user's function wants its value large or small.
135
+
136
+ The callable takes the **whole batch** and returns one float per graph,
137
+ in the same order:
138
+
139
+ A batch element is `(num_nodes, edges)`, and `direction` is
140
+ `"minimize"` or `"maximize"`.
141
+
142
+ Registering a callable when the config did not select Python is a
143
+ `ValueError`, not a silent no-op.
144
+ """
145
+ ...
146
+
147
+ class Config:
148
+ """Everything the genetic algorithm needs for a run."""
149
+ def __init__(self, evolution: EvolutionConfig, population_size: int, network_size: int, crossover_rate: float, mutation_rate: float, scope: ScopeConfig, selection: SelectionConfig, genome: GenomeConfig, fitness: FitnessConfig, max_edge_multiplicity: int=1, max_mutations: int=1, crossover: CrossoverConfig | None=None) -> None:
150
+ ...
151
+ @property
152
+ def crossover(self) -> CrossoverConfig:
153
+ """Recombination operator; two-point when unset."""
154
+ ...
155
+ @property
156
+ def crossover_rate(self) -> float:
157
+ """Probability that a selected pair is recombined."""
158
+ ...
159
+ @property
160
+ def evolution(self) -> EvolutionConfig:
161
+ """Which evolution strategy to run."""
162
+ ...
163
+ @property
164
+ def fitness(self) -> FitnessConfig:
165
+ """Fitness objective."""
166
+ ...
167
+ @property
168
+ def genome(self) -> GenomeConfig:
169
+ """Genome representation and its dimensions."""
170
+ ...
171
+ @property
172
+ def max_edge_multiplicity(self) -> int:
173
+ """Edge-weight cap; 1 is unweighted."""
174
+ ...
175
+ @property
176
+ def max_mutations(self) -> int:
177
+ """How many mutations a mutating child takes, drawn uniformly from
178
+ `1..=max_mutations`.
179
+ """
180
+ ...
181
+ @property
182
+ def mutation_rate(self) -> float:
183
+ """Probability that a child is mutated at all."""
184
+ ...
185
+ @property
186
+ def network_size(self) -> int:
187
+ """Number of nodes in every expressed graph."""
188
+ ...
189
+ @property
190
+ def population_size(self) -> int:
191
+ """Number of individuals in the population."""
192
+ ...
193
+ @property
194
+ def scope(self) -> ScopeConfig:
195
+ """Which slice of the population one breeding event draws from."""
196
+ ...
197
+ @property
198
+ def selection(self) -> SelectionConfig:
199
+ """Parent-selection strategy, applied within that scope."""
200
+ ...
201
+ def to_toml(self) -> str:
202
+ """Render this config as the TOML document GET parses — the record of what
203
+ was run, byte for byte.
204
+
205
+ `ValueError` if a field is too large for a TOML integer, `2**63 - 1`.
206
+ """
207
+ ...
208
+
209
+ class EvolutionConfig:
210
+ """Which evolution strategy to run, and its strategy-specific settings."""
211
+
212
+ class Generational(EvolutionConfig):
213
+ """Whole-population replacement, run `num_generations` times."""
214
+ def __init__(self, num_generations: int, elite_count: int=1) -> None:
215
+ ...
216
+ @property
217
+ def elite_count(self) -> int:
218
+ """Best individuals carried forward untouched each generation.
219
+
220
+ Must be less than `population_size` — equal would mean nothing ever
221
+ changes.
222
+ """
223
+ ...
224
+ @property
225
+ def num_generations(self) -> int:
226
+ """Whole-population replacements to run."""
227
+ ...
228
+
229
+ class SteadyState(EvolutionConfig):
230
+ """Single breeding events, `num_mating_events` of them.
231
+
232
+ One event touches one scope, not the whole population.
233
+ """
234
+ def __init__(self, num_mating_events: int, replacement: ReplacementConfig | None=None) -> None:
235
+ ...
236
+ @property
237
+ def num_mating_events(self) -> int:
238
+ """Single breeding events to run. One event touches one scope, not the whole
239
+ population.
240
+ """
241
+ ...
242
+ @property
243
+ def replacement(self) -> ReplacementConfig:
244
+ """Who the children overwrite; omitted, the least fit.
245
+
246
+ `Worst` is what makes steady-state self-elitist; `Random` gives that up.
247
+ """
248
+ ...
249
+
250
+ class ReplacementConfig:
251
+ """Which members of a scope a mating event's children overwrite."""
252
+
253
+ class Random(ReplacementConfig):
254
+ """Children overwrite individuals drawn uniformly from the scope, giving up
255
+ steady-state's self-elitism.
256
+ """
257
+ def __init__(self) -> None:
258
+ ...
259
+
260
+ class Worst(ReplacementConfig):
261
+ """Children overwrite the least fit of the scope.
262
+
263
+ This is what makes steady-state self-elitist.
264
+ """
265
+ def __init__(self) -> None:
266
+ ...
267
+
268
+ class ScopeConfig:
269
+ """The slice of the population one breeding event draws from."""
270
+
271
+ class Global(ScopeConfig):
272
+ """Every individual is a candidate. Consumes no randomness."""
273
+ def __init__(self) -> None:
274
+ ...
275
+
276
+ class RandomSubset(ScopeConfig):
277
+ """`size` distinct individuals, drawn uniformly without replacement."""
278
+ def __init__(self, size: int) -> None:
279
+ ...
280
+ @property
281
+ def size(self) -> int:
282
+ """At least 1 and at most `population_size` — and at least 4 under steady-
283
+ state, which needs two parents and two distinct individuals for them to
284
+ replace. Generational has no such floor.
285
+ """
286
+ ...
287
+
288
+ class SelectionConfig:
289
+ """Parent-selection strategy."""
290
+
291
+ class Best(SelectionConfig):
292
+ """Takes the scope's fittest, in rank order.
293
+
294
+ Consumes no randomness — the scope did the drawing.
295
+ """
296
+ def __init__(self) -> None:
297
+ ...
298
+
299
+ class Tournament(SelectionConfig):
300
+ """Draws `tournament_size` members of the scope with replacement and takes the
301
+ best, once per parent.
302
+ """
303
+ def __init__(self, tournament_size: int) -> None:
304
+ ...
305
+ @property
306
+ def tournament_size(self) -> int:
307
+ """At least 1. May exceed the population — the floor is checked against the
308
+ scope's `size` instead.
309
+ """
310
+ ...
311
+
312
+ class CrossoverConfig:
313
+ """Recombination operator."""
314
+
315
+ class TwoPoint(CrossoverConfig):
316
+ """Two cut points, the middle segment exchanged. The only operator that
317
+ ships.
318
+ """
319
+ def __init__(self) -> None:
320
+ ...
321
+
322
+ class EdgeEditMutationConfig:
323
+ """Which mutation an edge-edit genome applies."""
324
+
325
+ class RerollGene(EdgeEditMutationConfig):
326
+ """Redraws one edit operation in the genome."""
327
+ def __init__(self) -> None:
328
+ ...
329
+
330
+ class SdaMutationConfig:
331
+ """Which mutation an SDA genome applies."""
332
+
333
+ class RedrawOne(SdaMutationConfig):
334
+ """Redraws one element of the automaton — the initial character, a transition's
335
+ target, or its response.
336
+ """
337
+ def __init__(self) -> None:
338
+ ...
339
+
340
+ class GenomeConfig:
341
+ """Genome representation and the dimensions used to build random individuals."""
342
+
343
+ class EdgeEdit(GenomeConfig):
344
+ """A list of edit operations; the graph is what replaying them produces."""
345
+ def __init__(self, gene_length: int, operation_weights: OperationWeights | None=None, mutation: EdgeEditMutationConfig | None=None) -> None:
346
+ ...
347
+ @property
348
+ def gene_length(self) -> int:
349
+ """Number of edit operations in the genome."""
350
+ ...
351
+ @property
352
+ def mutation(self) -> EdgeEditMutationConfig | None:
353
+ """Which mutation the run applies; omitted, the default one."""
354
+ ...
355
+ @property
356
+ def operation_weights(self) -> OperationWeights | None:
357
+ """Relative weight per edit operation; omitted, every operation weighs
358
+ 1.0.
359
+ """
360
+ ...
361
+
362
+ class Sda(GenomeConfig):
363
+ """A self-driving automaton whose output is read as the graph's edges.
364
+
365
+ No `num_chars`: the alphabet is `max_edge_multiplicity + 1`, so every
366
+ character is a legal edge weight.
367
+ """
368
+ def __init__(self, num_states: int, max_resp_len: int, init_state: int=0, init_char_mutation_rate: float | None=None, transition_vs_response_rate: float | None=None, mutation: SdaMutationConfig | None=None) -> None:
369
+ ...
370
+ @property
371
+ def init_char_mutation_rate(self) -> float | None:
372
+ """Chance a mutation redraws the initial character instead of touching the
373
+ transition table; omitted, the default rate.
374
+ """
375
+ ...
376
+ @property
377
+ def init_state(self) -> int:
378
+ """State the automaton starts in. Must be less than `num_states`, or
379
+ expression panics.
380
+ """
381
+ ...
382
+ @property
383
+ def max_resp_len(self) -> int:
384
+ """Longest response string a transition may emit.
385
+
386
+ Responses are drawn from `1..=max_resp_len` and are never empty, which is
387
+ what guarantees the automaton terminates.
388
+ """
389
+ ...
390
+ @property
391
+ def mutation(self) -> SdaMutationConfig | None:
392
+ """Which mutation the run applies; omitted, the default one."""
393
+ ...
394
+ @property
395
+ def num_states(self) -> int:
396
+ """States in the automaton."""
397
+ ...
398
+ @property
399
+ def transition_vs_response_rate(self) -> float | None:
400
+ """Chance of redrawing a transition's target rather than its response, once
401
+ the initial character was not chosen.
402
+ """
403
+ ...
404
+
405
+ class FitnessConfig:
406
+ """Fitness objective and its parameters.
407
+
408
+ The epidemic objectives read one simulation differently, so they share a
409
+ single `SirParams` block.
410
+ """
411
+
412
+ class EpiLength(FitnessConfig):
413
+ """Timesteps to burn out. Maximized."""
414
+ def __init__(self, sir: SirParams) -> None:
415
+ ...
416
+ @property
417
+ def sir(self) -> SirParams:
418
+ """Epidemic sampling parameters, shared by the three epidemic objectives."""
419
+ ...
420
+
421
+ class EpiProfMatch(FitnessConfig):
422
+ """RMSE against a target profile. Minimized."""
423
+ def __init__(self, sir: SirParams, target_profile: list[float]) -> None:
424
+ ...
425
+ @property
426
+ def sir(self) -> SirParams:
427
+ """Epidemic sampling parameters, shared by the three epidemic objectives."""
428
+ ...
429
+ @property
430
+ def target_profile(self) -> list[float]:
431
+ """The profile the run is scored against, compared verbatim — nothing is
432
+ prepended to it and nothing is rescaled.
433
+ """
434
+ ...
435
+
436
+ class EpiSpread(FitnessConfig):
437
+ """Total ever-infected. Maximized."""
438
+ def __init__(self, sir: SirParams) -> None:
439
+ ...
440
+ @property
441
+ def sir(self) -> SirParams:
442
+ """Epidemic sampling parameters, shared by the three epidemic objectives."""
443
+ ...
444
+
445
+ class Python(FitnessConfig):
446
+ """A Python callable registered before the run, via
447
+ `GraphEvolver.set_fitness_function`. Whether it is maximized or minimized is
448
+ declared at registration, not here.
449
+ """
450
+ def __init__(self) -> None:
451
+ ...
452
+
453
+ class StructMatch(FitnessConfig):
454
+ """How closely a graph's structure matches a set of reference graphs.
455
+
456
+ Minimized; requires `max_edge_multiplicity = 1`.
457
+ """
458
+ def __init__(self, reference_folder: str, degree_bins: int=50, clustering_bins: int=50, spectral_bins: int=50, degree_gamma: float=1.0, clustering_gamma: float=1.0, spectral_gamma: float=1.0, degree_weight: float=1.0, clustering_weight: float=1.0, spectral_weight: float=1.0, density_weight: float=1.0) -> None:
459
+ ...
460
+ @property
461
+ def clustering_bins(self) -> int:
462
+ """Histogram bins for the clustering statistics. More bins resolve finer
463
+ differences and need more reference graphs to fill them.
464
+ """
465
+ ...
466
+ @property
467
+ def clustering_gamma(self) -> float:
468
+ """RBF bandwidth for the clustering statistics. Must be finite and greater
469
+ than zero — the kernel divides by it. Too large is the dangerous
470
+ direction: the kernel collapses to zero for every candidate, the whole
471
+ population scores about the same, and evolution stalls while appearing to
472
+ run normally.
473
+ """
474
+ ...
475
+ @property
476
+ def clustering_weight(self) -> float:
477
+ """How much the clustering family counts. Finite and non-negative; the three
478
+ family weights cannot all be zero, which would score every candidate
479
+ identically.
480
+ """
481
+ ...
482
+ @property
483
+ def degree_bins(self) -> int:
484
+ """Histogram bins for the degree statistics. More bins resolve finer
485
+ differences and need more reference graphs to fill them.
486
+ """
487
+ ...
488
+ @property
489
+ def degree_gamma(self) -> float:
490
+ """RBF bandwidth for the degree statistics. Must be finite and greater than
491
+ zero — the kernel divides by it. Too large is the dangerous direction:
492
+ the kernel collapses to zero for every candidate, the whole population
493
+ scores about the same, and evolution stalls while appearing to run
494
+ normally.
495
+ """
496
+ ...
497
+ @property
498
+ def degree_weight(self) -> float:
499
+ """How much the degree family counts. Finite and non-negative; the three
500
+ family weights cannot all be zero, which would score every candidate
501
+ identically.
502
+ """
503
+ ...
504
+ @property
505
+ def density_weight(self) -> float:
506
+ """How much distance from the reference set's mean density counts. Zero
507
+ switches the penalty off.
508
+ """
509
+ ...
510
+ @property
511
+ def reference_folder(self) -> str:
512
+ """Folder of reference graphs, one edge-list file each.
513
+
514
+ Not checked when the config is parsed — validation does no I/O, so a
515
+ missing or empty folder is reported when the run starts.
516
+ """
517
+ ...
518
+ @property
519
+ def spectral_bins(self) -> int:
520
+ """Histogram bins for the spectral statistics. More bins resolve finer
521
+ differences and need more reference graphs to fill them.
522
+ """
523
+ ...
524
+ @property
525
+ def spectral_gamma(self) -> float:
526
+ """RBF bandwidth for the spectral statistics. Must be finite and greater
527
+ than zero — the kernel divides by it. Too large is the dangerous
528
+ direction: the kernel collapses to zero for every candidate, the whole
529
+ population scores about the same, and evolution stalls while appearing to
530
+ run normally.
531
+ """
532
+ ...
533
+ @property
534
+ def spectral_weight(self) -> float:
535
+ """How much the spectral family counts. Finite and non-negative; the three
536
+ family weights cannot all be zero, which would score every candidate
537
+ identically.
538
+ """
539
+ ...
540
+
541
+ class SirParams:
542
+ """Epidemic sampling parameters, shared by the epidemic objectives.
543
+
544
+ Nothing is range-checked here; every field is checked when the config is
545
+ handed to an evolver.
546
+ """
547
+ def __init__(self, infection_rate: float, num_epidemics: int, patient_zero: int | None=None, min_epidemic_length: int=3, max_epidemic_retries: int=5) -> None:
548
+ ...
549
+ @property
550
+ def infection_rate(self) -> float:
551
+ """Per-edge transmission probability per timestep."""
552
+ ...
553
+ @property
554
+ def max_epidemic_retries(self) -> int:
555
+ """Attempts before keeping whatever came out."""
556
+ ...
557
+ @property
558
+ def min_epidemic_length(self) -> int:
559
+ """Outbreaks shorter than this are re-rolled; 1 disables the re-roll."""
560
+ ...
561
+ @property
562
+ def num_epidemics(self) -> int:
563
+ """Outbreaks averaged per evaluation."""
564
+ ...
565
+ @property
566
+ def patient_zero(self) -> int | None:
567
+ """Pinned patient zero; left unset, a fresh node is drawn per epidemic."""
568
+ ...
569
+
570
+ class OperationWeights:
571
+ """Relative probability of each edge-edit operation.
572
+
573
+ Every weight defaults to 1.0, so the operations are equally likely; 0.0
574
+ disables an operation outright.
575
+ """
576
+ def __init__(self, toggle: float=1.0, hop: float=1.0, add: float=1.0, delete: float=1.0, swap: float=1.0, local_toggle: float=1.0, local_add: float=1.0, local_delete: float=1.0, null: float=1.0) -> None:
577
+ ...
578
+
579
+ class RunResult:
580
+ """Everything one run produced.
581
+
582
+ Returned by `GraphEvolver.run`. The evolver keeps none of it, so it is
583
+ reusable across runs and never reports a previous one's numbers.
584
+ """
585
+ @property
586
+ def best_edges(self) -> list[tuple[int, int, int]]:
587
+ """The best individual's expressed network, as `(u, v, multiplicity)`."""
588
+ ...
589
+ @property
590
+ def best_fitness(self) -> float:
591
+ """Best of the **final** population, **as-measured** — the units and sign
592
+ your objective returned. Matches `history`'s last row, which a
593
+ stochastic objective may have scored worse than an earlier one.
594
+ """
595
+ ...
596
+ @property
597
+ def best_genome_repr(self) -> str:
598
+ """The best individual's genome, via `Genome::print`."""
599
+ ...
600
+ @property
601
+ def config_toml(self) -> str:
602
+ """The TOML document this run's config was parsed from. `save_config`
603
+ writes it into a folder, so the run can be reproduced.
604
+ """
605
+ ...
606
+ @property
607
+ def history(self) -> list[GenerationStats]:
608
+ """The convergence log, one row per logged iteration."""
609
+ ...
610
+ @property
611
+ def num_nodes(self) -> int:
612
+ """How many nodes that network has, isolated ones included."""
613
+ ...
614
+ @property
615
+ def run_index(self) -> int:
616
+ """Which replicate this is, `0`-based. With `seed`, the pair that
617
+ reproduces this exact run.
618
+ """
619
+ ...
620
+ def save_config(self, directory: str) -> None:
621
+ """Write the run's config TOML into `directory` as `config.toml`.
622
+
623
+ Called once per invocation rather than once per replicate: every
624
+ replicate of one invocation was produced by the same document, and a
625
+ copy beside each would be the same bytes N times.
626
+ """
627
+ ...
628
+ def save_logs(self, filename: str) -> None:
629
+ """Write the convergence log to `filename` as CSV.
630
+
631
+ Every row carries `seed` and `run_index`, so logs from several runs
632
+ concatenate into one file and stay separable.
633
+ """
634
+ ...
635
+ def save_results(self, filename: str) -> None:
636
+ """Write the best individual to `filename`.
637
+
638
+ **The file is a loadable edge list**, which GET reads back unedited.
639
+
640
+ The config that produced it is not written here — it belongs to the
641
+ whole invocation rather than to one replicate, so `save_config` writes
642
+ it once into the folder the replicates share.
643
+ """
644
+ ...
645
+ @property
646
+ def seed(self) -> int:
647
+ """The seed `run` was called with."""
648
+ ...
649
+
650
+ class GenerationStats:
651
+ """One row of the convergence log.
652
+
653
+ `iteration` counts generations under the generational strategy and mating
654
+ events under steady-state.
655
+ """
656
+ @property
657
+ def best_fitness(self) -> float:
658
+ """Best fitness in the population at this iteration."""
659
+ ...
660
+ @property
661
+ def ci_95(self) -> float:
662
+ """Half-width of the 95% confidence interval on `mean_fitness`, using the
663
+ **sample** deviation, dividing by `n - 1` — not `std_dev` beside it.
664
+ Zero for a population of one, never `NaN`.
665
+ """
666
+ ...
667
+ @property
668
+ def iteration(self) -> int:
669
+ """Generation number, or mating-event number."""
670
+ ...
671
+ @property
672
+ def mean_fitness(self) -> float:
673
+ """Population mean fitness at this iteration."""
674
+ ...
675
+ @property
676
+ def std_dev(self) -> float:
677
+ """**Population** standard deviation, dividing by `n`. Zero for a
678
+ population of one.
679
+ """
680
+ ...
get/get.pyd ADDED
Binary file