@reicek/neataptic-ts 0.1.0
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.
- package/.github/ISSUE_TEMPLATE/bug_report.md +33 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +27 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +28 -0
- package/.github/workflows/ci.yml +41 -0
- package/.github/workflows/deploy-pages.yml +29 -0
- package/.github/workflows/manual_release_pipeline.yml +62 -0
- package/.github/workflows/publish.yml +85 -0
- package/.github/workflows/release_dispatch.yml +38 -0
- package/.travis.yml +5 -0
- package/CONTRIBUTING.md +92 -0
- package/LICENSE +24 -0
- package/ONNX_EXPORT.md +87 -0
- package/README.md +1173 -0
- package/RELEASE.md +54 -0
- package/dist-docs/package.json +1 -0
- package/dist-docs/scripts/generate-docs.d.ts +2 -0
- package/dist-docs/scripts/generate-docs.d.ts.map +1 -0
- package/dist-docs/scripts/generate-docs.js +536 -0
- package/dist-docs/scripts/generate-docs.js.map +1 -0
- package/dist-docs/scripts/render-docs-html.d.ts +2 -0
- package/dist-docs/scripts/render-docs-html.d.ts.map +1 -0
- package/dist-docs/scripts/render-docs-html.js +148 -0
- package/dist-docs/scripts/render-docs-html.js.map +1 -0
- package/docs/FOLDERS.md +14 -0
- package/docs/README.md +1173 -0
- package/docs/architecture/README.md +1391 -0
- package/docs/architecture/index.html +938 -0
- package/docs/architecture/network/README.md +1210 -0
- package/docs/architecture/network/index.html +908 -0
- package/docs/assets/ascii-maze.bundle.js +16542 -0
- package/docs/assets/ascii-maze.bundle.js.map +7 -0
- package/docs/index.html +1419 -0
- package/docs/methods/README.md +670 -0
- package/docs/methods/index.html +477 -0
- package/docs/multithreading/README.md +274 -0
- package/docs/multithreading/index.html +215 -0
- package/docs/multithreading/workers/README.md +23 -0
- package/docs/multithreading/workers/browser/README.md +39 -0
- package/docs/multithreading/workers/browser/index.html +70 -0
- package/docs/multithreading/workers/index.html +57 -0
- package/docs/multithreading/workers/node/README.md +33 -0
- package/docs/multithreading/workers/node/index.html +66 -0
- package/docs/neat/README.md +1284 -0
- package/docs/neat/index.html +906 -0
- package/docs/src/README.md +2659 -0
- package/docs/src/index.html +1579 -0
- package/jest.config.ts +32 -0
- package/package.json +99 -0
- package/plans/HyperMorphoNEAT.md +293 -0
- package/plans/ONNX_EXPORT_PLAN.md +46 -0
- package/scripts/generate-docs.ts +486 -0
- package/scripts/render-docs-html.ts +138 -0
- package/scripts/types.d.ts +2 -0
- package/src/README.md +2659 -0
- package/src/architecture/README.md +1391 -0
- package/src/architecture/activationArrayPool.ts +135 -0
- package/src/architecture/architect.ts +635 -0
- package/src/architecture/connection.ts +148 -0
- package/src/architecture/group.ts +406 -0
- package/src/architecture/layer.ts +804 -0
- package/src/architecture/network/README.md +1210 -0
- package/src/architecture/network/network.activate.ts +223 -0
- package/src/architecture/network/network.connect.ts +157 -0
- package/src/architecture/network/network.deterministic.ts +167 -0
- package/src/architecture/network/network.evolve.ts +426 -0
- package/src/architecture/network/network.gating.ts +186 -0
- package/src/architecture/network/network.genetic.ts +247 -0
- package/src/architecture/network/network.mutate.ts +624 -0
- package/src/architecture/network/network.onnx.ts +463 -0
- package/src/architecture/network/network.prune.ts +216 -0
- package/src/architecture/network/network.remove.ts +96 -0
- package/src/architecture/network/network.serialize.ts +309 -0
- package/src/architecture/network/network.slab.ts +262 -0
- package/src/architecture/network/network.standalone.ts +246 -0
- package/src/architecture/network/network.stats.ts +59 -0
- package/src/architecture/network/network.topology.ts +86 -0
- package/src/architecture/network/network.training.ts +1278 -0
- package/src/architecture/network.ts +1302 -0
- package/src/architecture/node.ts +1288 -0
- package/src/architecture/onnx.ts +3 -0
- package/src/config.ts +83 -0
- package/src/methods/README.md +670 -0
- package/src/methods/activation.ts +372 -0
- package/src/methods/connection.ts +31 -0
- package/src/methods/cost.ts +347 -0
- package/src/methods/crossover.ts +63 -0
- package/src/methods/gating.ts +43 -0
- package/src/methods/methods.ts +8 -0
- package/src/methods/mutation.ts +300 -0
- package/src/methods/rate.ts +257 -0
- package/src/methods/selection.ts +65 -0
- package/src/multithreading/README.md +274 -0
- package/src/multithreading/multi.ts +339 -0
- package/src/multithreading/workers/README.md +23 -0
- package/src/multithreading/workers/browser/README.md +39 -0
- package/src/multithreading/workers/browser/testworker.ts +99 -0
- package/src/multithreading/workers/node/README.md +33 -0
- package/src/multithreading/workers/node/testworker.ts +72 -0
- package/src/multithreading/workers/node/worker.ts +70 -0
- package/src/multithreading/workers/workers.ts +22 -0
- package/src/neat/README.md +1284 -0
- package/src/neat/neat.adaptive.ts +544 -0
- package/src/neat/neat.compat.ts +164 -0
- package/src/neat/neat.constants.ts +20 -0
- package/src/neat/neat.diversity.ts +217 -0
- package/src/neat/neat.evaluate.ts +328 -0
- package/src/neat/neat.evolve.ts +1026 -0
- package/src/neat/neat.export.ts +249 -0
- package/src/neat/neat.helpers.ts +235 -0
- package/src/neat/neat.lineage.ts +220 -0
- package/src/neat/neat.multiobjective.ts +260 -0
- package/src/neat/neat.mutation.ts +718 -0
- package/src/neat/neat.objectives.ts +157 -0
- package/src/neat/neat.pruning.ts +190 -0
- package/src/neat/neat.selection.ts +269 -0
- package/src/neat/neat.speciation.ts +460 -0
- package/src/neat/neat.species.ts +151 -0
- package/src/neat/neat.telemetry.exports.ts +469 -0
- package/src/neat/neat.telemetry.ts +933 -0
- package/src/neat/neat.types.ts +275 -0
- package/src/neat.ts +1042 -0
- package/src/neataptic.ts +10 -0
- package/test/architecture/activationArrayPool.capacity.test.ts +19 -0
- package/test/architecture/activationArrayPool.test.ts +46 -0
- package/test/architecture/connection.test.ts +290 -0
- package/test/architecture/group.test.ts +950 -0
- package/test/architecture/layer.test.ts +1535 -0
- package/test/architecture/network.pruning.test.ts +65 -0
- package/test/architecture/node.test.ts +1602 -0
- package/test/examples/asciiMaze/asciiMaze.e2e.test.ts +499 -0
- package/test/examples/asciiMaze/asciiMaze.ts +41 -0
- package/test/examples/asciiMaze/browser-entry.ts +164 -0
- package/test/examples/asciiMaze/browserLogger.ts +221 -0
- package/test/examples/asciiMaze/browserTerminalUtility.ts +48 -0
- package/test/examples/asciiMaze/colors.ts +119 -0
- package/test/examples/asciiMaze/dashboardManager.ts +968 -0
- package/test/examples/asciiMaze/evolutionEngine.ts +1248 -0
- package/test/examples/asciiMaze/fitness.ts +136 -0
- package/test/examples/asciiMaze/index.html +128 -0
- package/test/examples/asciiMaze/index.ts +26 -0
- package/test/examples/asciiMaze/interfaces.ts +235 -0
- package/test/examples/asciiMaze/mazeMovement.ts +996 -0
- package/test/examples/asciiMaze/mazeUtils.ts +278 -0
- package/test/examples/asciiMaze/mazeVision.ts +402 -0
- package/test/examples/asciiMaze/mazeVisualization.ts +585 -0
- package/test/examples/asciiMaze/mazes.ts +245 -0
- package/test/examples/asciiMaze/networkRefinement.ts +76 -0
- package/test/examples/asciiMaze/networkVisualization.ts +901 -0
- package/test/examples/asciiMaze/terminalUtility.ts +73 -0
- package/test/methods/activation.test.ts +1142 -0
- package/test/methods/connection.test.ts +146 -0
- package/test/methods/cost.test.ts +1123 -0
- package/test/methods/crossover.test.ts +202 -0
- package/test/methods/gating.test.ts +144 -0
- package/test/methods/mutation.test.ts +451 -0
- package/test/methods/optimizers.advanced.test.ts +80 -0
- package/test/methods/optimizers.behavior.test.ts +105 -0
- package/test/methods/optimizers.formula.test.ts +89 -0
- package/test/methods/rate.cosineWarmRestarts.test.ts +44 -0
- package/test/methods/rate.linearWarmupDecay.test.ts +41 -0
- package/test/methods/rate.reduceOnPlateau.test.ts +45 -0
- package/test/methods/rate.test.ts +684 -0
- package/test/methods/selection.test.ts +245 -0
- package/test/multithreading/activations.functions.test.ts +54 -0
- package/test/multithreading/multi.test.ts +290 -0
- package/test/multithreading/worker.node.process.test.ts +39 -0
- package/test/multithreading/workers.coverage.test.ts +36 -0
- package/test/multithreading/workers.dynamic.import.test.ts +8 -0
- package/test/neat/neat.adaptive.complexityBudget.test.ts +34 -0
- package/test/neat/neat.adaptive.criterion.complexity.test.ts +50 -0
- package/test/neat/neat.adaptive.mutation.strategy.test.ts +37 -0
- package/test/neat/neat.adaptive.operator.decay.test.ts +31 -0
- package/test/neat/neat.adaptive.phasedComplexity.test.ts +25 -0
- package/test/neat/neat.adaptive.pruning.test.ts +25 -0
- package/test/neat/neat.adaptive.targetSpecies.test.ts +43 -0
- package/test/neat/neat.additional.coverage.test.ts +126 -0
- package/test/neat/neat.advanced.enhancements.test.ts +85 -0
- package/test/neat/neat.advanced.test.ts +589 -0
- package/test/neat/neat.diversity.autocompat.test.ts +47 -0
- package/test/neat/neat.diversity.metrics.test.ts +21 -0
- package/test/neat/neat.diversity.stats.test.ts +44 -0
- package/test/neat/neat.enhancements.test.ts +79 -0
- package/test/neat/neat.entropy.ancestorAdaptive.test.ts +133 -0
- package/test/neat/neat.entropy.compat.csv.test.ts +108 -0
- package/test/neat/neat.evolution.pruning.test.ts +39 -0
- package/test/neat/neat.fastmode.autotune.test.ts +42 -0
- package/test/neat/neat.innovation.test.ts +134 -0
- package/test/neat/neat.lineage.antibreeding.test.ts +35 -0
- package/test/neat/neat.lineage.entropy.test.ts +56 -0
- package/test/neat/neat.lineage.inbreeding.test.ts +49 -0
- package/test/neat/neat.lineage.pressure.test.ts +29 -0
- package/test/neat/neat.multiobjective.adaptive.test.ts +57 -0
- package/test/neat/neat.multiobjective.dynamic.schedule.test.ts +46 -0
- package/test/neat/neat.multiobjective.dynamic.test.ts +31 -0
- package/test/neat/neat.multiobjective.fastsort.delegation.test.ts +51 -0
- package/test/neat/neat.multiobjective.prune.test.ts +39 -0
- package/test/neat/neat.multiobjective.test.ts +21 -0
- package/test/neat/neat.mutation.undefined.pool.test.ts +24 -0
- package/test/neat/neat.objective.events.test.ts +26 -0
- package/test/neat/neat.objective.importance.test.ts +21 -0
- package/test/neat/neat.objective.lifetimes.test.ts +33 -0
- package/test/neat/neat.offspring.allocation.test.ts +22 -0
- package/test/neat/neat.operator.bandit.test.ts +17 -0
- package/test/neat/neat.operator.phases.test.ts +38 -0
- package/test/neat/neat.pruneInactive.behavior.test.ts +54 -0
- package/test/neat/neat.reenable.adaptation.test.ts +18 -0
- package/test/neat/neat.rng.state.test.ts +22 -0
- package/test/neat/neat.spawn.add.test.ts +123 -0
- package/test/neat/neat.speciation.test.ts +96 -0
- package/test/neat/neat.species.allocation.telemetry.test.ts +26 -0
- package/test/neat/neat.species.history.csv.test.ts +24 -0
- package/test/neat/neat.telemetry.advanced.test.ts +226 -0
- package/test/neat/neat.telemetry.csv.lineage.test.ts +19 -0
- package/test/neat/neat.telemetry.parity.test.ts +42 -0
- package/test/neat/neat.telemetry.stream.test.ts +19 -0
- package/test/neat/neat.telemetry.test.ts +16 -0
- package/test/neat/neat.test.ts +422 -0
- package/test/neat/neat.utilities.test.ts +44 -0
- package/test/network/__suppress_console.ts +9 -0
- package/test/network/acyclic.topoorder.test.ts +17 -0
- package/test/network/checkpoint.metricshook.test.ts +36 -0
- package/test/network/error.handling.test.ts +581 -0
- package/test/network/evolution.test.ts +285 -0
- package/test/network/genetic.test.ts +208 -0
- package/test/network/learning.capability.test.ts +244 -0
- package/test/network/mutation.effects.test.ts +492 -0
- package/test/network/network.activate.test.ts +115 -0
- package/test/network/network.activateBatch.test.ts +30 -0
- package/test/network/network.deterministic.test.ts +64 -0
- package/test/network/network.evolve.branches.test.ts +75 -0
- package/test/network/network.evolve.multithread.branches.test.ts +83 -0
- package/test/network/network.evolve.test.ts +100 -0
- package/test/network/network.gating.removal.test.ts +93 -0
- package/test/network/network.mutate.additional.test.ts +145 -0
- package/test/network/network.mutate.edgecases.test.ts +101 -0
- package/test/network/network.mutate.test.ts +101 -0
- package/test/network/network.prune.earlyexit.test.ts +38 -0
- package/test/network/network.remove.errors.test.ts +45 -0
- package/test/network/network.slab.fallbacks.test.ts +22 -0
- package/test/network/network.stats.test.ts +45 -0
- package/test/network/network.training.advanced.test.ts +149 -0
- package/test/network/network.training.basic.test.ts +228 -0
- package/test/network/network.training.helpers.test.ts +183 -0
- package/test/network/onnx.export.test.ts +310 -0
- package/test/network/onnx.import.test.ts +129 -0
- package/test/network/pruning.topology.test.ts +282 -0
- package/test/network/regularization.determinism.test.ts +83 -0
- package/test/network/regularization.dropconnect.test.ts +17 -0
- package/test/network/regularization.dropconnect.validation.test.ts +18 -0
- package/test/network/regularization.stochasticdepth.test.ts +27 -0
- package/test/network/regularization.test.ts +843 -0
- package/test/network/regularization.weightnoise.test.ts +30 -0
- package/test/network/setupTests.ts +2 -0
- package/test/network/standalone.test.ts +332 -0
- package/test/network/structure.serialization.test.ts +660 -0
- package/test/training/training.determinism.mixed-precision.test.ts +134 -0
- package/test/training/training.earlystopping.test.ts +91 -0
- package/test/training/training.edge-cases.test.ts +91 -0
- package/test/training/training.extensions.test.ts +47 -0
- package/test/training/training.gradient.features.test.ts +110 -0
- package/test/training/training.gradient.refinements.test.ts +170 -0
- package/test/training/training.gradient.separate-bias.test.ts +41 -0
- package/test/training/training.optimizer.test.ts +48 -0
- package/test/training/training.plateau.smoothing.test.ts +58 -0
- package/test/training/training.smoothing.types.test.ts +174 -0
- package/test/training/training.train.options.coverage.test.ts +52 -0
- package/test/utils/console-helper.ts +76 -0
- package/test/utils/jest-setup.ts +60 -0
- package/test/utils/test-helpers.ts +175 -0
- package/tsconfig.docs.json +12 -0
- package/tsconfig.json +21 -0
- package/webpack.config.js +49 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { NeatLike } from './neat.types';
|
|
2
|
+
|
|
3
|
+
// ----------------------------------------------------------------------------------
|
|
4
|
+
// Export / Import helpers for NEAT evolutionary state.
|
|
5
|
+
// These utilities deliberately avoid importing the concrete Neat class directly so
|
|
6
|
+
// they can be mixed into lighter-weight facades or used in static contexts.
|
|
7
|
+
// ----------------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* JSON representation of an individual genome (network). The concrete shape is
|
|
11
|
+
* produced by `Network#toJSON()` and re‑hydrated via `Network.fromJSON()`. We use
|
|
12
|
+
* an open record signature here because the network architecture may evolve with
|
|
13
|
+
* plugins / future features (e.g. CPPNs, substrate metadata, ONNX export tags).
|
|
14
|
+
*/
|
|
15
|
+
export interface GenomeJSON {
|
|
16
|
+
[key: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Serialized meta information describing a NEAT run, excluding the concrete
|
|
21
|
+
* population genomes. This allows you to persist & resume experiment context
|
|
22
|
+
* (innovation history, current generation, IO sizes, hyper‑parameters) without
|
|
23
|
+
* committing to a particular population snapshot.
|
|
24
|
+
*/
|
|
25
|
+
export interface NeatMetaJSON {
|
|
26
|
+
/** Number of input nodes expected by evolved networks. */
|
|
27
|
+
input: number;
|
|
28
|
+
/** Number of output nodes produced by evolved networks. */
|
|
29
|
+
output: number;
|
|
30
|
+
/** Current evolutionary generation index (0-based). */
|
|
31
|
+
generation: number;
|
|
32
|
+
/** Full options object (hyper‑parameters) used to configure NEAT. */
|
|
33
|
+
options: any; // retained as any until options interface is extracted
|
|
34
|
+
/** Innovation records for node split mutations: [compositeKey, innovationId]. */
|
|
35
|
+
nodeSplitInnovations: [any, any][]; // key/value pairs serialised from Map
|
|
36
|
+
/** Innovation records for connection mutations: [compositeKey, innovationId]. */
|
|
37
|
+
connInnovations: [any, any][];
|
|
38
|
+
/** Next global innovation number that will be assigned. */
|
|
39
|
+
nextGlobalInnovation: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Top‑level bundle containing both NEAT meta information and the full array of
|
|
44
|
+
* serialized genomes (population). This is what you get from `exportState()` and
|
|
45
|
+
* feed into `importStateImpl()` to resume exactly where you left off.
|
|
46
|
+
*/
|
|
47
|
+
export interface NeatStateJSON {
|
|
48
|
+
/** Serialized NEAT meta (innovation history, generation, options, etc.). */
|
|
49
|
+
neat: NeatMetaJSON;
|
|
50
|
+
/** Array of serialized genomes representing the current population. */
|
|
51
|
+
population: GenomeJSON[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Export the current population (array of genomes) into plain JSON objects.
|
|
56
|
+
* Each genome is converted via its `toJSON()` method. You can persist this
|
|
57
|
+
* result (e.g. to disk, a database, or localStorage) and later rehydrate it
|
|
58
|
+
* with {@link importPopulation}.
|
|
59
|
+
*
|
|
60
|
+
* Why export population only? Sometimes you want to snapshot *just* the set of
|
|
61
|
+
* candidate solutions (e.g. for ensemble evaluation) without freezing the
|
|
62
|
+
* innovation counters or hyper‑parameters.
|
|
63
|
+
*
|
|
64
|
+
* Example:
|
|
65
|
+
* ```ts
|
|
66
|
+
* // Assuming `neat` is an instance exposing this helper
|
|
67
|
+
* const popSnapshot = neat.exportPopulation();
|
|
68
|
+
* fs.writeFileSync('population.json', JSON.stringify(popSnapshot, null, 2));
|
|
69
|
+
* ```
|
|
70
|
+
* @category Serialization
|
|
71
|
+
* @returns Array of genome JSON objects.
|
|
72
|
+
*/
|
|
73
|
+
export function exportPopulation(this: NeatLike): GenomeJSON[] {
|
|
74
|
+
// 1. Map each genome in the current population to its serializable form
|
|
75
|
+
return (this as any).population.map((genome: any) => genome.toJSON());
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Import (replace) the current population from an array of serialized genomes.
|
|
80
|
+
* This does not touch NEAT meta state (generation, innovations, etc.)—only the
|
|
81
|
+
* population array and implied `popsize` are updated.
|
|
82
|
+
*
|
|
83
|
+
* Example:
|
|
84
|
+
* ```ts
|
|
85
|
+
* const populationData: GenomeJSON[] = JSON.parse(fs.readFileSync('population.json','utf8'));
|
|
86
|
+
* neat.importPopulation(populationData); // population replaced
|
|
87
|
+
* neat.evolve(); // continue evolving with new starting genomes
|
|
88
|
+
* ```
|
|
89
|
+
*
|
|
90
|
+
* Edge cases handled:
|
|
91
|
+
* - Empty array => becomes an empty population (popsize=0).
|
|
92
|
+
* - Malformed entries will throw if `Network.fromJSON` rejects them.
|
|
93
|
+
*
|
|
94
|
+
* @param populationJSON Array of serialized genome objects.
|
|
95
|
+
*/
|
|
96
|
+
export function importPopulation(
|
|
97
|
+
this: NeatLike,
|
|
98
|
+
populationJSON: GenomeJSON[]
|
|
99
|
+
): void {
|
|
100
|
+
/** const Network class used for genome (network) rehydration */
|
|
101
|
+
const Network = require('../architecture/network').default;
|
|
102
|
+
// 1. Recreate each genome via Network.fromJSON
|
|
103
|
+
(this as any).population = populationJSON.map((serializedGenome: any) =>
|
|
104
|
+
Network.fromJSON(serializedGenome)
|
|
105
|
+
);
|
|
106
|
+
// 2. Keep popsize option in sync with actual population length
|
|
107
|
+
(this as any).options.popsize = (this as any).population.length;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Convenience helper that returns a full evolutionary snapshot: both NEAT meta
|
|
112
|
+
* information and the serialized population array. Use this when you want a
|
|
113
|
+
* truly *pause‑and‑resume* capability including innovation bookkeeping.
|
|
114
|
+
*
|
|
115
|
+
* Example:
|
|
116
|
+
* ```ts
|
|
117
|
+
* const state = neat.exportState();
|
|
118
|
+
* fs.writeFileSync('state.json', JSON.stringify(state));
|
|
119
|
+
* // ...later / elsewhere...
|
|
120
|
+
* const raw = JSON.parse(fs.readFileSync('state.json','utf8')) as NeatStateJSON;
|
|
121
|
+
* const neat2 = Neat.importState(raw, fitnessFn); // identical evolutionary context
|
|
122
|
+
* ```
|
|
123
|
+
* @returns A {@link NeatStateJSON} bundle containing meta + population.
|
|
124
|
+
*/
|
|
125
|
+
export function exportState(this: NeatLike): NeatStateJSON {
|
|
126
|
+
/** const lazily loaded export helpers (avoids circular deps) */
|
|
127
|
+
const { toJSONImpl, exportPopulation } = require('./neat.export');
|
|
128
|
+
// 1. Serialize meta
|
|
129
|
+
// 2. Serialize population
|
|
130
|
+
// 3. Package into a bundle for persistence
|
|
131
|
+
return {
|
|
132
|
+
neat: toJSONImpl.call(this as any),
|
|
133
|
+
population: exportPopulation.call(this as any),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Static-style helper that rehydrates a full evolutionary state previously
|
|
139
|
+
* produced by {@link exportState}. Invoke this with the NEAT *class* (not an
|
|
140
|
+
* instance) bound as `this`, e.g. `Neat.importStateImpl(bundle, fitnessFn)`.
|
|
141
|
+
* It constructs a new NEAT instance using the meta data, then imports the
|
|
142
|
+
* population (if present).
|
|
143
|
+
*
|
|
144
|
+
* Safety & validation:
|
|
145
|
+
* - Throws if the bundle is not an object.
|
|
146
|
+
* - Silently skips population import if `population` is missing or not an array.
|
|
147
|
+
*
|
|
148
|
+
* Example:
|
|
149
|
+
* ```ts
|
|
150
|
+
* const bundle: NeatStateJSON = JSON.parse(fs.readFileSync('state.json','utf8'));
|
|
151
|
+
* const neat = Neat.importStateImpl(bundle, fitnessFn);
|
|
152
|
+
* neat.evolve();
|
|
153
|
+
* ```
|
|
154
|
+
* @param stateBundle Full state bundle from {@link exportState}.
|
|
155
|
+
* @param fitnessFunction Fitness evaluation callback used for new instance.
|
|
156
|
+
* @returns Rehydrated NEAT instance ready to continue evolving.
|
|
157
|
+
*/
|
|
158
|
+
export function importStateImpl(
|
|
159
|
+
this: any,
|
|
160
|
+
stateBundle: NeatStateJSON,
|
|
161
|
+
fitnessFunction: (network: any) => number
|
|
162
|
+
): any {
|
|
163
|
+
// 1. Basic validation of bundle shape
|
|
164
|
+
if (!stateBundle || typeof stateBundle !== 'object')
|
|
165
|
+
throw new Error('Invalid state bundle');
|
|
166
|
+
// 2. Reconstruct Neat meta & instance
|
|
167
|
+
const neatInstance = this.fromJSON(stateBundle.neat, fitnessFunction);
|
|
168
|
+
// 3. Import population if provided
|
|
169
|
+
if (Array.isArray(stateBundle.population))
|
|
170
|
+
neatInstance.import(stateBundle.population);
|
|
171
|
+
// 4. Return fully restored instance
|
|
172
|
+
return neatInstance;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Serialize NEAT meta (excluding the mutable population) for persistence of
|
|
177
|
+
* innovation history and experiment configuration. This is sufficient to
|
|
178
|
+
* recreate a *blank* NEAT run at the same evolutionary generation with the
|
|
179
|
+
* same innovation counters, enabling deterministic continuation when combined
|
|
180
|
+
* later with a saved population.
|
|
181
|
+
*
|
|
182
|
+
* Example:
|
|
183
|
+
* ```ts
|
|
184
|
+
* const meta = neat.toJSONImpl();
|
|
185
|
+
* fs.writeFileSync('neat-meta.json', JSON.stringify(meta));
|
|
186
|
+
* // ... later ...
|
|
187
|
+
* const metaLoaded = JSON.parse(fs.readFileSync('neat-meta.json','utf8')) as NeatMetaJSON;
|
|
188
|
+
* const neat2 = Neat.fromJSONImpl(metaLoaded, fitnessFn); // empty population
|
|
189
|
+
* ```
|
|
190
|
+
* @returns {@link NeatMetaJSON} object describing current NEAT meta state.
|
|
191
|
+
*/
|
|
192
|
+
export function toJSONImpl(this: NeatLike): NeatMetaJSON {
|
|
193
|
+
// 1. Return a plain object with primitive / array serializable fields only
|
|
194
|
+
return {
|
|
195
|
+
input: (this as any).input,
|
|
196
|
+
output: (this as any).output,
|
|
197
|
+
generation: (this as any).generation,
|
|
198
|
+
options: (this as any).options,
|
|
199
|
+
nodeSplitInnovations: Array.from(
|
|
200
|
+
(this as any)._nodeSplitInnovations.entries()
|
|
201
|
+
),
|
|
202
|
+
connInnovations: Array.from((this as any)._connInnovations.entries()),
|
|
203
|
+
nextGlobalInnovation: (this as any)._nextGlobalInnovation,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Static-style implementation that rehydrates a NEAT instance from previously
|
|
209
|
+
* exported meta JSON produced by {@link toJSONImpl}. This does *not* restore a
|
|
210
|
+
* population; callers typically follow up with `importPopulation` or use
|
|
211
|
+
* {@link importStateImpl} for a complete restore.
|
|
212
|
+
*
|
|
213
|
+
* Example:
|
|
214
|
+
* ```ts
|
|
215
|
+
* const meta: NeatMetaJSON = JSON.parse(fs.readFileSync('neat-meta.json','utf8'));
|
|
216
|
+
* const neat = Neat.fromJSONImpl(meta, fitnessFn); // empty population, same innovations
|
|
217
|
+
* neat.importPopulation(popSnapshot); // optional
|
|
218
|
+
* ```
|
|
219
|
+
* @param neatJSON Serialized meta (no population).
|
|
220
|
+
* @param fitnessFunction Fitness callback used to construct the new instance.
|
|
221
|
+
* @returns Fresh NEAT instance with restored innovation history.
|
|
222
|
+
*/
|
|
223
|
+
export function fromJSONImpl(
|
|
224
|
+
this: any,
|
|
225
|
+
neatJSON: NeatMetaJSON,
|
|
226
|
+
fitnessFunction: (network: any) => number
|
|
227
|
+
): any {
|
|
228
|
+
/** const alias for the constructor (class) this function is bound to */
|
|
229
|
+
const NeatClass = this as any;
|
|
230
|
+
// 1. Instantiate with stored IO sizes & options
|
|
231
|
+
const neatInstance = new NeatClass(
|
|
232
|
+
neatJSON.input,
|
|
233
|
+
neatJSON.output,
|
|
234
|
+
fitnessFunction,
|
|
235
|
+
neatJSON.options || {}
|
|
236
|
+
);
|
|
237
|
+
// 2. Restore generation index
|
|
238
|
+
neatInstance.generation = neatJSON.generation || 0;
|
|
239
|
+
// 3. Restore innovation maps when present
|
|
240
|
+
if (Array.isArray(neatJSON.nodeSplitInnovations))
|
|
241
|
+
neatInstance._nodeSplitInnovations = new Map(neatJSON.nodeSplitInnovations);
|
|
242
|
+
if (Array.isArray(neatJSON.connInnovations))
|
|
243
|
+
neatInstance._connInnovations = new Map(neatJSON.connInnovations);
|
|
244
|
+
// 4. Restore next innovation counter
|
|
245
|
+
if (typeof neatJSON.nextGlobalInnovation === 'number')
|
|
246
|
+
neatInstance._nextGlobalInnovation = neatJSON.nextGlobalInnovation;
|
|
247
|
+
// 5. Return reconstructed instance (empty population)
|
|
248
|
+
return neatInstance;
|
|
249
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { NeatLike } from './neat.types';
|
|
2
|
+
import Network from '../architecture/network';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Helper utilities that augment the core NEAT (NeuroEvolution of Augmenting Topologies)
|
|
6
|
+
* implementation. These functions are kept separate from the main class so they can
|
|
7
|
+
* be tree‑shaken when unused and independently documented for educational purposes.
|
|
8
|
+
*
|
|
9
|
+
* The helpers focus on three core lifecycle operations:
|
|
10
|
+
* 1. Spawning children from an existing parent genome with mutation ("sexual" reproduction not handled here).
|
|
11
|
+
* 2. Registering externally created genomes so lineage & invariants remain consistent.
|
|
12
|
+
* 3. Creating the initial population pool (bootstrapping evolution) either from a seed
|
|
13
|
+
* network or by synthesizing fresh minimal networks.
|
|
14
|
+
*
|
|
15
|
+
* All helpers expect to be invoked with a `this` context that matches `NeatLike`.
|
|
16
|
+
* They intentionally use defensive try/catch blocks to avoid aborting broader
|
|
17
|
+
* evolutionary runs when an individual genome operation fails; this mirrors the
|
|
18
|
+
* tolerant/robust nature of many historical NEAT library implementations.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Spawn (clone & mutate) a child genome from an existing parent genome.
|
|
23
|
+
*
|
|
24
|
+
* The returned child is intentionally NOT auto‑inserted into the population;
|
|
25
|
+
* call {@link addGenome} (or the class method wrapper) once you decide to
|
|
26
|
+
* keep it. This separation allows callers to perform custom validation or
|
|
27
|
+
* scoring heuristics before committing the child genome.
|
|
28
|
+
*
|
|
29
|
+
* Evolutionary rationale:
|
|
30
|
+
* - Cloning preserves the full topology & weights of the parent.
|
|
31
|
+
* - A configurable number of mutation passes are applied sequentially; each
|
|
32
|
+
* pass may alter structure (add/remove nodes / connections) or weights.
|
|
33
|
+
* - Lineage annotations (`_parents`, `_depth`) enable later analytics (e.g.,
|
|
34
|
+
* diversity statistics, genealogy visualization, pruning heuristics).
|
|
35
|
+
*
|
|
36
|
+
* Robustness philosophy: individual mutation failures are silently ignored so
|
|
37
|
+
* a single stochastic edge case (e.g., no valid structural mutation) does not
|
|
38
|
+
* derail evolutionary progress.
|
|
39
|
+
*
|
|
40
|
+
* @param this Bound NEAT instance (inferred when used as a method).
|
|
41
|
+
* @param parentGenome Parent genome/network to clone. Must implement either
|
|
42
|
+
* `clone()` OR a pair of `toJSON()` / static `fromJSON()` for deep copying.
|
|
43
|
+
* @param mutateCount Number of sequential mutation operations to attempt; each
|
|
44
|
+
* iteration chooses a mutation method using the instance's selection logic.
|
|
45
|
+
* Defaults to 1 for conservative structural drift.
|
|
46
|
+
* @returns A new genome (unregistered) whose score is reset and whose lineage
|
|
47
|
+
* metadata references the parent.
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* // Assume `neat` is an instance implementing NeatLike and `parent` is a genome in neat.population
|
|
51
|
+
* const child = neat.spawnFromParent(parent, 3); // apply 3 mutation passes
|
|
52
|
+
* // Optionally inspect / filter the child before adding
|
|
53
|
+
* neat.addGenome(child, [parent._id]);
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export function spawnFromParent(
|
|
57
|
+
this: NeatLike,
|
|
58
|
+
parentGenome: any,
|
|
59
|
+
mutateCount: number = 1
|
|
60
|
+
) {
|
|
61
|
+
// Step 1: Deep clone the parent (prefer direct clone() for performance).
|
|
62
|
+
const clone = parentGenome.clone
|
|
63
|
+
? parentGenome.clone()
|
|
64
|
+
: require('../architecture/network').default.fromJSON(
|
|
65
|
+
parentGenome.toJSON()
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
// Step 2: Reset evaluation state for the fresh offspring.
|
|
69
|
+
clone.score = undefined;
|
|
70
|
+
(clone as any)._reenableProb = (this as any).options.reenableProb;
|
|
71
|
+
(clone as any)._id = (this as any)._nextGenomeId++;
|
|
72
|
+
|
|
73
|
+
// Step 3: Record minimal lineage (single direct parent) and generation depth.
|
|
74
|
+
(clone as any)._parents = [(parentGenome as any)._id];
|
|
75
|
+
(clone as any)._depth = ((parentGenome as any)._depth ?? 0) + 1;
|
|
76
|
+
|
|
77
|
+
// Step 4: Enforce structural invariants (minimum hidden nodes, no dead ends).
|
|
78
|
+
(this as any).ensureMinHiddenNodes(clone);
|
|
79
|
+
(this as any).ensureNoDeadEnds(clone);
|
|
80
|
+
|
|
81
|
+
// Step 5: Apply the requested number of mutation passes.
|
|
82
|
+
for (let mutationIndex = 0; mutationIndex < mutateCount; mutationIndex++) {
|
|
83
|
+
try {
|
|
84
|
+
// Select a mutation operator; may return a single method or an array of candidates.
|
|
85
|
+
let selectedMutationMethod = (this as any).selectMutationMethod(
|
|
86
|
+
clone,
|
|
87
|
+
false
|
|
88
|
+
);
|
|
89
|
+
if (Array.isArray(selectedMutationMethod)) {
|
|
90
|
+
const candidateMutations = selectedMutationMethod as any[];
|
|
91
|
+
selectedMutationMethod =
|
|
92
|
+
candidateMutations[
|
|
93
|
+
Math.floor((this as any)._getRNG()() * candidateMutations.length)
|
|
94
|
+
];
|
|
95
|
+
}
|
|
96
|
+
// Execute mutation if a valid operator with a name (convention) is present.
|
|
97
|
+
if (selectedMutationMethod && selectedMutationMethod.name) {
|
|
98
|
+
clone.mutate(selectedMutationMethod);
|
|
99
|
+
}
|
|
100
|
+
} catch {
|
|
101
|
+
// Intentionally ignore individual mutation failures to keep evolution moving.
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Step 6: Invalidate any cached compatibility / distance metrics tied to the genome.
|
|
106
|
+
(this as any)._invalidateGenomeCaches(clone);
|
|
107
|
+
return clone;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Register an externally constructed genome (e.g., deserialized, custom‑built,
|
|
112
|
+
* or imported from another run) into the active population. Ensures lineage
|
|
113
|
+
* metadata and structural invariants are consistent with internally spawned
|
|
114
|
+
* genomes.
|
|
115
|
+
*
|
|
116
|
+
* Defensive design: If invariant enforcement fails, the genome is still added
|
|
117
|
+
* (best effort) so experiments remain reproducible and do not abort mid‑run.
|
|
118
|
+
* Caller can optionally inspect or prune later during evaluation.
|
|
119
|
+
*
|
|
120
|
+
* @param this Bound NEAT instance.
|
|
121
|
+
* @param genome Genome / network object to insert. Mutated in place to add
|
|
122
|
+
* internal metadata fields (`_id`, `_parents`, `_depth`, `_reenableProb`).
|
|
123
|
+
* @param parents Optional explicit list of parent genome IDs (e.g., 2 parents
|
|
124
|
+
* for crossover). If omitted, lineage metadata is left empty.
|
|
125
|
+
* @example
|
|
126
|
+
* ```ts
|
|
127
|
+
* const imported = Network.fromJSON(saved);
|
|
128
|
+
* neat.addGenome(imported, [parentA._id, parentB._id]);
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
export function addGenome(this: NeatLike, genome: any, parents?: number[]) {
|
|
132
|
+
try {
|
|
133
|
+
// Step 1: Reset score so future evaluations are not biased by stale values.
|
|
134
|
+
genome.score = undefined;
|
|
135
|
+
(genome as any)._reenableProb = (this as any).options.reenableProb;
|
|
136
|
+
(genome as any)._id = (this as any)._nextGenomeId++;
|
|
137
|
+
|
|
138
|
+
// Step 2: Copy lineage from provided parent IDs (if any).
|
|
139
|
+
(genome as any)._parents = Array.isArray(parents) ? parents.slice() : [];
|
|
140
|
+
(genome as any)._depth = 0;
|
|
141
|
+
if ((genome as any)._parents.length) {
|
|
142
|
+
// Compute depth = (max parent depth) + 1 for genealogical layering.
|
|
143
|
+
const parentDepths = (genome as any)._parents
|
|
144
|
+
.map((pid: number) =>
|
|
145
|
+
(this as any).population.find((g: any) => g._id === pid)
|
|
146
|
+
)
|
|
147
|
+
.filter(Boolean)
|
|
148
|
+
.map((g: any) => g._depth ?? 0);
|
|
149
|
+
(genome as any)._depth = parentDepths.length
|
|
150
|
+
? Math.max(...parentDepths) + 1
|
|
151
|
+
: 1;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Step 3: Ensure structural invariants.
|
|
155
|
+
(this as any).ensureMinHiddenNodes(genome);
|
|
156
|
+
(this as any).ensureNoDeadEnds(genome);
|
|
157
|
+
|
|
158
|
+
// Step 4: Invalidate caches & persist.
|
|
159
|
+
(this as any)._invalidateGenomeCaches(genome);
|
|
160
|
+
(this as any).population.push(genome);
|
|
161
|
+
} catch (error) {
|
|
162
|
+
// Fallback: still add genome so the evolutionary run can continue.
|
|
163
|
+
(this as any).population.push(genome);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Create (or reset) the initial population pool for a NEAT run.
|
|
169
|
+
*
|
|
170
|
+
* If a `seedNetwork` is supplied, every genome is a structural + weight clone
|
|
171
|
+
* of that seed. This is useful for transfer learning or continuing evolution
|
|
172
|
+
* from a known good architecture. When omitted, brand‑new minimal networks are
|
|
173
|
+
* synthesized using the configured input/output sizes (and optional minimum
|
|
174
|
+
* hidden layer size).
|
|
175
|
+
*
|
|
176
|
+
* Design notes:
|
|
177
|
+
* - Population size is derived from `options.popsize` (default 50).
|
|
178
|
+
* - Each genome gets a unique sequential `_id` for reproducible lineage.
|
|
179
|
+
* - When lineage tracking is enabled (`_lineageEnabled`), parent & depth fields
|
|
180
|
+
* are initialized for later analytics.
|
|
181
|
+
* - Structural invariant checks are best effort. A single failure should not
|
|
182
|
+
* prevent other genomes from being created, hence broad try/catch blocks.
|
|
183
|
+
*
|
|
184
|
+
* @param this Bound NEAT instance.
|
|
185
|
+
* @param seedNetwork Optional prototype network to clone for every initial genome.
|
|
186
|
+
* @example
|
|
187
|
+
* ```ts
|
|
188
|
+
* // Basic: create 50 fresh minimal networks
|
|
189
|
+
* neat.createPool(null);
|
|
190
|
+
*
|
|
191
|
+
* // Seeded: start with a known topology
|
|
192
|
+
* const seed = new Network(neat.input, neat.output, { minHidden: 4 });
|
|
193
|
+
* neat.createPool(seed);
|
|
194
|
+
* ```
|
|
195
|
+
*/
|
|
196
|
+
export function createPool(this: NeatLike, seedNetwork: any | null) {
|
|
197
|
+
try {
|
|
198
|
+
// Step 1: Reset population container.
|
|
199
|
+
(this as any).population = [];
|
|
200
|
+
const poolSize = ((this as any).options?.popsize as number) || 50;
|
|
201
|
+
|
|
202
|
+
// Step 2: Generate each initial genome.
|
|
203
|
+
for (let genomeIndex = 0; genomeIndex < poolSize; genomeIndex++) {
|
|
204
|
+
// Clone from seed OR build a fresh network.
|
|
205
|
+
const genomeCopy = seedNetwork
|
|
206
|
+
? Network.fromJSON(seedNetwork.toJSON())
|
|
207
|
+
: new Network((this as any).input, (this as any).output, {
|
|
208
|
+
minHidden: (this as any).options?.minHidden,
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Step 2a: Ensure no stale scoring information.
|
|
212
|
+
genomeCopy.score = undefined;
|
|
213
|
+
|
|
214
|
+
// Step 2b: Attempt structural invariant enforcement (best effort).
|
|
215
|
+
try {
|
|
216
|
+
(this as any).ensureNoDeadEnds(genomeCopy);
|
|
217
|
+
} catch {
|
|
218
|
+
// Ignored; genome may still be viable or corrected by later mutations.
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Step 2c: Annotate runtime metadata.
|
|
222
|
+
(genomeCopy as any)._reenableProb = (this as any).options.reenableProb;
|
|
223
|
+
(genomeCopy as any)._id = (this as any)._nextGenomeId++;
|
|
224
|
+
if ((this as any)._lineageEnabled) {
|
|
225
|
+
(genomeCopy as any)._parents = [];
|
|
226
|
+
(genomeCopy as any)._depth = 0;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Step 2d: Insert into population.
|
|
230
|
+
(this as any).population.push(genomeCopy);
|
|
231
|
+
}
|
|
232
|
+
} catch {
|
|
233
|
+
// Swallow: partial population is acceptable; caller may decide to refill or continue.
|
|
234
|
+
}
|
|
235
|
+
}
|