@holoscript/framework 6.0.3 → 6.0.4

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.
Files changed (160) hide show
  1. package/CHANGELOG.md +1 -2
  2. package/ROADMAP.md +68 -66
  3. package/dist/{InvisibleWallet-BB6tFvRA.d.cts → InvisibleWallet-EFiuaLn3.d.cts} +1 -1
  4. package/dist/{OrchestratorAgent-BvWgf9uw.d.cts → OrchestratorAgent-CrLDGNL6.d.cts} +1 -1
  5. package/dist/agents/index.cjs +11 -10
  6. package/dist/agents/index.d.cts +4 -16
  7. package/dist/ai/index.cjs +2 -2
  8. package/dist/behavior.cjs +10 -0
  9. package/dist/economy/index.cjs +4 -4
  10. package/dist/economy/index.d.cts +2 -2
  11. package/dist/index.cjs +33 -11
  12. package/dist/index.d.cts +3 -3
  13. package/dist/swarm/index.cjs +3 -0
  14. package/package.json +14 -9
  15. package/src/__tests__/bounty-marketplace.test.ts +53 -21
  16. package/src/__tests__/delegation.test.ts +1 -4
  17. package/src/__tests__/done-log-audit.test.ts +38 -46
  18. package/src/__tests__/framework.test.ts +172 -53
  19. package/src/__tests__/goal-synthesizer.test.ts +9 -6
  20. package/src/__tests__/presence.test.ts +1 -1
  21. package/src/__tests__/protocol-agent.test.ts +12 -11
  22. package/src/__tests__/revenue-splitter.test.ts +22 -15
  23. package/src/__tests__/scenario-driven-todo.test.ts +55 -35
  24. package/src/__tests__/self-improve.test.ts +28 -9
  25. package/src/__tests__/service-lifecycle.test.ts +9 -3
  26. package/src/__tests__/skill-router.test.ts +3 -3
  27. package/src/agents/CulturalMemory.ts +6 -6
  28. package/src/agents/DelegationTraceHooks.ts +560 -0
  29. package/src/agents/FederatedRegistryAdapter.ts +1 -1
  30. package/src/agents/NormEngine.ts +3 -8
  31. package/src/agents/OrchestratorAgent.ts +1 -1
  32. package/src/agents/TaskDelegationService.ts +5 -9
  33. package/src/agents/__tests__/AgentWalletRegistry.test.ts +5 -4
  34. package/src/agents/__tests__/CrossRealityHandoff.test.ts +9 -3
  35. package/src/agents/__tests__/DelegationTraceHooks.test.ts +390 -0
  36. package/src/agents/__tests__/TaskDelegationService.test.ts +4 -2
  37. package/src/agents/spatial-comms/Layer1RealTime.ts +36 -19
  38. package/src/agents/spatial-comms/Layer2A2A.ts +1 -3
  39. package/src/agents/spatial-comms/Layer3MCP.ts +13 -4
  40. package/src/agents/spatial-comms/ProtocolTypes.ts +5 -2
  41. package/src/agents/spatial-comms/examples/multi-agent-world-creation.ts +2 -2
  42. package/src/ai/HoloScriptGenerator.ts +2 -2
  43. package/src/ai/__tests__/PerceptionSystem.prod.test.ts +1 -1
  44. package/src/ai/__tests__/PerceptionSystem.test.ts +14 -14
  45. package/src/ai/__tests__/SteeringBehaviors.prod.test.ts +1 -1
  46. package/src/ai/index.ts +5 -1
  47. package/src/board/audit.ts +17 -6
  48. package/src/board/board-ops.ts +45 -15
  49. package/src/board/board-types.ts +94 -20
  50. package/src/delegation.ts +5 -3
  51. package/src/distributed-claimer.ts +13 -2
  52. package/src/economy/BountyManager.ts +40 -18
  53. package/src/economy/KnowledgeMarketplace.ts +27 -8
  54. package/src/economy/PaymentWebhookService.ts +0 -1
  55. package/src/economy/RevenueSplitter.ts +2 -4
  56. package/src/economy/UnifiedBudgetOptimizer.ts +8 -9
  57. package/src/economy/_core-stubs.ts +1 -1
  58. package/src/economy/x402-facilitator.ts +17 -8
  59. package/src/index.ts +16 -12
  60. package/src/knowledge/__tests__/knowledge-consolidator.test.ts +138 -89
  61. package/src/knowledge/__tests__/knowledge-store-vector.test.ts +59 -16
  62. package/src/knowledge/brain.ts +7 -7
  63. package/src/knowledge/consolidation.ts +16 -16
  64. package/src/knowledge/knowledge-consolidator.ts +60 -30
  65. package/src/knowledge/knowledge-store.ts +83 -45
  66. package/src/learning/ProceduralCompiler.ts +6 -1
  67. package/src/learning/learning/MemoryConsolidator.ts +102 -0
  68. package/src/learning/learning/MemoryScorer.ts +69 -0
  69. package/src/learning/learning/ProceduralCompiler.ts +45 -0
  70. package/src/learning/learning/SemanticClusterer.ts +66 -0
  71. package/src/llm/llm-adapter.ts +24 -10
  72. package/src/mesh/index.ts +37 -17
  73. package/src/protocol/goal-synthesizer.ts +24 -34
  74. package/src/protocol/implementations.ts +91 -22
  75. package/src/protocol/micro-phase-decomposer.ts +25 -17
  76. package/src/protocol/micro-step-decomposer.test.ts +104 -39
  77. package/src/protocol-agent.test.ts +17 -7
  78. package/src/protocol-agent.ts +45 -42
  79. package/src/self-improve/absorb-scanner.ts +9 -6
  80. package/src/self-improve/evolution-engine.ts +36 -18
  81. package/src/self-improve/framework-absorber.ts +21 -16
  82. package/src/self-improve/index.ts +2 -10
  83. package/src/self-improve/prompt-optimizer.ts +31 -19
  84. package/src/self-improve/test-generator.ts +16 -12
  85. package/src/skill-router.ts +7 -6
  86. package/src/swarm/messaging/GossipProtocol.ts +1 -1
  87. package/src/swarm/messaging/__tests__/BroadcastChannel.prod.test.ts +31 -9
  88. package/src/swarm/messaging/__tests__/GossipProtocol.prod.test.ts +21 -7
  89. package/src/swarm/messaging/__tests__/SwarmEventBus.prod.test.ts +24 -8
  90. package/src/swarm/messaging/__tests__/SwarmEventBus.test.ts +6 -2
  91. package/src/team.ts +277 -122
  92. package/src/training/scripts/generate-spatial-dataset.ts +1 -1
  93. package/src/training/training/LRScheduler.ts +377 -0
  94. package/src/training/training/QualityScoringPipeline.ts +139 -0
  95. package/src/training/training/SoftDedup.ts +461 -0
  96. package/src/training/training/SparsityMonitor.ts +685 -0
  97. package/src/training/training/SparsityMonitorTypes.ts +209 -0
  98. package/src/training/training/SpatialTrainingDataGenerator.ts +1526 -0
  99. package/src/training/training/SpatialTrainingDataTypes.ts +216 -0
  100. package/src/training/training/TrainingPipelineConfig.ts +215 -0
  101. package/src/training/training/__tests__/CorpusValidation.test.ts +87 -0
  102. package/src/training/training/__tests__/LRScheduler.test.ts +592 -0
  103. package/src/training/training/__tests__/SoftDedup.test.ts +415 -0
  104. package/src/training/training/__tests__/SparsityMonitor.test.ts +1623 -0
  105. package/src/training/training/__tests__/SpatialCorpusValidation.test.ts +72 -0
  106. package/src/training/training/__tests__/SpatialTrainingDataGenerator.test.ts +1244 -0
  107. package/src/training/training/__tests__/TrainingMonkeyIntegration.test.ts +897 -0
  108. package/src/training/training/__tests__/TrainingPipelineConfig.test.ts +202 -0
  109. package/src/training/training/__tests__/schema.test.ts +72 -0
  110. package/src/training/training/__tests__/training-constants.test.ts +106 -0
  111. package/src/training/training/__tests__/trait-mappings.test.ts +81 -0
  112. package/src/training/training/constants.ts +94 -0
  113. package/src/training/training/index.ts +17 -0
  114. package/src/training/training/schema.ts +147 -0
  115. package/src/training/training/scripts/generate-novel-use-cases-dataset.ts +272 -0
  116. package/src/training/training/scripts/generate-spatial-dataset.ts +521 -0
  117. package/src/training/training/trainingmonkey/TrainingMonkeyIntegration.ts +477 -0
  118. package/src/training/training/trainingmonkey/TrainingMonkeyTypes.ts +230 -0
  119. package/src/training/training/trainingmonkey/index.ts +26 -0
  120. package/src/training/training/trait-mappings.ts +157 -0
  121. package/src/types.ts +2 -7
  122. package/ALL-test-results.json +0 -1
  123. package/LICENSE +0 -21
  124. package/dist/AgentManifest-CB4xM-Ma.d.ts +0 -704
  125. package/dist/BehaviorTree-BrBFECv5.d.ts +0 -103
  126. package/dist/InvisibleWallet-rtRrBOA8.d.ts +0 -1732
  127. package/dist/OrchestratorAgent-Q_CbVTmO.d.ts +0 -798
  128. package/dist/agents/index.d.ts +0 -1788
  129. package/dist/agents/index.js +0 -4695
  130. package/dist/ai/index.d.ts +0 -1753
  131. package/dist/ai/index.js +0 -5244
  132. package/dist/behavior.d.ts +0 -130
  133. package/dist/behavior.js +0 -407
  134. package/dist/economy/index.d.ts +0 -747
  135. package/dist/economy/index.js +0 -3617
  136. package/dist/implementations-D9T3un9D.d.ts +0 -236
  137. package/dist/index.d.ts +0 -1729
  138. package/dist/index.js +0 -24277
  139. package/dist/learning/index.d.ts +0 -104
  140. package/dist/learning/index.js +0 -189
  141. package/dist/negotiation/index.d.ts +0 -610
  142. package/dist/negotiation/index.js +0 -931
  143. package/dist/skills/index.d.ts +0 -289
  144. package/dist/skills/index.js +0 -1079
  145. package/dist/swarm/index.d.ts +0 -2433
  146. package/dist/swarm/index.js +0 -5221
  147. package/dist/training/index.d.ts +0 -1734
  148. package/dist/training/index.js +0 -2687
  149. package/extract-failures.js +0 -10
  150. package/src/training/training/data/novel-use-cases.jsonl +0 -153
  151. package/src/training/training/data/spatial-reasoning-10k.jsonl +0 -9354
  152. package/src/types/core-stubs.d.ts +0 -113
  153. package/test-output.txt +0 -0
  154. package/test-result.json +0 -1
  155. package/tsc-errors.txt +0 -4
  156. package/tsc_output.txt +0 -0
  157. package/typescript-errors-2.txt +0 -0
  158. package/typescript-errors.txt +0 -22
  159. package/vitest-log-utf8.txt +0 -268
  160. package/vitest-log.txt +0 -0
