tnfr 4.5.2__py3-none-any.whl → 8.5.0__py3-none-any.whl

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.

Potentially problematic release.


This version of tnfr might be problematic. Click here for more details.

Files changed (365) hide show
  1. tnfr/__init__.py +334 -50
  2. tnfr/__init__.pyi +33 -0
  3. tnfr/_compat.py +10 -0
  4. tnfr/_generated_version.py +34 -0
  5. tnfr/_version.py +49 -0
  6. tnfr/_version.pyi +7 -0
  7. tnfr/alias.py +214 -37
  8. tnfr/alias.pyi +108 -0
  9. tnfr/backends/__init__.py +354 -0
  10. tnfr/backends/jax_backend.py +173 -0
  11. tnfr/backends/numpy_backend.py +238 -0
  12. tnfr/backends/optimized_numpy.py +420 -0
  13. tnfr/backends/torch_backend.py +408 -0
  14. tnfr/cache.py +149 -556
  15. tnfr/cache.pyi +13 -0
  16. tnfr/cli/__init__.py +51 -16
  17. tnfr/cli/__init__.pyi +26 -0
  18. tnfr/cli/arguments.py +344 -32
  19. tnfr/cli/arguments.pyi +29 -0
  20. tnfr/cli/execution.py +676 -50
  21. tnfr/cli/execution.pyi +70 -0
  22. tnfr/cli/interactive_validator.py +614 -0
  23. tnfr/cli/utils.py +18 -3
  24. tnfr/cli/utils.pyi +7 -0
  25. tnfr/cli/validate.py +236 -0
  26. tnfr/compat/__init__.py +85 -0
  27. tnfr/compat/dataclass.py +136 -0
  28. tnfr/compat/jsonschema_stub.py +61 -0
  29. tnfr/compat/matplotlib_stub.py +73 -0
  30. tnfr/compat/numpy_stub.py +155 -0
  31. tnfr/config/__init__.py +224 -0
  32. tnfr/config/__init__.pyi +10 -0
  33. tnfr/{constants_glyphs.py → config/constants.py} +26 -20
  34. tnfr/config/constants.pyi +12 -0
  35. tnfr/config/defaults.py +54 -0
  36. tnfr/{constants/core.py → config/defaults_core.py} +59 -6
  37. tnfr/config/defaults_init.py +33 -0
  38. tnfr/config/defaults_metric.py +104 -0
  39. tnfr/config/feature_flags.py +81 -0
  40. tnfr/config/feature_flags.pyi +16 -0
  41. tnfr/config/glyph_constants.py +31 -0
  42. tnfr/config/init.py +77 -0
  43. tnfr/config/init.pyi +8 -0
  44. tnfr/config/operator_names.py +254 -0
  45. tnfr/config/operator_names.pyi +36 -0
  46. tnfr/config/physics_derivation.py +354 -0
  47. tnfr/config/presets.py +83 -0
  48. tnfr/config/presets.pyi +7 -0
  49. tnfr/config/security.py +927 -0
  50. tnfr/config/thresholds.py +114 -0
  51. tnfr/config/tnfr_config.py +498 -0
  52. tnfr/constants/__init__.py +51 -133
  53. tnfr/constants/__init__.pyi +92 -0
  54. tnfr/constants/aliases.py +33 -0
  55. tnfr/constants/aliases.pyi +27 -0
  56. tnfr/constants/init.py +3 -1
  57. tnfr/constants/init.pyi +12 -0
  58. tnfr/constants/metric.py +9 -15
  59. tnfr/constants/metric.pyi +19 -0
  60. tnfr/core/__init__.py +33 -0
  61. tnfr/core/container.py +226 -0
  62. tnfr/core/default_implementations.py +329 -0
  63. tnfr/core/interfaces.py +279 -0
  64. tnfr/dynamics/__init__.py +213 -633
  65. tnfr/dynamics/__init__.pyi +83 -0
  66. tnfr/dynamics/adaptation.py +267 -0
  67. tnfr/dynamics/adaptation.pyi +7 -0
  68. tnfr/dynamics/adaptive_sequences.py +189 -0
  69. tnfr/dynamics/adaptive_sequences.pyi +14 -0
  70. tnfr/dynamics/aliases.py +23 -0
  71. tnfr/dynamics/aliases.pyi +19 -0
  72. tnfr/dynamics/bifurcation.py +232 -0
  73. tnfr/dynamics/canonical.py +229 -0
  74. tnfr/dynamics/canonical.pyi +48 -0
  75. tnfr/dynamics/coordination.py +385 -0
  76. tnfr/dynamics/coordination.pyi +25 -0
  77. tnfr/dynamics/dnfr.py +2699 -398
  78. tnfr/dynamics/dnfr.pyi +26 -0
  79. tnfr/dynamics/dynamic_limits.py +225 -0
  80. tnfr/dynamics/feedback.py +252 -0
  81. tnfr/dynamics/feedback.pyi +24 -0
  82. tnfr/dynamics/fused_dnfr.py +454 -0
  83. tnfr/dynamics/homeostasis.py +157 -0
  84. tnfr/dynamics/homeostasis.pyi +14 -0
  85. tnfr/dynamics/integrators.py +496 -102
  86. tnfr/dynamics/integrators.pyi +36 -0
  87. tnfr/dynamics/learning.py +310 -0
  88. tnfr/dynamics/learning.pyi +33 -0
  89. tnfr/dynamics/metabolism.py +254 -0
  90. tnfr/dynamics/nbody.py +796 -0
  91. tnfr/dynamics/nbody_tnfr.py +783 -0
  92. tnfr/dynamics/propagation.py +326 -0
  93. tnfr/dynamics/runtime.py +908 -0
  94. tnfr/dynamics/runtime.pyi +77 -0
  95. tnfr/dynamics/sampling.py +10 -5
  96. tnfr/dynamics/sampling.pyi +7 -0
  97. tnfr/dynamics/selectors.py +711 -0
  98. tnfr/dynamics/selectors.pyi +85 -0
  99. tnfr/dynamics/structural_clip.py +207 -0
  100. tnfr/errors/__init__.py +37 -0
  101. tnfr/errors/contextual.py +492 -0
  102. tnfr/execution.py +77 -55
  103. tnfr/execution.pyi +45 -0
  104. tnfr/extensions/__init__.py +205 -0
  105. tnfr/extensions/__init__.pyi +18 -0
  106. tnfr/extensions/base.py +173 -0
  107. tnfr/extensions/base.pyi +35 -0
  108. tnfr/extensions/business/__init__.py +71 -0
  109. tnfr/extensions/business/__init__.pyi +11 -0
  110. tnfr/extensions/business/cookbook.py +88 -0
  111. tnfr/extensions/business/cookbook.pyi +8 -0
  112. tnfr/extensions/business/health_analyzers.py +202 -0
  113. tnfr/extensions/business/health_analyzers.pyi +9 -0
  114. tnfr/extensions/business/patterns.py +183 -0
  115. tnfr/extensions/business/patterns.pyi +8 -0
  116. tnfr/extensions/medical/__init__.py +73 -0
  117. tnfr/extensions/medical/__init__.pyi +11 -0
  118. tnfr/extensions/medical/cookbook.py +88 -0
  119. tnfr/extensions/medical/cookbook.pyi +8 -0
  120. tnfr/extensions/medical/health_analyzers.py +181 -0
  121. tnfr/extensions/medical/health_analyzers.pyi +9 -0
  122. tnfr/extensions/medical/patterns.py +163 -0
  123. tnfr/extensions/medical/patterns.pyi +8 -0
  124. tnfr/flatten.py +29 -50
  125. tnfr/flatten.pyi +21 -0
  126. tnfr/gamma.py +66 -53
  127. tnfr/gamma.pyi +36 -0
  128. tnfr/glyph_history.py +144 -57
  129. tnfr/glyph_history.pyi +35 -0
  130. tnfr/glyph_runtime.py +19 -0
  131. tnfr/glyph_runtime.pyi +8 -0
  132. tnfr/immutable.py +70 -30
  133. tnfr/immutable.pyi +36 -0
  134. tnfr/initialization.py +22 -16
  135. tnfr/initialization.pyi +65 -0
  136. tnfr/io.py +5 -241
  137. tnfr/io.pyi +13 -0
  138. tnfr/locking.pyi +7 -0
  139. tnfr/mathematics/__init__.py +79 -0
  140. tnfr/mathematics/backend.py +453 -0
  141. tnfr/mathematics/backend.pyi +99 -0
  142. tnfr/mathematics/dynamics.py +408 -0
  143. tnfr/mathematics/dynamics.pyi +90 -0
  144. tnfr/mathematics/epi.py +391 -0
  145. tnfr/mathematics/epi.pyi +65 -0
  146. tnfr/mathematics/generators.py +242 -0
  147. tnfr/mathematics/generators.pyi +29 -0
  148. tnfr/mathematics/metrics.py +119 -0
  149. tnfr/mathematics/metrics.pyi +16 -0
  150. tnfr/mathematics/operators.py +239 -0
  151. tnfr/mathematics/operators.pyi +59 -0
  152. tnfr/mathematics/operators_factory.py +124 -0
  153. tnfr/mathematics/operators_factory.pyi +11 -0
  154. tnfr/mathematics/projection.py +87 -0
  155. tnfr/mathematics/projection.pyi +33 -0
  156. tnfr/mathematics/runtime.py +182 -0
  157. tnfr/mathematics/runtime.pyi +64 -0
  158. tnfr/mathematics/spaces.py +256 -0
  159. tnfr/mathematics/spaces.pyi +83 -0
  160. tnfr/mathematics/transforms.py +305 -0
  161. tnfr/mathematics/transforms.pyi +62 -0
  162. tnfr/metrics/__init__.py +47 -9
  163. tnfr/metrics/__init__.pyi +20 -0
  164. tnfr/metrics/buffer_cache.py +163 -0
  165. tnfr/metrics/buffer_cache.pyi +24 -0
  166. tnfr/metrics/cache_utils.py +214 -0
  167. tnfr/metrics/coherence.py +1510 -330
  168. tnfr/metrics/coherence.pyi +129 -0
  169. tnfr/metrics/common.py +23 -16
  170. tnfr/metrics/common.pyi +35 -0
  171. tnfr/metrics/core.py +251 -36
  172. tnfr/metrics/core.pyi +13 -0
  173. tnfr/metrics/diagnosis.py +709 -110
  174. tnfr/metrics/diagnosis.pyi +86 -0
  175. tnfr/metrics/emergence.py +245 -0
  176. tnfr/metrics/export.py +60 -18
  177. tnfr/metrics/export.pyi +7 -0
  178. tnfr/metrics/glyph_timing.py +233 -43
  179. tnfr/metrics/glyph_timing.pyi +81 -0
  180. tnfr/metrics/learning_metrics.py +280 -0
  181. tnfr/metrics/learning_metrics.pyi +21 -0
  182. tnfr/metrics/phase_coherence.py +351 -0
  183. tnfr/metrics/phase_compatibility.py +349 -0
  184. tnfr/metrics/reporting.py +63 -28
  185. tnfr/metrics/reporting.pyi +25 -0
  186. tnfr/metrics/sense_index.py +1126 -43
  187. tnfr/metrics/sense_index.pyi +9 -0
  188. tnfr/metrics/trig.py +215 -23
  189. tnfr/metrics/trig.pyi +13 -0
  190. tnfr/metrics/trig_cache.py +148 -24
  191. tnfr/metrics/trig_cache.pyi +10 -0
  192. tnfr/multiscale/__init__.py +32 -0
  193. tnfr/multiscale/hierarchical.py +517 -0
  194. tnfr/node.py +646 -140
  195. tnfr/node.pyi +139 -0
  196. tnfr/observers.py +160 -45
  197. tnfr/observers.pyi +31 -0
  198. tnfr/ontosim.py +23 -19
  199. tnfr/ontosim.pyi +28 -0
  200. tnfr/operators/__init__.py +1358 -106
  201. tnfr/operators/__init__.pyi +31 -0
  202. tnfr/operators/algebra.py +277 -0
  203. tnfr/operators/canonical_patterns.py +420 -0
  204. tnfr/operators/cascade.py +267 -0
  205. tnfr/operators/cycle_detection.py +358 -0
  206. tnfr/operators/definitions.py +4108 -0
  207. tnfr/operators/definitions.pyi +78 -0
  208. tnfr/operators/grammar.py +1164 -0
  209. tnfr/operators/grammar.pyi +140 -0
  210. tnfr/operators/hamiltonian.py +710 -0
  211. tnfr/operators/health_analyzer.py +809 -0
  212. tnfr/operators/jitter.py +107 -38
  213. tnfr/operators/jitter.pyi +11 -0
  214. tnfr/operators/lifecycle.py +314 -0
  215. tnfr/operators/metabolism.py +618 -0
  216. tnfr/operators/metrics.py +2138 -0
  217. tnfr/operators/network_analysis/__init__.py +27 -0
  218. tnfr/operators/network_analysis/source_detection.py +186 -0
  219. tnfr/operators/nodal_equation.py +395 -0
  220. tnfr/operators/pattern_detection.py +660 -0
  221. tnfr/operators/patterns.py +669 -0
  222. tnfr/operators/postconditions/__init__.py +38 -0
  223. tnfr/operators/postconditions/mutation.py +236 -0
  224. tnfr/operators/preconditions/__init__.py +1226 -0
  225. tnfr/operators/preconditions/coherence.py +305 -0
  226. tnfr/operators/preconditions/dissonance.py +236 -0
  227. tnfr/operators/preconditions/emission.py +128 -0
  228. tnfr/operators/preconditions/mutation.py +580 -0
  229. tnfr/operators/preconditions/reception.py +125 -0
  230. tnfr/operators/preconditions/resonance.py +364 -0
  231. tnfr/operators/registry.py +74 -0
  232. tnfr/operators/registry.pyi +9 -0
  233. tnfr/operators/remesh.py +1415 -91
  234. tnfr/operators/remesh.pyi +26 -0
  235. tnfr/operators/structural_units.py +268 -0
  236. tnfr/operators/unified_grammar.py +105 -0
  237. tnfr/parallel/__init__.py +54 -0
  238. tnfr/parallel/auto_scaler.py +234 -0
  239. tnfr/parallel/distributed.py +384 -0
  240. tnfr/parallel/engine.py +238 -0
  241. tnfr/parallel/gpu_engine.py +420 -0
  242. tnfr/parallel/monitoring.py +248 -0
  243. tnfr/parallel/partitioner.py +459 -0
  244. tnfr/py.typed +0 -0
  245. tnfr/recipes/__init__.py +22 -0
  246. tnfr/recipes/cookbook.py +743 -0
  247. tnfr/rng.py +75 -151
  248. tnfr/rng.pyi +26 -0
  249. tnfr/schemas/__init__.py +8 -0
  250. tnfr/schemas/grammar.json +94 -0
  251. tnfr/sdk/__init__.py +107 -0
  252. tnfr/sdk/__init__.pyi +19 -0
  253. tnfr/sdk/adaptive_system.py +173 -0
  254. tnfr/sdk/adaptive_system.pyi +21 -0
  255. tnfr/sdk/builders.py +370 -0
  256. tnfr/sdk/builders.pyi +51 -0
  257. tnfr/sdk/fluent.py +1121 -0
  258. tnfr/sdk/fluent.pyi +74 -0
  259. tnfr/sdk/templates.py +342 -0
  260. tnfr/sdk/templates.pyi +41 -0
  261. tnfr/sdk/utils.py +341 -0
  262. tnfr/secure_config.py +46 -0
  263. tnfr/security/__init__.py +70 -0
  264. tnfr/security/database.py +514 -0
  265. tnfr/security/subprocess.py +503 -0
  266. tnfr/security/validation.py +290 -0
  267. tnfr/selector.py +59 -22
  268. tnfr/selector.pyi +19 -0
  269. tnfr/sense.py +92 -67
  270. tnfr/sense.pyi +23 -0
  271. tnfr/services/__init__.py +17 -0
  272. tnfr/services/orchestrator.py +325 -0
  273. tnfr/sparse/__init__.py +39 -0
  274. tnfr/sparse/representations.py +492 -0
  275. tnfr/structural.py +639 -263
  276. tnfr/structural.pyi +83 -0
  277. tnfr/telemetry/__init__.py +35 -0
  278. tnfr/telemetry/cache_metrics.py +226 -0
  279. tnfr/telemetry/cache_metrics.pyi +64 -0
  280. tnfr/telemetry/nu_f.py +422 -0
  281. tnfr/telemetry/nu_f.pyi +108 -0
  282. tnfr/telemetry/verbosity.py +36 -0
  283. tnfr/telemetry/verbosity.pyi +15 -0
  284. tnfr/tokens.py +2 -4
  285. tnfr/tokens.pyi +36 -0
  286. tnfr/tools/__init__.py +20 -0
  287. tnfr/tools/domain_templates.py +478 -0
  288. tnfr/tools/sequence_generator.py +846 -0
  289. tnfr/topology/__init__.py +13 -0
  290. tnfr/topology/asymmetry.py +151 -0
  291. tnfr/trace.py +300 -126
  292. tnfr/trace.pyi +42 -0
  293. tnfr/tutorials/__init__.py +38 -0
  294. tnfr/tutorials/autonomous_evolution.py +285 -0
  295. tnfr/tutorials/interactive.py +1576 -0
  296. tnfr/tutorials/structural_metabolism.py +238 -0
  297. tnfr/types.py +743 -12
  298. tnfr/types.pyi +357 -0
  299. tnfr/units.py +68 -0
  300. tnfr/units.pyi +13 -0
  301. tnfr/utils/__init__.py +282 -0
  302. tnfr/utils/__init__.pyi +215 -0
  303. tnfr/utils/cache.py +4223 -0
  304. tnfr/utils/cache.pyi +470 -0
  305. tnfr/{callback_utils.py → utils/callbacks.py} +26 -39
  306. tnfr/utils/callbacks.pyi +49 -0
  307. tnfr/utils/chunks.py +108 -0
  308. tnfr/utils/chunks.pyi +22 -0
  309. tnfr/utils/data.py +428 -0
  310. tnfr/utils/data.pyi +74 -0
  311. tnfr/utils/graph.py +85 -0
  312. tnfr/utils/graph.pyi +10 -0
  313. tnfr/utils/init.py +821 -0
  314. tnfr/utils/init.pyi +80 -0
  315. tnfr/utils/io.py +559 -0
  316. tnfr/utils/io.pyi +66 -0
  317. tnfr/{helpers → utils}/numeric.py +51 -24
  318. tnfr/utils/numeric.pyi +21 -0
  319. tnfr/validation/__init__.py +257 -0
  320. tnfr/validation/__init__.pyi +85 -0
  321. tnfr/validation/compatibility.py +460 -0
  322. tnfr/validation/compatibility.pyi +6 -0
  323. tnfr/validation/config.py +73 -0
  324. tnfr/validation/graph.py +139 -0
  325. tnfr/validation/graph.pyi +18 -0
  326. tnfr/validation/input_validation.py +755 -0
  327. tnfr/validation/invariants.py +712 -0
  328. tnfr/validation/rules.py +253 -0
  329. tnfr/validation/rules.pyi +44 -0
  330. tnfr/validation/runtime.py +279 -0
  331. tnfr/validation/runtime.pyi +28 -0
  332. tnfr/validation/sequence_validator.py +162 -0
  333. tnfr/validation/soft_filters.py +170 -0
  334. tnfr/validation/soft_filters.pyi +32 -0
  335. tnfr/validation/spectral.py +164 -0
  336. tnfr/validation/spectral.pyi +42 -0
  337. tnfr/validation/validator.py +1266 -0
  338. tnfr/validation/window.py +39 -0
  339. tnfr/validation/window.pyi +1 -0
  340. tnfr/visualization/__init__.py +98 -0
  341. tnfr/visualization/cascade_viz.py +256 -0
  342. tnfr/visualization/hierarchy.py +284 -0
  343. tnfr/visualization/sequence_plotter.py +784 -0
  344. tnfr/viz/__init__.py +60 -0
  345. tnfr/viz/matplotlib.py +278 -0
  346. tnfr/viz/matplotlib.pyi +35 -0
  347. tnfr-8.5.0.dist-info/METADATA +573 -0
  348. tnfr-8.5.0.dist-info/RECORD +353 -0
  349. {tnfr-4.5.2.dist-info → tnfr-8.5.0.dist-info}/entry_points.txt +1 -0
  350. {tnfr-4.5.2.dist-info → tnfr-8.5.0.dist-info}/licenses/LICENSE.md +1 -1
  351. tnfr/collections_utils.py +0 -300
  352. tnfr/config.py +0 -32
  353. tnfr/grammar.py +0 -344
  354. tnfr/graph_utils.py +0 -84
  355. tnfr/helpers/__init__.py +0 -71
  356. tnfr/import_utils.py +0 -228
  357. tnfr/json_utils.py +0 -162
  358. tnfr/logging_utils.py +0 -116
  359. tnfr/presets.py +0 -60
  360. tnfr/validators.py +0 -84
  361. tnfr/value_utils.py +0 -59
  362. tnfr-4.5.2.dist-info/METADATA +0 -379
  363. tnfr-4.5.2.dist-info/RECORD +0 -67
  364. {tnfr-4.5.2.dist-info → tnfr-8.5.0.dist-info}/WHEEL +0 -0
  365. {tnfr-4.5.2.dist-info → tnfr-8.5.0.dist-info}/top_level.txt +0 -0
