@holoscript/framework 6.0.3

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 (329) hide show
  1. package/ALL-test-results.json +1 -0
  2. package/CHANGELOG.md +8 -0
  3. package/LICENSE +21 -0
  4. package/ROADMAP.md +175 -0
  5. package/dist/AgentManifest-CB4xM-Ma.d.cts +704 -0
  6. package/dist/AgentManifest-CB4xM-Ma.d.ts +704 -0
  7. package/dist/BehaviorTree-BrBFECv5.d.cts +103 -0
  8. package/dist/BehaviorTree-BrBFECv5.d.ts +103 -0
  9. package/dist/InvisibleWallet-BB6tFvRA.d.cts +1732 -0
  10. package/dist/InvisibleWallet-rtRrBOA8.d.ts +1732 -0
  11. package/dist/OrchestratorAgent-BvWgf9uw.d.cts +798 -0
  12. package/dist/OrchestratorAgent-Q_CbVTmO.d.ts +798 -0
  13. package/dist/agents/index.cjs +4790 -0
  14. package/dist/agents/index.d.cts +1788 -0
  15. package/dist/agents/index.d.ts +1788 -0
  16. package/dist/agents/index.js +4695 -0
  17. package/dist/ai/index.cjs +5347 -0
  18. package/dist/ai/index.d.cts +1753 -0
  19. package/dist/ai/index.d.ts +1753 -0
  20. package/dist/ai/index.js +5244 -0
  21. package/dist/behavior.cjs +449 -0
  22. package/dist/behavior.d.cts +130 -0
  23. package/dist/behavior.d.ts +130 -0
  24. package/dist/behavior.js +407 -0
  25. package/dist/economy/index.cjs +3659 -0
  26. package/dist/economy/index.d.cts +747 -0
  27. package/dist/economy/index.d.ts +747 -0
  28. package/dist/economy/index.js +3617 -0
  29. package/dist/implementations-D9T3un9D.d.cts +236 -0
  30. package/dist/implementations-D9T3un9D.d.ts +236 -0
  31. package/dist/index.cjs +24550 -0
  32. package/dist/index.d.cts +1729 -0
  33. package/dist/index.d.ts +1729 -0
  34. package/dist/index.js +24277 -0
  35. package/dist/learning/index.cjs +219 -0
  36. package/dist/learning/index.d.cts +104 -0
  37. package/dist/learning/index.d.ts +104 -0
  38. package/dist/learning/index.js +189 -0
  39. package/dist/negotiation/index.cjs +970 -0
  40. package/dist/negotiation/index.d.cts +610 -0
  41. package/dist/negotiation/index.d.ts +610 -0
  42. package/dist/negotiation/index.js +931 -0
  43. package/dist/skills/index.cjs +1118 -0
  44. package/dist/skills/index.d.cts +289 -0
  45. package/dist/skills/index.d.ts +289 -0
  46. package/dist/skills/index.js +1079 -0
  47. package/dist/swarm/index.cjs +5268 -0
  48. package/dist/swarm/index.d.cts +2433 -0
  49. package/dist/swarm/index.d.ts +2433 -0
  50. package/dist/swarm/index.js +5221 -0
  51. package/dist/training/index.cjs +2745 -0
  52. package/dist/training/index.d.cts +1734 -0
  53. package/dist/training/index.d.ts +1734 -0
  54. package/dist/training/index.js +2687 -0
  55. package/extract-failures.js +10 -0
  56. package/package.json +82 -0
  57. package/src/__tests__/bounty-marketplace.test.ts +374 -0
  58. package/src/__tests__/delegation.test.ts +144 -0
  59. package/src/__tests__/distributed-claimer.test.ts +147 -0
  60. package/src/__tests__/done-log-audit.test.ts +342 -0
  61. package/src/__tests__/framework.test.ts +865 -0
  62. package/src/__tests__/goal-synthesizer.test.ts +236 -0
  63. package/src/__tests__/presence.test.ts +223 -0
  64. package/src/__tests__/protocol-agent.test.ts +254 -0
  65. package/src/__tests__/revenue-splitter.test.ts +114 -0
  66. package/src/__tests__/scenario-driven-todo.test.ts +197 -0
  67. package/src/__tests__/self-improve.test.ts +349 -0
  68. package/src/__tests__/service-lifecycle.test.ts +237 -0
  69. package/src/__tests__/skill-router.test.ts +121 -0
  70. package/src/agents/AgentManifest.ts +493 -0
  71. package/src/agents/AgentRegistry.ts +475 -0
  72. package/src/agents/AgentTypes.ts +585 -0
  73. package/src/agents/AgentWalletRegistry.ts +83 -0
  74. package/src/agents/AuthenticatedCRDT.ts +388 -0
  75. package/src/agents/CapabilityMatcher.ts +453 -0
  76. package/src/agents/CrossRealityHandoff.ts +305 -0
  77. package/src/agents/CulturalMemory.ts +454 -0
  78. package/src/agents/FederatedRegistryAdapter.ts +429 -0
  79. package/src/agents/NormEngine.ts +450 -0
  80. package/src/agents/OrchestratorAgent.ts +414 -0
  81. package/src/agents/SkillWorkflowEngine.ts +472 -0
  82. package/src/agents/TaskDelegationService.ts +551 -0
  83. package/src/agents/__tests__/AgentManifest.prod.test.ts +134 -0
  84. package/src/agents/__tests__/AgentManifest.test.ts +182 -0
  85. package/src/agents/__tests__/AgentModule.test.ts +864 -0
  86. package/src/agents/__tests__/AgentRegistry.prod.test.ts +125 -0
  87. package/src/agents/__tests__/AgentRegistry.test.ts +148 -0
  88. package/src/agents/__tests__/AgentTypes.test.ts +534 -0
  89. package/src/agents/__tests__/AgentWalletRegistry.test.ts +152 -0
  90. package/src/agents/__tests__/AuthenticatedCRDT.test.ts +558 -0
  91. package/src/agents/__tests__/CapabilityMatcher.prod.test.ts +117 -0
  92. package/src/agents/__tests__/CapabilityMatcher.test.ts +178 -0
  93. package/src/agents/__tests__/CrossRealityHandoff.test.ts +402 -0
  94. package/src/agents/__tests__/CulturalMemory.test.ts +200 -0
  95. package/src/agents/__tests__/FederatedRegistryAdapter.test.ts +409 -0
  96. package/src/agents/__tests__/NormEngine.test.ts +276 -0
  97. package/src/agents/__tests__/OrchestratorAgent.test.ts +182 -0
  98. package/src/agents/__tests__/SkillWorkflowEngine.test.ts +357 -0
  99. package/src/agents/__tests__/TaskDelegationService.test.ts +446 -0
  100. package/src/agents/index.ts +107 -0
  101. package/src/agents/spatial-comms/Layer1RealTime.ts +621 -0
  102. package/src/agents/spatial-comms/Layer2A2A.ts +661 -0
  103. package/src/agents/spatial-comms/Layer3MCP.ts +651 -0
  104. package/src/agents/spatial-comms/ProtocolTypes.ts +543 -0
  105. package/src/agents/spatial-comms/SpatialCommClient.ts +483 -0
  106. package/src/agents/spatial-comms/__tests__/performance-benchmark.test.ts +465 -0
  107. package/src/agents/spatial-comms/examples/multi-agent-world-creation.ts +409 -0
  108. package/src/agents/spatial-comms/index.ts +66 -0
  109. package/src/ai/AIAdapter.ts +313 -0
  110. package/src/ai/AICopilot.ts +331 -0
  111. package/src/ai/AIOutputValidator.ts +203 -0
  112. package/src/ai/BTNodes.ts +239 -0
  113. package/src/ai/BehaviorSelector.ts +135 -0
  114. package/src/ai/BehaviorTree.ts +153 -0
  115. package/src/ai/Blackboard.ts +165 -0
  116. package/src/ai/GenerationAnalytics.ts +461 -0
  117. package/src/ai/GenerationCache.ts +265 -0
  118. package/src/ai/GoalPlanner.ts +165 -0
  119. package/src/ai/HoloScriptGenerator.ts +580 -0
  120. package/src/ai/InfluenceMap.ts +180 -0
  121. package/src/ai/NavMesh.ts +168 -0
  122. package/src/ai/PerceptionSystem.ts +178 -0
  123. package/src/ai/PromptTemplates.ts +453 -0
  124. package/src/ai/SemanticSearchService.ts +80 -0
  125. package/src/ai/StateMachine.ts +196 -0
  126. package/src/ai/SteeringBehavior.ts +150 -0
  127. package/src/ai/SteeringBehaviors.ts +244 -0
  128. package/src/ai/TrainingDataGenerator.ts +1082 -0
  129. package/src/ai/UtilityAI.ts +145 -0
  130. package/src/ai/__tests__/AIAdapter.prod.test.ts +259 -0
  131. package/src/ai/__tests__/AIAdapter.test.ts +109 -0
  132. package/src/ai/__tests__/AICopilot.prod.test.ts +341 -0
  133. package/src/ai/__tests__/AICopilot.test.ts +178 -0
  134. package/src/ai/__tests__/AIOutputValidator.prod.test.ts +226 -0
  135. package/src/ai/__tests__/AIOutputValidator.test.ts +138 -0
  136. package/src/ai/__tests__/BTNodes.prod.test.ts +391 -0
  137. package/src/ai/__tests__/BTNodes.test.ts +263 -0
  138. package/src/ai/__tests__/BehaviorSelector.prod.test.ts +129 -0
  139. package/src/ai/__tests__/BehaviorSelector.test.ts +132 -0
  140. package/src/ai/__tests__/BehaviorTree.prod.test.ts +266 -0
  141. package/src/ai/__tests__/BehaviorTree.test.ts +216 -0
  142. package/src/ai/__tests__/Blackboard.prod.test.ts +339 -0
  143. package/src/ai/__tests__/Blackboard.test.ts +183 -0
  144. package/src/ai/__tests__/GenerationAnalytics.prod.test.ts +141 -0
  145. package/src/ai/__tests__/GenerationAnalytics.test.ts +165 -0
  146. package/src/ai/__tests__/GenerationCache.prod.test.ts +144 -0
  147. package/src/ai/__tests__/GenerationCache.test.ts +171 -0
  148. package/src/ai/__tests__/GoalPlanner.prod.test.ts +189 -0
  149. package/src/ai/__tests__/GoalPlanner.test.ts +137 -0
  150. package/src/ai/__tests__/GoalPlannerDepth.prod.test.ts +217 -0
  151. package/src/ai/__tests__/HoloScriptGenerator.test.ts +125 -0
  152. package/src/ai/__tests__/InfluenceMap.prod.test.ts +146 -0
  153. package/src/ai/__tests__/InfluenceMap.test.ts +149 -0
  154. package/src/ai/__tests__/NavMesh.prod.test.ts +141 -0
  155. package/src/ai/__tests__/NavMesh.test.ts +159 -0
  156. package/src/ai/__tests__/PerceptionSystem.prod.test.ts +135 -0
  157. package/src/ai/__tests__/PerceptionSystem.test.ts +250 -0
  158. package/src/ai/__tests__/PromptTemplates.prod.test.ts +313 -0
  159. package/src/ai/__tests__/PromptTemplates.test.ts +146 -0
  160. package/src/ai/__tests__/SemanticSearch.test.ts +37 -0
  161. package/src/ai/__tests__/StateMachine.prod.test.ts +162 -0
  162. package/src/ai/__tests__/StateMachine.test.ts +163 -0
  163. package/src/ai/__tests__/SteeringBehavior.prod.test.ts +251 -0
  164. package/src/ai/__tests__/SteeringBehavior.test.ts +135 -0
  165. package/src/ai/__tests__/SteeringBehaviors.prod.test.ts +133 -0
  166. package/src/ai/__tests__/SteeringBehaviors.test.ts +151 -0
  167. package/src/ai/__tests__/TrainingDataGenerator.prod.test.ts +286 -0
  168. package/src/ai/__tests__/TrainingDataGenerator.test.ts +286 -0
  169. package/src/ai/__tests__/UtilityAI.prod.test.ts +207 -0
  170. package/src/ai/__tests__/UtilityAI.test.ts +155 -0
  171. package/src/ai/__tests__/adapters.prod.test.ts +263 -0
  172. package/src/ai/__tests__/adapters.test.ts +320 -0
  173. package/src/ai/adapters.ts +1585 -0
  174. package/src/ai/index.ts +130 -0
  175. package/src/behavior/BehaviorPresets.ts +140 -0
  176. package/src/behavior/BehaviorTree.ts +236 -0
  177. package/src/behavior/StateMachine.ts +176 -0
  178. package/src/behavior/StateTrait.ts +67 -0
  179. package/src/behavior/index.ts +8 -0
  180. package/src/behavior.ts +8 -0
  181. package/src/board/audit.ts +284 -0
  182. package/src/board/board-ops.ts +336 -0
  183. package/src/board/board-types.ts +302 -0
  184. package/src/board/index.ts +69 -0
  185. package/src/define-agent.ts +46 -0
  186. package/src/define-team.ts +33 -0
  187. package/src/delegation.ts +265 -0
  188. package/src/distributed-claimer.ts +228 -0
  189. package/src/economy/AgentBudgetEnforcer.ts +464 -0
  190. package/src/economy/BountyManager.ts +185 -0
  191. package/src/economy/CreatorRevenueAggregator.ts +460 -0
  192. package/src/economy/InvisibleWallet.ts +82 -0
  193. package/src/economy/KnowledgeMarketplace.ts +193 -0
  194. package/src/economy/PaymentWebhookService.ts +512 -0
  195. package/src/economy/RevenueSplitter.ts +156 -0
  196. package/src/economy/SubscriptionManager.ts +546 -0
  197. package/src/economy/UnifiedBudgetOptimizer.ts +635 -0
  198. package/src/economy/UsageMeter.ts +440 -0
  199. package/src/economy/_core-stubs.ts +219 -0
  200. package/src/economy/index.ts +100 -0
  201. package/src/economy/x402-facilitator.ts +1978 -0
  202. package/src/index.ts +348 -0
  203. package/src/knowledge/__tests__/knowledge-consolidator.test.ts +444 -0
  204. package/src/knowledge/__tests__/knowledge-store-vector.test.ts +291 -0
  205. package/src/knowledge/brain.ts +167 -0
  206. package/src/knowledge/consolidation.ts +581 -0
  207. package/src/knowledge/knowledge-consolidator.ts +510 -0
  208. package/src/knowledge/knowledge-store.ts +616 -0
  209. package/src/learning/MemoryConsolidator.ts +102 -0
  210. package/src/learning/MemoryScorer.ts +69 -0
  211. package/src/learning/ProceduralCompiler.ts +45 -0
  212. package/src/learning/SemanticClusterer.ts +66 -0
  213. package/src/learning/index.ts +8 -0
  214. package/src/llm/llm-adapter.ts +159 -0
  215. package/src/mesh/index.ts +309 -0
  216. package/src/negotiation/NegotiationProtocol.ts +694 -0
  217. package/src/negotiation/NegotiationTypes.ts +473 -0
  218. package/src/negotiation/VotingMechanisms.ts +691 -0
  219. package/src/negotiation/index.ts +49 -0
  220. package/src/protocol/goal-synthesizer.ts +317 -0
  221. package/src/protocol/implementations.ts +474 -0
  222. package/src/protocol/micro-phase-decomposer.ts +299 -0
  223. package/src/protocol/micro-step-decomposer.test.ts +306 -0
  224. package/src/protocol-agent.test.ts +353 -0
  225. package/src/protocol-agent.ts +670 -0
  226. package/src/self-improve/absorb-scanner.ts +252 -0
  227. package/src/self-improve/evolution-engine.ts +149 -0
  228. package/src/self-improve/framework-absorber.ts +214 -0
  229. package/src/self-improve/index.ts +50 -0
  230. package/src/self-improve/prompt-optimizer.ts +212 -0
  231. package/src/self-improve/test-generator.ts +175 -0
  232. package/src/skill-router.ts +186 -0
  233. package/src/skills/index.ts +5 -0
  234. package/src/skills/skill-md-bridge.ts +1699 -0
  235. package/src/swarm/ACOEngine.ts +261 -0
  236. package/src/swarm/CollectiveIntelligence.ts +383 -0
  237. package/src/swarm/ContributionSynthesizer.ts +481 -0
  238. package/src/swarm/LeaderElection.ts +393 -0
  239. package/src/swarm/PSOEngine.ts +206 -0
  240. package/src/swarm/QuorumPolicy.ts +173 -0
  241. package/src/swarm/SwarmCoordinator.ts +335 -0
  242. package/src/swarm/SwarmManager.ts +442 -0
  243. package/src/swarm/SwarmMembership.ts +456 -0
  244. package/src/swarm/VotingRound.ts +255 -0
  245. package/src/swarm/__tests__/ACOEngine.prod.test.ts +164 -0
  246. package/src/swarm/__tests__/ACOEngine.test.ts +117 -0
  247. package/src/swarm/__tests__/CollectiveIntelligence.prod.test.ts +296 -0
  248. package/src/swarm/__tests__/CollectiveIntelligence.test.ts +457 -0
  249. package/src/swarm/__tests__/ContributionSynthesizer.prod.test.ts +269 -0
  250. package/src/swarm/__tests__/ContributionSynthesizer.test.ts +254 -0
  251. package/src/swarm/__tests__/LeaderElection.prod.test.ts +196 -0
  252. package/src/swarm/__tests__/LeaderElection.test.ts +151 -0
  253. package/src/swarm/__tests__/PSOEngine.prod.test.ts +162 -0
  254. package/src/swarm/__tests__/PSOEngine.test.ts +106 -0
  255. package/src/swarm/__tests__/QuorumPolicy.prod.test.ts +216 -0
  256. package/src/swarm/__tests__/QuorumPolicy.test.ts +177 -0
  257. package/src/swarm/__tests__/SwarmCoordinator.prod.test.ts +186 -0
  258. package/src/swarm/__tests__/SwarmCoordinator.test.ts +167 -0
  259. package/src/swarm/__tests__/SwarmManager.prod.test.ts +308 -0
  260. package/src/swarm/__tests__/SwarmManager.test.ts +373 -0
  261. package/src/swarm/__tests__/SwarmMembership.prod.test.ts +273 -0
  262. package/src/swarm/__tests__/SwarmMembership.test.ts +264 -0
  263. package/src/swarm/__tests__/VotingRound.prod.test.ts +233 -0
  264. package/src/swarm/__tests__/VotingRound.test.ts +174 -0
  265. package/src/swarm/analytics/SwarmInspector.ts +476 -0
  266. package/src/swarm/analytics/SwarmMetrics.ts +449 -0
  267. package/src/swarm/analytics/__tests__/SwarmInspector.prod.test.ts +366 -0
  268. package/src/swarm/analytics/__tests__/SwarmInspector.test.ts +454 -0
  269. package/src/swarm/analytics/__tests__/SwarmMetrics.prod.test.ts +254 -0
  270. package/src/swarm/analytics/__tests__/SwarmMetrics.test.ts +370 -0
  271. package/src/swarm/analytics/index.ts +7 -0
  272. package/src/swarm/index.ts +69 -0
  273. package/src/swarm/messaging/BroadcastChannel.ts +509 -0
  274. package/src/swarm/messaging/GossipProtocol.ts +565 -0
  275. package/src/swarm/messaging/SwarmEventBus.ts +443 -0
  276. package/src/swarm/messaging/__tests__/BroadcastChannel.prod.test.ts +331 -0
  277. package/src/swarm/messaging/__tests__/BroadcastChannel.test.ts +333 -0
  278. package/src/swarm/messaging/__tests__/GossipProtocol.prod.test.ts +356 -0
  279. package/src/swarm/messaging/__tests__/GossipProtocol.test.ts +437 -0
  280. package/src/swarm/messaging/__tests__/SwarmEventBus.prod.test.ts +191 -0
  281. package/src/swarm/messaging/__tests__/SwarmEventBus.test.ts +247 -0
  282. package/src/swarm/messaging/index.ts +8 -0
  283. package/src/swarm/spatial/FlockingBehavior.ts +462 -0
  284. package/src/swarm/spatial/FormationController.ts +500 -0
  285. package/src/swarm/spatial/Vector3.ts +170 -0
  286. package/src/swarm/spatial/ZoneClaiming.ts +509 -0
  287. package/src/swarm/spatial/__tests__/FlockingBehavior.prod.test.ts +239 -0
  288. package/src/swarm/spatial/__tests__/FlockingBehavior.test.ts +298 -0
  289. package/src/swarm/spatial/__tests__/FormationController.prod.test.ts +240 -0
  290. package/src/swarm/spatial/__tests__/FormationController.test.ts +297 -0
  291. package/src/swarm/spatial/__tests__/Vector3.prod.test.ts +283 -0
  292. package/src/swarm/spatial/__tests__/Vector3.test.ts +224 -0
  293. package/src/swarm/spatial/__tests__/ZoneClaiming.prod.test.ts +246 -0
  294. package/src/swarm/spatial/__tests__/ZoneClaiming.test.ts +374 -0
  295. package/src/swarm/spatial/index.ts +28 -0
  296. package/src/team.ts +1245 -0
  297. package/src/training/LRScheduler.ts +377 -0
  298. package/src/training/QualityScoringPipeline.ts +139 -0
  299. package/src/training/SoftDedup.ts +461 -0
  300. package/src/training/SparsityMonitor.ts +685 -0
  301. package/src/training/SparsityMonitorTypes.ts +209 -0
  302. package/src/training/SpatialTrainingDataGenerator.ts +1526 -0
  303. package/src/training/SpatialTrainingDataTypes.ts +216 -0
  304. package/src/training/TrainingPipelineConfig.ts +215 -0
  305. package/src/training/constants.ts +94 -0
  306. package/src/training/index.ts +138 -0
  307. package/src/training/schema.ts +147 -0
  308. package/src/training/scripts/generate-novel-use-cases-dataset.ts +272 -0
  309. package/src/training/scripts/generate-spatial-dataset.ts +521 -0
  310. package/src/training/training/data/novel-use-cases.jsonl +153 -0
  311. package/src/training/training/data/spatial-reasoning-10k.jsonl +9354 -0
  312. package/src/training/trainingmonkey/TrainingMonkeyIntegration.ts +477 -0
  313. package/src/training/trainingmonkey/TrainingMonkeyTypes.ts +230 -0
  314. package/src/training/trainingmonkey/index.ts +26 -0
  315. package/src/training/trait-mappings.ts +157 -0
  316. package/src/types/core-stubs.d.ts +113 -0
  317. package/src/types.ts +304 -0
  318. package/test-output.txt +0 -0
  319. package/test-result.json +1 -0
  320. package/tsc-errors.txt +4 -0
  321. package/tsc_output.txt +0 -0
  322. package/tsconfig.json +14 -0
  323. package/tsup-learning-esm.config.ts +12 -0
  324. package/tsup.config.ts +21 -0
  325. package/typescript-errors-2.txt +0 -0
  326. package/typescript-errors.txt +22 -0
  327. package/vitest-log-utf8.txt +268 -0
  328. package/vitest-log.txt +0 -0
  329. package/vitest.config.ts +8 -0