@@ -0,0 +1,1623 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { SparsityMonitor, createSparsityMonitor } from '../SparsityMonitor';
3
+ import type { LayerActivityInput } from '../SparsityMonitor';
4
+ import type {
5
+ SNNLayerMetrics,
6
+ SparsitySnapshot,
7
+ EnergyEfficiencyMetrics,
8
+ SparsityViolation,
9
+ SparsityMonitorConfig,
10
+ SparsityMonitorStats,
11
+ SparsityQualityHistoryEntry,
12
+ } from '../SparsityMonitorTypes';
13
+
14
+ // =============================================================================
15
+ // HELPERS
16
+ // =============================================================================
17
+
18
+ function makeInput(overrides: Partial<LayerActivityInput> = {}): LayerActivityInput {
19
+ return {
20
+ neuronCount: 1000,
21
+ spikeCount: 50,
22
+ timestep: 0,
23
+ ...overrides,
24
+ };
25
+ }
26
+
27
+ /**
28
+ * Create a monitor with common test defaults:
29
+ * - 93% sparsity threshold (per W.041)
30
+ * - Per-layer tracking enabled
31
+ * - Energy metrics enabled
32
+ */
33
+ function createTestMonitor(overrides: Partial<SparsityMonitorConfig> = {}): SparsityMonitor {
34
+ return new SparsityMonitor({
35
+ sparsityThreshold: 0.93,
36
+ windowSize: 50,
37
+ perLayerTracking: true,
38
+ energyMetricsEnabled: true,
39
+ avgSynapsesPerNeuron: 100,
40
+ opsPerSynapse: 2,
41
+ criticalThreshold: 0.85,
42
+ maxViolationHistory: 1000,
43
+ ...overrides,
44
+ });
45
+ }
46
+
47
+ // =============================================================================
48
+ // TESTS
49
+ // =============================================================================
50
+
51
+ describe('SparsityMonitor', () => {
52
+ // ---------------------------------------------------------------------------
53
+ // Initialization
54
+ // ---------------------------------------------------------------------------
55
+
56
+ describe('initialization', () => {
57
+ it('should create a monitor with default config', () => {
58
+ const monitor = new SparsityMonitor();
59
+ expect(monitor).toBeDefined();
60
+ });
61
+
62
+ it('should create a monitor via factory function', () => {
63
+ const monitor = createSparsityMonitor();
64
+ expect(monitor).toBeDefined();
65
+ });
66
+
67
+ it('should create a monitor with custom config', () => {
68
+ const monitor = new SparsityMonitor({
69
+ sparsityThreshold: 0.95,
70
+ windowSize: 100,
71
+ });
72
+ expect(monitor).toBeDefined();
73
+ expect(monitor.getConfig().sparsityThreshold).toBe(0.95);
74
+ expect(monitor.getConfig().windowSize).toBe(100);
75
+ });
76
+
77
+ it('should merge custom config with defaults', () => {
78
+ const monitor = new SparsityMonitor({ sparsityThreshold: 0.9 });
79
+ const config = monitor.getConfig();
80
+ expect(config.sparsityThreshold).toBe(0.9);
81
+ expect(config.windowSize).toBe(50); // default
82
+ expect(config.perLayerTracking).toBe(true); // default
83
+ expect(config.avgSynapsesPerNeuron).toBe(100); // default
84
+ });
85
+ });
86
+
87
+ // ---------------------------------------------------------------------------
88
+ // Recording Layer Activity
89
+ // ---------------------------------------------------------------------------
90
+
91
+ describe('recordLayerActivity()', () => {
92
+ let monitor: SparsityMonitor;
93
+
94
+ beforeEach(() => {
95
+ monitor = createTestMonitor();
96
+ });
97
+
98
+ it('should record activity and return computed metrics', () => {
99
+ const metrics = monitor.recordLayerActivity('layer1', makeInput());
100
+ expect(metrics.layerId).toBe('layer1');
101
+ expect(metrics.neuronCount).toBe(1000);
102
+ expect(metrics.spikeCount).toBe(50);
103
+ expect(metrics.spikeRate).toBeCloseTo(0.05, 4);
104
+ expect(metrics.activationSparsity).toBeCloseTo(0.95, 4);
105
+ expect(metrics.timestep).toBe(0);
106
+ });
107
+
108
+ it('should compute spike rate correctly', () => {
109
+ const metrics = monitor.recordLayerActivity(
110
+ 'layer1',
111
+ makeInput({
112
+ neuronCount: 200,
113
+ spikeCount: 40,
114
+ })
115
+ );
116
+ expect(metrics.spikeRate).toBeCloseTo(0.2, 4);
117
+ });
118
+
119
+ it('should compute activation sparsity as 1 - spikeRate', () => {
120
+ const metrics = monitor.recordLayerActivity(
121
+ 'layer1',
122
+ makeInput({
123
+ neuronCount: 200,
124
+ spikeCount: 40,
125
+ })
126
+ );
127
+ expect(metrics.activationSparsity).toBeCloseTo(0.8, 4);
128
+ });
129
+
130
+ it('should handle zero spikes (100% sparsity)', () => {
131
+ const metrics = monitor.recordLayerActivity(
132
+ 'layer1',
133
+ makeInput({
134
+ spikeCount: 0,
135
+ })
136
+ );
137
+ expect(metrics.spikeRate).toBe(0);
138
+ expect(metrics.activationSparsity).toBe(1);
139
+ });
140
+
141
+ it('should handle all neurons spiking (0% sparsity)', () => {
142
+ const metrics = monitor.recordLayerActivity(
143
+ 'layer1',
144
+ makeInput({
145
+ neuronCount: 100,
146
+ spikeCount: 100,
147
+ })
148
+ );
149
+ expect(metrics.spikeRate).toBe(1);
150
+ expect(metrics.activationSparsity).toBe(0);
151
+ });
152
+
153
+ it('should store average membrane potential when provided', () => {
154
+ const metrics = monitor.recordLayerActivity(
155
+ 'layer1',
156
+ makeInput({
157
+ avgMembranePotential: -0.65,
158
+ })
159
+ );
160
+ expect(metrics.avgMembranePotential).toBe(-0.65);
161
+ });
162
+
163
+ it('should throw on non-positive neuronCount', () => {
164
+ expect(() => monitor.recordLayerActivity('layer1', makeInput({ neuronCount: 0 }))).toThrow(
165
+ 'neuronCount must be positive'
166
+ );
167
+ expect(() => monitor.recordLayerActivity('layer1', makeInput({ neuronCount: -5 }))).toThrow(
168
+ 'neuronCount must be positive'
169
+ );
170
+ });
171
+
172
+ it('should throw on negative spikeCount', () => {
173
+ expect(() => monitor.recordLayerActivity('layer1', makeInput({ spikeCount: -1 }))).toThrow(
174
+ 'spikeCount must be non-negative'
175
+ );
176
+ });
177
+
178
+ it('should throw when spikeCount exceeds neuronCount', () => {
179
+ expect(() =>
180
+ monitor.recordLayerActivity(
181
+ 'layer1',
182
+ makeInput({
183
+ neuronCount: 100,
184
+ spikeCount: 150,
185
+ })
186
+ )
187
+ ).toThrow('spikeCount (150) cannot exceed neuronCount (100)');
188
+ });
189
+
190
+ it('should update current layer metrics on successive recordings', () => {
191
+ monitor.recordLayerActivity('layer1', makeInput({ spikeCount: 50, timestep: 0 }));
192
+ monitor.recordLayerActivity('layer1', makeInput({ spikeCount: 30, timestep: 1 }));
193
+
194
+ const current = monitor.getCurrentLayerMetrics();
195
+ const layer1 = current.get('layer1');
196
+ expect(layer1).toBeDefined();
197
+ expect(layer1!.spikeCount).toBe(30);
198
+ expect(layer1!.timestep).toBe(1);
199
+ });
200
+
201
+ it('should track multiple layers independently', () => {
202
+ monitor.recordLayerActivity('hidden_1', makeInput({ spikeCount: 50 }));
203
+ monitor.recordLayerActivity('hidden_2', makeInput({ spikeCount: 100 }));
204
+ monitor.recordLayerActivity('output', makeInput({ neuronCount: 10, spikeCount: 1 }));
205
+
206
+ const current = monitor.getCurrentLayerMetrics();
207
+ expect(current.size).toBe(3);
208
+ expect(current.get('hidden_1')!.spikeCount).toBe(50);
209
+ expect(current.get('hidden_2')!.spikeCount).toBe(100);
210
+ expect(current.get('output')!.neuronCount).toBe(10);
211
+ });
212
+
213
+ it('should accumulate layer history when perLayerTracking is true', () => {
214
+ monitor.recordLayerActivity('layer1', makeInput({ spikeCount: 50, timestep: 0 }));
215
+ monitor.recordLayerActivity('layer1', makeInput({ spikeCount: 30, timestep: 1 }));
216
+ monitor.recordLayerActivity('layer1', makeInput({ spikeCount: 70, timestep: 2 }));
217
+
218
+ const history = monitor.getLayerHistory('layer1');
219
+ expect(history.length).toBe(3);
220
+ expect(history[0].spikeCount).toBe(50);
221
+ expect(history[1].spikeCount).toBe(30);
222
+ expect(history[2].spikeCount).toBe(70);
223
+ });
224
+
225
+ it('should not accumulate history when perLayerTracking is false', () => {
226
+ const mon = createTestMonitor({ perLayerTracking: false });
227
+ mon.recordLayerActivity('layer1', makeInput({ spikeCount: 50, timestep: 0 }));
228
+ mon.recordLayerActivity('layer1', makeInput({ spikeCount: 30, timestep: 1 }));
229
+
230
+ const history = mon.getLayerHistory('layer1');
231
+ expect(history.length).toBe(0);
232
+ });
233
+
234
+ it('should return empty array for unknown layer history', () => {
235
+ expect(monitor.getLayerHistory('nonexistent')).toEqual([]);
236
+ });
237
+ });
238
+
239
+ // ---------------------------------------------------------------------------
240
+ // Batch Recording
241
+ // ---------------------------------------------------------------------------
242
+
243
+ describe('recordBatchActivity()', () => {
244
+ let monitor: SparsityMonitor;
245
+
246
+ beforeEach(() => {
247
+ monitor = createTestMonitor();
248
+ });
249
+
250
+ it('should record multiple layers from a Map', () => {
251
+ const inputs = new Map<string, LayerActivityInput>([
252
+ ['layer1', makeInput({ spikeCount: 50 })],
253
+ ['layer2', makeInput({ spikeCount: 100 })],
254
+ ]);
255
+ const results = monitor.recordBatchActivity(inputs);
256
+
257
+ expect(results.length).toBe(2);
258
+ expect(results[0].layerId).toBe('layer1');
259
+ expect(results[1].layerId).toBe('layer2');
260
+ });
261
+
262
+ it('should record multiple layers from a plain object', () => {
263
+ const inputs: Record<string, LayerActivityInput> = {
264
+ layer1: makeInput({ spikeCount: 50 }),
265
+ layer2: makeInput({ spikeCount: 100 }),
266
+ };
267
+ const results = monitor.recordBatchActivity(inputs);
268
+
269
+ expect(results.length).toBe(2);
270
+ expect(monitor.getCurrentLayerMetrics().size).toBe(2);
271
+ });
272
+
273
+ it('should handle empty batch', () => {
274
+ const results = monitor.recordBatchActivity(new Map());
275
+ expect(results.length).toBe(0);
276
+ });
277
+ });
278
+
279
+ // ---------------------------------------------------------------------------
280
+ // Snapshots
281
+ // ---------------------------------------------------------------------------
282
+
283
+ describe('takeSnapshot()', () => {
284
+ let monitor: SparsityMonitor;
285
+
286
+ beforeEach(() => {
287
+ monitor = createTestMonitor();
288
+ });
289
+
290
+ it('should return null when no layer metrics recorded', () => {
291
+ expect(monitor.takeSnapshot()).toBeNull();
292
+ });
293
+
294
+ it('should capture aggregate metrics across all layers', () => {
295
+ // Layer 1: 1000 neurons, 50 spikes = 5% spike rate, 95% sparsity
296
+ monitor.recordLayerActivity(
297
+ 'layer1',
298
+ makeInput({
299
+ neuronCount: 1000,
300
+ spikeCount: 50,
301
+ timestep: 0,
302
+ })
303
+ );
304
+ // Layer 2: 500 neurons, 25 spikes = 5% spike rate, 95% sparsity
305
+ monitor.recordLayerActivity(
306
+ 'layer2',
307
+ makeInput({
308
+ neuronCount: 500,
309
+ spikeCount: 25,
310
+ timestep: 0,
311
+ })
312
+ );
313
+
314
+ const snapshot = monitor.takeSnapshot();
315
+ expect(snapshot).not.toBeNull();
316
+ expect(snapshot!.totalNeurons).toBe(1500);
317
+ expect(snapshot!.totalSpikes).toBe(75);
318
+ expect(snapshot!.aggregateSpikeRate).toBeCloseTo(0.05, 4);
319
+ expect(snapshot!.aggregateSparsity).toBeCloseTo(0.95, 4);
320
+ });
321
+
322
+ it('should weight aggregate sparsity by neuron count', () => {
323
+ // Layer 1: 900 neurons, 90 spikes (10% spike rate)
324
+ monitor.recordLayerActivity(
325
+ 'big',
326
+ makeInput({
327
+ neuronCount: 900,
328
+ spikeCount: 90,
329
+ timestep: 0,
330
+ })
331
+ );
332
+ // Layer 2: 100 neurons, 50 spikes (50% spike rate)
333
+ monitor.recordLayerActivity(
334
+ 'small',
335
+ makeInput({
336
+ neuronCount: 100,
337
+ spikeCount: 50,
338
+ timestep: 0,
339
+ })
340
+ );
341
+
342
+ const snapshot = monitor.takeSnapshot()!;
343
+ // Total: 1000 neurons, 140 spikes -> 14% spike rate
344
+ expect(snapshot.totalNeurons).toBe(1000);
345
+ expect(snapshot.totalSpikes).toBe(140);
346
+ expect(snapshot.aggregateSpikeRate).toBeCloseTo(0.14, 4);
347
+ expect(snapshot.aggregateSparsity).toBeCloseTo(0.86, 4);
348
+ });
349
+
350
+ it('should include per-layer details', () => {
351
+ monitor.recordLayerActivity('layer1', makeInput({ spikeCount: 50 }));
352
+ monitor.recordLayerActivity('layer2', makeInput({ spikeCount: 100 }));
353
+
354
+ const snapshot = monitor.takeSnapshot()!;
355
+ expect(snapshot.layers.length).toBe(2);
356
+ expect(snapshot.layers.find((l) => l.layerId === 'layer1')).toBeDefined();
357
+ expect(snapshot.layers.find((l) => l.layerId === 'layer2')).toBeDefined();
358
+ });
359
+
360
+ it('should include energy efficiency metrics', () => {
361
+ monitor.recordLayerActivity('layer1', makeInput({ spikeCount: 50 }));
362
+ const snapshot = monitor.takeSnapshot()!;
363
+
364
+ expect(snapshot.energyEfficiency).toBeDefined();
365
+ expect(snapshot.energyEfficiency.denseOps).toBeGreaterThan(0);
366
+ expect(snapshot.energyEfficiency.sparseOps).toBeGreaterThan(0);
367
+ expect(snapshot.energyEfficiency.opsSaved).toBeGreaterThan(0);
368
+ expect(snapshot.energyEfficiency.efficiencyRatio).toBeGreaterThan(0);
369
+ });
370
+
371
+ it('should return zero energy metrics when energy metrics are disabled', () => {
372
+ const mon = createTestMonitor({ energyMetricsEnabled: false });
373
+ mon.recordLayerActivity('layer1', makeInput({ spikeCount: 50 }));
374
+ const snapshot = mon.takeSnapshot()!;
375
+
376
+ expect(snapshot.energyEfficiency.denseOps).toBe(0);
377
+ expect(snapshot.energyEfficiency.sparseOps).toBe(0);
378
+ expect(snapshot.energyEfficiency.opsSaved).toBe(0);
379
+ });
380
+
381
+ it('should detect violations in snapshot', () => {
382
+ // Layer with < 93% sparsity (50% spike rate)
383
+ monitor.recordLayerActivity(
384
+ 'bad_layer',
385
+ makeInput({
386
+ neuronCount: 100,
387
+ spikeCount: 50,
388
+ timestep: 0,
389
+ })
390
+ );
391
+
392
+ const snapshot = monitor.takeSnapshot()!;
393
+ expect(snapshot.violations.length).toBe(1);
394
+ expect(snapshot.violations[0].layerId).toBe('bad_layer');
395
+ });
396
+
397
+ it('should accumulate snapshots over time', () => {
398
+ monitor.recordLayerActivity('layer1', makeInput({ spikeCount: 50, timestep: 0 }));
399
+ monitor.takeSnapshot();
400
+
401
+ monitor.recordLayerActivity('layer1', makeInput({ spikeCount: 30, timestep: 1 }));
402
+ monitor.takeSnapshot();
403
+
404
+ const snapshots = monitor.getSnapshots();
405
+ expect(snapshots.length).toBe(2);
406
+ });
407
+
408
+ it('should return correct latest snapshot', () => {
409
+ monitor.recordLayerActivity('layer1', makeInput({ spikeCount: 50, timestep: 0 }));
410
+ monitor.takeSnapshot();
411
+
412
+ monitor.recordLayerActivity('layer1', makeInput({ spikeCount: 30, timestep: 1 }));
413
+ monitor.takeSnapshot();
414
+
415
+ const latest = monitor.getLatestSnapshot();
416
+ expect(latest).not.toBeNull();
417
+ expect(latest!.layers[0].spikeCount).toBe(30);
418
+ });
419
+
420
+ it('should return null for latest snapshot when none taken', () => {
421
+ expect(monitor.getLatestSnapshot()).toBeNull();
422
+ });
423
+
424
+ it('should have correct timestamp format', () => {
425
+ monitor.recordLayerActivity('layer1', makeInput());
426
+ const snapshot = monitor.takeSnapshot()!;
427
+ // ISO 8601 format check
428
+ expect(snapshot.timestamp).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/);
429
+ });
430
+ });
431
+
432
+ // ---------------------------------------------------------------------------
433
+ // Energy Efficiency Calculation
434
+ // ---------------------------------------------------------------------------
435
+
436
+ describe('calculateEnergyEfficiency()', () => {
437
+ let monitor: SparsityMonitor;
438
+
439
+ beforeEach(() => {
440
+ monitor = createTestMonitor({
441
+ avgSynapsesPerNeuron: 100,
442
+ opsPerSynapse: 2,
443
+ });
444
+ });
445
+
446
+ it('should calculate dense ops correctly', () => {
447
+ const layers: SNNLayerMetrics[] = [
448
+ {
449
+ layerId: 'layer1',
450
+ neuronCount: 1000,
451
+ spikeCount: 50,
452
+ spikeRate: 0.05,
453
+ activationSparsity: 0.95,
454
+ timestep: 0,
455
+ },
456
+ ];
457
+ const result = monitor.calculateEnergyEfficiency(layers);
458
+ // 1000 neurons * 100 synapses * 2 ops = 200,000
459
+ expect(result.denseOps).toBe(200000);
460
+ });
461
+
462
+ it('should calculate sparse ops correctly', () => {
463
+ const layers: SNNLayerMetrics[] = [
464
+ {
465
+ layerId: 'layer1',
466
+ neuronCount: 1000,
467
+ spikeCount: 50,
468
+ spikeRate: 0.05,
469
+ activationSparsity: 0.95,
470
+ timestep: 0,
471
+ },
472
+ ];
473
+ const result = monitor.calculateEnergyEfficiency(layers);
474
+ // 50 spikes * 100 synapses * 2 ops = 10,000
475
+ expect(result.sparseOps).toBe(10000);
476
+ });
477
+
478
+ it('should calculate ops saved', () => {
479
+ const layers: SNNLayerMetrics[] = [
480
+ {
481
+ layerId: 'layer1',
482
+ neuronCount: 1000,
483
+ spikeCount: 50,
484
+ spikeRate: 0.05,
485
+ activationSparsity: 0.95,
486
+ timestep: 0,
487
+ },
488
+ ];
489
+ const result = monitor.calculateEnergyEfficiency(layers);
490
+ expect(result.opsSaved).toBe(190000);
491
+ });
492
+
493
+ it('should calculate efficiency ratio correctly', () => {
494
+ const layers: SNNLayerMetrics[] = [
495
+ {
496
+ layerId: 'layer1',
497
+ neuronCount: 1000,
498
+ spikeCount: 50,
499
+ spikeRate: 0.05,
500
+ activationSparsity: 0.95,
501
+ timestep: 0,
502
+ },
503
+ ];
504
+ const result = monitor.calculateEnergyEfficiency(layers);
505
+ // 190000 / 200000 = 0.95
506
+ expect(result.efficiencyRatio).toBeCloseTo(0.95, 4);
507
+ });
508
+
509
+ it('should calculate energy savings factor', () => {
510
+ const layers: SNNLayerMetrics[] = [
511
+ {
512
+ layerId: 'layer1',
513
+ neuronCount: 1000,
514
+ spikeCount: 50,
515
+ spikeRate: 0.05,
516
+ activationSparsity: 0.95,
517
+ timestep: 0,
518
+ },
519
+ ];
520
+ const result = monitor.calculateEnergyEfficiency(layers);
521
+ // 200000 / 10000 = 20x
522
+ expect(result.energySavingsFactor).toBe(20);
523
+ });
524
+
525
+ it('should sum across multiple layers', () => {
526
+ const layers: SNNLayerMetrics[] = [
527
+ {
528
+ layerId: 'layer1',
529
+ neuronCount: 1000,
530
+ spikeCount: 50,
531
+ spikeRate: 0.05,
532
+ activationSparsity: 0.95,
533
+ timestep: 0,
534
+ },
535
+ {
536
+ layerId: 'layer2',
537
+ neuronCount: 500,
538
+ spikeCount: 100,
539
+ spikeRate: 0.2,
540
+ activationSparsity: 0.8,
541
+ timestep: 0,
542
+ },
543
+ ];
544
+ const result = monitor.calculateEnergyEfficiency(layers);
545
+ // Dense: (1000 + 500) * 100 * 2 = 300,000
546
+ expect(result.denseOps).toBe(300000);
547
+ // Sparse: (50 + 100) * 100 * 2 = 30,000
548
+ expect(result.sparseOps).toBe(30000);
549
+ expect(result.opsSaved).toBe(270000);
550
+ });
551
+
552
+ it('should handle zero spikes (maximum efficiency)', () => {
553
+ const layers: SNNLayerMetrics[] = [
554
+ {
555
+ layerId: 'layer1',
556
+ neuronCount: 1000,
557
+ spikeCount: 0,
558
+ spikeRate: 0,
559
+ activationSparsity: 1,
560
+ timestep: 0,
561
+ },
562
+ ];
563
+ const result = monitor.calculateEnergyEfficiency(layers);
564
+ expect(result.sparseOps).toBe(0);
565
+ expect(result.efficiencyRatio).toBe(1);
566
+ // energySavingsFactor: denseOps / max(1, 0) = 200000
567
+ expect(result.energySavingsFactor).toBe(200000);
568
+ });
569
+
570
+ it('should handle all neurons spiking (no efficiency)', () => {
571
+ const layers: SNNLayerMetrics[] = [
572
+ {
573
+ layerId: 'layer1',
574
+ neuronCount: 100,
575
+ spikeCount: 100,
576
+ spikeRate: 1,
577
+ activationSparsity: 0,
578
+ timestep: 0,
579
+ },
580
+ ];
581
+ const result = monitor.calculateEnergyEfficiency(layers);
582
+ expect(result.opsSaved).toBe(0);
583
+ expect(result.efficiencyRatio).toBe(0);
584
+ expect(result.energySavingsFactor).toBe(1);
585
+ });
586
+
587
+ it('should handle empty layers array', () => {
588
+ const result = monitor.calculateEnergyEfficiency([]);
589
+ expect(result.denseOps).toBe(0);
590
+ expect(result.sparseOps).toBe(0);
591
+ expect(result.opsSaved).toBe(0);
592
+ expect(result.efficiencyRatio).toBe(0);
593
+ });
594
+ });
595
+
596
+ // ---------------------------------------------------------------------------
597
+ // Violation Detection
598
+ // ---------------------------------------------------------------------------
599
+
600
+ describe('violation detection', () => {
601
+ let monitor: SparsityMonitor;
602
+
603
+ beforeEach(() => {
604
+ monitor = createTestMonitor({
605
+ sparsityThreshold: 0.93,
606
+ criticalThreshold: 0.85,
607
+ });
608
+ });
609
+
610
+ it('should not flag layers at or above threshold', () => {
611
+ // 95% sparsity >= 93% threshold
612
+ monitor.recordLayerActivity(
613
+ 'good_layer',
614
+ makeInput({
615
+ neuronCount: 1000,
616
+ spikeCount: 50,
617
+ })
618
+ );
619
+
620
+ const violations = monitor.getActiveViolations();
621
+ expect(violations.length).toBe(0);
622
+ });
623
+
624
+ it('should flag layers below threshold as warning', () => {
625
+ // 90% sparsity < 93% threshold but >= 85% critical
626
+ monitor.recordLayerActivity(
627
+ 'warn_layer',
628
+ makeInput({
629
+ neuronCount: 100,
630
+ spikeCount: 10,
631
+ })
632
+ );
633
+
634
+ const violations = monitor.getActiveViolations();
635
+ expect(violations.length).toBe(1);
636
+ expect(violations[0].severity).toBe('warning');
637
+ expect(violations[0].layerId).toBe('warn_layer');
638
+ });
639
+
640
+ it('should flag layers below critical threshold as critical', () => {
641
+ // 50% sparsity < 85% critical threshold
642
+ monitor.recordLayerActivity(
643
+ 'bad_layer',
644
+ makeInput({
645
+ neuronCount: 100,
646
+ spikeCount: 50,
647
+ })
648
+ );
649
+
650
+ const violations = monitor.getActiveViolations();
651
+ expect(violations.length).toBe(1);
652
+ expect(violations[0].severity).toBe('critical');
653
+ });
654
+
655
+ it('should calculate deficit correctly', () => {
656
+ // 90% sparsity, threshold 93%
657
+ monitor.recordLayerActivity(
658
+ 'layer1',
659
+ makeInput({
660
+ neuronCount: 100,
661
+ spikeCount: 10,
662
+ })
663
+ );
664
+
665
+ const violations = monitor.getActiveViolations();
666
+ expect(violations.length).toBe(1);
667
+ expect(violations[0].deficit).toBeCloseTo(0.03, 4);
668
+ });
669
+
670
+ it('should track violation history', () => {
671
+ monitor.recordLayerActivity(
672
+ 'layer1',
673
+ makeInput({
674
+ neuronCount: 100,
675
+ spikeCount: 20,
676
+ timestep: 0,
677
+ })
678
+ );
679
+ monitor.recordLayerActivity(
680
+ 'layer1',
681
+ makeInput({
682
+ neuronCount: 100,
683
+ spikeCount: 15,
684
+ timestep: 1,
685
+ })
686
+ );
687
+
688
+ const history = monitor.getViolationHistory();
689
+ expect(history.length).toBe(2);
690
+ });
691
+
692
+ it('should trim violation history at max capacity', () => {
693
+ const mon = createTestMonitor({ maxViolationHistory: 3 });
694
+
695
+ for (let i = 0; i < 5; i++) {
696
+ mon.recordLayerActivity(
697
+ 'layer1',
698
+ makeInput({
699
+ neuronCount: 100,
700
+ spikeCount: 20,
701
+ timestep: i,
702
+ })
703
+ );
704
+ }
705
+
706
+ const history = mon.getViolationHistory();
707
+ expect(history.length).toBe(3);
708
+ });
709
+
710
+ it('should clear active violations when layer comes back into compliance', () => {
711
+ monitor.recordLayerActivity(
712
+ 'layer1',
713
+ makeInput({
714
+ neuronCount: 100,
715
+ spikeCount: 20,
716
+ timestep: 0,
717
+ })
718
+ );
719
+ expect(monitor.getActiveViolations().length).toBe(1);
720
+
721
+ // Layer now has 1% spike rate (99% sparsity) = in compliance
722
+ monitor.recordLayerActivity(
723
+ 'layer1',
724
+ makeInput({
725
+ neuronCount: 100,
726
+ spikeCount: 1,
727
+ timestep: 1,
728
+ })
729
+ );
730
+ expect(monitor.getActiveViolations().length).toBe(0);
731
+ });
732
+
733
+ it('should detect violations across multiple layers independently', () => {
734
+ // Layer 1: in compliance (95% sparsity)
735
+ monitor.recordLayerActivity(
736
+ 'good',
737
+ makeInput({
738
+ neuronCount: 1000,
739
+ spikeCount: 50,
740
+ })
741
+ );
742
+ // Layer 2: warning (90% sparsity)
743
+ monitor.recordLayerActivity(
744
+ 'warn',
745
+ makeInput({
746
+ neuronCount: 100,
747
+ spikeCount: 10,
748
+ })
749
+ );
750
+ // Layer 3: critical (50% sparsity)
751
+ monitor.recordLayerActivity(
752
+ 'bad',
753
+ makeInput({
754
+ neuronCount: 100,
755
+ spikeCount: 50,
756
+ })
757
+ );
758
+
759
+ const violations = monitor.getActiveViolations();
760
+ expect(violations.length).toBe(2);
761
+
762
+ const warnV = violations.find((v) => v.layerId === 'warn');
763
+ const badV = violations.find((v) => v.layerId === 'bad');
764
+ expect(warnV).toBeDefined();
765
+ expect(warnV!.severity).toBe('warning');
766
+ expect(badV).toBeDefined();
767
+ expect(badV!.severity).toBe('critical');
768
+ });
769
+
770
+ it('should include required threshold in violation', () => {
771
+ monitor.recordLayerActivity(
772
+ 'layer1',
773
+ makeInput({
774
+ neuronCount: 100,
775
+ spikeCount: 20,
776
+ })
777
+ );
778
+
779
+ const violations = monitor.getActiveViolations();
780
+ expect(violations[0].requiredThreshold).toBe(0.93);
781
+ });
782
+
783
+ it('should include timestep in violation', () => {
784
+ monitor.recordLayerActivity(
785
+ 'layer1',
786
+ makeInput({
787
+ neuronCount: 100,
788
+ spikeCount: 20,
789
+ timestep: 42,
790
+ })
791
+ );
792
+
793
+ const violations = monitor.getActiveViolations();
794
+ expect(violations[0].timestep).toBe(42);
795
+ });
796
+
797
+ it('should include timestamp in violation', () => {
798
+ monitor.recordLayerActivity(
799
+ 'layer1',
800
+ makeInput({
801
+ neuronCount: 100,
802
+ spikeCount: 20,
803
+ })
804
+ );
805
+
806
+ const violations = monitor.getActiveViolations();
807
+ expect(violations[0].detectedAt).toMatch(/^\d{4}-\d{2}-\d{2}T/);
808
+ });
809
+ });
810
+
811
+ // ---------------------------------------------------------------------------
812
+ // Statistics
813
+ // ---------------------------------------------------------------------------
814
+
815
+ describe('getStats()', () => {
816
+ let monitor: SparsityMonitor;
817
+
818
+ beforeEach(() => {
819
+ monitor = createTestMonitor();
820
+ });
821
+
822
+ it('should return zeroed stats when no data recorded', () => {
823
+ const stats = monitor.getStats();
824
+ expect(stats.totalTimesteps).toBe(0);
825
+ expect(stats.totalSnapshots).toBe(0);
826
+ expect(stats.trackedLayers).toBe(0);
827
+ expect(stats.meanSparsity).toBe(0);
828
+ expect(stats.totalViolations).toBe(0);
829
+ expect(stats.inCompliance).toBe(true);
830
+ });
831
+
832
+ it('should track total timesteps', () => {
833
+ monitor.recordLayerActivity('layer1', makeInput({ timestep: 0 }));
834
+ monitor.recordLayerActivity('layer1', makeInput({ timestep: 1 }));
835
+ monitor.recordLayerActivity('layer2', makeInput({ timestep: 0 }));
836
+
837
+ const stats = monitor.getStats();
838
+ expect(stats.totalTimesteps).toBe(3);
839
+ });
840
+
841
+ it('should track total snapshots', () => {
842
+ monitor.recordLayerActivity('layer1', makeInput());
843
+ monitor.takeSnapshot();
844
+ monitor.takeSnapshot();
845
+
846
+ expect(monitor.getStats().totalSnapshots).toBe(2);
847
+ });
848
+
849
+ it('should track number of layers', () => {
850
+ monitor.recordLayerActivity('layer1', makeInput());
851
+ monitor.recordLayerActivity('layer2', makeInput());
852
+
853
+ expect(monitor.getStats().trackedLayers).toBe(2);
854
+ });
855
+
856
+ it('should calculate mean sparsity', () => {
857
+ // All layers with 95% sparsity
858
+ monitor.recordLayerActivity(
859
+ 'layer1',
860
+ makeInput({
861
+ neuronCount: 1000,
862
+ spikeCount: 50,
863
+ timestep: 0,
864
+ })
865
+ );
866
+ monitor.recordLayerActivity(
867
+ 'layer1',
868
+ makeInput({
869
+ neuronCount: 1000,
870
+ spikeCount: 50,
871
+ timestep: 1,
872
+ })
873
+ );
874
+
875
+ const stats = monitor.getStats();
876
+ expect(stats.meanSparsity).toBeCloseTo(0.95, 4);
877
+ });
878
+
879
+ it('should calculate min and max sparsity', () => {
880
+ monitor.recordLayerActivity(
881
+ 'layer1',
882
+ makeInput({
883
+ neuronCount: 1000,
884
+ spikeCount: 50,
885
+ timestep: 0,
886
+ })
887
+ );
888
+ monitor.recordLayerActivity(
889
+ 'layer1',
890
+ makeInput({
891
+ neuronCount: 1000,
892
+ spikeCount: 100,
893
+ timestep: 1,
894
+ })
895
+ );
896
+
897
+ const stats = monitor.getStats();
898
+ expect(stats.minSparsity).toBeCloseTo(0.9, 4);
899
+ expect(stats.maxSparsity).toBeCloseTo(0.95, 4);
900
+ });
901
+
902
+ it('should calculate standard deviation', () => {
903
+ // Record varying sparsities
904
+ monitor.recordLayerActivity(
905
+ 'layer1',
906
+ makeInput({
907
+ neuronCount: 100,
908
+ spikeCount: 5,
909
+ timestep: 0,
910
+ })
911
+ ); // 95%
912
+ monitor.recordLayerActivity(
913
+ 'layer1',
914
+ makeInput({
915
+ neuronCount: 100,
916
+ spikeCount: 10,
917
+ timestep: 1,
918
+ })
919
+ ); // 90%
920
+ monitor.recordLayerActivity(
921
+ 'layer1',
922
+ makeInput({
923
+ neuronCount: 100,
924
+ spikeCount: 1,
925
+ timestep: 2,
926
+ })
927
+ ); // 99%
928
+
929
+ const stats = monitor.getStats();
930
+ expect(stats.stdDevSparsity).toBeGreaterThan(0);
931
+ });
932
+
933
+ it('should report zero stdDev for single measurement', () => {
934
+ monitor.recordLayerActivity('layer1', makeInput());
935
+ expect(monitor.getStats().stdDevSparsity).toBe(0);
936
+ });
937
+
938
+ it('should count violations by severity', () => {
939
+ // Warning violation
940
+ monitor.recordLayerActivity(
941
+ 'warn_layer',
942
+ makeInput({
943
+ neuronCount: 100,
944
+ spikeCount: 10,
945
+ timestep: 0,
946
+ })
947
+ ); // 90% sparsity
948
+ // Critical violation
949
+ monitor.recordLayerActivity(
950
+ 'bad_layer',
951
+ makeInput({
952
+ neuronCount: 100,
953
+ spikeCount: 50,
954
+ timestep: 0,
955
+ })
956
+ ); // 50% sparsity
957
+
958
+ const stats = monitor.getStats();
959
+ expect(stats.totalViolations).toBe(2);
960
+ expect(stats.violationsBySeverity.warning).toBe(1);
961
+ expect(stats.violationsBySeverity.critical).toBe(1);
962
+ });
963
+
964
+ it('should track per-layer mean sparsity', () => {
965
+ monitor.recordLayerActivity(
966
+ 'layer1',
967
+ makeInput({
968
+ neuronCount: 100,
969
+ spikeCount: 5,
970
+ timestep: 0,
971
+ })
972
+ ); // 95%
973
+ monitor.recordLayerActivity(
974
+ 'layer1',
975
+ makeInput({
976
+ neuronCount: 100,
977
+ spikeCount: 3,
978
+ timestep: 1,
979
+ })
980
+ ); // 97%
981
+ monitor.recordLayerActivity(
982
+ 'layer2',
983
+ makeInput({
984
+ neuronCount: 100,
985
+ spikeCount: 10,
986
+ timestep: 0,
987
+ })
988
+ ); // 90%
989
+
990
+ const stats = monitor.getStats();
991
+ expect(stats.perLayerMeanSparsity['layer1']).toBeCloseTo(0.96, 2);
992
+ expect(stats.perLayerMeanSparsity['layer2']).toBeCloseTo(0.9, 4);
993
+ });
994
+
995
+ it('should calculate mean energy efficiency', () => {
996
+ monitor.recordLayerActivity('layer1', makeInput({ spikeCount: 50, timestep: 0 }));
997
+ monitor.takeSnapshot();
998
+ monitor.recordLayerActivity('layer1', makeInput({ spikeCount: 30, timestep: 1 }));
999
+ monitor.takeSnapshot();
1000
+
1001
+ const stats = monitor.getStats();
1002
+ expect(stats.meanEnergyEfficiency).toBeGreaterThan(0);
1003
+ });
1004
+
1005
+ it('should report inCompliance correctly', () => {
1006
+ // All layers in compliance
1007
+ monitor.recordLayerActivity(
1008
+ 'layer1',
1009
+ makeInput({
1010
+ neuronCount: 1000,
1011
+ spikeCount: 50,
1012
+ })
1013
+ );
1014
+ expect(monitor.getStats().inCompliance).toBe(true);
1015
+
1016
+ // Add a violating layer
1017
+ monitor.recordLayerActivity(
1018
+ 'bad',
1019
+ makeInput({
1020
+ neuronCount: 100,
1021
+ spikeCount: 20,
1022
+ })
1023
+ );
1024
+ expect(monitor.getStats().inCompliance).toBe(false);
1025
+ });
1026
+
1027
+ it('should fall back to current metrics when no history (perLayerTracking disabled)', () => {
1028
+ const mon = createTestMonitor({ perLayerTracking: false });
1029
+ mon.recordLayerActivity('layer1', makeInput({ spikeCount: 50 }));
1030
+
1031
+ const stats = mon.getStats();
1032
+ expect(stats.meanSparsity).toBeCloseTo(0.95, 4);
1033
+ });
1034
+ });
1035
+
1036
+ // ---------------------------------------------------------------------------
1037
+ // Quality History Integration
1038
+ // ---------------------------------------------------------------------------
1039
+
1040
+ describe('toQualityHistoryEntry()', () => {
1041
+ let monitor: SparsityMonitor;
1042
+
1043
+ beforeEach(() => {
1044
+ monitor = createTestMonitor();
1045
+ });
1046
+
1047
+ it('should generate entry with correct structure', () => {
1048
+ monitor.recordLayerActivity('layer1', makeInput({ spikeCount: 50 }));
1049
+ monitor.takeSnapshot();
1050
+
1051
+ const entry = monitor.toQualityHistoryEntry(1);
1052
+
1053
+ expect(entry.timestamp).toMatch(/^\d{4}-\d{2}-\d{2}T/);
1054
+ expect(entry.cycle).toBe(1);
1055
+ expect(typeof entry.composite).toBe('number');
1056
+ expect(['A', 'B', 'C', 'D', 'F']).toContain(entry.grade);
1057
+ expect(entry.focus).toBe('snn-sparsity');
1058
+ expect(typeof entry.summary).toBe('string');
1059
+ expect(entry.sparsityMetrics).toBeDefined();
1060
+ });
1061
+
1062
+ it('should calculate composite as sparsity/threshold ratio', () => {
1063
+ // 95% sparsity / 93% threshold = ~1.02 -> capped at 1.0
1064
+ monitor.recordLayerActivity(
1065
+ 'layer1',
1066
+ makeInput({
1067
+ neuronCount: 1000,
1068
+ spikeCount: 50,
1069
+ })
1070
+ );
1071
+ monitor.takeSnapshot();
1072
+
1073
+ const entry = monitor.toQualityHistoryEntry(1);
1074
+ expect(entry.composite).toBe(1);
1075
+ });
1076
+
1077
+ it('should scale composite below 1 when sparsity is below threshold', () => {
1078
+ // 80% sparsity / 93% threshold = ~0.86
1079
+ monitor.recordLayerActivity(
1080
+ 'layer1',
1081
+ makeInput({
1082
+ neuronCount: 100,
1083
+ spikeCount: 20,
1084
+ })
1085
+ );
1086
+ monitor.takeSnapshot();
1087
+
1088
+ const entry = monitor.toQualityHistoryEntry(1);
1089
+ expect(entry.composite).toBeLessThan(1);
1090
+ expect(entry.composite).toBeGreaterThan(0.5);
1091
+ });
1092
+
1093
+ it('should assign correct grade A (>= 95% composite)', () => {
1094
+ monitor.recordLayerActivity(
1095
+ 'layer1',
1096
+ makeInput({
1097
+ neuronCount: 1000,
1098
+ spikeCount: 10,
1099
+ })
1100
+ ); // 99% sparsity
1101
+ monitor.takeSnapshot();
1102
+
1103
+ const entry = monitor.toQualityHistoryEntry(1);
1104
+ expect(entry.grade).toBe('A');
1105
+ });
1106
+
1107
+ it('should assign correct grade B (>= 85% composite)', () => {
1108
+ // Need sparsity such that sparsity/0.93 is between 0.85 and 0.95
1109
+ // 0.85 * 0.93 = 0.7905, 0.95 * 0.93 = 0.8835
1110
+ // Use sparsity = 0.84 -> 0.84/0.93 = 0.9032 -> grade B
1111
+ monitor.recordLayerActivity(
1112
+ 'layer1',
1113
+ makeInput({
1114
+ neuronCount: 100,
1115
+ spikeCount: 16,
1116
+ })
1117
+ ); // 84% sparsity
1118
+ monitor.takeSnapshot();
1119
+
1120
+ const entry = monitor.toQualityHistoryEntry(1);
1121
+ expect(entry.grade).toBe('B');
1122
+ });
1123
+
1124
+ it('should assign correct grade F (< 50% composite)', () => {
1125
+ // Need sparsity such that sparsity/0.93 < 0.5
1126
+ // 0.5 * 0.93 = 0.465
1127
+ // Use sparsity = 0.40 -> 0.40/0.93 = 0.43 -> grade F
1128
+ monitor.recordLayerActivity(
1129
+ 'layer1',
1130
+ makeInput({
1131
+ neuronCount: 100,
1132
+ spikeCount: 60,
1133
+ })
1134
+ ); // 40% sparsity
1135
+ monitor.takeSnapshot();
1136
+
1137
+ const entry = monitor.toQualityHistoryEntry(1);
1138
+ expect(entry.grade).toBe('F');
1139
+ });
1140
+
1141
+ it('should include sparsity metrics', () => {
1142
+ monitor.recordLayerActivity('layer1', makeInput({ spikeCount: 50 }));
1143
+ monitor.recordLayerActivity('layer2', makeInput({ spikeCount: 30 }));
1144
+ monitor.takeSnapshot();
1145
+
1146
+ const entry = monitor.toQualityHistoryEntry(1);
1147
+ const metrics = entry.sparsityMetrics;
1148
+
1149
+ expect(metrics.aggregateSparsity).toBeGreaterThan(0);
1150
+ expect(metrics.aggregateSpikeRate).toBeGreaterThan(0);
1151
+ expect(typeof metrics.energyEfficiencyRatio).toBe('number');
1152
+ expect(metrics.violationCount).toBe(0);
1153
+ expect(metrics.layerCount).toBe(2);
1154
+ expect(metrics.totalNeurons).toBe(2000);
1155
+ expect(metrics.inCompliance).toBe(true);
1156
+ });
1157
+
1158
+ it('should include violation count in sparsity metrics', () => {
1159
+ monitor.recordLayerActivity(
1160
+ 'bad',
1161
+ makeInput({
1162
+ neuronCount: 100,
1163
+ spikeCount: 20,
1164
+ })
1165
+ );
1166
+ monitor.takeSnapshot();
1167
+
1168
+ const entry = monitor.toQualityHistoryEntry(1);
1169
+ expect(entry.sparsityMetrics.violationCount).toBeGreaterThan(0);
1170
+ expect(entry.sparsityMetrics.inCompliance).toBe(false);
1171
+ });
1172
+
1173
+ it('should generate meaningful summary string', () => {
1174
+ monitor.recordLayerActivity('layer1', makeInput({ spikeCount: 50 }));
1175
+ monitor.takeSnapshot();
1176
+
1177
+ const entry = monitor.toQualityHistoryEntry(1);
1178
+ expect(entry.summary).toContain('SNN Sparsity Monitor');
1179
+ expect(entry.summary).toContain('Grade');
1180
+ expect(entry.summary).toContain('Sparsity');
1181
+ });
1182
+
1183
+ it('should work without any snapshots taken', () => {
1184
+ monitor.recordLayerActivity('layer1', makeInput({ spikeCount: 50 }));
1185
+
1186
+ const entry = monitor.toQualityHistoryEntry(1);
1187
+ expect(entry.cycle).toBe(1);
1188
+ expect(entry.focus).toBe('snn-sparsity');
1189
+ });
1190
+
1191
+ it('should increment cycle correctly', () => {
1192
+ monitor.recordLayerActivity('layer1', makeInput());
1193
+ monitor.takeSnapshot();
1194
+
1195
+ expect(monitor.toQualityHistoryEntry(5).cycle).toBe(5);
1196
+ expect(monitor.toQualityHistoryEntry(10).cycle).toBe(10);
1197
+ });
1198
+ });
1199
+
1200
+ // ---------------------------------------------------------------------------
1201
+ // Harvester Integration
1202
+ // ---------------------------------------------------------------------------
1203
+
1204
+ describe('getHarvesterMetrics()', () => {
1205
+ let monitor: SparsityMonitor;
1206
+
1207
+ beforeEach(() => {
1208
+ monitor = createTestMonitor();
1209
+ });
1210
+
1211
+ it('should return all expected metric keys', () => {
1212
+ monitor.recordLayerActivity('layer1', makeInput({ spikeCount: 50 }));
1213
+
1214
+ const metrics = monitor.getHarvesterMetrics();
1215
+ expect(metrics).toHaveProperty('snn_mean_sparsity');
1216
+ expect(metrics).toHaveProperty('snn_min_sparsity');
1217
+ expect(metrics).toHaveProperty('snn_max_sparsity');
1218
+ expect(metrics).toHaveProperty('snn_violation_count');
1219
+ expect(metrics).toHaveProperty('snn_energy_efficiency');
1220
+ expect(metrics).toHaveProperty('snn_in_compliance');
1221
+ expect(metrics).toHaveProperty('snn_tracked_layers');
1222
+ expect(metrics).toHaveProperty('snn_total_timesteps');
1223
+ });
1224
+
1225
+ it('should return numeric values for sparsity metrics', () => {
1226
+ monitor.recordLayerActivity('layer1', makeInput({ spikeCount: 50 }));
1227
+
1228
+ const metrics = monitor.getHarvesterMetrics();
1229
+ expect(typeof metrics.snn_mean_sparsity).toBe('number');
1230
+ expect(typeof metrics.snn_min_sparsity).toBe('number');
1231
+ expect(typeof metrics.snn_max_sparsity).toBe('number');
1232
+ });
1233
+
1234
+ it('should return boolean for compliance', () => {
1235
+ monitor.recordLayerActivity('layer1', makeInput({ spikeCount: 50 }));
1236
+
1237
+ const metrics = monitor.getHarvesterMetrics();
1238
+ expect(typeof metrics.snn_in_compliance).toBe('boolean');
1239
+ });
1240
+
1241
+ it('should reflect current state accurately', () => {
1242
+ monitor.recordLayerActivity(
1243
+ 'layer1',
1244
+ makeInput({
1245
+ neuronCount: 1000,
1246
+ spikeCount: 50,
1247
+ })
1248
+ );
1249
+
1250
+ const metrics = monitor.getHarvesterMetrics();
1251
+ expect(metrics.snn_mean_sparsity).toBeCloseTo(0.95, 4);
1252
+ expect(metrics.snn_tracked_layers).toBe(1);
1253
+ expect(metrics.snn_total_timesteps).toBe(1);
1254
+ expect(metrics.snn_in_compliance).toBe(true);
1255
+ });
1256
+
1257
+ it('should show violation count when violations exist', () => {
1258
+ monitor.recordLayerActivity(
1259
+ 'bad',
1260
+ makeInput({
1261
+ neuronCount: 100,
1262
+ spikeCount: 20,
1263
+ })
1264
+ );
1265
+
1266
+ const metrics = monitor.getHarvesterMetrics();
1267
+ expect(metrics.snn_violation_count).toBeGreaterThan(0);
1268
+ expect(metrics.snn_in_compliance).toBe(false);
1269
+ });
1270
+ });
1271
+
1272
+ // ---------------------------------------------------------------------------
1273
+ // Reset
1274
+ // ---------------------------------------------------------------------------
1275
+
1276
+ describe('reset()', () => {
1277
+ it('should clear all recorded data', () => {
1278
+ const monitor = createTestMonitor();
1279
+
1280
+ monitor.recordLayerActivity('layer1', makeInput({ spikeCount: 50, timestep: 0 }));
1281
+ monitor.recordLayerActivity('layer2', makeInput({ spikeCount: 20, timestep: 0 }));
1282
+ monitor.takeSnapshot();
1283
+
1284
+ monitor.reset();
1285
+
1286
+ expect(monitor.getCurrentLayerMetrics().size).toBe(0);
1287
+ expect(monitor.getSnapshots().length).toBe(0);
1288
+ expect(monitor.getViolationHistory().length).toBe(0);
1289
+ expect(monitor.getLayerHistory('layer1').length).toBe(0);
1290
+ expect(monitor.getLatestSnapshot()).toBeNull();
1291
+ });
1292
+
1293
+ it('should produce zeroed stats after reset', () => {
1294
+ const monitor = createTestMonitor();
1295
+
1296
+ monitor.recordLayerActivity('layer1', makeInput());
1297
+ monitor.takeSnapshot();
1298
+
1299
+ monitor.reset();
1300
+
1301
+ const stats = monitor.getStats();
1302
+ expect(stats.totalTimesteps).toBe(0);
1303
+ expect(stats.totalSnapshots).toBe(0);
1304
+ expect(stats.trackedLayers).toBe(0);
1305
+ expect(stats.meanSparsity).toBe(0);
1306
+ });
1307
+
1308
+ it('should allow recording new data after reset', () => {
1309
+ const monitor = createTestMonitor();
1310
+
1311
+ monitor.recordLayerActivity('layer1', makeInput({ spikeCount: 50 }));
1312
+ monitor.reset();
1313
+
1314
+ monitor.recordLayerActivity('new_layer', makeInput({ spikeCount: 10 }));
1315
+ expect(monitor.getCurrentLayerMetrics().size).toBe(1);
1316
+ expect(monitor.getCurrentLayerMetrics().has('new_layer')).toBe(true);
1317
+ });
1318
+ });
1319
+
1320
+ // ---------------------------------------------------------------------------
1321
+ // W.041 Threshold Compliance (93% Sparsity)
1322
+ // ---------------------------------------------------------------------------
1323
+
1324
+ describe('W.041 compliance (93% sparsity threshold)', () => {
1325
+ it('should use 93% as default threshold', () => {
1326
+ const monitor = new SparsityMonitor();
1327
+ expect(monitor.getConfig().sparsityThreshold).toBe(0.93);
1328
+ });
1329
+
1330
+ it('should pass layers with exactly 93% sparsity', () => {
1331
+ const monitor = createTestMonitor();
1332
+ // 93% sparsity = 7% spike rate = 70 spikes out of 1000
1333
+ monitor.recordLayerActivity(
1334
+ 'layer1',
1335
+ makeInput({
1336
+ neuronCount: 1000,
1337
+ spikeCount: 70,
1338
+ })
1339
+ );
1340
+
1341
+ const violations = monitor.getActiveViolations();
1342
+ expect(violations.length).toBe(0);
1343
+ });
1344
+
1345
+ it('should fail layers with 92.9% sparsity', () => {
1346
+ const monitor = createTestMonitor();
1347
+ // 92.9% sparsity = 7.1% spike rate = 71 spikes out of 1000
1348
+ monitor.recordLayerActivity(
1349
+ 'layer1',
1350
+ makeInput({
1351
+ neuronCount: 1000,
1352
+ spikeCount: 71,
1353
+ })
1354
+ );
1355
+
1356
+ const violations = monitor.getActiveViolations();
1357
+ expect(violations.length).toBe(1);
1358
+ });
1359
+
1360
+ it('should report compliance status accurately in stats', () => {
1361
+ const monitor = createTestMonitor();
1362
+
1363
+ // Start compliant
1364
+ monitor.recordLayerActivity(
1365
+ 'layer1',
1366
+ makeInput({
1367
+ neuronCount: 1000,
1368
+ spikeCount: 50,
1369
+ })
1370
+ );
1371
+ expect(monitor.getStats().inCompliance).toBe(true);
1372
+
1373
+ // Become non-compliant
1374
+ monitor.recordLayerActivity(
1375
+ 'layer2',
1376
+ makeInput({
1377
+ neuronCount: 100,
1378
+ spikeCount: 20,
1379
+ })
1380
+ );
1381
+ expect(monitor.getStats().inCompliance).toBe(false);
1382
+ });
1383
+
1384
+ it('should allow custom threshold override', () => {
1385
+ const monitor = createTestMonitor({ sparsityThreshold: 0.95 });
1386
+
1387
+ // 94% sparsity would be fine at 93% but fails at 95%
1388
+ monitor.recordLayerActivity(
1389
+ 'layer1',
1390
+ makeInput({
1391
+ neuronCount: 1000,
1392
+ spikeCount: 60,
1393
+ })
1394
+ );
1395
+
1396
+ const violations = monitor.getActiveViolations();
1397
+ expect(violations.length).toBe(1);
1398
+ });
1399
+ });
1400
+
1401
+ // ---------------------------------------------------------------------------
1402
+ // Edge Cases
1403
+ // ---------------------------------------------------------------------------
1404
+
1405
+ describe('edge cases', () => {
1406
+ it('should handle single neuron layer', () => {
1407
+ const monitor = createTestMonitor();
1408
+ const metrics = monitor.recordLayerActivity(
1409
+ 'tiny',
1410
+ makeInput({
1411
+ neuronCount: 1,
1412
+ spikeCount: 0,
1413
+ })
1414
+ );
1415
+ expect(metrics.activationSparsity).toBe(1);
1416
+ });
1417
+
1418
+ it('should handle very large neuron counts', () => {
1419
+ const monitor = createTestMonitor();
1420
+ const metrics = monitor.recordLayerActivity(
1421
+ 'huge',
1422
+ makeInput({
1423
+ neuronCount: 1_000_000,
1424
+ spikeCount: 50_000,
1425
+ })
1426
+ );
1427
+ expect(metrics.spikeRate).toBeCloseTo(0.05, 4);
1428
+ expect(metrics.activationSparsity).toBeCloseTo(0.95, 4);
1429
+ });
1430
+
1431
+ it('should handle rapid successive recordings', () => {
1432
+ const monitor = createTestMonitor();
1433
+ for (let t = 0; t < 1000; t++) {
1434
+ monitor.recordLayerActivity(
1435
+ 'layer1',
1436
+ makeInput({
1437
+ spikeCount: Math.floor(Math.random() * 100),
1438
+ timestep: t,
1439
+ })
1440
+ );
1441
+ }
1442
+ const stats = monitor.getStats();
1443
+ expect(stats.totalTimesteps).toBe(1000);
1444
+ });
1445
+
1446
+ it('should handle many layers simultaneously', () => {
1447
+ const monitor = createTestMonitor();
1448
+ for (let i = 0; i < 100; i++) {
1449
+ monitor.recordLayerActivity(
1450
+ `layer_${i}`,
1451
+ makeInput({
1452
+ spikeCount: i,
1453
+ })
1454
+ );
1455
+ }
1456
+ expect(monitor.getCurrentLayerMetrics().size).toBe(100);
1457
+ });
1458
+
1459
+ it('should handle snapshot with all layers compliant', () => {
1460
+ const monitor = createTestMonitor();
1461
+ monitor.recordLayerActivity('a', makeInput({ spikeCount: 10 }));
1462
+ monitor.recordLayerActivity('b', makeInput({ spikeCount: 20 }));
1463
+
1464
+ const snapshot = monitor.takeSnapshot()!;
1465
+ expect(snapshot.violations.length).toBe(0);
1466
+ });
1467
+
1468
+ it('should handle snapshot with all layers violating', () => {
1469
+ const monitor = createTestMonitor();
1470
+ monitor.recordLayerActivity(
1471
+ 'a',
1472
+ makeInput({
1473
+ neuronCount: 100,
1474
+ spikeCount: 50,
1475
+ })
1476
+ );
1477
+ monitor.recordLayerActivity(
1478
+ 'b',
1479
+ makeInput({
1480
+ neuronCount: 100,
1481
+ spikeCount: 40,
1482
+ })
1483
+ );
1484
+
1485
+ const snapshot = monitor.takeSnapshot()!;
1486
+ expect(snapshot.violations.length).toBe(2);
1487
+ });
1488
+ });
1489
+
1490
+ // ---------------------------------------------------------------------------
1491
+ // Integration: Full Pipeline
1492
+ // ---------------------------------------------------------------------------
1493
+
1494
+ describe('full pipeline integration', () => {
1495
+ it('should run a complete monitoring cycle: record -> snapshot -> stats -> history', () => {
1496
+ const monitor = createTestMonitor();
1497
+
1498
+ // Simulate 10 timesteps across 3 layers
1499
+ for (let t = 0; t < 10; t++) {
1500
+ monitor.recordLayerActivity(
1501
+ 'lif_input',
1502
+ makeInput({
1503
+ neuronCount: 784,
1504
+ spikeCount: Math.floor(784 * 0.03), // ~3% spike rate (97% sparsity)
1505
+ timestep: t,
1506
+ })
1507
+ );
1508
+ monitor.recordLayerActivity(
1509
+ 'lif_hidden',
1510
+ makeInput({
1511
+ neuronCount: 256,
1512
+ spikeCount: Math.floor(256 * 0.05), // ~5% spike rate (95% sparsity)
1513
+ timestep: t,
1514
+ })
1515
+ );
1516
+ monitor.recordLayerActivity(
1517
+ 'lif_output',
1518
+ makeInput({
1519
+ neuronCount: 10,
1520
+ spikeCount: Math.floor(10 * 0.02), // ~2% spike rate (98% sparsity)
1521
+ timestep: t,
1522
+ })
1523
+ );
1524
+
1525
+ monitor.takeSnapshot();
1526
+ }
1527
+
1528
+ // Verify stats
1529
+ const stats = monitor.getStats();
1530
+ expect(stats.totalTimesteps).toBe(30); // 3 layers * 10 timesteps
1531
+ expect(stats.totalSnapshots).toBe(10);
1532
+ expect(stats.trackedLayers).toBe(3);
1533
+ expect(stats.meanSparsity).toBeGreaterThan(0.9);
1534
+ expect(stats.inCompliance).toBe(true);
1535
+ expect(stats.totalViolations).toBe(0);
1536
+ expect(stats.meanEnergyEfficiency).toBeGreaterThan(0.9);
1537
+
1538
+ // Verify quality history entry
1539
+ const entry = monitor.toQualityHistoryEntry(1);
1540
+ expect(entry.composite).toBe(1); // Above threshold
1541
+ expect(entry.grade).toBe('A');
1542
+ expect(entry.focus).toBe('snn-sparsity');
1543
+ expect(entry.sparsityMetrics.layerCount).toBe(3);
1544
+ expect(entry.sparsityMetrics.inCompliance).toBe(true);
1545
+
1546
+ // Verify harvester metrics
1547
+ const harvesterMetrics = monitor.getHarvesterMetrics();
1548
+ expect(harvesterMetrics.snn_mean_sparsity).toBeGreaterThan(0.9);
1549
+ expect(harvesterMetrics.snn_in_compliance).toBe(true);
1550
+ });
1551
+
1552
+ it('should detect violations in a mixed compliance scenario', () => {
1553
+ const monitor = createTestMonitor();
1554
+
1555
+ // Good layer (96% sparsity)
1556
+ monitor.recordLayerActivity(
1557
+ 'healthy',
1558
+ makeInput({
1559
+ neuronCount: 1000,
1560
+ spikeCount: 40,
1561
+ })
1562
+ );
1563
+
1564
+ // Warning layer (90% sparsity) - below 93% threshold but above 85% critical
1565
+ monitor.recordLayerActivity(
1566
+ 'struggling',
1567
+ makeInput({
1568
+ neuronCount: 100,
1569
+ spikeCount: 10,
1570
+ })
1571
+ );
1572
+
1573
+ // Critical layer (50% sparsity) - below 85% critical threshold
1574
+ monitor.recordLayerActivity(
1575
+ 'failing',
1576
+ makeInput({
1577
+ neuronCount: 100,
1578
+ spikeCount: 50,
1579
+ })
1580
+ );
1581
+
1582
+ const snapshot = monitor.takeSnapshot()!;
1583
+ const stats = monitor.getStats();
1584
+ const entry = monitor.toQualityHistoryEntry(1);
1585
+
1586
+ // Should have 2 violations
1587
+ expect(snapshot.violations.length).toBe(2);
1588
+ expect(stats.violationsBySeverity.warning).toBe(1);
1589
+ expect(stats.violationsBySeverity.critical).toBe(1);
1590
+ expect(stats.inCompliance).toBe(false);
1591
+ expect(entry.sparsityMetrics.inCompliance).toBe(false);
1592
+ expect(entry.sparsityMetrics.violationCount).toBe(2);
1593
+ });
1594
+
1595
+ it('should produce quality-history.json compatible output', () => {
1596
+ const monitor = createTestMonitor();
1597
+
1598
+ monitor.recordLayerActivity('layer1', makeInput({ spikeCount: 50 }));
1599
+ monitor.takeSnapshot();
1600
+
1601
+ const entry = monitor.toQualityHistoryEntry(1);
1602
+
1603
+ // Verify the entry could be serialized to JSON and parsed back
1604
+ const json = JSON.stringify(entry);
1605
+ const parsed = JSON.parse(json) as SparsityQualityHistoryEntry;
1606
+
1607
+ expect(parsed.timestamp).toBeDefined();
1608
+ expect(parsed.cycle).toBe(1);
1609
+ expect(parsed.composite).toBeDefined();
1610
+ expect(parsed.grade).toBeDefined();
1611
+ expect(parsed.focus).toBe('snn-sparsity');
1612
+ expect(parsed.summary).toBeDefined();
1613
+ expect(parsed.sparsityMetrics).toBeDefined();
1614
+ expect(parsed.sparsityMetrics.aggregateSparsity).toBeDefined();
1615
+ expect(parsed.sparsityMetrics.aggregateSpikeRate).toBeDefined();
1616
+ expect(parsed.sparsityMetrics.energyEfficiencyRatio).toBeDefined();
1617
+ expect(parsed.sparsityMetrics.violationCount).toBeDefined();
1618
+ expect(parsed.sparsityMetrics.layerCount).toBeDefined();
1619
+ expect(parsed.sparsityMetrics.totalNeurons).toBeDefined();
1620
+ expect(parsed.sparsityMetrics.inCompliance).toBeDefined();
1621
+ });
1622
+ });
1623
+ });