@@ -2,24 +2,75 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import math
5
6
  from collections import Counter, defaultdict
7
+ from concurrent.futures import ProcessPoolExecutor
6
8
  from dataclasses import dataclass
7
- from typing import Any, Callable
9
+ from types import ModuleType
10
+ from typing import (
11
+ Any,
12
+ Callable,
13
+ Mapping,
14
+ MutableMapping,
15
+ Sequence,
16
+ cast,
17
+ )
8
18
 
9
19
  from ..alias import get_attr
10
- from ..constants import get_aliases, get_param
11
- from ..constants_glyphs import GLYPH_GROUPS, GLYPHS_CANONICAL
12
- from ..glyph_history import append_metric, last_glyph
13
- from ..types import Glyph
20
+ from ..config.constants import GLYPH_GROUPS, GLYPHS_CANONICAL
21
+ from ..constants import get_param
22
+ from ..constants.aliases import ALIAS_EPI
23
+ from ..glyph_history import append_metric
24
+ from ..glyph_runtime import last_glyph
25
+ from ..utils import resolve_chunk_size
26
+ from ..types import (
27
+ GlyphCounts,
28
+ GlyphMetricsHistory,
29
+ GlyphTimingByNode,
30
+ GlyphTimingTotals,
31
+ GlyphogramRow,
32
+ GraphLike,
33
+ MetricsListHistory,
34
+ SigmaTrace,
35
+ )
36
+
37
+ LATENT_GLYPH: str = "SHA"
38
+ DEFAULT_EPI_SUPPORT_LIMIT = 0.05
14
39
 