@@ -0,0 +1,685 @@
1
+ /**
2
+ * SparsityMonitor.ts
3
+ *
4
+ * Monitors SNN (Spiking Neural Network) sparsity during simulation,
5
+ * tracking spike rates and activation sparsity across layers, calculating
6
+ * energy efficiency metrics, and detecting sparsity regime violations.
7
+ *
8
+ * Integrates with the self-improvement pipeline via:
9
+ * - SelfImproveHarvester: provides metrics for continuous quality monitoring
10
+ * - quality-history.json: outputs compatible entries for historical tracking
11
+ *
12
+ * Key threshold (W.041): SNN layers must maintain >= 93% activation sparsity.
13
+ *
14
+ * Usage:
15
+ * ```ts
16
+ * const monitor = new SparsityMonitor({ sparsityThreshold: 0.93 });
17
+ *
18
+ * // Record layer activity during simulation
19
+ * monitor.recordLayerActivity('lif_hidden_1', {
20
+ * neuronCount: 1000,
21
+ * spikeCount: 50,
22
+ * timestep: 0,
23
+ * });
24
+ *
25
+ * // Take a snapshot
26
+ * const snapshot = monitor.takeSnapshot();
27
+ *
28
+ * // Check for violations
29
+ * const violations = monitor.getActiveViolations();
30
+ *
31
+ * // Get quality-history.json compatible entry
32
+ * const entry = monitor.toQualityHistoryEntry(1);
33
+ * ```
34
+ *
35
+ * @module training/SparsityMonitor
36
+ */
37
+
38
+ import type {
39
+ SNNLayerMetrics,
40
+ SparsitySnapshot,
41
+ EnergyEfficiencyMetrics,
42
+ SparsityViolation,
43
+ SparsityMonitorConfig,
44
+ SparsityMonitorStats,
45
+ SparsityQualityHistoryEntry,
46
+ } from './SparsityMonitorTypes';
47
+
48
+ // =============================================================================
49
+ // DEFAULTS
50
+ // =============================================================================
51
+
52
+ const DEFAULT_CONFIG: SparsityMonitorConfig = {
53
+ sparsityThreshold: 0.93,
54
+ windowSize: 50,
55
+ perLayerTracking: true,
56
+ energyMetricsEnabled: true,
57
+ avgSynapsesPerNeuron: 100,
58
+ opsPerSynapse: 2,
59
+ criticalThreshold: 0.85,
60
+ maxViolationHistory: 1000,
61
+ };
62
+
63
+ // =============================================================================
64
+ // INPUT TYPE FOR RECORDING ACTIVITY
65
+ // =============================================================================
66
+
67
+ /**
68
+ * Input data for recording layer activity. The monitor computes
69
+ * derived fields (spikeRate, activationSparsity) from these inputs.
70
+ */
71
+ export interface LayerActivityInput {
72
+ /** Total number of neurons in the layer */
73
+ neuronCount: number;
74
+ /** Number of neurons that spiked */
75
+ spikeCount: number;
76
+ /** Simulation timestep index */
77
+ timestep: number;
78
+ /** Optional: average membrane potential */
79
+ avgMembranePotential?: number;
80
+ }
81
+
82
+ // =============================================================================
83
+ // SPARSITY MONITOR
84
+ // =============================================================================
85
+
86
+ /**
87
+ * Monitors SNN activation sparsity across layers during simulation.
88
+ *
89
+ * Provides:
90
+ * 1. Per-layer spike rate and activation sparsity tracking
91
+ * 2. Energy efficiency calculation (theoretical ops saved via sparsity)
92
+ * 3. Sparsity regime violation detection (W.041: >= 93% threshold)
93
+ * 4. Integration with SelfImproveHarvester for continuous quality monitoring
94
+ * 5. Output compatible with quality-history.json format
95
+ */
96
+ export class SparsityMonitor {
97
+ private config: SparsityMonitorConfig;
98
+
99
+ /** Current layer metrics indexed by layerId, latest values per layer */
100
+ private currentLayerMetrics: Map<string, SNNLayerMetrics> = new Map();
101
+
102
+ /** Historical layer metrics: layerId -> array of metrics over time */
103
+ private layerHistory: Map<string, SNNLayerMetrics[]> = new Map();
104
+
105
+ /** Snapshots taken over time */
106
+ private snapshots: SparsitySnapshot[] = [];
107
+
108
+ /** Detected violations */
109
+ private violations: SparsityViolation[] = [];
110
+
111
+ /** Rolling window of aggregate sparsity values for stats */
112
+ private sparsityWindow: number[] = [];
113
+
114
+ /** Total timesteps recorded across all layers */
115
+ private totalTimestepsRecorded = 0;
116
+
117
+ constructor(config: Partial<SparsityMonitorConfig> = {}) {
118
+ this.config = { ...DEFAULT_CONFIG, ...config };
119
+ }
120
+
121
+ // ---------------------------------------------------------------------------
122
+ // Recording
123
+ // ---------------------------------------------------------------------------
124
+
125
+ /**
126
+ * Record activity for a single SNN layer at a given timestep.
127
+ *
128
+ * Computes spike rate and activation sparsity from the raw input,
129
+ * checks for threshold violations, and stores the metrics.
130
+ *
131
+ * @param layerId - Unique identifier for the layer
132
+ * @param input - Raw activity data (neuronCount, spikeCount, timestep)
133
+ * @returns The computed SNNLayerMetrics for this recording
134
+ */
135
+ recordLayerActivity(layerId: string, input: LayerActivityInput): SNNLayerMetrics {
136
+ if (input.neuronCount <= 0) {
137
+ throw new Error(`neuronCount must be positive, got ${input.neuronCount}`);
138
+ }
139
+ if (input.spikeCount < 0) {
140
+ throw new Error(`spikeCount must be non-negative, got ${input.spikeCount}`);
141
+ }
142
+ if (input.spikeCount > input.neuronCount) {
143
+ throw new Error(
144
+ `spikeCount (${input.spikeCount}) cannot exceed neuronCount (${input.neuronCount})`
145
+ );
146
+ }
147
+
148
+ const spikeRate = input.spikeCount / input.neuronCount;
149
+ const activationSparsity = 1 - spikeRate;
150
+
151
+ const metrics: SNNLayerMetrics = {
152
+ layerId,
153
+ neuronCount: input.neuronCount,
154
+ spikeCount: input.spikeCount,
155
+ spikeRate: roundTo4(spikeRate),
156
+ activationSparsity: roundTo4(activationSparsity),
157
+ avgMembranePotential: input.avgMembranePotential,
158
+ timestep: input.timestep,
159
+ };
160
+
161
+ // Store current metrics
162
+ this.currentLayerMetrics.set(layerId, metrics);
163
+
164
+ // Track history if per-layer tracking is enabled
165
+ if (this.config.perLayerTracking) {
166
+ if (!this.layerHistory.has(layerId)) {
167
+ this.layerHistory.set(layerId, []);
168
+ }
169
+ this.layerHistory.get(layerId)!.push(metrics);
170
+ }
171
+
172
+ this.totalTimestepsRecorded++;
173
+
174
+ // Check for violations
175
+ this.checkViolation(metrics);
176
+
177
+ return metrics;
178
+ }
179
+
180
+ /**
181
+ * Record activity for multiple layers at the same timestep (batch recording).
182
+ *
183
+ * @param layerInputs - Map of layerId -> activity input
184
+ * @returns Array of computed metrics for each layer
185
+ */
186
+ recordBatchActivity(
187
+ layerInputs: Map<string, LayerActivityInput> | Record<string, LayerActivityInput>
188
+ ): SNNLayerMetrics[] {
189
+ const entries =
190
+ layerInputs instanceof Map ? Array.from(layerInputs.entries()) : Object.entries(layerInputs);
191
+
192
+ return entries.map(([layerId, input]) => this.recordLayerActivity(layerId, input));
193
+ }
194
+
195
+ // ---------------------------------------------------------------------------
196
+ // Snapshots
197
+ // ---------------------------------------------------------------------------
198
+
199
+ /**
200
+ * Take a point-in-time snapshot of all current layer metrics.
201
+ *
202
+ * The snapshot captures aggregate statistics across all tracked layers,
203
+ * computes energy efficiency, and checks for violations.
204
+ *
205
+ * @returns The snapshot, or null if no layer metrics have been recorded
206
+ */
207
+ takeSnapshot(): SparsitySnapshot | null {
208
+ if (this.currentLayerMetrics.size === 0) {
209
+ return null;
210
+ }
211
+
212
+ const layers = Array.from(this.currentLayerMetrics.values());
213
+ const totalNeurons = layers.reduce((sum, l) => sum + l.neuronCount, 0);
214
+ const totalSpikes = layers.reduce((sum, l) => sum + l.spikeCount, 0);
215
+
216
+ // Weighted aggregate sparsity (weighted by neuron count per layer)
217
+ const aggregateSparsity = totalNeurons > 0 ? roundTo4(1 - totalSpikes / totalNeurons) : 1;
218
+
219
+ const aggregateSpikeRate = totalNeurons > 0 ? roundTo4(totalSpikes / totalNeurons) : 0;
220
+
221
+ // Energy efficiency
222
+ const energyEfficiency = this.config.energyMetricsEnabled
223
+ ? this.calculateEnergyEfficiency(layers)
224
+ : createZeroEnergyMetrics();
225
+
226
+ // Detect violations in this snapshot
227
+ const violations = this.detectViolationsForLayers(layers);
228
+
229
+ const snapshot: SparsitySnapshot = {
230
+ timestamp: new Date().toISOString(),
231
+ layers: layers.map((l) => ({ ...l })),
232
+ aggregateSparsity,
233
+ aggregateSpikeRate,
234
+ totalNeurons,
235
+ totalSpikes,
236
+ energyEfficiency,
237
+ violations,
238
+ };
239
+
240
+ this.snapshots.push(snapshot);
241
+
242
+ // Update rolling sparsity window
243
+ this.sparsityWindow.push(aggregateSparsity);
244
+ if (this.sparsityWindow.length > this.config.windowSize) {
245
+ this.sparsityWindow.shift();
246
+ }
247
+
248
+ return snapshot;
249
+ }
250
+
251
+ // ---------------------------------------------------------------------------
252
+ // Energy Efficiency
253
+ // ---------------------------------------------------------------------------
254
+
255
+ /**
256
+ * Calculate theoretical energy efficiency metrics for the given layers.
257
+ *
258
+ * Dense ops = sum of (neuronCount * avgSynapsesPerNeuron * opsPerSynapse) per layer
259
+ * Sparse ops = sum of (spikeCount * avgSynapsesPerNeuron * opsPerSynapse) per layer
260
+ *
261
+ * The ratio of ops saved reflects the theoretical computational advantage
262
+ * of SNN sparsity over equivalent dense ANN computation.
263
+ */
264
+ calculateEnergyEfficiency(layers: SNNLayerMetrics[]): EnergyEfficiencyMetrics {
265
+ const { avgSynapsesPerNeuron, opsPerSynapse } = this.config;
266
+
267
+ let denseOps = 0;
268
+ let sparseOps = 0;
269
+
270
+ for (const layer of layers) {
271
+ const layerDenseOps = layer.neuronCount * avgSynapsesPerNeuron * opsPerSynapse;
272
+ const layerSparseOps = layer.spikeCount * avgSynapsesPerNeuron * opsPerSynapse;
273
+ denseOps += layerDenseOps;
274
+ sparseOps += layerSparseOps;
275
+ }
276
+
277
+ const opsSaved = denseOps - sparseOps;
278
+ const efficiencyRatio = denseOps > 0 ? roundTo4(opsSaved / denseOps) : 0;
279
+ const energySavingsFactor = denseOps > 0 ? roundTo4(denseOps / Math.max(1, sparseOps)) : 1;
280
+
281
+ return {
282
+ denseOps,
283
+ sparseOps,
284
+ opsSaved,
285
+ efficiencyRatio,
286
+ energySavingsFactor,
287
+ };
288
+ }
289
+
290
+ // ---------------------------------------------------------------------------
291
+ // Violation Detection
292
+ // ---------------------------------------------------------------------------
293
+
294
+ /**
295
+ * Check a single layer's metrics against the sparsity threshold.
296
+ * If a violation is detected, it is recorded in the violations history.
297
+ */
298
+ private checkViolation(metrics: SNNLayerMetrics): SparsityViolation | null {
299
+ if (metrics.activationSparsity >= this.config.sparsityThreshold) {
300
+ return null;
301
+ }
302
+
303
+ const deficit = roundTo4(this.config.sparsityThreshold - metrics.activationSparsity);
304
+ const severity: SparsityViolation['severity'] =
305
+ metrics.activationSparsity < this.config.criticalThreshold ? 'critical' : 'warning';
306
+
307
+ const violation: SparsityViolation = {
308
+ layerId: metrics.layerId,
309
+ measuredSparsity: metrics.activationSparsity,
310
+ requiredThreshold: this.config.sparsityThreshold,
311
+ deficit,
312
+ severity,
313
+ detectedAt: new Date().toISOString(),
314
+ timestep: metrics.timestep,
315
+ };
316
+
317
+ this.violations.push(violation);
318
+
319
+ // Trim violation history if it exceeds the maximum
320
+ if (this.violations.length > this.config.maxViolationHistory) {
321
+ this.violations = this.violations.slice(-this.config.maxViolationHistory);
322
+ }
323
+
324
+ return violation;
325
+ }
326
+
327
+ /**
328
+ * Detect violations for an array of layer metrics (used in snapshots).
329
+ */
330
+ private detectViolationsForLayers(layers: SNNLayerMetrics[]): SparsityViolation[] {
331
+ const snapshotViolations: SparsityViolation[] = [];
332
+ for (const layer of layers) {
333
+ if (layer.activationSparsity < this.config.sparsityThreshold) {
334
+ const deficit = roundTo4(this.config.sparsityThreshold - layer.activationSparsity);
335
+ const severity: SparsityViolation['severity'] =
336
+ layer.activationSparsity < this.config.criticalThreshold ? 'critical' : 'warning';
337
+
338
+ snapshotViolations.push({
339
+ layerId: layer.layerId,
340
+ measuredSparsity: layer.activationSparsity,
341
+ requiredThreshold: this.config.sparsityThreshold,
342
+ deficit,
343
+ severity,
344
+ detectedAt: new Date().toISOString(),
345
+ timestep: layer.timestep,
346
+ });
347
+ }
348
+ }
349
+ return snapshotViolations;
350
+ }
351
+
352
+ /**
353
+ * Get all violations currently affecting active layers
354
+ * (the most recent metric per layer that is below threshold).
355
+ */
356
+ getActiveViolations(): SparsityViolation[] {
357
+ const active: SparsityViolation[] = [];
358
+ for (const [, metrics] of this.currentLayerMetrics) {
359
+ if (metrics.activationSparsity < this.config.sparsityThreshold) {
360
+ const deficit = roundTo4(this.config.sparsityThreshold - metrics.activationSparsity);
361
+ const severity: SparsityViolation['severity'] =
362
+ metrics.activationSparsity < this.config.criticalThreshold ? 'critical' : 'warning';
363
+ active.push({
364
+ layerId: metrics.layerId,
365
+ measuredSparsity: metrics.activationSparsity,
366
+ requiredThreshold: this.config.sparsityThreshold,
367
+ deficit,
368
+ severity,
369
+ detectedAt: new Date().toISOString(),
370
+ timestep: metrics.timestep,
371
+ });
372
+ }
373
+ }
374
+ return active;
375
+ }
376
+
377
+ /**
378
+ * Get all historical violations.
379
+ */
380
+ getViolationHistory(): SparsityViolation[] {
381
+ return [...this.violations];
382
+ }
383
+
384
+ // ---------------------------------------------------------------------------
385
+ // Statistics
386
+ // ---------------------------------------------------------------------------
387
+
388
+ /**
389
+ * Compute aggregate statistics across all recorded data.
390
+ */
391
+ getStats(): SparsityMonitorStats {
392
+ const allSparsities: number[] = [];
393
+ const perLayerSums: Record<string, { sum: number; count: number }> = {};
394
+
395
+ for (const [layerId, history] of this.layerHistory) {
396
+ for (const m of history) {
397
+ allSparsities.push(m.activationSparsity);
398
+ if (!perLayerSums[layerId]) {
399
+ perLayerSums[layerId] = { sum: 0, count: 0 };
400
+ }
401
+ perLayerSums[layerId].sum += m.activationSparsity;
402
+ perLayerSums[layerId].count++;
403
+ }
404
+ }
405
+
406
+ // If no per-layer history, fall back to current metrics
407
+ if (allSparsities.length === 0) {
408
+ for (const [layerId, m] of this.currentLayerMetrics) {
409
+ allSparsities.push(m.activationSparsity);
410
+ perLayerSums[layerId] = { sum: m.activationSparsity, count: 1 };
411
+ }
412
+ }
413
+
414
+ const meanSparsity =
415
+ allSparsities.length > 0
416
+ ? roundTo4(allSparsities.reduce((a, b) => a + b, 0) / allSparsities.length)
417
+ : 0;
418
+
419
+ const minSparsity = allSparsities.length > 0 ? Math.min(...allSparsities) : 0;
420
+
421
+ const maxSparsity = allSparsities.length > 0 ? Math.max(...allSparsities) : 0;
422
+
423
+ const stdDevSparsity =
424
+ allSparsities.length > 1 ? roundTo4(standardDeviation(allSparsities)) : 0;
425
+
426
+ const perLayerMeanSparsity: Record<string, number> = {};
427
+ for (const [layerId, data] of Object.entries(perLayerSums)) {
428
+ perLayerMeanSparsity[layerId] = roundTo4(data.sum / data.count);
429
+ }
430
+
431
+ const warningCount = this.violations.filter((v) => v.severity === 'warning').length;
432
+ const criticalCount = this.violations.filter((v) => v.severity === 'critical').length;
433
+
434
+ // Mean energy efficiency from snapshots
435
+ const efficiencies = this.snapshots
436
+ .map((s) => s.energyEfficiency.efficiencyRatio)
437
+ .filter((e) => e > 0);
438
+ const meanEnergyEfficiency =
439
+ efficiencies.length > 0
440
+ ? roundTo4(efficiencies.reduce((a, b) => a + b, 0) / efficiencies.length)
441
+ : 0;
442
+
443
+ const activeViolations = this.getActiveViolations();
444
+
445
+ return {
446
+ totalTimesteps: this.totalTimestepsRecorded,
447
+ totalSnapshots: this.snapshots.length,
448
+ trackedLayers: this.currentLayerMetrics.size,
449
+ meanSparsity,
450
+ minSparsity,
451
+ maxSparsity,
452
+ stdDevSparsity,
453
+ totalViolations: this.violations.length,
454
+ violationsBySeverity: {
455
+ warning: warningCount,
456
+ critical: criticalCount,
457
+ },
458
+ perLayerMeanSparsity,
459
+ meanEnergyEfficiency,
460
+ inCompliance: activeViolations.length === 0,
461
+ };
462
+ }
463
+
464
+ // ---------------------------------------------------------------------------
465
+ // Quality History Integration
466
+ // ---------------------------------------------------------------------------
467
+
468
+ /**
469
+ * Generate an entry compatible with the quality-history.json format.
470
+ *
471
+ * The composite score is based on the aggregate sparsity relative to
472
+ * the threshold: score = min(1, aggregateSparsity / threshold).
473
+ *
474
+ * @param cycle - The monitoring cycle number
475
+ * @returns A quality history entry with sparsity-specific metrics
476
+ */
477
+ toQualityHistoryEntry(cycle: number): SparsityQualityHistoryEntry {
478
+ const stats = this.getStats();
479
+ const latestSnapshot =
480
+ this.snapshots.length > 0 ? this.snapshots[this.snapshots.length - 1] : null;
481
+
482
+ const aggregateSparsity = latestSnapshot?.aggregateSparsity ?? stats.meanSparsity;
483
+ const aggregateSpikeRate = latestSnapshot?.aggregateSpikeRate ?? 1 - stats.meanSparsity;
484
+
485
+ // Composite score: how well the system meets the sparsity threshold
486
+ // 1.0 = at or above threshold, scales linearly below
487
+ const composite = roundTo4(Math.min(1, aggregateSparsity / this.config.sparsityThreshold));
488
+
489
+ const grade = gradeFromComposite(composite);
490
+
491
+ const summary = this.generateSummary(stats, latestSnapshot, composite, grade);
492
+
493
+ return {
494
+ timestamp: new Date().toISOString(),
495
+ cycle,
496
+ composite,
497
+ grade,
498
+ focus: 'snn-sparsity',
499
+ summary,
500
+ sparsityMetrics: {
501
+ aggregateSparsity,
502
+ aggregateSpikeRate: roundTo4(aggregateSpikeRate),
503
+ energyEfficiencyRatio: stats.meanEnergyEfficiency,
504
+ violationCount: stats.totalViolations,
505
+ layerCount: stats.trackedLayers,
506
+ totalNeurons: latestSnapshot?.totalNeurons ?? 0,
507
+ inCompliance: stats.inCompliance,
508
+ },
509
+ };
510
+ }
511
+
512
+ /**
513
+ * Generate a human-readable summary string for the quality history entry.
514
+ */
515
+ private generateSummary(
516
+ stats: SparsityMonitorStats,
517
+ latestSnapshot: SparsitySnapshot | null,
518
+ composite: number,
519
+ grade: SparsityQualityHistoryEntry['grade']
520
+ ): string {
521
+ const lines: string[] = [];
522
+
523
+ lines.push(`SNN Sparsity Monitor - Grade: ${grade} (${(composite * 100).toFixed(1)}%)`);
524
+ lines.push(`Layers: ${stats.trackedLayers}, Timesteps: ${stats.totalTimesteps}`);
525
+ lines.push(
526
+ `Mean Sparsity: ${(stats.meanSparsity * 100).toFixed(1)}% (threshold: ${(this.config.sparsityThreshold * 100).toFixed(0)}%)`
527
+ );
528
+ lines.push(
529
+ `Range: [${(stats.minSparsity * 100).toFixed(1)}%, ${(stats.maxSparsity * 100).toFixed(1)}%]`
530
+ );
531
+
532
+ if (latestSnapshot) {
533
+ const eff = latestSnapshot.energyEfficiency;
534
+ lines.push(
535
+ `Energy Efficiency: ${(eff.efficiencyRatio * 100).toFixed(1)}% ops saved (${eff.energySavingsFactor.toFixed(1)}x factor)`
536
+ );
537
+ }
538
+
539
+ if (stats.totalViolations > 0) {
540
+ lines.push(
541
+ `Violations: ${stats.totalViolations} (${stats.violationsBySeverity.critical} critical, ${stats.violationsBySeverity.warning} warning)`
542
+ );
543
+ } else {
544
+ lines.push('Compliance: All layers within threshold');
545
+ }
546
+
547
+ return lines.join('\n');
548
+ }
549
+
550
+ // ---------------------------------------------------------------------------
551
+ // Harvester Integration
552
+ // ---------------------------------------------------------------------------
553
+
554
+ /**
555
+ * Get metrics formatted for integration with SelfImproveHarvester.
556
+ *
557
+ * Returns a record of key metrics that can be attached to harvest records
558
+ * as additional metadata for training data quality assessment.
559
+ */
560
+ getHarvesterMetrics(): Record<string, number | boolean> {
561
+ const stats = this.getStats();
562
+ return {
563
+ snn_mean_sparsity: stats.meanSparsity,
564
+ snn_min_sparsity: stats.minSparsity,
565
+ snn_max_sparsity: stats.maxSparsity,
566
+ snn_violation_count: stats.totalViolations,
567
+ snn_energy_efficiency: stats.meanEnergyEfficiency,
568
+ snn_in_compliance: stats.inCompliance,
569
+ snn_tracked_layers: stats.trackedLayers,
570
+ snn_total_timesteps: stats.totalTimesteps,
571
+ };
572
+ }
573
+
574
+ // ---------------------------------------------------------------------------
575
+ // Accessors
576
+ // ---------------------------------------------------------------------------
577
+
578
+ /**
579
+ * Get all recorded snapshots.
580
+ */
581
+ getSnapshots(): SparsitySnapshot[] {
582
+ return [...this.snapshots];
583
+ }
584
+
585
+ /**
586
+ * Get the most recent snapshot, or null if none have been taken.
587
+ */
588
+ getLatestSnapshot(): SparsitySnapshot | null {
589
+ return this.snapshots.length > 0 ? { ...this.snapshots[this.snapshots.length - 1] } : null;
590
+ }
591
+
592
+ /**
593
+ * Get current layer metrics.
594
+ */
595
+ getCurrentLayerMetrics(): Map<string, SNNLayerMetrics> {
596
+ return new Map(this.currentLayerMetrics);
597
+ }
598
+
599
+ /**
600
+ * Get history for a specific layer.
601
+ */
602
+ getLayerHistory(layerId: string): SNNLayerMetrics[] {
603
+ return [...(this.layerHistory.get(layerId) ?? [])];
604
+ }
605
+
606
+ /**
607
+ * Get the current configuration.
608
+ */
609
+ getConfig(): SparsityMonitorConfig {
610
+ return { ...this.config };
611
+ }
612
+
613
+ // ---------------------------------------------------------------------------
614
+ // Reset
615
+ // ---------------------------------------------------------------------------
616
+
617
+ /**
618
+ * Reset all recorded data and start fresh.
619
+ */
620
+ reset(): void {
621
+ this.currentLayerMetrics.clear();
622
+ this.layerHistory.clear();
623
+ this.snapshots = [];
624
+ this.violations = [];
625
+ this.sparsityWindow = [];
626
+ this.totalTimestepsRecorded = 0;
627
+ }
628
+ }
629
+
630
+ // =============================================================================
631
+ // HELPERS
632
+ // =============================================================================
633
+
634
+ /**
635
+ * Round a number to 4 decimal places.
636
+ */
637
+ function roundTo4(value: number): number {
638
+ return Math.round(value * 10000) / 10000;
639
+ }
640
+
641
+ /**
642
+ * Calculate standard deviation of an array of numbers.
643
+ */
644
+ function standardDeviation(values: number[]): number {
645
+ if (values.length < 2) return 0;
646
+ const mean = values.reduce((a, b) => a + b, 0) / values.length;
647
+ const squaredDiffs = values.map((v) => (v - mean) ** 2);
648
+ const variance = squaredDiffs.reduce((a, b) => a + b, 0) / (values.length - 1);
649
+ return Math.sqrt(variance);
650
+ }
651
+
652
+ /**
653
+ * Convert a composite score to a letter grade.
654
+ */
655
+ function gradeFromComposite(composite: number): SparsityQualityHistoryEntry['grade'] {
656
+ if (composite >= 0.95) return 'A';
657
+ if (composite >= 0.85) return 'B';
658
+ if (composite >= 0.7) return 'C';
659
+ if (composite >= 0.5) return 'D';
660
+ return 'F';
661
+ }
662
+
663
+ /**
664
+ * Create zero-valued energy metrics (for when energy metrics are disabled).
665
+ */
666
+ function createZeroEnergyMetrics(): EnergyEfficiencyMetrics {
667
+ return {
668
+ denseOps: 0,
669
+ sparseOps: 0,
670
+ opsSaved: 0,
671
+ efficiencyRatio: 0,
672
+ energySavingsFactor: 1,
673
+ };
674
+ }
675
+
676
+ // =============================================================================
677
+ // FACTORY
678
+ // =============================================================================
679
+
680
+ /**
681
+ * Create a new SparsityMonitor with optional configuration overrides.
682
+ */
683
+ export function createSparsityMonitor(config?: Partial<SparsityMonitorConfig>): SparsityMonitor {
684
+ return new SparsityMonitor(config);
685
+ }