tnfr 3.0.3__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 (360) hide show
  1. tnfr/__init__.py +375 -56
  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 +723 -0
  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 +171 -0
  15. tnfr/cache.pyi +13 -0
  16. tnfr/cli/__init__.py +110 -0
  17. tnfr/cli/__init__.pyi +26 -0
  18. tnfr/cli/arguments.py +489 -0
  19. tnfr/cli/arguments.pyi +29 -0
  20. tnfr/cli/execution.py +914 -0
  21. tnfr/cli/execution.pyi +70 -0
  22. tnfr/cli/interactive_validator.py +614 -0
  23. tnfr/cli/utils.py +51 -0
  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/config/constants.py +104 -0
  34. tnfr/config/constants.pyi +12 -0
  35. tnfr/config/defaults.py +54 -0
  36. tnfr/config/defaults_core.py +212 -0
  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 +92 -0
  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 +33 -0
  57. tnfr/constants/init.pyi +12 -0
  58. tnfr/constants/metric.py +104 -0
  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 +238 -0
  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 +3034 -0
  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 +661 -0
  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 +36 -0
  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 +223 -0
  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 +262 -0
  125. tnfr/flatten.pyi +21 -0
  126. tnfr/gamma.py +354 -0
  127. tnfr/gamma.pyi +36 -0
  128. tnfr/glyph_history.py +377 -0
  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 +218 -0
  133. tnfr/immutable.pyi +36 -0
  134. tnfr/initialization.py +203 -0
  135. tnfr/initialization.pyi +65 -0
  136. tnfr/io.py +10 -0
  137. tnfr/io.pyi +13 -0
  138. tnfr/locking.py +37 -0
  139. tnfr/locking.pyi +7 -0
  140. tnfr/mathematics/__init__.py +79 -0
  141. tnfr/mathematics/backend.py +453 -0
  142. tnfr/mathematics/backend.pyi +99 -0
  143. tnfr/mathematics/dynamics.py +408 -0
  144. tnfr/mathematics/dynamics.pyi +90 -0
  145. tnfr/mathematics/epi.py +391 -0
  146. tnfr/mathematics/epi.pyi +65 -0
  147. tnfr/mathematics/generators.py +242 -0
  148. tnfr/mathematics/generators.pyi +29 -0
  149. tnfr/mathematics/metrics.py +119 -0
  150. tnfr/mathematics/metrics.pyi +16 -0
  151. tnfr/mathematics/operators.py +239 -0
  152. tnfr/mathematics/operators.pyi +59 -0
  153. tnfr/mathematics/operators_factory.py +124 -0
  154. tnfr/mathematics/operators_factory.pyi +11 -0
  155. tnfr/mathematics/projection.py +87 -0
  156. tnfr/mathematics/projection.pyi +33 -0
  157. tnfr/mathematics/runtime.py +182 -0
  158. tnfr/mathematics/runtime.pyi +64 -0
  159. tnfr/mathematics/spaces.py +256 -0
  160. tnfr/mathematics/spaces.pyi +83 -0
  161. tnfr/mathematics/transforms.py +305 -0
  162. tnfr/mathematics/transforms.pyi +62 -0
  163. tnfr/metrics/__init__.py +79 -0
  164. tnfr/metrics/__init__.pyi +20 -0
  165. tnfr/metrics/buffer_cache.py +163 -0
  166. tnfr/metrics/buffer_cache.pyi +24 -0
  167. tnfr/metrics/cache_utils.py +214 -0
  168. tnfr/metrics/coherence.py +2009 -0
  169. tnfr/metrics/coherence.pyi +129 -0
  170. tnfr/metrics/common.py +158 -0
  171. tnfr/metrics/common.pyi +35 -0
  172. tnfr/metrics/core.py +316 -0
  173. tnfr/metrics/core.pyi +13 -0
  174. tnfr/metrics/diagnosis.py +833 -0
  175. tnfr/metrics/diagnosis.pyi +86 -0
  176. tnfr/metrics/emergence.py +245 -0
  177. tnfr/metrics/export.py +179 -0
  178. tnfr/metrics/export.pyi +7 -0
  179. tnfr/metrics/glyph_timing.py +379 -0
  180. tnfr/metrics/glyph_timing.pyi +81 -0
  181. tnfr/metrics/learning_metrics.py +280 -0
  182. tnfr/metrics/learning_metrics.pyi +21 -0
  183. tnfr/metrics/phase_coherence.py +351 -0
  184. tnfr/metrics/phase_compatibility.py +349 -0
  185. tnfr/metrics/reporting.py +183 -0
  186. tnfr/metrics/reporting.pyi +25 -0
  187. tnfr/metrics/sense_index.py +1203 -0
  188. tnfr/metrics/sense_index.pyi +9 -0
  189. tnfr/metrics/trig.py +373 -0
  190. tnfr/metrics/trig.pyi +13 -0
  191. tnfr/metrics/trig_cache.py +233 -0
  192. tnfr/metrics/trig_cache.pyi +10 -0
  193. tnfr/multiscale/__init__.py +32 -0
  194. tnfr/multiscale/hierarchical.py +517 -0
  195. tnfr/node.py +763 -0
  196. tnfr/node.pyi +139 -0
  197. tnfr/observers.py +255 -130
  198. tnfr/observers.pyi +31 -0
  199. tnfr/ontosim.py +144 -137
  200. tnfr/ontosim.pyi +28 -0
  201. tnfr/operators/__init__.py +1672 -0
  202. tnfr/operators/__init__.pyi +31 -0
  203. tnfr/operators/algebra.py +277 -0
  204. tnfr/operators/canonical_patterns.py +420 -0
  205. tnfr/operators/cascade.py +267 -0
  206. tnfr/operators/cycle_detection.py +358 -0
  207. tnfr/operators/definitions.py +4108 -0
  208. tnfr/operators/definitions.pyi +78 -0
  209. tnfr/operators/grammar.py +1164 -0
  210. tnfr/operators/grammar.pyi +140 -0
  211. tnfr/operators/hamiltonian.py +710 -0
  212. tnfr/operators/health_analyzer.py +809 -0
  213. tnfr/operators/jitter.py +272 -0
  214. tnfr/operators/jitter.pyi +11 -0
  215. tnfr/operators/lifecycle.py +314 -0
  216. tnfr/operators/metabolism.py +618 -0
  217. tnfr/operators/metrics.py +2138 -0
  218. tnfr/operators/network_analysis/__init__.py +27 -0
  219. tnfr/operators/network_analysis/source_detection.py +186 -0
  220. tnfr/operators/nodal_equation.py +395 -0
  221. tnfr/operators/pattern_detection.py +660 -0
  222. tnfr/operators/patterns.py +669 -0
  223. tnfr/operators/postconditions/__init__.py +38 -0
  224. tnfr/operators/postconditions/mutation.py +236 -0
  225. tnfr/operators/preconditions/__init__.py +1226 -0
  226. tnfr/operators/preconditions/coherence.py +305 -0
  227. tnfr/operators/preconditions/dissonance.py +236 -0
  228. tnfr/operators/preconditions/emission.py +128 -0
  229. tnfr/operators/preconditions/mutation.py +580 -0
  230. tnfr/operators/preconditions/reception.py +125 -0
  231. tnfr/operators/preconditions/resonance.py +364 -0
  232. tnfr/operators/registry.py +74 -0
  233. tnfr/operators/registry.pyi +9 -0
  234. tnfr/operators/remesh.py +1809 -0
  235. tnfr/operators/remesh.pyi +26 -0
  236. tnfr/operators/structural_units.py +268 -0
  237. tnfr/operators/unified_grammar.py +105 -0
  238. tnfr/parallel/__init__.py +54 -0
  239. tnfr/parallel/auto_scaler.py +234 -0
  240. tnfr/parallel/distributed.py +384 -0
  241. tnfr/parallel/engine.py +238 -0
  242. tnfr/parallel/gpu_engine.py +420 -0
  243. tnfr/parallel/monitoring.py +248 -0
  244. tnfr/parallel/partitioner.py +459 -0
  245. tnfr/py.typed +0 -0
  246. tnfr/recipes/__init__.py +22 -0
  247. tnfr/recipes/cookbook.py +743 -0
  248. tnfr/rng.py +178 -0
  249. tnfr/rng.pyi +26 -0
  250. tnfr/schemas/__init__.py +8 -0
  251. tnfr/schemas/grammar.json +94 -0
  252. tnfr/sdk/__init__.py +107 -0
  253. tnfr/sdk/__init__.pyi +19 -0
  254. tnfr/sdk/adaptive_system.py +173 -0
  255. tnfr/sdk/adaptive_system.pyi +21 -0
  256. tnfr/sdk/builders.py +370 -0
  257. tnfr/sdk/builders.pyi +51 -0
  258. tnfr/sdk/fluent.py +1121 -0
  259. tnfr/sdk/fluent.pyi +74 -0
  260. tnfr/sdk/templates.py +342 -0
  261. tnfr/sdk/templates.pyi +41 -0
  262. tnfr/sdk/utils.py +341 -0
  263. tnfr/secure_config.py +46 -0
  264. tnfr/security/__init__.py +70 -0
  265. tnfr/security/database.py +514 -0
  266. tnfr/security/subprocess.py +503 -0
  267. tnfr/security/validation.py +290 -0
  268. tnfr/selector.py +247 -0
  269. tnfr/selector.pyi +19 -0
  270. tnfr/sense.py +378 -0
  271. tnfr/sense.pyi +23 -0
  272. tnfr/services/__init__.py +17 -0
  273. tnfr/services/orchestrator.py +325 -0
  274. tnfr/sparse/__init__.py +39 -0
  275. tnfr/sparse/representations.py +492 -0
  276. tnfr/structural.py +705 -0
  277. tnfr/structural.pyi +83 -0
  278. tnfr/telemetry/__init__.py +35 -0
  279. tnfr/telemetry/cache_metrics.py +226 -0
  280. tnfr/telemetry/cache_metrics.pyi +64 -0
  281. tnfr/telemetry/nu_f.py +422 -0
  282. tnfr/telemetry/nu_f.pyi +108 -0
  283. tnfr/telemetry/verbosity.py +36 -0
  284. tnfr/telemetry/verbosity.pyi +15 -0
  285. tnfr/tokens.py +58 -0
  286. tnfr/tokens.pyi +36 -0
  287. tnfr/tools/__init__.py +20 -0
  288. tnfr/tools/domain_templates.py +478 -0
  289. tnfr/tools/sequence_generator.py +846 -0
  290. tnfr/topology/__init__.py +13 -0
  291. tnfr/topology/asymmetry.py +151 -0
  292. tnfr/trace.py +543 -0
  293. tnfr/trace.pyi +42 -0
  294. tnfr/tutorials/__init__.py +38 -0
  295. tnfr/tutorials/autonomous_evolution.py +285 -0
  296. tnfr/tutorials/interactive.py +1576 -0
  297. tnfr/tutorials/structural_metabolism.py +238 -0
  298. tnfr/types.py +775 -0
  299. tnfr/types.pyi +357 -0
  300. tnfr/units.py +68 -0
  301. tnfr/units.pyi +13 -0
  302. tnfr/utils/__init__.py +282 -0
  303. tnfr/utils/__init__.pyi +215 -0
  304. tnfr/utils/cache.py +4223 -0
  305. tnfr/utils/cache.pyi +470 -0
  306. tnfr/utils/callbacks.py +375 -0
  307. tnfr/utils/callbacks.pyi +49 -0
  308. tnfr/utils/chunks.py +108 -0
  309. tnfr/utils/chunks.pyi +22 -0
  310. tnfr/utils/data.py +428 -0
  311. tnfr/utils/data.pyi +74 -0
  312. tnfr/utils/graph.py +85 -0
  313. tnfr/utils/graph.pyi +10 -0
  314. tnfr/utils/init.py +821 -0
  315. tnfr/utils/init.pyi +80 -0
  316. tnfr/utils/io.py +559 -0
  317. tnfr/utils/io.pyi +66 -0
  318. tnfr/utils/numeric.py +114 -0
  319. tnfr/utils/numeric.pyi +21 -0
  320. tnfr/validation/__init__.py +257 -0
  321. tnfr/validation/__init__.pyi +85 -0
  322. tnfr/validation/compatibility.py +460 -0
  323. tnfr/validation/compatibility.pyi +6 -0
  324. tnfr/validation/config.py +73 -0
  325. tnfr/validation/graph.py +139 -0
  326. tnfr/validation/graph.pyi +18 -0
  327. tnfr/validation/input_validation.py +755 -0
  328. tnfr/validation/invariants.py +712 -0
  329. tnfr/validation/rules.py +253 -0
  330. tnfr/validation/rules.pyi +44 -0
  331. tnfr/validation/runtime.py +279 -0
  332. tnfr/validation/runtime.pyi +28 -0
  333. tnfr/validation/sequence_validator.py +162 -0
  334. tnfr/validation/soft_filters.py +170 -0
  335. tnfr/validation/soft_filters.pyi +32 -0
  336. tnfr/validation/spectral.py +164 -0
  337. tnfr/validation/spectral.pyi +42 -0
  338. tnfr/validation/validator.py +1266 -0
  339. tnfr/validation/window.py +39 -0
  340. tnfr/validation/window.pyi +1 -0
  341. tnfr/visualization/__init__.py +98 -0
  342. tnfr/visualization/cascade_viz.py +256 -0
  343. tnfr/visualization/hierarchy.py +284 -0
  344. tnfr/visualization/sequence_plotter.py +784 -0
  345. tnfr/viz/__init__.py +60 -0
  346. tnfr/viz/matplotlib.py +278 -0
  347. tnfr/viz/matplotlib.pyi +35 -0
  348. tnfr-8.5.0.dist-info/METADATA +573 -0
  349. tnfr-8.5.0.dist-info/RECORD +353 -0
  350. tnfr-8.5.0.dist-info/entry_points.txt +3 -0
  351. tnfr-3.0.3.dist-info/licenses/LICENSE.txt → tnfr-8.5.0.dist-info/licenses/LICENSE.md +1 -1
  352. tnfr/constants.py +0 -183
  353. tnfr/dynamics.py +0 -543
  354. tnfr/helpers.py +0 -198
  355. tnfr/main.py +0 -37
  356. tnfr/operators.py +0 -296
  357. tnfr-3.0.3.dist-info/METADATA +0 -35
  358. tnfr-3.0.3.dist-info/RECORD +0 -13
  359. {tnfr-3.0.3.dist-info → tnfr-8.5.0.dist-info}/WHEEL +0 -0
  360. {tnfr-3.0.3.dist-info → tnfr-8.5.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,379 @@
1
+ """Glyph timing utilities and advanced metrics."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ from collections import Counter, defaultdict
7
+ from concurrent.futures import ProcessPoolExecutor
8
+ from dataclasses import dataclass
9
+ from types import ModuleType
10
+ from typing import (
11
+ Any,
12
+ Callable,
13
+ Mapping,
14
+ MutableMapping,
15
+ Sequence,
16
+ cast,
17
+ )
18
+
19
+ from ..alias import get_attr
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
39
+
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
44
+
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
68
+
69
+
70
+ @dataclass
71
+ class GlyphTiming:
72
+ """Mutable accumulator tracking the active glyph and its dwell time."""
73
+
74
+ curr: str | None = None
75
+ run: float = 0.0
76
+
77
+
78
+ __all__ = [
79
+ "LATENT_GLYPH",
80
+ "GlyphTiming",
81
+ "SigmaTrace",
82
+ "GlyphogramRow",
83
+ "GlyphTimingTotals",
84
+ "GlyphTimingByNode",
85
+ "_tg_state",
86
+ "for_each_glyph",
87
+ "_update_tg_node",
88
+ "_update_tg",
89
+ "_update_glyphogram",
90
+ "_update_latency_index",
91
+ "_update_epi_support",
92
+ "_update_morph_metrics",
93
+ "_compute_advanced_metrics",
94
+ ]
95
+
96
+ # ---------------------------------------------------------------------------
97
+ # Internal utilities
98
+ # ---------------------------------------------------------------------------
99
+
100
+
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:
123
+ """Expose per-node glyph timing state."""
124
+
125
+ return nd.setdefault("_Tg", GlyphTiming())
126
+
127
+
128
+ def for_each_glyph(fn: Callable[[str], None]) -> None:
129
+ """Apply ``fn`` to each canonical structural operator."""
130
+
131
+ for g in GLYPHS_CANONICAL:
132
+ fn(g)
133
+
134
+
135
+ # ---------------------------------------------------------------------------
136
+ # Glyph timing helpers
137
+ # ---------------------------------------------------------------------------
138
+
139
+
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]:
147
+ """Track a node's glyph transition and accumulate run time."""
148
+
149
+ g = last_glyph(nd)
150
+ if not g:
151
+ return None, False
152
+ st = _tg_state(nd)
153
+ curr = st.curr
154
+ if curr is None:
155
+ st.curr = g
156
+ st.run = dt
157
+ elif g == curr:
158
+ st.run += dt
159
+ else:
160
+ dur = st.run
161
+ tg_total[curr] += dur
162
+ if tg_by_node is not None:
163
+ tg_by_node[n][curr].append(dur)
164
+ st.curr = g
165
+ st.run = dt
166
+ return g, g == LATENT_GLYPH
167
+
168
+
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]:
176
+ """Accumulate glyph dwell times for the entire graph."""
177
+
178
+ tg_total = cast(GlyphTimingTotals, hist.setdefault("Tg_total", defaultdict(float)))
179
+ tg_by_node = (
180
+ cast(
181
+ GlyphTimingByNode,
182
+ hist.setdefault(
183
+ "Tg_by_node",
184
+ defaultdict(lambda: defaultdict(list)),
185
+ ),
186
+ )
187
+ if save_by_node
188
+ else None
189
+ )
190
+
191
+ n_total = 0
192
+ n_latent = 0
193
+ glyph_sequence: list[str] = []
194
+ for n, nd in G.nodes(data=True):
195
+ g, is_latent = _update_tg_node(n, nd, dt, tg_total, tg_by_node)
196
+ if g is None:
197
+ continue
198
+ n_total += 1
199
+ if is_latent:
200
+ n_latent += 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
+
238
+ return counts, n_total, n_latent
239
+
240
+
241
+ def _update_glyphogram(
242
+ G: GraphLike,
243
+ hist: GlyphMetricsHistory,
244
+ counts: GlyphCounts,
245
+ t: float,
246
+ n_total: int,
247
+ ) -> None:
248
+ """Record glyphogram row from glyph counts."""
249
+
250
+ normalize_series = bool(get_param(G, "METRICS").get("normalize_series", False))
251
+ row: GlyphogramRow = {"t": t}
252
+ total = max(1, n_total)
253
+ for g in GLYPHS_CANONICAL:
254
+ c = counts.get(g, 0)
255
+ row[g] = (c / total) if normalize_series else c
256
+ append_metric(cast(MetricsListHistory, hist), "glyphogram", row)
257
+
258
+
259
+ def _update_latency_index(
260
+ G: GraphLike,
261
+ hist: GlyphMetricsHistory,
262
+ n_total: int,
263
+ n_latent: int,
264
+ t: float,
265
+ ) -> None:
266
+ """Record latency index for the current step."""
267
+
268
+ li = n_latent / max(1, n_total)
269
+ append_metric(
270
+ cast(MetricsListHistory, hist),
271
+ "latency_index",
272
+ {"t": t, "value": li},
273
+ )
274
+
275
+
276
+ def _update_epi_support(
277
+ G: GraphLike,
278
+ hist: GlyphMetricsHistory,
279
+ t: float,
280
+ threshold: float = DEFAULT_EPI_SUPPORT_LIMIT,
281
+ n_jobs: int | None = None,
282
+ ) -> None:
283
+ """Measure EPI support and normalized magnitude."""
284
+
285
+ node_count = G.number_of_nodes()
286
+ total = 0.0
287
+ count = 0
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
330
+ epi_norm = (total / count) if count else 0.0
331
+ append_metric(
332
+ cast(MetricsListHistory, hist),
333
+ "EPI_support",
334
+ {"t": t, "size": count, "epi_norm": float(epi_norm)},
335
+ )
336
+
337
+
338
+ def _update_morph_metrics(
339
+ G: GraphLike,
340
+ hist: GlyphMetricsHistory,
341
+ counts: GlyphCounts,
342
+ t: float,
343
+ ) -> None:
344
+ """Capture morphosyntactic distribution of glyphs."""
345
+
346
+ def get_count(keys: Sequence[str]) -> int:
347
+ return sum(counts.get(k, 0) for k in keys)
348
+
349
+ total = max(1, sum(counts.values()))
350
+ id_val = get_count(GLYPH_GROUPS.get("ID", ())) / total
351
+ cm_val = get_count(GLYPH_GROUPS.get("CM", ())) / total
352
+ ne_val = get_count(GLYPH_GROUPS.get("NE", ())) / total
353
+ num = get_count(GLYPH_GROUPS.get("PP_num", ()))
354
+ den = get_count(GLYPH_GROUPS.get("PP_den", ()))
355
+ pp_val = 0.0 if den == 0 else num / den
356
+ append_metric(
357
+ cast(MetricsListHistory, hist),
358
+ "morph",
359
+ {"t": t, "ID": id_val, "CM": cm_val, "NE": ne_val, "PP": pp_val},
360
+ )
361
+
362
+
363
+ def _compute_advanced_metrics(
364
+ G: GraphLike,
365
+ hist: GlyphMetricsHistory,
366
+ t: float,
367
+ dt: float,
368
+ cfg: Mapping[str, Any],
369
+ threshold: float = DEFAULT_EPI_SUPPORT_LIMIT,
370
+ n_jobs: int | None = None,
371
+ ) -> None:
372
+ """Compute glyph timing derived metrics."""
373
+
374
+ save_by_node = bool(cfg.get("save_by_node", True))
375
+ counts, n_total, n_latent = _update_tg(G, hist, dt, save_by_node, n_jobs=n_jobs)
376
+ _update_glyphogram(G, hist, counts, t, n_total)
377
+ _update_latency_index(G, hist, n_total, n_latent, t)
378
+ _update_epi_support(G, hist, t, threshold, n_jobs=n_jobs)
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: ...