15
- ALIAS_EPI = get_aliases("EPI")
40
+ try: # pragma: no cover - import guard exercised via tests
41
+ import numpy as _np # type: ignore[import-not-found]
42
+ except Exception: # pragma: no cover - numpy optional dependency
43
+ _np = None
16
44
 
17
- LATENT_GLYPH = Glyph.SHA.value
18
- DEFAULT_EPI_SUPPORT_LIMIT = 0.05
45
+ np: ModuleType | None = cast(ModuleType | None, _np)
46
+
47
+
48
+ def _has_numpy_support(np_obj: object) -> bool:
49
+ """Return ``True`` when ``np_obj`` exposes the required NumPy API."""
50
+
51
+ return isinstance(np_obj, ModuleType) or (
52
+ np_obj is not None
53
+ and hasattr(np_obj, "fromiter")
54
+ and hasattr(np_obj, "bincount")
55
+ )
56
+
57
+
58
+ _GLYPH_TO_INDEX = {glyph: idx for idx, glyph in enumerate(GLYPHS_CANONICAL)}
59
+
60
+
61
+ def _coerce_float(value: Any) -> float:
62
+ """Attempt to coerce ``value`` to ``float`` returning ``0.0`` on failure."""
63
+
64
+ try:
65
+ return float(value)
66
+ except (TypeError, ValueError):
67
+ return 0.0
19
68
 
