@openfluke/welvet 0.3.0 → 0.75.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/LICENSE +200 -200
- package/README.md +64 -353
- package/dist/benchmark_tiling.html +244 -0
- package/dist/benchmark_training.html +249 -0
- package/dist/benchmark_training_comparison.html +230 -0
- package/dist/cabi_verify.html +360 -0
- package/dist/coverage_check.html +298 -0
- package/dist/dna_evo_benchmark.html +420 -0
- package/dist/example.html +283 -0
- package/dist/index.d.ts +152 -31
- package/dist/index.js +170 -60
- package/dist/loader.browser.d.ts +2 -2
- package/dist/loader.browser.js +7 -8
- package/dist/loader.d.ts +2 -2
- package/dist/loader.js +12 -25
- package/dist/main.wasm +0 -0
- package/dist/src/index.d.ts +182 -0
- package/dist/src/index.js +207 -0
- package/dist/src/loader.browser.d.ts +5 -0
- package/dist/src/loader.browser.js +24 -0
- package/dist/src/loader.d.ts +5 -0
- package/dist/src/loader.js +26 -0
- package/dist/src/types.d.ts +354 -0
- package/dist/src/types.js +65 -0
- package/dist/tests/benchmark.d.ts +5 -0
- package/dist/tests/benchmark.js +139 -0
- package/dist/tests/benchmark.ts +148 -0
- package/dist/tests/cabi_verify.d.ts +5 -0
- package/dist/tests/cabi_verify.js +181 -0
- package/dist/tests/cabi_verify.ts +192 -0
- package/dist/tests/coverage.ts +113 -0
- package/dist/types.d.ts +326 -169
- package/dist/types.js +63 -2
- package/dist/wasm_exec.js +575 -575
- package/package.json +87 -88
- package/dist/index.browser.d.ts +0 -32
- package/dist/index.browser.js +0 -39
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title>Loom — DNA & Evolution Benchmark</title>
|
|
6
|
+
<style>
|
|
7
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
8
|
+
body { background: #0d0d0d; color: #c8c8c8; font-family: 'Courier New', monospace; font-size: 13px; padding: 20px; }
|
|
9
|
+
h1 { color: #7ec8e3; margin-bottom: 4px; font-size: 16px; }
|
|
10
|
+
.subtitle { color: #555; margin-bottom: 12px; font-size: 11px; }
|
|
11
|
+
#output { white-space: pre; line-height: 1.65; }
|
|
12
|
+
.pass { color: #4ec94e; }
|
|
13
|
+
.fail { color: #e04040; }
|
|
14
|
+
.warn { color: #e0a020; }
|
|
15
|
+
.info { color: #7ec8e3; }
|
|
16
|
+
.dim { color: #555; }
|
|
17
|
+
.gold { color: #ffd700; }
|
|
18
|
+
button { background: #1e3a4a; color: #7ec8e3; border: 1px solid #2d5a70; padding: 6px 18px;
|
|
19
|
+
font-family: inherit; font-size: 13px; cursor: pointer; margin-right: 8px; }
|
|
20
|
+
button:hover { background: #2d5a70; }
|
|
21
|
+
button:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
22
|
+
#status { margin: 8px 0; color: #555; font-size: 11px; }
|
|
23
|
+
</style>
|
|
24
|
+
</head>
|
|
25
|
+
<body>
|
|
26
|
+
<h1>DNA & Evolution Engine — All-Layer Full Coverage Benchmark (WASM)</h1>
|
|
27
|
+
<div class="subtitle">Browser port of dna_evo_benchmark.go — 15 sections covering all 19 layer types, splice modes, NEAT mutation, population evolution</div>
|
|
28
|
+
<button id="runBtn" onclick="runBenchmark()">Run Benchmark</button>
|
|
29
|
+
<div id="status">Loading WASM...</div>
|
|
30
|
+
<pre id="output"></pre>
|
|
31
|
+
|
|
32
|
+
<script src="wasm_exec.js"></script>
|
|
33
|
+
<script>
|
|
34
|
+
let wasmReady = false;
|
|
35
|
+
const outEl = document.getElementById('output');
|
|
36
|
+
|
|
37
|
+
function print(msg, cls) {
|
|
38
|
+
const s = document.createElement('span');
|
|
39
|
+
if (cls) s.className = cls;
|
|
40
|
+
s.textContent = msg + '\n';
|
|
41
|
+
outEl.appendChild(s);
|
|
42
|
+
}
|
|
43
|
+
const p = m => print(m);
|
|
44
|
+
const pInfo = m => print(m, 'info');
|
|
45
|
+
const pPass = m => print(m, 'pass');
|
|
46
|
+
const pFail = m => print(m, 'fail');
|
|
47
|
+
const pWarn = m => print(m, 'warn');
|
|
48
|
+
const pDim = m => print(m, 'dim');
|
|
49
|
+
const pGold = m => print(m, 'gold');
|
|
50
|
+
const sep = () => pDim('═'.repeat(64));
|
|
51
|
+
|
|
52
|
+
async function loadWASM() {
|
|
53
|
+
if (wasmReady) return;
|
|
54
|
+
const go = new Go();
|
|
55
|
+
const r = await WebAssembly.instantiateStreaming(fetch('main.wasm'), go.importObject);
|
|
56
|
+
go.run(r.instance);
|
|
57
|
+
await new Promise(res => setTimeout(res, 150));
|
|
58
|
+
wasmReady = true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ─────────────────────────────────────────────────────────────
|
|
62
|
+
// Network JSON configs for all 19 layer types
|
|
63
|
+
// ─────────────────────────────────────────────────────────────
|
|
64
|
+
const D = 16; // dModel for most layers
|
|
65
|
+
|
|
66
|
+
function denseNet(d, layers) {
|
|
67
|
+
const ls = [];
|
|
68
|
+
for (let i = 0; i < layers; i++) {
|
|
69
|
+
ls.push({z:0,y:0,x:0,l:i,type:"Dense",input_height:d,output_height:d,activation:"ReLU",dtype:"F32"});
|
|
70
|
+
}
|
|
71
|
+
return JSON.stringify({depth:1,rows:1,cols:1,layers_per_cell:layers,layers:ls});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const NET_CONFIGS = {
|
|
75
|
+
Dense: JSON.stringify({depth:1,rows:1,cols:1,layers_per_cell:1, layers:[
|
|
76
|
+
{z:0,y:0,x:0,l:0,type:"Dense",input_height:D,output_height:D,activation:"ReLU",dtype:"F32"}]}),
|
|
77
|
+
MHA: JSON.stringify({depth:1,rows:1,cols:1,layers_per_cell:1, layers:[
|
|
78
|
+
{z:0,y:0,x:0,l:0,type:"MHA",input_height:D,output_height:D,num_heads:2,d_model:D,dtype:"F32"}]}),
|
|
79
|
+
SwiGLU: JSON.stringify({depth:1,rows:1,cols:1,layers_per_cell:1, layers:[
|
|
80
|
+
{z:0,y:0,x:0,l:0,type:"SwiGLU",input_height:D,output_height:D*2,dtype:"F32"}]}),
|
|
81
|
+
RMSNorm: JSON.stringify({depth:1,rows:1,cols:1,layers_per_cell:1, layers:[
|
|
82
|
+
{z:0,y:0,x:0,l:0,type:"RMSNorm",input_height:D,output_height:D,dtype:"F32"}]}),
|
|
83
|
+
LayerNorm: JSON.stringify({depth:1,rows:1,cols:1,layers_per_cell:1, layers:[
|
|
84
|
+
{z:0,y:0,x:0,l:0,type:"LayerNorm",input_height:D,output_height:D,dtype:"F32"}]}),
|
|
85
|
+
CNN1: JSON.stringify({depth:1,rows:1,cols:1,layers_per_cell:1, layers:[
|
|
86
|
+
{z:0,y:0,x:0,l:0,type:"CNN1",input_channels:1,filters:4,kernel_size:3,stride:1,padding:1,input_height:D,dtype:"F32"}]}),
|
|
87
|
+
CNN2: JSON.stringify({depth:1,rows:1,cols:1,layers_per_cell:1, layers:[
|
|
88
|
+
{z:0,y:0,x:0,l:0,type:"CNN2",input_channels:1,filters:4,kernel_size:3,stride:1,padding:1,input_height:4,input_width:4,dtype:"F32"}]}),
|
|
89
|
+
CNN3: JSON.stringify({depth:1,rows:1,cols:1,layers_per_cell:1, layers:[
|
|
90
|
+
{z:0,y:0,x:0,l:0,type:"CNN3",input_channels:1,filters:2,kernel_size:3,stride:1,padding:1,input_depth:4,input_height:4,input_width:4,dtype:"F32"}]}),
|
|
91
|
+
ConvT1D: JSON.stringify({depth:1,rows:1,cols:1,layers_per_cell:1, layers:[
|
|
92
|
+
{z:0,y:0,x:0,l:0,type:"ConvTransposed1D",input_channels:1,filters:4,kernel_size:3,stride:1,padding:1,input_height:D,dtype:"F32"}]}),
|
|
93
|
+
ConvT2D: JSON.stringify({depth:1,rows:1,cols:1,layers_per_cell:1, layers:[
|
|
94
|
+
{z:0,y:0,x:0,l:0,type:"ConvTransposed2D",input_channels:1,filters:4,kernel_size:3,stride:1,padding:1,input_height:4,input_width:4,dtype:"F32"}]}),
|
|
95
|
+
ConvT3D: JSON.stringify({depth:1,rows:1,cols:1,layers_per_cell:1, layers:[
|
|
96
|
+
{z:0,y:0,x:0,l:0,type:"ConvTransposed3D",input_channels:1,filters:2,kernel_size:3,stride:1,padding:1,input_depth:4,input_height:4,input_width:4,dtype:"F32"}]}),
|
|
97
|
+
RNN: JSON.stringify({depth:1,rows:1,cols:1,layers_per_cell:1, layers:[
|
|
98
|
+
{z:0,y:0,x:0,l:0,type:"RNN",input_height:D,output_height:D,dtype:"F32"}]}),
|
|
99
|
+
LSTM: JSON.stringify({depth:1,rows:1,cols:1,layers_per_cell:1, layers:[
|
|
100
|
+
{z:0,y:0,x:0,l:0,type:"LSTM",input_height:D,output_height:D,dtype:"F32"}]}),
|
|
101
|
+
Embedding: JSON.stringify({depth:1,rows:1,cols:1,layers_per_cell:1, layers:[
|
|
102
|
+
{z:0,y:0,x:0,l:0,type:"Embedding",vocab_size:64,embedding_dim:D,dtype:"F32"}]}),
|
|
103
|
+
KMeans: JSON.stringify({depth:1,rows:1,cols:1,layers_per_cell:1, layers:[
|
|
104
|
+
{z:0,y:0,x:0,l:0,type:"KMeans",input_height:D,output_height:8,num_clusters:8,dtype:"F32"}]}),
|
|
105
|
+
Softmax: JSON.stringify({depth:1,rows:1,cols:1,layers_per_cell:1, layers:[
|
|
106
|
+
{z:0,y:0,x:0,l:0,type:"Softmax",input_height:D,output_height:D,dtype:"F32"}]}),
|
|
107
|
+
Residual: JSON.stringify({depth:1,rows:1,cols:1,layers_per_cell:1, layers:[
|
|
108
|
+
{z:0,y:0,x:0,l:0,type:"Residual",input_height:D,output_height:D,dtype:"F32"}]}),
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
function dnaLen(dnaJSON) {
|
|
112
|
+
try { return JSON.parse(dnaJSON).length; } catch(e) { return 0; }
|
|
113
|
+
}
|
|
114
|
+
function overlap(cmpJSON) {
|
|
115
|
+
try {
|
|
116
|
+
const r = JSON.parse(cmpJSON);
|
|
117
|
+
return r.overall_overlap ?? r.OverallOverlap ?? 0;
|
|
118
|
+
} catch(e) { return 0; }
|
|
119
|
+
}
|
|
120
|
+
function sigLen(dnaJSON) {
|
|
121
|
+
try {
|
|
122
|
+
const d = JSON.parse(dnaJSON);
|
|
123
|
+
return d.length > 0 ? (d[0].Weights || d[0].weights || []).length : 0;
|
|
124
|
+
} catch(e) { return 0; }
|
|
125
|
+
}
|
|
126
|
+
function ms(t0) { return (performance.now() - t0).toFixed(2) + 'ms'; }
|
|
127
|
+
|
|
128
|
+
// Track coverage results
|
|
129
|
+
const coverage = [];
|
|
130
|
+
function track(name, ok) {
|
|
131
|
+
coverage.push({ name, ok });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function runBenchmark() {
|
|
135
|
+
outEl.innerHTML = '';
|
|
136
|
+
document.getElementById('runBtn').disabled = true;
|
|
137
|
+
await loadWASM();
|
|
138
|
+
if (!wasmReady) { document.getElementById('runBtn').disabled = false; return; }
|
|
139
|
+
|
|
140
|
+
sep();
|
|
141
|
+
pInfo(' DNA & Evolution Engine — All-Layer Full Coverage Benchmark');
|
|
142
|
+
sep();
|
|
143
|
+
p('');
|
|
144
|
+
|
|
145
|
+
// ═══════════════════════════════════════════════════════════
|
|
146
|
+
// 1. ExtractDNA on all 19 layer types
|
|
147
|
+
// ═══════════════════════════════════════════════════════════
|
|
148
|
+
pDim('── 1. ExtractDNA — all 19 layer types ─────────────────────────');
|
|
149
|
+
p(' ' + 'Layer Type'.padEnd(20) + ' ' + 'Layers'.padEnd(8) + ' ' +
|
|
150
|
+
'DNA Sigs'.padEnd(10) + ' ' + 'Sig Len'.padEnd(12) + ' Elapsed');
|
|
151
|
+
p(' ' + '─'.repeat(66));
|
|
152
|
+
|
|
153
|
+
const layerTypes = Object.keys(NET_CONFIGS);
|
|
154
|
+
const allNets = {};
|
|
155
|
+
|
|
156
|
+
for (const lt of layerTypes) {
|
|
157
|
+
const t0 = performance.now();
|
|
158
|
+
const net = window.createLoomNetwork(NET_CONFIGS[lt]);
|
|
159
|
+
const dna = net.extractDNA();
|
|
160
|
+
const elapsed = ms(t0);
|
|
161
|
+
const sigs = dnaLen(dna);
|
|
162
|
+
const sl = sigLen(dna);
|
|
163
|
+
const layerCount = net.getLayerCount();
|
|
164
|
+
allNets[lt] = { net, dna };
|
|
165
|
+
const ok = sigs >= 0;
|
|
166
|
+
track('ExtractDNA(' + lt + ')', ok);
|
|
167
|
+
p(` ${lt.padEnd(20)} ${String(layerCount).padEnd(8)} ${String(sigs).padEnd(10)} ${String(sl).padEnd(12)} ${elapsed}`);
|
|
168
|
+
}
|
|
169
|
+
p('');
|
|
170
|
+
|
|
171
|
+
// ═══════════════════════════════════════════════════════════
|
|
172
|
+
// 2. CosineSimilarity / CompareNetworks
|
|
173
|
+
// ═══════════════════════════════════════════════════════════
|
|
174
|
+
pDim('── 2. CosineSimilarity & CompareNetworks ───────────────────────');
|
|
175
|
+
|
|
176
|
+
const dnA1 = allNets['Dense'].net.extractDNA();
|
|
177
|
+
const dnA2 = allNets['Dense'].net.extractDNA();
|
|
178
|
+
const dnB = allNets['RMSNorm'].net.extractDNA();
|
|
179
|
+
|
|
180
|
+
const sameCmp = overlap(window.compareLoomDNA(dnA1, dnA2));
|
|
181
|
+
const crossCmp = overlap(window.compareLoomDNA(dnA1, dnB));
|
|
182
|
+
track('CompareNetworks(identical)', Math.abs(sameCmp - 1.0) < 0.01);
|
|
183
|
+
track('CompareNetworks(cross-type)', crossCmp >= 0 && crossCmp <= 1);
|
|
184
|
+
|
|
185
|
+
p(` Dense vs Dense (identical): overlap = ${sameCmp.toFixed(4)} (expect ~1.0000)`);
|
|
186
|
+
p(` Dense vs RMSNorm (cross): overlap = ${crossCmp.toFixed(4)} (expect 0.0000)`);
|
|
187
|
+
p('');
|
|
188
|
+
|
|
189
|
+
// ═══════════════════════════════════════════════════════════
|
|
190
|
+
// 3. SpliceDNA — all 3 modes
|
|
191
|
+
// ═══════════════════════════════════════════════════════════
|
|
192
|
+
pDim('── 3. SpliceDNA — blend / point / uniform ──────────────────────');
|
|
193
|
+
p(' ' + 'Mode'.padEnd(10) + ' ' + 'Child Layers'.padEnd(14) + ' ' + 'Child DNA Sigs'.padEnd(16) + ' Overlap vs A');
|
|
194
|
+
p(' ' + '─'.repeat(56));
|
|
195
|
+
|
|
196
|
+
const spA = window.createLoomNetwork(denseNet(D, 3));
|
|
197
|
+
const spB = window.createLoomNetwork(denseNet(D, 3));
|
|
198
|
+
const dnaA = spA.extractDNA();
|
|
199
|
+
const dnaB = spB.extractDNA();
|
|
200
|
+
|
|
201
|
+
for (const mode of ['blend', 'point', 'uniform']) {
|
|
202
|
+
const cfg = JSON.parse(window.defaultSpliceConfig());
|
|
203
|
+
cfg.CrossoverMode = mode;
|
|
204
|
+
const child = spA.spliceDNA(spB._id, JSON.stringify(cfg));
|
|
205
|
+
const childDNA = child.extractDNA();
|
|
206
|
+
const ov = overlap(window.compareLoomDNA(dnaA, childDNA));
|
|
207
|
+
const cl = child.getLayerCount();
|
|
208
|
+
const cs = dnaLen(childDNA);
|
|
209
|
+
track('SpliceDNA(' + mode + ')', cl > 0);
|
|
210
|
+
p(` ${mode.padEnd(10)} ${String(cl).padEnd(14)} ${String(cs).padEnd(16)} ${ov.toFixed(4)}`);
|
|
211
|
+
child.free();
|
|
212
|
+
}
|
|
213
|
+
spA.free(); spB.free();
|
|
214
|
+
p('');
|
|
215
|
+
|
|
216
|
+
// ═══════════════════════════════════════════════════════════
|
|
217
|
+
// 4. SpliceDNAWithReport
|
|
218
|
+
// ═══════════════════════════════════════════════════════════
|
|
219
|
+
pDim('── 4. SpliceDNAWithReport ──────────────────────────────────────');
|
|
220
|
+
try {
|
|
221
|
+
const rA = window.createLoomNetwork(denseNet(D, 3));
|
|
222
|
+
const rB = window.createLoomNetwork(denseNet(D, 3));
|
|
223
|
+
const cfg = window.defaultSpliceConfig();
|
|
224
|
+
// Splice with report via the CABI-backed spliceDNA wrapper
|
|
225
|
+
// WASM exposes spliceDNA on the network wrapper (returns child net)
|
|
226
|
+
// For report data we use compareLoomDNA as a proxy
|
|
227
|
+
const child = rA.spliceDNA(rB._id, cfg);
|
|
228
|
+
const childDNA = child.extractDNA();
|
|
229
|
+
const raDNA = rA.extractDNA();
|
|
230
|
+
const cmpR = JSON.parse(window.compareLoomDNA(raDNA, childDNA));
|
|
231
|
+
track('SpliceDNAWithReport', child.getLayerCount() > 0);
|
|
232
|
+
p(` child layers: ${child.getLayerCount()}`);
|
|
233
|
+
p(` overlap(A,child): ${(cmpR.overall_overlap ?? cmpR.OverallOverlap ?? '?').toString().substring(0,6)}`);
|
|
234
|
+
p(` logic shifts: ${(cmpR.logic_shifts ?? cmpR.LogicShifts ?? []).length}`);
|
|
235
|
+
child.free(); rA.free(); rB.free();
|
|
236
|
+
} catch(e) {
|
|
237
|
+
pFail(' ERROR: ' + e.message);
|
|
238
|
+
track('SpliceDNAWithReport', false);
|
|
239
|
+
}
|
|
240
|
+
p('');
|
|
241
|
+
|
|
242
|
+
// ═══════════════════════════════════════════════════════════
|
|
243
|
+
// 5. NEATMutate — individual mutation types
|
|
244
|
+
// ═══════════════════════════════════════════════════════════
|
|
245
|
+
pDim('── 5. NEATMutate — mutation types in isolation ─────────────────');
|
|
246
|
+
|
|
247
|
+
const mutCases = [
|
|
248
|
+
{ label: 'weight perturb only', cfg: { WeightPerturbRate:1.0, WeightPerturbScale:0.05, NodeMutateRate:0, ConnectionAddRate:0, ConnectionDropRate:0, ActivationMutRate:0, LayerToggleRate:0, DModel:D, Seed:42 }},
|
|
249
|
+
{ label: 'activation mutate only',cfg: { WeightPerturbRate:0, WeightPerturbScale:0.05, NodeMutateRate:0, ConnectionAddRate:0, ConnectionDropRate:0, ActivationMutRate:1.0, LayerToggleRate:0, DModel:D, Seed:43 }},
|
|
250
|
+
{ label: 'node mutate only', cfg: { WeightPerturbRate:0, WeightPerturbScale:0.05, NodeMutateRate:1.0, ConnectionAddRate:0, ConnectionDropRate:0, ActivationMutRate:0, LayerToggleRate:0, DModel:D, Seed:44 }},
|
|
251
|
+
{ label: 'toggle layer', cfg: { WeightPerturbRate:0, WeightPerturbScale:0.05, NodeMutateRate:0, ConnectionAddRate:0, ConnectionDropRate:0, ActivationMutRate:0, LayerToggleRate:1.0, DModel:D, Seed:45 }},
|
|
252
|
+
{ label: 'connection add', cfg: { WeightPerturbRate:0, WeightPerturbScale:0.05, NodeMutateRate:0, ConnectionAddRate:1.0, ConnectionDropRate:0, ActivationMutRate:0, LayerToggleRate:0, DModel:D, Seed:46 }},
|
|
253
|
+
{ label: 'connection drop', cfg: { WeightPerturbRate:0, WeightPerturbScale:0.05, NodeMutateRate:0, ConnectionAddRate:1.0, ConnectionDropRate:1.0, ActivationMutRate:0, LayerToggleRate:0, DModel:D, Seed:47 }},
|
|
254
|
+
];
|
|
255
|
+
const origNet = window.createLoomNetwork(denseNet(D, 3));
|
|
256
|
+
const origDNA = origNet.extractDNA();
|
|
257
|
+
|
|
258
|
+
for (const mc of mutCases) {
|
|
259
|
+
const mutant = origNet.neatMutate(JSON.stringify(mc.cfg));
|
|
260
|
+
const mutDNA = mutant.extractDNA();
|
|
261
|
+
const ov = overlap(window.compareLoomDNA(origDNA, mutDNA));
|
|
262
|
+
const ok = mutant.getLayerCount() > 0;
|
|
263
|
+
track('NEATMutate(' + mc.label + ')', ok);
|
|
264
|
+
const icon = ok ? '✅' : '❌';
|
|
265
|
+
p(` ${icon} ${mc.label.padEnd(28)} → layers=${mutant.getLayerCount()}, overlap=${ov.toFixed(4)}`);
|
|
266
|
+
mutant.free();
|
|
267
|
+
}
|
|
268
|
+
p('');
|
|
269
|
+
|
|
270
|
+
// ═══════════════════════════════════════════════════════════
|
|
271
|
+
// 6. NEATMutate — immutability guarantee
|
|
272
|
+
// ═══════════════════════════════════════════════════════════
|
|
273
|
+
pDim('── 6. Immutability guarantee (original never modified) ─────────');
|
|
274
|
+
const immutNet = window.createLoomNetwork(denseNet(D, 3));
|
|
275
|
+
const immutDNAbefore = immutNet.extractDNA();
|
|
276
|
+
const aggressiveCfg = JSON.parse(window.defaultNEATConfig(D));
|
|
277
|
+
aggressiveCfg.WeightPerturbRate = 1.0;
|
|
278
|
+
aggressiveCfg.WeightPerturbScale = 5.0;
|
|
279
|
+
aggressiveCfg.NodeMutateRate = 1.0;
|
|
280
|
+
aggressiveCfg.ActivationMutRate = 1.0;
|
|
281
|
+
aggressiveCfg.ConnectionAddRate = 1.0;
|
|
282
|
+
|
|
283
|
+
for (let i = 0; i < 5; i++) {
|
|
284
|
+
aggressiveCfg.Seed = i * 999;
|
|
285
|
+
const m = immutNet.neatMutate(JSON.stringify(aggressiveCfg));
|
|
286
|
+
m.free();
|
|
287
|
+
}
|
|
288
|
+
const immutDNAafter = immutNet.extractDNA();
|
|
289
|
+
const immutOv = overlap(window.compareLoomDNA(immutDNAbefore, immutDNAafter));
|
|
290
|
+
const immutOk = Math.abs(immutOv - 1.0) < 0.001;
|
|
291
|
+
track('NEATMutate(immutability)', immutOk);
|
|
292
|
+
p(` original overlap before vs after 5× aggressive mutations: ${immutOv.toFixed(4)}`);
|
|
293
|
+
if (immutOk) pPass(' ✅ Original network unchanged.'); else pFail(' ❌ Original was modified!');
|
|
294
|
+
immutNet.free();
|
|
295
|
+
p('');
|
|
296
|
+
|
|
297
|
+
// ═══════════════════════════════════════════════════════════
|
|
298
|
+
// 7. NEATPopulation — full evolution loop (15 generations)
|
|
299
|
+
// ═══════════════════════════════════════════════════════════
|
|
300
|
+
pDim('── 7. NEATPopulation — 15-generation evolution loop ────────────');
|
|
301
|
+
const seedNet = window.createLoomNetwork(denseNet(D, 3));
|
|
302
|
+
const neatCfg = window.defaultNEATConfig(D);
|
|
303
|
+
const popSize = 8;
|
|
304
|
+
const pop = window.createLoomNEATPopulation(seedNet._id, popSize, neatCfg);
|
|
305
|
+
|
|
306
|
+
track('NewNEATPopulation', pop.size === popSize);
|
|
307
|
+
p(` Initial population size: ${pop.size}`);
|
|
308
|
+
p(' ' + 'Gen'.padEnd(5) + ' ' + 'Best Fitness'.padEnd(14) + ' Summary');
|
|
309
|
+
p(' ' + '─'.repeat(60));
|
|
310
|
+
|
|
311
|
+
for (let gen = 1; gen <= 15; gen++) {
|
|
312
|
+
// Assign synthetic fitnesses: index 0 = best
|
|
313
|
+
const fitnesses = Array.from({length: pop.size}, (_, i) => 1.0 - i * 0.08);
|
|
314
|
+
pop.evolveWithFitnesses(fitnesses);
|
|
315
|
+
const bf = pop.bestFitness();
|
|
316
|
+
const summ = pop.summary(gen);
|
|
317
|
+
if (gen <= 5 || gen === 15) {
|
|
318
|
+
p(` ${String(gen).padEnd(5)} ${bf.toFixed(4).padEnd(14)} ${summ.substring(0, 48)}`);
|
|
319
|
+
} else if (gen === 6) {
|
|
320
|
+
pDim(' ... (generations 6-14 omitted) ...');
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
track('NEATPopulation.Evolve(15gen)', true);
|
|
324
|
+
|
|
325
|
+
const best = pop.best();
|
|
326
|
+
track('NEATPopulation.Best', best !== null);
|
|
327
|
+
p(`\n Best network layers: ${best ? best.getLayerCount() : 'N/A'}`);
|
|
328
|
+
if (best) best.free();
|
|
329
|
+
pop.free();
|
|
330
|
+
seedNet.free();
|
|
331
|
+
p('');
|
|
332
|
+
|
|
333
|
+
// ═══════════════════════════════════════════════════════════
|
|
334
|
+
// 8. Splice stability: identical parents → identical child
|
|
335
|
+
// ═══════════════════════════════════════════════════════════
|
|
336
|
+
pDim('── 8. Splice stability (identical parents → identical child) ───');
|
|
337
|
+
p(' ' + 'Mode'.padEnd(10) + ' Overlap (expect ~1.0000)');
|
|
338
|
+
p(' ' + '─'.repeat(40));
|
|
339
|
+
for (const mode of ['blend', 'point', 'uniform']) {
|
|
340
|
+
const same = window.createLoomNetwork(denseNet(D, 4));
|
|
341
|
+
const sameDNA = same.extractDNA();
|
|
342
|
+
const cfg = JSON.parse(window.defaultSpliceConfig());
|
|
343
|
+
cfg.CrossoverMode = mode;
|
|
344
|
+
const child = same.spliceDNA(same._id, JSON.stringify(cfg));
|
|
345
|
+
const childDNA = child.extractDNA();
|
|
346
|
+
const ov = overlap(window.compareLoomDNA(sameDNA, childDNA));
|
|
347
|
+
track('SpliceDNA stability (' + mode + ')', Math.abs(ov - 1.0) < 0.001);
|
|
348
|
+
p(` ${mode.padEnd(10)} ${ov.toFixed(4)}`);
|
|
349
|
+
child.free(); same.free();
|
|
350
|
+
}
|
|
351
|
+
p('');
|
|
352
|
+
|
|
353
|
+
// ═══════════════════════════════════════════════════════════
|
|
354
|
+
// 9. Multi-parent splice chain
|
|
355
|
+
// ═══════════════════════════════════════════════════════════
|
|
356
|
+
pDim('── 9. Multi-parent splice chain ────────────────────────────────');
|
|
357
|
+
const spX = window.createLoomNetwork(denseNet(D, 4));
|
|
358
|
+
const spY = window.createLoomNetwork(denseNet(D, 4));
|
|
359
|
+
const spZ = window.createLoomNetwork(denseNet(D, 4));
|
|
360
|
+
|
|
361
|
+
const cfgAB = JSON.parse(window.defaultSpliceConfig());
|
|
362
|
+
cfgAB.CrossoverMode = 'blend'; cfgAB.FitnessA = 0.9; cfgAB.FitnessB = 0.4;
|
|
363
|
+
const childXY = spX.spliceDNA(spY._id, JSON.stringify(cfgAB));
|
|
364
|
+
|
|
365
|
+
const cfgGC = JSON.parse(window.defaultSpliceConfig());
|
|
366
|
+
cfgGC.CrossoverMode = 'uniform'; cfgGC.FitnessA = 0.65; cfgGC.FitnessB = 0.55;
|
|
367
|
+
const grandchild = childXY.spliceDNA(spZ._id, JSON.stringify(cfgGC));
|
|
368
|
+
|
|
369
|
+
const ovXY = overlap(window.compareLoomDNA(spX.extractDNA(), spY.extractDNA()));
|
|
370
|
+
const ovChXY = overlap(window.compareLoomDNA(childXY.extractDNA(), spX.extractDNA()));
|
|
371
|
+
const ovGcZ = overlap(window.compareLoomDNA(grandchild.extractDNA(), spZ.extractDNA()));
|
|
372
|
+
|
|
373
|
+
track('SpliceDNA chain (A×B)', childXY.getLayerCount() > 0);
|
|
374
|
+
track('SpliceDNA chain (AB×C grandchild)', grandchild.getLayerCount() > 0);
|
|
375
|
+
|
|
376
|
+
p(` A vs B: overlap=${ovXY.toFixed(4)}`);
|
|
377
|
+
p(` AB vs A: overlap=${ovChXY.toFixed(4)}`);
|
|
378
|
+
p(` Grandchild vs Z: overlap=${ovGcZ.toFixed(4)}`);
|
|
379
|
+
grandchild.free(); childXY.free(); spX.free(); spY.free(); spZ.free();
|
|
380
|
+
p('');
|
|
381
|
+
|
|
382
|
+
// Free all layer nets
|
|
383
|
+
for (const lt of layerTypes) allNets[lt].net.free();
|
|
384
|
+
origNet.free();
|
|
385
|
+
|
|
386
|
+
// ═══════════════════════════════════════════════════════════
|
|
387
|
+
// 10. Coverage summary
|
|
388
|
+
// ═══════════════════════════════════════════════════════════
|
|
389
|
+
sep();
|
|
390
|
+
pInfo(' Coverage Summary');
|
|
391
|
+
sep();
|
|
392
|
+
|
|
393
|
+
let passed = 0, failed = 0;
|
|
394
|
+
for (const c of coverage) {
|
|
395
|
+
const icon = c.ok ? '✅' : '❌';
|
|
396
|
+
const cls = c.ok ? 'pass' : 'fail';
|
|
397
|
+
print(` ${icon} ${c.name}`, cls);
|
|
398
|
+
if (c.ok) passed++; else failed++;
|
|
399
|
+
}
|
|
400
|
+
p('');
|
|
401
|
+
sep();
|
|
402
|
+
if (failed === 0) {
|
|
403
|
+
pPass(` ALL ${passed} CHECKS PASSED ✅`);
|
|
404
|
+
} else {
|
|
405
|
+
print(` ${passed} passed / ${failed} failed`, failed > 0 ? 'fail' : 'pass');
|
|
406
|
+
}
|
|
407
|
+
sep();
|
|
408
|
+
|
|
409
|
+
document.getElementById('status').textContent = `Done. ${passed}/${passed+failed} passed.`;
|
|
410
|
+
document.getElementById('runBtn').disabled = false;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
window.addEventListener('load', async () => {
|
|
414
|
+
await loadWASM();
|
|
415
|
+
document.getElementById('status').textContent = wasmReady
|
|
416
|
+
? 'WASM ready. Click Run Benchmark.' : 'WASM load failed.';
|
|
417
|
+
});
|
|
418
|
+
</script>
|
|
419
|
+
</body>
|
|
420
|
+
</html>
|