@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,589 @@
|
|
|
1
|
+
import Neat from '../../src/neat';
|
|
2
|
+
import Network from '../../src/architecture/network';
|
|
3
|
+
import * as methods from '../../src/methods/methods';
|
|
4
|
+
import Node from '../../src/architecture/node';
|
|
5
|
+
import Connection from '../../src/architecture/connection';
|
|
6
|
+
|
|
7
|
+
describe('Neat advanced coverage', () => {
|
|
8
|
+
describe('constructor', () => {
|
|
9
|
+
describe('when no options are provided', () => {
|
|
10
|
+
it('should set default popsize to 50', () => {
|
|
11
|
+
// Arrange
|
|
12
|
+
const fitness = jest.fn();
|
|
13
|
+
// Act
|
|
14
|
+
const neat = new Neat(2, 1, fitness);
|
|
15
|
+
// Assert
|
|
16
|
+
expect(neat.options.popsize).toBe(50);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
describe('when options are provided', () => {
|
|
20
|
+
describe('popsize is set', () => {
|
|
21
|
+
it('should use provided popsize', () => {
|
|
22
|
+
// Arrange
|
|
23
|
+
const fitness = jest.fn();
|
|
24
|
+
// Act
|
|
25
|
+
const neat = new Neat(2, 1, fitness, { popsize: 10 });
|
|
26
|
+
// Assert
|
|
27
|
+
expect(neat.options.popsize).toBe(10);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
describe('elitism is set', () => {
|
|
31
|
+
it('should use provided elitism', () => {
|
|
32
|
+
// Arrange
|
|
33
|
+
const fitness = jest.fn();
|
|
34
|
+
// Act
|
|
35
|
+
const neat = new Neat(2, 1, fitness, { elitism: 2 });
|
|
36
|
+
// Assert
|
|
37
|
+
expect(neat.options.elitism).toBe(2);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
describe('when options is empty', () => {
|
|
42
|
+
it('should set default popsize to 50', () => {
|
|
43
|
+
// Arrange
|
|
44
|
+
const fitness = jest.fn();
|
|
45
|
+
// Act
|
|
46
|
+
const neat = new Neat(2, 1, fitness, {});
|
|
47
|
+
// Assert
|
|
48
|
+
expect(neat.options.popsize).toBe(50);
|
|
49
|
+
});
|
|
50
|
+
it('should set default equal to false', () => {
|
|
51
|
+
// Arrange
|
|
52
|
+
const fitness = jest.fn();
|
|
53
|
+
// Act
|
|
54
|
+
const neat = new Neat(2, 1, fitness, {});
|
|
55
|
+
// Assert
|
|
56
|
+
expect(neat.options.equal).toBe(false);
|
|
57
|
+
});
|
|
58
|
+
it('should set default clear to false', () => {
|
|
59
|
+
// Arrange
|
|
60
|
+
const fitness = jest.fn();
|
|
61
|
+
// Act
|
|
62
|
+
const neat = new Neat(2, 1, fitness, {});
|
|
63
|
+
// Assert
|
|
64
|
+
expect(neat.options.clear).toBe(false);
|
|
65
|
+
});
|
|
66
|
+
it('should set default elitism to 0', () => {
|
|
67
|
+
// Arrange
|
|
68
|
+
const fitness = jest.fn();
|
|
69
|
+
// Act
|
|
70
|
+
const neat = new Neat(2, 1, fitness, {});
|
|
71
|
+
// Assert
|
|
72
|
+
expect(neat.options.elitism).toBe(0);
|
|
73
|
+
});
|
|
74
|
+
it('should set default provenance to 0', () => {
|
|
75
|
+
// Arrange
|
|
76
|
+
const fitness = jest.fn();
|
|
77
|
+
// Act
|
|
78
|
+
const neat = new Neat(2, 1, fitness, {});
|
|
79
|
+
// Assert
|
|
80
|
+
expect(neat.options.provenance).toBe(0);
|
|
81
|
+
});
|
|
82
|
+
it('should set default mutationRate to 0.7', () => {
|
|
83
|
+
// Arrange
|
|
84
|
+
const fitness = jest.fn();
|
|
85
|
+
// Act
|
|
86
|
+
const neat = new Neat(2, 1, fitness, {});
|
|
87
|
+
// Assert
|
|
88
|
+
expect(neat.options.mutationRate).toBe(0.7);
|
|
89
|
+
});
|
|
90
|
+
it('should set default mutationAmount to 1', () => {
|
|
91
|
+
// Arrange
|
|
92
|
+
const fitness = jest.fn();
|
|
93
|
+
// Act
|
|
94
|
+
const neat = new Neat(2, 1, fitness, {});
|
|
95
|
+
// Assert
|
|
96
|
+
expect(neat.options.mutationAmount).toBe(1);
|
|
97
|
+
});
|
|
98
|
+
it('should set default fitnessPopulation to false', () => {
|
|
99
|
+
// Arrange
|
|
100
|
+
const fitness = jest.fn();
|
|
101
|
+
// Act
|
|
102
|
+
const neat = new Neat(2, 1, fitness, {});
|
|
103
|
+
// Assert
|
|
104
|
+
expect(neat.options.fitnessPopulation).toBe(false);
|
|
105
|
+
});
|
|
106
|
+
it('should set default selection to be defined', () => {
|
|
107
|
+
// Arrange
|
|
108
|
+
const fitness = jest.fn();
|
|
109
|
+
// Act
|
|
110
|
+
const neat = new Neat(2, 1, fitness, {});
|
|
111
|
+
// Assert
|
|
112
|
+
expect(neat.options.selection).toBeDefined();
|
|
113
|
+
});
|
|
114
|
+
it('should set default crossover to be defined', () => {
|
|
115
|
+
// Arrange
|
|
116
|
+
const fitness = jest.fn();
|
|
117
|
+
// Act
|
|
118
|
+
const neat = new Neat(2, 1, fitness, {});
|
|
119
|
+
// Assert
|
|
120
|
+
expect(neat.options.crossover).toBeDefined();
|
|
121
|
+
});
|
|
122
|
+
it('should set default mutation to be defined', () => {
|
|
123
|
+
// Arrange
|
|
124
|
+
const fitness = jest.fn();
|
|
125
|
+
// Act
|
|
126
|
+
const neat = new Neat(2, 1, fitness, {});
|
|
127
|
+
// Assert
|
|
128
|
+
expect(neat.options.mutation).toBeDefined();
|
|
129
|
+
});
|
|
130
|
+
it('should set default maxNodes to Infinity', () => {
|
|
131
|
+
// Arrange
|
|
132
|
+
const fitness = jest.fn();
|
|
133
|
+
// Act
|
|
134
|
+
const neat = new Neat(2, 1, fitness, {});
|
|
135
|
+
// Assert
|
|
136
|
+
expect(neat.options.maxNodes).toBe(Infinity);
|
|
137
|
+
});
|
|
138
|
+
it('should set default maxConns to Infinity', () => {
|
|
139
|
+
// Arrange
|
|
140
|
+
const fitness = jest.fn();
|
|
141
|
+
// Act
|
|
142
|
+
const neat = new Neat(2, 1, fitness, {});
|
|
143
|
+
// Assert
|
|
144
|
+
expect(neat.options.maxConns).toBe(Infinity);
|
|
145
|
+
});
|
|
146
|
+
it('should set default maxGates to Infinity', () => {
|
|
147
|
+
// Arrange
|
|
148
|
+
const fitness = jest.fn();
|
|
149
|
+
// Act
|
|
150
|
+
const neat = new Neat(2, 1, fitness, {});
|
|
151
|
+
// Assert
|
|
152
|
+
expect(neat.options.maxGates).toBe(Infinity);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe('createPool', () => {
|
|
158
|
+
describe('when base network is provided', () => {
|
|
159
|
+
it('should create population with base network', () => {
|
|
160
|
+
// Arrange
|
|
161
|
+
const fitness = jest.fn();
|
|
162
|
+
const base = new Network(2, 1);
|
|
163
|
+
const neat = new Neat(2, 1, fitness, { popsize: 2, network: base });
|
|
164
|
+
// Act
|
|
165
|
+
neat.createPool(base);
|
|
166
|
+
// Assert
|
|
167
|
+
expect(neat.population.length).toBe(2);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
describe('when base network is not provided', () => {
|
|
171
|
+
it('should create population without base network', () => {
|
|
172
|
+
// Arrange
|
|
173
|
+
const fitness = jest.fn();
|
|
174
|
+
const neat = new Neat(2, 1, fitness, { popsize: 2 });
|
|
175
|
+
// Act
|
|
176
|
+
neat.createPool(null);
|
|
177
|
+
// Assert
|
|
178
|
+
expect(neat.population.length).toBe(2);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe('selectMutationMethod', () => {
|
|
184
|
+
describe('when ADD_NODE and maxNodes reached', () => {
|
|
185
|
+
it('should return null', () => {
|
|
186
|
+
// Arrange
|
|
187
|
+
const fitness = jest.fn();
|
|
188
|
+
const neat = new Neat(2, 1, fitness, {
|
|
189
|
+
mutation: [methods.mutation.ADD_NODE],
|
|
190
|
+
maxNodes: 1,
|
|
191
|
+
});
|
|
192
|
+
const genome = new Network(2, 1);
|
|
193
|
+
genome.nodes.push(new Node());
|
|
194
|
+
// Act
|
|
195
|
+
const result = neat.selectMutationMethod(genome);
|
|
196
|
+
// Assert
|
|
197
|
+
expect(result).toBeNull();
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
describe('when ADD_CONN and maxConns reached', () => {
|
|
201
|
+
it('should return null', () => {
|
|
202
|
+
// Arrange
|
|
203
|
+
const fitness = jest.fn();
|
|
204
|
+
const neat = new Neat(2, 1, fitness, {
|
|
205
|
+
mutation: [methods.mutation.ADD_CONN],
|
|
206
|
+
maxConns: 1,
|
|
207
|
+
});
|
|
208
|
+
const genome = new Network(2, 1);
|
|
209
|
+
genome.connections.push(new Connection(new Node(), new Node()));
|
|
210
|
+
// Act
|
|
211
|
+
const result = neat.selectMutationMethod(genome);
|
|
212
|
+
// Assert
|
|
213
|
+
expect(result).toBeNull();
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
describe('when ADD_GATE and maxGates reached', () => {
|
|
217
|
+
it('should return null', () => {
|
|
218
|
+
// Arrange
|
|
219
|
+
const fitness = jest.fn();
|
|
220
|
+
const neat = new Neat(2, 1, fitness, {
|
|
221
|
+
mutation: [methods.mutation.ADD_GATE],
|
|
222
|
+
maxGates: 1,
|
|
223
|
+
});
|
|
224
|
+
const genome = new Network(2, 1);
|
|
225
|
+
genome.gates.push(new Connection(new Node(), new Node()));
|
|
226
|
+
// Act
|
|
227
|
+
const result = neat.selectMutationMethod(genome);
|
|
228
|
+
// Assert
|
|
229
|
+
expect(result).toBeNull();
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
describe('when constraints not reached', () => {
|
|
233
|
+
it('should return mutation method', () => {
|
|
234
|
+
// Arrange
|
|
235
|
+
const fitness = jest.fn();
|
|
236
|
+
const neat = new Neat(2, 1, fitness, {
|
|
237
|
+
mutation: [methods.mutation.FFW],
|
|
238
|
+
});
|
|
239
|
+
const genome = new Network(2, 1);
|
|
240
|
+
// Act
|
|
241
|
+
const result = neat.selectMutationMethod(genome);
|
|
242
|
+
// Assert
|
|
243
|
+
expect(result).toBe(methods.mutation.FFW);
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
describe('getParent', () => {
|
|
249
|
+
describe('when selection is POWER', () => {
|
|
250
|
+
it('should return a parent from the population', () => {
|
|
251
|
+
// Arrange
|
|
252
|
+
const fitness = jest.fn();
|
|
253
|
+
const neat = new Neat(2, 1, fitness, {
|
|
254
|
+
selection: methods.selection.POWER,
|
|
255
|
+
});
|
|
256
|
+
neat.population = [new Network(2, 1), new Network(2, 1)];
|
|
257
|
+
neat.population[0].score = 2;
|
|
258
|
+
neat.population[1].score = 1;
|
|
259
|
+
// Act
|
|
260
|
+
const parent = neat.getParent();
|
|
261
|
+
// Assert
|
|
262
|
+
expect(neat.population).toContain(parent);
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
describe('when selection is FITNESS_PROPORTIONATE', () => {
|
|
266
|
+
it('should return a parent from the population', () => {
|
|
267
|
+
// Arrange
|
|
268
|
+
const fitness = jest.fn();
|
|
269
|
+
const neat = new Neat(2, 1, fitness, {
|
|
270
|
+
selection: methods.selection.FITNESS_PROPORTIONATE,
|
|
271
|
+
});
|
|
272
|
+
neat.population = [new Network(2, 1), new Network(2, 1)];
|
|
273
|
+
neat.population[0].score = 2;
|
|
274
|
+
neat.population[1].score = 1;
|
|
275
|
+
// Act
|
|
276
|
+
const parent = neat.getParent();
|
|
277
|
+
// Assert
|
|
278
|
+
expect(neat.population).toContain(parent);
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
describe('when selection is TOURNAMENT', () => {
|
|
282
|
+
it('should return a parent from the population', () => {
|
|
283
|
+
// Arrange
|
|
284
|
+
const fitness = jest.fn();
|
|
285
|
+
const neat = new Neat(2, 1, fitness, {
|
|
286
|
+
selection: {
|
|
287
|
+
...methods.selection.TOURNAMENT,
|
|
288
|
+
size: 2,
|
|
289
|
+
probability: 1,
|
|
290
|
+
},
|
|
291
|
+
popsize: 2,
|
|
292
|
+
});
|
|
293
|
+
neat.population = [new Network(2, 1), new Network(2, 1)];
|
|
294
|
+
neat.population[0].score = 2;
|
|
295
|
+
neat.population[1].score = 1;
|
|
296
|
+
// Act
|
|
297
|
+
const parent = neat.getParent();
|
|
298
|
+
// Assert
|
|
299
|
+
expect(neat.population).toContain(parent);
|
|
300
|
+
});
|
|
301
|
+
it('should throw if tournament size is greater than population size', () => {
|
|
302
|
+
// Arrange
|
|
303
|
+
const fitness = jest.fn();
|
|
304
|
+
// popsize = 2, selection.size = 3
|
|
305
|
+
const neat = new Neat(2, 1, fitness, {
|
|
306
|
+
selection: {
|
|
307
|
+
...methods.selection.TOURNAMENT,
|
|
308
|
+
size: 3,
|
|
309
|
+
probability: 1,
|
|
310
|
+
},
|
|
311
|
+
popsize: 2,
|
|
312
|
+
});
|
|
313
|
+
neat.population = [new Network(2, 1), new Network(2, 1)];
|
|
314
|
+
// Act & Assert
|
|
315
|
+
expect(() => {
|
|
316
|
+
neat.getParent();
|
|
317
|
+
}).toThrow('Tournament size must be less than population size.');
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
describe('evaluate', () => {
|
|
323
|
+
describe('when fitnessPopulation is false', () => {
|
|
324
|
+
it('should call fitness for each genome', async () => {
|
|
325
|
+
// Arrange
|
|
326
|
+
const fitness = jest.fn().mockReturnValue(1); // Spy
|
|
327
|
+
const neat = new Neat(2, 1, fitness, { popsize: 2 });
|
|
328
|
+
neat.population[0].score = undefined;
|
|
329
|
+
neat.population[1].score = undefined;
|
|
330
|
+
// Act
|
|
331
|
+
await neat.evaluate();
|
|
332
|
+
// Assert
|
|
333
|
+
expect(fitness).toHaveBeenCalledTimes(2);
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
describe('when fitnessPopulation is true', () => {
|
|
337
|
+
it('should call fitness once for population', async () => {
|
|
338
|
+
// Arrange
|
|
339
|
+
const fitness = jest.fn().mockReturnValue(1); // Spy
|
|
340
|
+
const neat = new Neat(2, 1, fitness, {
|
|
341
|
+
popsize: 2,
|
|
342
|
+
fitnessPopulation: true,
|
|
343
|
+
});
|
|
344
|
+
// Act
|
|
345
|
+
await neat.evaluate();
|
|
346
|
+
// Assert
|
|
347
|
+
expect(fitness).toHaveBeenCalledTimes(1);
|
|
348
|
+
});
|
|
349
|
+
it('should clear genomes if clear is true', async () => {
|
|
350
|
+
// Arrange
|
|
351
|
+
const fitness = jest.fn().mockReturnValue(1); // Spy
|
|
352
|
+
const neat = new Neat(2, 1, fitness, {
|
|
353
|
+
popsize: 2,
|
|
354
|
+
fitnessPopulation: true,
|
|
355
|
+
clear: true,
|
|
356
|
+
});
|
|
357
|
+
const clearSpy = jest.spyOn(neat.population[0], 'clear');
|
|
358
|
+
// Act
|
|
359
|
+
await neat.evaluate();
|
|
360
|
+
// Assert
|
|
361
|
+
expect(clearSpy).toHaveBeenCalled();
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
describe('evolve', () => {
|
|
367
|
+
describe('when last genome score is undefined', () => {
|
|
368
|
+
it('should call evaluate', async () => {
|
|
369
|
+
// Arrange
|
|
370
|
+
const fitness = jest.fn().mockReturnValue(1); // Spy
|
|
371
|
+
const neat = new Neat(2, 1, fitness, { popsize: 2 });
|
|
372
|
+
neat.population[1].score = undefined;
|
|
373
|
+
const evalSpy = jest.spyOn(neat, 'evaluate');
|
|
374
|
+
// Act
|
|
375
|
+
await neat.evolve();
|
|
376
|
+
// Assert
|
|
377
|
+
expect(evalSpy).toHaveBeenCalled();
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
describe('when all genome scores are defined', () => {
|
|
381
|
+
it('should increment generation', async () => {
|
|
382
|
+
// Arrange
|
|
383
|
+
const fitness = jest.fn().mockReturnValue(1); // Spy
|
|
384
|
+
const neat = new Neat(2, 1, fitness, { popsize: 2 });
|
|
385
|
+
neat.population[0].score = 1;
|
|
386
|
+
neat.population[1].score = 2;
|
|
387
|
+
// Act
|
|
388
|
+
await neat.evolve();
|
|
389
|
+
// Assert
|
|
390
|
+
expect(neat.generation).toBe(1);
|
|
391
|
+
});
|
|
392
|
+
});
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
describe('getOffspring', () => {
|
|
396
|
+
describe('when called', () => {
|
|
397
|
+
it('should call Network.crossOver with two parents', () => {
|
|
398
|
+
// Arrange
|
|
399
|
+
const fitness = jest.fn();
|
|
400
|
+
const neat = new Neat(2, 1, fitness, { popsize: 2 });
|
|
401
|
+
const crossSpy = jest
|
|
402
|
+
.spyOn(Network, 'crossOver')
|
|
403
|
+
.mockReturnValue(new Network(2, 1));
|
|
404
|
+
// Act
|
|
405
|
+
neat.getOffspring();
|
|
406
|
+
// Assert
|
|
407
|
+
expect(crossSpy).toHaveBeenCalled();
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
describe('mutate', () => {
|
|
413
|
+
describe('when mutationMethod is valid', () => {
|
|
414
|
+
it('should call mutate on genome', () => {
|
|
415
|
+
// Arrange
|
|
416
|
+
const fitness = jest.fn();
|
|
417
|
+
const neat = new Neat(2, 1, fitness, {
|
|
418
|
+
popsize: 1,
|
|
419
|
+
mutation: [methods.mutation.FFW],
|
|
420
|
+
mutationRate: 1,
|
|
421
|
+
});
|
|
422
|
+
const mutateSpy = jest.spyOn(neat.population[0], 'mutate');
|
|
423
|
+
// Act
|
|
424
|
+
neat.mutate();
|
|
425
|
+
// Assert
|
|
426
|
+
expect(mutateSpy).toHaveBeenCalled();
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
describe('when mutationMethod is null', () => {
|
|
430
|
+
it('should not call mutate', () => {
|
|
431
|
+
// Arrange
|
|
432
|
+
const fitness = jest.fn();
|
|
433
|
+
const neat = new Neat(2, 1, fitness, {
|
|
434
|
+
popsize: 1,
|
|
435
|
+
mutation: [methods.mutation.ADD_NODE],
|
|
436
|
+
mutationRate: 1,
|
|
437
|
+
maxNodes: 0,
|
|
438
|
+
});
|
|
439
|
+
const mutateSpy = jest.spyOn(neat.population[0], 'mutate');
|
|
440
|
+
// Force selectMutationMethod to always return null
|
|
441
|
+
jest.spyOn(neat, 'selectMutationMethod').mockReturnValue(null);
|
|
442
|
+
// Act
|
|
443
|
+
neat.mutate();
|
|
444
|
+
// Assert
|
|
445
|
+
expect(mutateSpy).not.toHaveBeenCalled();
|
|
446
|
+
});
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
describe('import/export', () => {
|
|
451
|
+
describe('when exporting and importing population', () => {
|
|
452
|
+
it('should export and import population', () => {
|
|
453
|
+
// Arrange
|
|
454
|
+
const fitness = jest.fn();
|
|
455
|
+
const neat = new Neat(2, 1, fitness, { popsize: 2 });
|
|
456
|
+
neat.population[0].score = 1;
|
|
457
|
+
neat.population[1].score = 2;
|
|
458
|
+
// Act
|
|
459
|
+
const exported = neat.export();
|
|
460
|
+
neat.import(exported);
|
|
461
|
+
// Assert
|
|
462
|
+
expect(neat.population.length).toBe(2);
|
|
463
|
+
});
|
|
464
|
+
});
|
|
465
|
+
describe('when exporting empty population', () => {
|
|
466
|
+
it('should export empty population as empty array', () => {
|
|
467
|
+
// Arrange
|
|
468
|
+
const fitness = jest.fn();
|
|
469
|
+
const neat = new Neat(2, 1, fitness, { popsize: 0 });
|
|
470
|
+
neat.population = [];
|
|
471
|
+
// Act
|
|
472
|
+
const exported = neat.export();
|
|
473
|
+
// Assert
|
|
474
|
+
expect(exported).toEqual([]);
|
|
475
|
+
});
|
|
476
|
+
});
|
|
477
|
+
describe('when importing empty array', () => {
|
|
478
|
+
it('should import empty array as empty population', () => {
|
|
479
|
+
// Arrange
|
|
480
|
+
const fitness = jest.fn();
|
|
481
|
+
const neat = new Neat(2, 1, fitness, { popsize: 2 });
|
|
482
|
+
// Act
|
|
483
|
+
neat.import([]);
|
|
484
|
+
// Assert
|
|
485
|
+
expect(neat.population.length).toBe(0);
|
|
486
|
+
});
|
|
487
|
+
});
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
describe('sort', () => {
|
|
491
|
+
describe('when population has scores', () => {
|
|
492
|
+
it('should sort population by score descending', () => {
|
|
493
|
+
// Arrange
|
|
494
|
+
const fitness = jest.fn();
|
|
495
|
+
const neat = new Neat(2, 1, fitness, { popsize: 2 });
|
|
496
|
+
neat.population[0].score = 1;
|
|
497
|
+
neat.population[1].score = 2;
|
|
498
|
+
// Act
|
|
499
|
+
neat.sort();
|
|
500
|
+
// Assert
|
|
501
|
+
expect(neat.population[0].score).toBe(2);
|
|
502
|
+
});
|
|
503
|
+
});
|
|
504
|
+
describe('when population is empty', () => {
|
|
505
|
+
it('should handle empty population', () => {
|
|
506
|
+
// Arrange
|
|
507
|
+
const fitness = jest.fn();
|
|
508
|
+
const neat = new Neat(2, 1, fitness, { popsize: 0 });
|
|
509
|
+
neat.population = [];
|
|
510
|
+
// Act
|
|
511
|
+
neat.sort();
|
|
512
|
+
// Assert
|
|
513
|
+
expect(neat.population).toEqual([]);
|
|
514
|
+
});
|
|
515
|
+
});
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
describe('getFittest', () => {
|
|
519
|
+
describe('when population has scores', () => {
|
|
520
|
+
it('should return fittest genome', () => {
|
|
521
|
+
// Arrange
|
|
522
|
+
const fitness = jest.fn();
|
|
523
|
+
const neat = new Neat(2, 1, fitness, { popsize: 2 });
|
|
524
|
+
neat.population[0].score = 1;
|
|
525
|
+
neat.population[1].score = 2;
|
|
526
|
+
// Act
|
|
527
|
+
const fittest = neat.getFittest();
|
|
528
|
+
// Assert
|
|
529
|
+
expect(fittest.score).toBe(2);
|
|
530
|
+
});
|
|
531
|
+
});
|
|
532
|
+
describe('when last genome score is undefined', () => {
|
|
533
|
+
it('should call evaluate', () => {
|
|
534
|
+
// Arrange
|
|
535
|
+
const fitness = jest.fn().mockReturnValue(1); // Spy
|
|
536
|
+
const neat = new Neat(2, 1, fitness, { popsize: 2 });
|
|
537
|
+
neat.population[1].score = undefined;
|
|
538
|
+
const evalSpy = jest.spyOn(neat, 'evaluate');
|
|
539
|
+
// Act
|
|
540
|
+
neat.getFittest();
|
|
541
|
+
// Assert
|
|
542
|
+
expect(evalSpy).toHaveBeenCalled();
|
|
543
|
+
});
|
|
544
|
+
});
|
|
545
|
+
describe('when population[1] exists and population[0] is less fit', () => {
|
|
546
|
+
it('should call sort', () => {
|
|
547
|
+
// Arrange
|
|
548
|
+
const fitness = jest.fn();
|
|
549
|
+
const neat = new Neat(2, 1, fitness, { popsize: 2 });
|
|
550
|
+
neat.population[0].score = 1;
|
|
551
|
+
neat.population[1].score = 2;
|
|
552
|
+
const sortSpy = jest.spyOn(neat, 'sort');
|
|
553
|
+
// Act
|
|
554
|
+
neat.getFittest();
|
|
555
|
+
// Assert
|
|
556
|
+
expect(sortSpy).toHaveBeenCalled();
|
|
557
|
+
});
|
|
558
|
+
});
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
describe('getAverage', () => {
|
|
562
|
+
describe('when population has scores', () => {
|
|
563
|
+
it('should return average score', () => {
|
|
564
|
+
// Arrange
|
|
565
|
+
const fitness = jest.fn();
|
|
566
|
+
const neat = new Neat(2, 1, fitness, { popsize: 2 });
|
|
567
|
+
neat.population[0].score = 1;
|
|
568
|
+
neat.population[1].score = 3;
|
|
569
|
+
// Act
|
|
570
|
+
const avg = neat.getAverage();
|
|
571
|
+
// Assert
|
|
572
|
+
expect(avg).toBe(2);
|
|
573
|
+
});
|
|
574
|
+
});
|
|
575
|
+
describe('when last genome score is undefined', () => {
|
|
576
|
+
it('should call evaluate', () => {
|
|
577
|
+
// Arrange
|
|
578
|
+
const fitness = jest.fn().mockReturnValue(1); // Spy
|
|
579
|
+
const neat = new Neat(2, 1, fitness, { popsize: 2 });
|
|
580
|
+
neat.population[1].score = undefined;
|
|
581
|
+
const evalSpy = jest.spyOn(neat, 'evaluate');
|
|
582
|
+
// Act
|
|
583
|
+
neat.getAverage();
|
|
584
|
+
// Assert
|
|
585
|
+
expect(evalSpy).toHaveBeenCalled();
|
|
586
|
+
});
|
|
587
|
+
});
|
|
588
|
+
});
|
|
589
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import Neat from '../../src/neat';
|
|
2
|
+
import Network from '../../src/architecture/network';
|
|
3
|
+
|
|
4
|
+
describe('diversityPressure & autoCompatTuning', () => {
|
|
5
|
+
const fitness = (net: Network) => {
|
|
6
|
+
// Simple fitness proportional to hidden node count to create motif similarity pressure
|
|
7
|
+
const hidden = (net as any).nodes.filter((n: any) => n.type === 'H').length;
|
|
8
|
+
return hidden;
|
|
9
|
+
};
|
|
10
|
+
test('diversity pressure adjusts scores (no crash)', async () => {
|
|
11
|
+
const neat = new Neat(2, 1, fitness, {
|
|
12
|
+
popsize: 30,
|
|
13
|
+
speciation: true,
|
|
14
|
+
diversityPressure: {
|
|
15
|
+
enabled: true,
|
|
16
|
+
motifSample: 10,
|
|
17
|
+
penaltyStrength: 0.05,
|
|
18
|
+
},
|
|
19
|
+
targetSpecies: 5,
|
|
20
|
+
autoCompatTuning: { enabled: false },
|
|
21
|
+
});
|
|
22
|
+
await neat.evolve();
|
|
23
|
+
// Evaluate the newly created generation so all genomes have scores
|
|
24
|
+
await neat.evaluate();
|
|
25
|
+
const pop = neat.population;
|
|
26
|
+
expect(pop.every((g) => typeof g.score === 'number')).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
test('auto compatibility tuning nudges coefficients', async () => {
|
|
29
|
+
const neat = new Neat(2, 1, fitness, {
|
|
30
|
+
popsize: 40,
|
|
31
|
+
speciation: true,
|
|
32
|
+
targetSpecies: 6,
|
|
33
|
+
autoCompatTuning: {
|
|
34
|
+
enabled: true,
|
|
35
|
+
adjustRate: 0.05,
|
|
36
|
+
minCoeff: 0.2,
|
|
37
|
+
maxCoeff: 3,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
const startExcess = neat.options.excessCoeff!;
|
|
41
|
+
// run a few generations to allow adjustment
|
|
42
|
+
for (let i = 0; i < 5; i++) await neat.evolve();
|
|
43
|
+
const endExcess = neat.options.excessCoeff!;
|
|
44
|
+
// Coefficient should have moved (unless perfectly matched already which is unlikely)
|
|
45
|
+
expect(endExcess).not.toBe(startExcess);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import Neat from '../../src/neat';
|
|
2
|
+
import Network from '../../src/architecture/network';
|
|
3
|
+
|
|
4
|
+
describe('Diversity metrics', () => {
|
|
5
|
+
test('computes diversity snapshot with compatibility and entropy stats', async () => {
|
|
6
|
+
const neat = new Neat(4, 2, (n: Network) => n.connections.length, {
|
|
7
|
+
popsize: 25,
|
|
8
|
+
seed: 77,
|
|
9
|
+
speciation: true,
|
|
10
|
+
diversityMetrics: { enabled: true, pairSample: 30, graphletSample: 40 },
|
|
11
|
+
});
|
|
12
|
+
await neat.evaluate();
|
|
13
|
+
await neat.evolve();
|
|
14
|
+
const stats = neat.getDiversityStats();
|
|
15
|
+
expect(stats).toBeTruthy();
|
|
16
|
+
expect(typeof stats.meanCompat).toBe('number');
|
|
17
|
+
expect(typeof stats.graphletEntropy).toBe('number');
|
|
18
|
+
const tel = neat.getTelemetry().slice(-1)[0];
|
|
19
|
+
expect(tel.diversity).toBeTruthy();
|
|
20
|
+
});
|
|
21
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { computeDiversityStats } from '../../src/neat/neat.diversity';
|
|
2
|
+
|
|
3
|
+
describe('computeDiversityStats', () => {
|
|
4
|
+
function mockGenome(nodes: number, conns: number, depth?: number) {
|
|
5
|
+
return {
|
|
6
|
+
nodes: new Array(nodes)
|
|
7
|
+
.fill(0)
|
|
8
|
+
.map((_, i) => ({ id: i, connections: { out: [] as any[] } })),
|
|
9
|
+
connections: new Array(conns).fill(0).map((_, i) => ({ id: i })),
|
|
10
|
+
_depth: depth,
|
|
11
|
+
} as any;
|
|
12
|
+
}
|
|
13
|
+
const compat = {
|
|
14
|
+
_compatibilityDistance(a: any, b: any) {
|
|
15
|
+
return (
|
|
16
|
+
Math.abs(a.nodes.length - b.nodes.length) +
|
|
17
|
+
Math.abs(a.connections.length - b.connections.length)
|
|
18
|
+
);
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
it('returns undefined for empty population', () => {
|
|
23
|
+
expect(computeDiversityStats([], compat)).toBeUndefined();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('computes expected keys', () => {
|
|
27
|
+
const pop = [mockGenome(3, 2, 1), mockGenome(5, 4, 2), mockGenome(4, 3, 4)];
|
|
28
|
+
const stats = computeDiversityStats(pop, compat)!;
|
|
29
|
+
// Keys required by telemetry/tests
|
|
30
|
+
const keys = [
|
|
31
|
+
'lineageMeanDepth',
|
|
32
|
+
'lineageMeanPairDist',
|
|
33
|
+
'meanNodes',
|
|
34
|
+
'meanConns',
|
|
35
|
+
'nodeVar',
|
|
36
|
+
'connVar',
|
|
37
|
+
'meanCompat',
|
|
38
|
+
'graphletEntropy',
|
|
39
|
+
'population',
|
|
40
|
+
];
|
|
41
|
+
for (const k of keys) expect(stats).toHaveProperty(k);
|
|
42
|
+
expect(stats.population).toBe(pop.length);
|
|
43
|
+
});
|
|
44
|
+
});
|