20
69
 
21
70
  @dataclass
22
71
  class GlyphTiming:
72
+ """Mutable accumulator tracking the active glyph and its dwell time."""
73
+
23
74
  curr: str | None = None
24
75
  run: float = 0.0
25
76
 
@@ -27,6 +78,10 @@ class GlyphTiming:
27
78
  __all__ = [
28
79
  "LATENT_GLYPH",
29
80
  "GlyphTiming",
81
+ "SigmaTrace",
82
+ "GlyphogramRow",
83
+ "GlyphTimingTotals",
84
+ "GlyphTimingByNode",
30
85
  "_tg_state",
31
86
  "for_each_glyph",
32
87
  "_update_tg_node",
@@ -38,19 +93,39 @@ __all__ = [
38
93
  "_compute_advanced_metrics",
39
94
  ]
40
95
 
41
-
42
96
  # ---------------------------------------------------------------------------
43
97
  # Internal utilities
44
98
  # ---------------------------------------------------------------------------
45
99
 
46
100
 
47
- def _tg_state(nd: dict[str, Any]) -> GlyphTiming:
101
+ def _count_glyphs_chunk(chunk: Sequence[str]) -> Counter[str]:
102
+ """Count glyph occurrences within a chunk (multiprocessing helper)."""
103
+
104
+ counter: Counter[str] = Counter()
105
+ for glyph in chunk:
106
+ counter[glyph] += 1
107
+ return counter
108
+
109
+
110
+ def _epi_support_chunk(values: Sequence[float], threshold: float) -> tuple[float, int]:
111
+ """Compute EPI support contribution for a chunk."""
112
+
113
+ total = 0.0
114
+ count = 0
115
+ for value in values:
116
+ if value >= threshold:
117
+ total += value
118
+ count += 1
119
+ return total, count
120
+
121
+
122
+ def _tg_state(nd: MutableMapping[str, Any]) -> GlyphTiming:
48
123
  """Expose per-node glyph timing state."""
49
124
 
50
125
  return nd.setdefault("_Tg", GlyphTiming())
51
126
 
52
127
 
53
- def for_each_glyph(fn: Callable[[str], Any]) -> None:
128
+ def for_each_glyph(fn: Callable[[str], None]) -> None:
54
129
  """Apply ``fn`` to each canonical structural operator."""
55
130
 
56
131
  for g in GLYPHS_CANONICAL:
@@ -62,7 +137,13 @@ def for_each_glyph(fn: Callable[[str], Any]) -> None:
62
137
  # ---------------------------------------------------------------------------
63
138
 
64
139
 
65
- def _update_tg_node(n, nd, dt, tg_total, tg_by_node):
140
+ def _update_tg_node(
141
+ n: Any,
142
+ nd: MutableMapping[str, Any],
143
+ dt: float,
144
+ tg_total: GlyphTimingTotals,
145
+ tg_by_node: GlyphTimingByNode | None,
146
+ ) -> tuple[str | None, bool]:
66
147
  """Track a node's glyph transition and accumulate run time."""
67
148
 
68
149
  g = last_glyph(nd)
@@ -85,19 +166,31 @@ def _update_tg_node(n, nd, dt, tg_total, tg_by_node):
85
166
  return g, g == LATENT_GLYPH
86
167
 
87
168
 
88
- def _update_tg(G, hist, dt, save_by_node: bool):
169
+ def _update_tg(
170
+ G: GraphLike,
171
+ hist: GlyphMetricsHistory,
172
+ dt: float,
173
+ save_by_node: bool,
174
+ n_jobs: int | None = None,
175
+ ) -> tuple[Counter[str], int, int]:
89
176
  """Accumulate glyph dwell times for the entire graph."""
90
177
 
91
- counts = Counter()
92
- tg_total = hist.setdefault("Tg_total", defaultdict(float))
178
+ tg_total = cast(GlyphTimingTotals, hist.setdefault("Tg_total", defaultdict(float)))
93
179
  tg_by_node = (
94
- hist.setdefault("Tg_by_node", defaultdict(lambda: defaultdict(list)))
180
+ cast(
181
+ GlyphTimingByNode,
182
+ hist.setdefault(
183
+ "Tg_by_node",
184
+ defaultdict(lambda: defaultdict(list)),
185
+ ),
186
+ )
95
187
  if save_by_node
96
188
  else None
97
189
  )
98
190
 
99
191
  n_total = 0
100
192
  n_latent = 0
193
+ glyph_sequence: list[str] = []
101
194
  for n, nd in G.nodes(data=True):
102
195
  g, is_latent = _update_tg_node(n, nd, dt, tg_total, tg_by_node)
103
196
  if g is None:
@@ -105,56 +198,152 @@ def _update_tg(G, hist, dt, save_by_node: bool):
105
198
  n_total += 1
106
199
  if is_latent:
107
200
  n_latent += 1
108
- counts[g] += 1
201
+ glyph_sequence.append(g)
202
+
203
+ counts: Counter[str] = Counter()
204
+ if not glyph_sequence:
205
+ return counts, n_total, n_latent
206
+
207
+ if _has_numpy_support(np):
208
+ glyph_idx = np.fromiter(
209
+ (_GLYPH_TO_INDEX[glyph] for glyph in glyph_sequence),
210
+ dtype=np.int64,
211
+ count=len(glyph_sequence),
212
+ )
213
+ freq = np.bincount(glyph_idx, minlength=len(GLYPHS_CANONICAL))
214
+ counts.update(
215
+ {
216
+ glyph: int(freq[_GLYPH_TO_INDEX[glyph]])
217
+ for glyph in GLYPHS_CANONICAL
218
+ if freq[_GLYPH_TO_INDEX[glyph]]
219
+ }
220
+ )
221
+ elif n_jobs is not None and n_jobs > 1 and len(glyph_sequence) > 1:
222
+ approx_chunk = math.ceil(len(glyph_sequence) / n_jobs) if n_jobs else None
223
+ chunk_size = resolve_chunk_size(
224
+ approx_chunk,
225
+ len(glyph_sequence),
226
+ minimum=1,
227
+ )
228
+ futures = []
229
+ with ProcessPoolExecutor(max_workers=n_jobs) as executor:
230
+ for start in range(0, len(glyph_sequence), chunk_size):
231
+ chunk = glyph_sequence[start : start + chunk_size]
232
+ futures.append(executor.submit(_count_glyphs_chunk, chunk))
233
+ for future in futures:
234
+ counts.update(future.result())
235
+ else:
236
+ counts.update(glyph_sequence)
237
+
109
238
  return counts, n_total, n_latent
110
239
 
111
240
 
112
- def _update_glyphogram(G, hist, counts, t, n_total):
241
+ def _update_glyphogram(
242
+ G: GraphLike,
243
+ hist: GlyphMetricsHistory,
244
+ counts: GlyphCounts,
245
+ t: float,
246
+ n_total: int,
247
+ ) -> None:
113
248
  """Record glyphogram row from glyph counts."""
114
249
 
115
250
  normalize_series = bool(get_param(G, "METRICS").get("normalize_series", False))
116
- row = {"t": t}
251
+ row: GlyphogramRow = {"t": t}
117
252
  total = max(1, n_total)
118
253
  for g in GLYPHS_CANONICAL:
119
254
  c = counts.get(g, 0)
120
255
  row[g] = (c / total) if normalize_series else c
121
- append_metric(hist, "glyphogram", row)
256
+ append_metric(cast(MetricsListHistory, hist), "glyphogram", row)
122
257
 
123
258
 
124
- def _update_latency_index(G, hist, n_total, n_latent, t):
259
+ def _update_latency_index(
260
+ G: GraphLike,
261
+ hist: GlyphMetricsHistory,
262
+ n_total: int,
263
+ n_latent: int,
264
+ t: float,
265
+ ) -> None:
125
266
  """Record latency index for the current step."""
126
267
 
127
268
  li = n_latent / max(1, n_total)
128
- append_metric(hist, "latency_index", {"t": t, "value": li})
269
+ append_metric(
270
+ cast(MetricsListHistory, hist),
271
+ "latency_index",
272
+ {"t": t, "value": li},
273
+ )
129
274
 
130
275
 
131
276
  def _update_epi_support(
132
- G,
133
- hist,
134
- t,
277
+ G: GraphLike,
278
+ hist: GlyphMetricsHistory,
279
+ t: float,
135
280
  threshold: float = DEFAULT_EPI_SUPPORT_LIMIT,
136
- ):
281
+ n_jobs: int | None = None,
282
+ ) -> None:
137
283
  """Measure EPI support and normalized magnitude."""
138
284
 
285
+ node_count = G.number_of_nodes()
139
286
  total = 0.0
140
287
  count = 0
141
- for _, nd in G.nodes(data=True):
142
- epi_val = abs(get_attr(nd, ALIAS_EPI, 0.0))
143
- if epi_val >= threshold:
144
- total += epi_val
145
- count += 1
288
+
289
+ if _has_numpy_support(np) and node_count:
290
+ epi_values = np.fromiter(
291
+ (
292
+ abs(_coerce_float(get_attr(nd, ALIAS_EPI, 0.0)))
293
+ for _, nd in G.nodes(data=True)
294
+ ),
295
+ dtype=float,
296
+ count=node_count,
297
+ )
298
+ mask = epi_values >= threshold
299
+ count = int(mask.sum())
300
+ if count:
301
+ total = float(epi_values[mask].sum())
302
+ elif n_jobs is not None and n_jobs > 1 and node_count > 1:
303
+ values = [
304
+ abs(_coerce_float(get_attr(nd, ALIAS_EPI, 0.0)))
305
+ for _, nd in G.nodes(data=True)
306
+ ]
307
+ approx_chunk = math.ceil(len(values) / n_jobs) if n_jobs else None
308
+ chunk_size = resolve_chunk_size(
309
+ approx_chunk,
310
+ len(values),
311
+ minimum=1,
312
+ )
313
+ totals: list[tuple[float, int]] = []
314
+ with ProcessPoolExecutor(max_workers=n_jobs) as executor:
315
+ futures = []
316
+ for start in range(0, len(values), chunk_size):
317
+ chunk = values[start : start + chunk_size]
318
+ futures.append(executor.submit(_epi_support_chunk, chunk, threshold))
319
+ for future in futures:
320
+ totals.append(future.result())
321
+ for part_total, part_count in totals:
322
+ total += part_total
323
+ count += part_count
324
+ else:
325
+ for _, nd in G.nodes(data=True):
326
+ epi_val = abs(_coerce_float(get_attr(nd, ALIAS_EPI, 0.0)))
327
+ if epi_val >= threshold:
328
+ total += epi_val
329
+ count += 1
146
330
  epi_norm = (total / count) if count else 0.0
147
331
  append_metric(
148
- hist,
332
+ cast(MetricsListHistory, hist),
149
333
  "EPI_support",
150
334
  {"t": t, "size": count, "epi_norm": float(epi_norm)},
151
335
  )
152
336
 
153
337
 
154
- def _update_morph_metrics(G, hist, counts, t):
338
+ def _update_morph_metrics(
339
+ G: GraphLike,
340
+ hist: GlyphMetricsHistory,
341
+ counts: GlyphCounts,
342
+ t: float,
343
+ ) -> None:
155
344
  """Capture morphosyntactic distribution of glyphs."""
156
345
 
157
- def get_count(keys):
346
+ def get_count(keys: Sequence[str]) -> int:
158
347
  return sum(counts.get(k, 0) for k in keys)
159
348
 
160
349
  total = max(1, sum(counts.values()))
@@ -165,25 +354,26 @@ def _update_morph_metrics(G, hist, counts, t):
165
354
  den = get_count(GLYPH_GROUPS.get("PP_den", ()))
166
355
  pp_val = 0.0 if den == 0 else num / den
167
356
  append_metric(
168
- hist,
357
+ cast(MetricsListHistory, hist),
169
358
  "morph",
170
359
  {"t": t, "ID": id_val, "CM": cm_val, "NE": ne_val, "PP": pp_val},
171
360
  )
172
361
 
173
362
 
174
363
  def _compute_advanced_metrics(
175
- G,
176
- hist,
177
- t,
178
- dt,
179
- cfg,
364
+ G: GraphLike,
365
+ hist: GlyphMetricsHistory,
366
+ t: float,
367
+ dt: float,
368
+ cfg: Mapping[str, Any],
180
369
  threshold: float = DEFAULT_EPI_SUPPORT_LIMIT,
181
- ):
370
+ n_jobs: int | None = None,
371
+ ) -> None:
182
372
  """Compute glyph timing derived metrics."""
183
373
 
184
374
  save_by_node = bool(cfg.get("save_by_node", True))
185
- counts, n_total, n_latent = _update_tg(G, hist, dt, save_by_node)
375
+ counts, n_total, n_latent = _update_tg(G, hist, dt, save_by_node, n_jobs=n_jobs)
186
376
  _update_glyphogram(G, hist, counts, t, n_total)
187
377
  _update_latency_index(G, hist, n_total, n_latent, t)
188
- _update_epi_support(G, hist, t, threshold)
378
+ _update_epi_support(G, hist, t, threshold, n_jobs=n_jobs)
189
379
  _update_morph_metrics(G, hist, counts, t)
@@ -0,0 +1,81 @@
1
+ from __future__ import annotations
2
+
3
+ from ..types import (
4
+ GlyphCounts,
5
+ GlyphMetricsHistory,
6
+ GlyphTimingByNode as GlyphTimingByNode,
7
+ GlyphTimingTotals as GlyphTimingTotals,
8
+ GlyphogramRow as GlyphogramRow,
9
+ GraphLike,
10
+ SigmaTrace as SigmaTrace,
11
+ )
12
+ from collections import Counter
13
+ from dataclasses import dataclass
14
+ from typing import Any, Callable, Mapping, MutableMapping
15
+
16
+ __all__ = [
17
+ "LATENT_GLYPH",
18
+ "GlyphTiming",
19
+ "SigmaTrace",
20
+ "GlyphogramRow",
21
+ "GlyphTimingTotals",
22
+ "GlyphTimingByNode",
23
+ "_tg_state",
24
+ "for_each_glyph",
25
+ "_update_tg_node",
26
+ "_update_tg",
27
+ "_update_glyphogram",
28
+ "_update_latency_index",
29
+ "_update_epi_support",
30
+ "_update_morph_metrics",
31
+ "_compute_advanced_metrics",
32
+ ]
33
+
34
+ LATENT_GLYPH: str
35
+
36
+ @dataclass
37
+ class GlyphTiming:
38
+ curr: str | None = ...
39
+ run: float = ...
40
+
41
+ def _tg_state(nd: MutableMapping[str, Any]) -> GlyphTiming: ...
42
+ def for_each_glyph(fn: Callable[[str], None]) -> None: ...
43
+ def _update_tg_node(
44
+ n: Any,
45
+ nd: MutableMapping[str, Any],
46
+ dt: float,
47
+ tg_total: GlyphTimingTotals,
48
+ tg_by_node: GlyphTimingByNode | None,
49
+ ) -> tuple[str | None, bool]: ...
50
+ def _update_tg(
51
+ G: GraphLike,
52
+ hist: GlyphMetricsHistory,
53
+ dt: float,
54
+ save_by_node: bool,
55
+ n_jobs: int | None = None,
56
+ ) -> tuple[Counter[str], int, int]: ...
57
+ def _update_glyphogram(
58
+ G: GraphLike, hist: GlyphMetricsHistory, counts: GlyphCounts, t: float, n_total: int
59
+ ) -> None: ...
60
+ def _update_latency_index(
61
+ G: GraphLike, hist: GlyphMetricsHistory, n_total: int, n_latent: int, t: float
62
+ ) -> None: ...
63
+ def _update_epi_support(
64
+ G: GraphLike,
65
+ hist: GlyphMetricsHistory,
66
+ t: float,
67
+ threshold: float = ...,
68
+ n_jobs: int | None = None,
69
+ ) -> None: ...
70
+ def _update_morph_metrics(
71
+ G: GraphLike, hist: GlyphMetricsHistory, counts: GlyphCounts, t: float
72
+ ) -> None: ...
73
+ def _compute_advanced_metrics(
74
+ G: GraphLike,
75
+ hist: GlyphMetricsHistory,
76
+ t: float,
77
+ dt: float,
78
+ cfg: Mapping[str, Any],
79
+ threshold: float = ...,
80
+ n_jobs: int | None = None,
81
+ ) -> None: ...