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
@@ -0,0 +1,809 @@
1
+ """Structural health metrics analyzer for TNFR operator sequences.
2
+
3
+ Provides quantitative assessment of sequence structural quality through
4
+ canonical TNFR metrics: coherence, balance, sustainability, and efficiency.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from functools import lru_cache
10
+ from typing import Any, List, Tuple, TYPE_CHECKING
11
+
12
+ if TYPE_CHECKING:
13
+ from ..types import TNFRGraph
14
+
15
+ from ..compat.dataclass import dataclass
16
+ from ..config.operator_names import (
17
+ COHERENCE,
18
+ DISSONANCE,
19
+ RECURSIVITY,
20
+ RESONANCE,
21
+ SELF_ORGANIZATION,
22
+ SILENCE,
23
+ TRANSITION,
24
+ DESTABILIZERS,
25
+ TRANSFORMERS,
26
+ )
27
+
28
+ __all__ = [
29
+ "SequenceHealthMetrics",
30
+ "SequenceHealthAnalyzer",
31
+ ]
32
+
33
+
34
+ # Operator categories for health analysis
35
+ _STABILIZERS = frozenset({COHERENCE, SELF_ORGANIZATION, SILENCE, RESONANCE})
36
+ _REGENERATORS = frozenset({TRANSITION, RECURSIVITY}) # NAV, REMESH
37
+
38
+
39
+ @dataclass
40
+ class SequenceHealthMetrics:
41
+ """Structural health metrics for a TNFR operator sequence.
42
+
43
+ All metrics range from 0.0 (poor) to 1.0 (excellent), measuring different
44
+ aspects of sequence structural quality according to TNFR principles.
45
+
46
+ Attributes
47
+ ----------
48
+ coherence_index : float
49
+ Global sequential flow quality (0.0-1.0). Measures how well operators
50
+ transition and whether the sequence forms a recognizable pattern.
51
+ balance_score : float
52
+ Equilibrium between stabilizers and destabilizers (0.0-1.0). Ideal
53
+ sequences have balanced structural forces.
54
+ sustainability_index : float
55
+ Capacity for long-term maintenance (0.0-1.0). Considers final stabilization,
56
+ resolved dissonance, and regenerative elements.
57
+ complexity_efficiency : float
58
+ Value-to-complexity ratio (0.0-1.0). Penalizes unnecessarily long sequences
59
+ that don't provide proportional structural value.
60
+ frequency_harmony : float
61
+ Structural frequency transition smoothness (0.0-1.0). High when transitions
62
+ respect νf harmonics.
63
+ pattern_completeness : float
64
+ How complete the detected pattern is (0.0-1.0). Full cycles score higher.
65
+ transition_smoothness : float
66
+ Quality of operator transitions (0.0-1.0). Measures valid transitions vs
67
+ total transitions.
68
+ overall_health : float
69
+ Composite health index (0.0-1.0). Weighted average of primary metrics.
70
+ sequence_length : int
71
+ Number of operators in the sequence.
72
+ dominant_pattern : str
73
+ Detected structural pattern type (e.g., "activation", "therapeutic", "unknown").
74
+ recommendations : List[str]
75
+ Specific suggestions for improving sequence health.
76
+ """
77
+
78
+ coherence_index: float
79
+ balance_score: float
80
+ sustainability_index: float
81
+ complexity_efficiency: float
82
+ frequency_harmony: float
83
+ pattern_completeness: float
84
+ transition_smoothness: float
85
+ overall_health: float
86
+ sequence_length: int
87
+ dominant_pattern: str
88
+ recommendations: List[str]
89
+
90
+
91
+ class SequenceHealthAnalyzer:
92
+ """Analyzer for structural health of TNFR operator sequences.
93
+
94
+ Evaluates sequences along multiple dimensions to provide quantitative
95
+ assessment of structural quality, coherence, and sustainability.
96
+
97
+ Uses caching to optimize repeated analysis of identical sequences,
98
+ which is common in pattern exploration and batch validation workflows.
99
+
100
+ Examples
101
+ --------
102
+ >>> from tnfr.operators.health_analyzer import SequenceHealthAnalyzer
103
+ >>> analyzer = SequenceHealthAnalyzer()
104
+ >>> sequence = ["emission", "reception", "coherence", "silence"]
105
+ >>> health = analyzer.analyze_health(sequence)
106
+ >>> health.overall_health
107
+ 0.82
108
+ >>> health.recommendations
109
+ []
110
+ """
111
+
112
+ def __init__(self) -> None:
113
+ """Initialize the health analyzer with caching support."""
114
+ self._recommendations: List[str] = []
115
+ # Cache for single-pass analysis results keyed by sequence tuple
116
+ # Using maxsize=128 to avoid unbounded growth while caching common sequences
117
+ self._analysis_cache = lru_cache(maxsize=128)(self._compute_single_pass)
118
+
119
+ def _compute_single_pass(
120
+ self, sequence_tuple: Tuple[str, ...]
121
+ ) -> Tuple[int, int, int, int, int, List[Tuple[str, str]]]:
122
+ """Compute sequence statistics in a single pass for efficiency.
123
+
124
+ This method scans the sequence once and extracts all the information
125
+ needed for the various health metrics, avoiding redundant iterations.
126
+
127
+ Parameters
128
+ ----------
129
+ sequence_tuple : Tuple[str, ...]
130
+ Immutable sequence of operators (tuple for hashability in cache).
131
+
132
+ Returns
133
+ -------
134
+ Tuple containing:
135
+ - stabilizer_count: int
136
+ - destabilizer_count: int
137
+ - transformer_count: int
138
+ - regenerator_count: int
139
+ - unique_ops: int
140
+ - problematic_transitions: List[(op1, op2)] pairs
141
+
142
+ Notes
143
+ -----
144
+ This function is cached using lru_cache to optimize repeated analysis
145
+ of identical sequences, which is common in batch validation and
146
+ pattern exploration workflows.
147
+ """
148
+ sequence = list(sequence_tuple)
149
+
150
+ # Initialize counters
151
+ stabilizer_count = 0
152
+ destabilizer_count = 0
153
+ transformer_count = 0
154
+ regenerator_count = 0
155
+ unique_ops_set = set()
156
+ problematic_transitions = []
157
+
158
+ # Single pass through sequence
159
+ for i, op in enumerate(sequence):
160
+ unique_ops_set.add(op)
161
+
162
+ # Count operator categories
163
+ if op in _STABILIZERS:
164
+ stabilizer_count += 1
165
+ if op in DESTABILIZERS:
166
+ destabilizer_count += 1
167
+ if op in TRANSFORMERS:
168
+ transformer_count += 1
169
+ if op in _REGENERATORS:
170
+ regenerator_count += 1
171
+
172
+ # Check transitions
173
+ if i < len(sequence) - 1:
174
+ next_op = sequence[i + 1]
175
+ # Destabilizer → destabilizer is problematic
176
+ if op in DESTABILIZERS and next_op in DESTABILIZERS:
177
+ problematic_transitions.append((op, next_op))
178
+
179
+ return (
180
+ stabilizer_count,
181
+ destabilizer_count,
182
+ transformer_count,
183
+ regenerator_count,
184
+ len(unique_ops_set),
185
+ problematic_transitions,
186
+ )
187
+
188
+ def analyze_health(self, sequence: List[str]) -> SequenceHealthMetrics:
189
+ """Perform complete structural health analysis of a sequence.
190
+
191
+ Parameters
192
+ ----------
193
+ sequence : List[str]
194
+ Operator sequence to analyze (canonical names like "emission", "coherence").
195
+
196
+ Returns
197
+ -------
198
+ SequenceHealthMetrics
199
+ Comprehensive health metrics for the sequence.
200
+
201
+ Examples
202
+ --------
203
+ >>> analyzer = SequenceHealthAnalyzer()
204
+ >>> health = analyzer.analyze_health(["emission", "reception", "coherence", "silence"])
205
+ >>> health.coherence_index > 0.7
206
+ True
207
+ """
208
+ self._recommendations = []
209
+
210
+ # Use single-pass analysis for efficiency (cached)
211
+ sequence_tuple = tuple(sequence)
212
+ analysis = self._analysis_cache(sequence_tuple)
213
+
214
+ # Extract results from single-pass analysis
215
+ (
216
+ stabilizer_count,
217
+ destabilizer_count,
218
+ transformer_count,
219
+ regenerator_count,
220
+ unique_count,
221
+ problematic_transitions,
222
+ ) = analysis
223
+
224
+ coherence = self._calculate_coherence(sequence, problematic_transitions)
225
+ balance = self._calculate_balance(
226
+ sequence, stabilizer_count, destabilizer_count
227
+ )
228
+ sustainability = self._calculate_sustainability(
229
+ sequence, stabilizer_count, destabilizer_count, regenerator_count
230
+ )
231
+ efficiency = self._calculate_efficiency(sequence, unique_count)
232
+ frequency = self._calculate_frequency_harmony(sequence)
233
+ completeness = self._calculate_completeness(
234
+ sequence, stabilizer_count, destabilizer_count, transformer_count
235
+ )
236
+ smoothness = self._calculate_smoothness(sequence, problematic_transitions)
237
+
238
+ # Calculate overall health as weighted average
239
+ # Primary metrics weighted more heavily
240
+ overall = (
241
+ coherence * 0.20
242
+ + balance * 0.20
243
+ + sustainability * 0.20
244
+ + efficiency * 0.15
245
+ + frequency * 0.10
246
+ + completeness * 0.10
247
+ + smoothness * 0.05
248
+ )
249
+
250
+ pattern = self._detect_pattern(sequence)
251
+
252
+ return SequenceHealthMetrics(
253
+ coherence_index=coherence,
254
+ balance_score=balance,
255
+ sustainability_index=sustainability,
256
+ complexity_efficiency=efficiency,
257
+ frequency_harmony=frequency,
258
+ pattern_completeness=completeness,
259
+ transition_smoothness=smoothness,
260
+ overall_health=overall,
261
+ sequence_length=len(sequence),
262
+ dominant_pattern=pattern,
263
+ recommendations=self._recommendations.copy(),
264
+ )
265
+
266
+ def _calculate_coherence(
267
+ self, sequence: List[str], problematic_transitions: List[Tuple[str, str]]
268
+ ) -> float:
269
+ """Calculate coherence index: how well the sequence flows.
270
+
271
+ Factors:
272
+ - Valid transitions between operators
273
+ - Recognizable pattern structure
274
+ - Structural closure (proper ending)
275
+
276
+ Parameters
277
+ ----------
278
+ sequence : List[str]
279
+ Operator sequence
280
+ problematic_transitions : List[Tuple[str, str]]
281
+ Pre-computed list of problematic transition pairs
282
+
283
+ Returns
284
+ -------
285
+ float
286
+ Coherence score (0.0-1.0)
287
+ """
288
+ if not sequence:
289
+ return 0.0
290
+
291
+ # Transition quality: use pre-computed problematic transitions
292
+ if len(sequence) < 2:
293
+ transition_quality = 1.0
294
+ else:
295
+ total_transitions = len(sequence) - 1
296
+ # Each problematic transition gets 0.5 penalty
297
+ penalty = len(problematic_transitions) * 0.5
298
+ transition_quality = max(0.0, 1.0 - (penalty / total_transitions))
299
+
300
+ # Pattern clarity: does it form a recognizable structure?
301
+ pattern_clarity = self._assess_pattern_clarity(sequence)
302
+
303
+ # Structural closure: does it end properly?
304
+ structural_closure = self._assess_closure(sequence)
305
+
306
+ return (transition_quality + pattern_clarity + structural_closure) / 3.0
307
+
308
+ def _calculate_balance(
309
+ self, sequence: List[str], stabilizer_count: int, destabilizer_count: int
310
+ ) -> float:
311
+ """Calculate balance score: equilibrium between stabilizers and destabilizers.
312
+
313
+ Ideal sequences have roughly equal stabilization and transformation forces.
314
+ Severe imbalance reduces structural health.
315
+
316
+ Parameters
317
+ ----------
318
+ sequence : List[str]
319
+ Operator sequence
320
+ stabilizer_count : int
321
+ Pre-computed count of stabilizing operators
322
+ destabilizer_count : int
323
+ Pre-computed count of destabilizing operators
324
+
325
+ Returns
326
+ -------
327
+ float
328
+ Balance score (0.0-1.0)
329
+ """
330
+ if not sequence:
331
+ return 0.5 # Neutral for empty
332
+
333
+ # If neither present, neutral balance
334
+ if stabilizer_count == 0 and destabilizer_count == 0:
335
+ return 0.5
336
+
337
+ # Calculate ratio: closer to 1.0 means better balance
338
+ max_count = max(stabilizer_count, destabilizer_count)
339
+ min_count = min(stabilizer_count, destabilizer_count)
340
+
341
+ if max_count == 0:
342
+ return 0.5
343
+
344
+ ratio = min_count / max_count
345
+
346
+ # Penalize severe imbalance (difference > half the sequence length)
347
+ imbalance = abs(stabilizer_count - destabilizer_count)
348
+ if imbalance > len(sequence) // 2:
349
+ ratio *= 0.7 # Apply penalty
350
+ self._recommendations.append(
351
+ "Severe imbalance detected: add stabilizers or reduce destabilizers"
352
+ )
353
+
354
+ return ratio
355
+
356
+ def _calculate_sustainability(
357
+ self,
358
+ sequence: List[str],
359
+ stabilizer_count: int,
360
+ destabilizer_count: int,
361
+ regenerator_count: int,
362
+ ) -> float:
363
+ """Calculate sustainability index: capacity to maintain without collapse.
364
+
365
+ Factors:
366
+ - Final operator is a stabilizer
367
+ - Dissonance is resolved (not left unbalanced)
368
+ - Contains regenerative elements
369
+
370
+ Parameters
371
+ ----------
372
+ sequence : List[str]
373
+ Operator sequence
374
+ stabilizer_count : int
375
+ Pre-computed count of stabilizing operators
376
+ destabilizer_count : int
377
+ Pre-computed count of destabilizing operators
378
+ regenerator_count : int
379
+ Pre-computed count of regenerative operators
380
+
381
+ Returns
382
+ -------
383
+ float
384
+ Sustainability score (0.0-1.0)
385
+ """
386
+ if not sequence:
387
+ return 0.0
388
+
389
+ sustainability = 0.0
390
+
391
+ # Factor 1: Ends with stabilizer (0.4 points)
392
+ has_final_stabilizer = sequence[-1] in _STABILIZERS
393
+ if has_final_stabilizer:
394
+ sustainability += 0.4
395
+ else:
396
+ sustainability += 0.1 # Some credit for other endings
397
+ self._recommendations.append(
398
+ "Consider ending with a stabilizer (coherence, silence, resonance, or self_organization)"
399
+ )
400
+
401
+ # Factor 2: Resolved dissonance (0.3 points)
402
+ unresolved_dissonance = self._count_unresolved_dissonance(sequence)
403
+ if unresolved_dissonance == 0:
404
+ sustainability += 0.3
405
+ else:
406
+ penalty = min(0.3, unresolved_dissonance * 0.1)
407
+ sustainability += max(0, 0.3 - penalty)
408
+ if unresolved_dissonance > 1:
409
+ self._recommendations.append(
410
+ "Multiple unresolved dissonances detected: add stabilizers after destabilizing operators"
411
+ )
412
+
413
+ # Factor 3: Regenerative elements (0.3 points)
414
+ # Use pre-computed regenerator count
415
+ if regenerator_count > 0:
416
+ sustainability += 0.3
417
+ else:
418
+ sustainability += 0.1 # Some credit even without
419
+
420
+ return min(1.0, sustainability)
421
+
422
+ def _calculate_efficiency(self, sequence: List[str], unique_count: int) -> float:
423
+ """Calculate complexity efficiency: value achieved relative to length.
424
+
425
+ Penalizes unnecessarily long sequences that don't provide proportional value.
426
+
427
+ Parameters
428
+ ----------
429
+ sequence : List[str]
430
+ Operator sequence
431
+ unique_count : int
432
+ Pre-computed count of unique operators in sequence
433
+
434
+ Returns
435
+ -------
436
+ float
437
+ Efficiency score (0.0-1.0)
438
+ """
439
+ if not sequence:
440
+ return 0.0
441
+
442
+ # Assess structural value using pre-computed unique count
443
+ diversity_score = min(
444
+ 1.0, unique_count / 6.0
445
+ ) # 6+ operators is excellent diversity
446
+
447
+ # Note: We still need to call _assess_pattern_value for category coverage
448
+ # This is minimal overhead as it's a single pass checking set memberships
449
+ pattern_value = self._assess_pattern_value_optimized(sequence, unique_count)
450
+
451
+ # Length penalty: sequences longer than 10 operators get penalized
452
+ # Optimal range is 3-8 operators
453
+ length = len(sequence)
454
+ if length < 3:
455
+ length_factor = 0.7 # Too short, limited value
456
+ elif length <= 8:
457
+ length_factor = 1.0 # Optimal range
458
+ else:
459
+ # Gradual penalty for length > 8
460
+ excess = length - 8
461
+ length_factor = max(0.5, 1.0 - (excess * 0.05))
462
+
463
+ if length > 12:
464
+ self._recommendations.append(
465
+ f"Sequence is long ({length} operators): consider breaking into sub-sequences"
466
+ )
467
+
468
+ return pattern_value * length_factor
469
+
470
+ def _calculate_frequency_harmony(self, sequence: List[str]) -> float:
471
+ """Calculate frequency harmony: smoothness of νf transitions.
472
+
473
+ TODO: Full implementation requires integration with STRUCTURAL_FREQUENCIES
474
+ and FREQUENCY_TRANSITIONS from the grammar module. Currently returns
475
+ a conservative estimate based on transition patterns.
476
+
477
+ Parameters
478
+ ----------
479
+ sequence : List[str]
480
+ Operator sequence
481
+
482
+ Returns
483
+ -------
484
+ float
485
+ Harmony score (0.0-1.0)
486
+ """
487
+ # Conservative estimate: assume good harmony unless obvious issues detected
488
+ # Future enhancement: integrate with grammar.STRUCTURAL_FREQUENCIES
489
+ return 0.85
490
+
491
+ def _calculate_completeness(
492
+ self,
493
+ sequence: List[str],
494
+ stabilizer_count: int,
495
+ destabilizer_count: int,
496
+ transformer_count: int,
497
+ ) -> float:
498
+ """Calculate pattern completeness: how complete the pattern is.
499
+
500
+ Complete patterns (with activation, transformation, stabilization) score higher.
501
+
502
+ Parameters
503
+ ----------
504
+ sequence : List[str]
505
+ Operator sequence
506
+ stabilizer_count : int
507
+ Pre-computed count of stabilizing operators
508
+ destabilizer_count : int
509
+ Pre-computed count of destabilizing operators
510
+ transformer_count : int
511
+ Pre-computed count of transforming operators
512
+
513
+ Returns
514
+ -------
515
+ float
516
+ Completeness score (0.0-1.0)
517
+ """
518
+ if not sequence:
519
+ return 0.0
520
+
521
+ # Check for key phases using pre-computed counts and minimal checks
522
+ has_activation = any(op in {"emission", "reception"} for op in sequence)
523
+ has_transformation = destabilizer_count > 0 or transformer_count > 0
524
+ has_stabilization = stabilizer_count > 0
525
+ has_completion = any(op in {"silence", "transition"} for op in sequence)
526
+
527
+ phase_count = sum(
528
+ [has_activation, has_transformation, has_stabilization, has_completion]
529
+ )
530
+
531
+ # All 4 phases = 1.0, 3 phases = 0.75, 2 phases = 0.5, 1 phase = 0.25
532
+ return phase_count / 4.0
533
+
534
+ def _calculate_smoothness(
535
+ self, sequence: List[str], problematic_transitions: List[Tuple[str, str]]
536
+ ) -> float:
537
+ """Calculate transition smoothness: quality of operator transitions.
538
+
539
+ Measures ratio of valid/smooth transitions vs total transitions.
540
+
541
+ Parameters
542
+ ----------
543
+ sequence : List[str]
544
+ Operator sequence
545
+ problematic_transitions : List[Tuple[str, str]]
546
+ Pre-computed list of problematic transition pairs
547
+
548
+ Returns
549
+ -------
550
+ float
551
+ Smoothness score (0.0-1.0)
552
+ """
553
+ if len(sequence) < 2:
554
+ return 1.0 # No transitions to assess
555
+
556
+ total_transitions = len(sequence) - 1
557
+ # Each problematic transition gets 0.5 penalty (same as in _calculate_coherence)
558
+ penalty = len(problematic_transitions) * 0.5
559
+ return max(0.0, 1.0 - (penalty / total_transitions))
560
+
561
+ def _assess_pattern_clarity(self, sequence: List[str]) -> float:
562
+ """Assess how clearly the sequence forms a recognizable pattern.
563
+
564
+ Parameters
565
+ ----------
566
+ sequence : List[str]
567
+ Operator sequence
568
+
569
+ Returns
570
+ -------
571
+ float
572
+ Pattern clarity score (0.0-1.0)
573
+ """
574
+ if len(sequence) < 3:
575
+ return 0.5 # Too short for clear pattern
576
+
577
+ # Check for canonical patterns
578
+ pattern = self._detect_pattern(sequence)
579
+
580
+ if pattern in {"activation", "therapeutic", "regenerative", "transformative"}:
581
+ return 0.9 # Clear, recognized pattern
582
+ elif pattern in {"stabilization", "exploratory"}:
583
+ return 0.7 # Recognizable but simpler
584
+ else:
585
+ return 0.5 # No clear pattern
586
+
587
+ def _assess_closure(self, sequence: List[str]) -> float:
588
+ """Assess structural closure quality.
589
+
590
+ Parameters
591
+ ----------
592
+ sequence : List[str]
593
+ Operator sequence
594
+
595
+ Returns
596
+ -------
597
+ float
598
+ Closure quality score (0.0-1.0)
599
+ """
600
+ if not sequence:
601
+ return 0.0
602
+
603
+ # Valid endings per grammar
604
+ valid_endings = {SILENCE, TRANSITION, RECURSIVITY, DISSONANCE}
605
+
606
+ if sequence[-1] in valid_endings:
607
+ # Stabilizer endings are best
608
+ if sequence[-1] in _STABILIZERS:
609
+ return 1.0
610
+ # Other valid endings are good
611
+ return 0.8
612
+
613
+ # Invalid ending
614
+ return 0.3
615
+
616
+ def _count_unresolved_dissonance(self, sequence: List[str]) -> int:
617
+ """Count destabilizers not followed by stabilizers within reasonable window.
618
+
619
+ Parameters
620
+ ----------
621
+ sequence : List[str]
622
+ Operator sequence
623
+
624
+ Returns
625
+ -------
626
+ int
627
+ Count of unresolved dissonant operators
628
+ """
629
+ unresolved = 0
630
+ window = 3 # Look ahead up to 3 operators
631
+
632
+ for i, op in enumerate(sequence):
633
+ if op in DESTABILIZERS:
634
+ # Check if a stabilizer appears in the next 'window' operators
635
+ lookahead = sequence[i + 1 : i + 1 + window]
636
+ if not any(stabilizer in _STABILIZERS for stabilizer in lookahead):
637
+ unresolved += 1
638
+
639
+ return unresolved
640
+
641
+ def _assess_pattern_value_optimized(
642
+ self, sequence: List[str], unique_count: int
643
+ ) -> float:
644
+ """Assess the structural value of the pattern using pre-computed unique count.
645
+
646
+ Value is higher when:
647
+ - Multiple operator types present (diversity)
648
+ - Key structural phases are included
649
+ - Balance between forces
650
+
651
+ Parameters
652
+ ----------
653
+ sequence : List[str]
654
+ Operator sequence
655
+ unique_count : int
656
+ Pre-computed count of unique operators in sequence
657
+
658
+ Returns
659
+ -------
660
+ float
661
+ Pattern value score (0.0-1.0)
662
+ """
663
+ if not sequence:
664
+ return 0.0
665
+
666
+ # Diversity: use pre-computed unique count
667
+ diversity_score = min(
668
+ 1.0, unique_count / 6.0
669
+ ) # 6+ operators is excellent diversity
670
+
671
+ # Coverage: how many operator categories are represented
672
+ # This is still a minimal single-pass check
673
+ categories_present = 0
674
+ if any(op in {"emission", "reception"} for op in sequence):
675
+ categories_present += 1 # Activation
676
+ if any(op in _STABILIZERS for op in sequence):
677
+ categories_present += 1 # Stabilization
678
+ if any(op in DESTABILIZERS for op in sequence):
679
+ categories_present += 1 # Destabilization
680
+ if any(op in TRANSFORMERS for op in sequence):
681
+ categories_present += 1 # Transformation
682
+
683
+ coverage_score = categories_present / 4.0
684
+
685
+ # Combine factors
686
+ return (diversity_score * 0.5) + (coverage_score * 0.5)
687
+
688
+ def _detect_pattern(self, sequence: List[str]) -> str:
689
+ """Detect the dominant structural pattern type.
690
+
691
+ Parameters
692
+ ----------
693
+ sequence : List[str]
694
+ Operator sequence
695
+
696
+ Returns
697
+ -------
698
+ str
699
+ Pattern name (e.g., "activation", "therapeutic", "unknown")
700
+ """
701
+ if not sequence:
702
+ return "empty"
703
+
704
+ # Check for common patterns
705
+ starts_with_emission = sequence[0] == "emission"
706
+ has_reception = "reception" in sequence
707
+ has_coherence = COHERENCE in sequence
708
+ has_dissonance = DISSONANCE in sequence
709
+ has_self_org = SELF_ORGANIZATION in sequence
710
+ has_regenerator = any(op in _REGENERATORS for op in sequence)
711
+
712
+ # Pattern detection logic
713
+ if starts_with_emission and has_reception and has_coherence:
714
+ if has_dissonance and has_self_org:
715
+ return "therapeutic"
716
+ elif has_regenerator:
717
+ return "regenerative"
718
+ else:
719
+ return "activation"
720
+
721
+ if has_dissonance and has_self_org:
722
+ return "transformative"
723
+
724
+ if sum(1 for op in sequence if op in _STABILIZERS) > len(sequence) // 2:
725
+ return "stabilization"
726
+
727
+ if sum(1 for op in sequence if op in DESTABILIZERS) > len(sequence) // 2:
728
+ return "exploratory"
729
+
730
+ return "unknown"
731
+
732
+ def analyze_thol_coherence(self, G: TNFRGraph) -> dict[str, Any] | None:
733
+ """Analyze collective coherence of THOL bifurcations across the network.
734
+
735
+ Examines all nodes that have undergone THOL bifurcation and provides
736
+ statistics on their collective coherence metrics.
737
+
738
+ Parameters
739
+ ----------
740
+ G : TNFRGraph
741
+ Graph containing nodes with potential THOL bifurcations
742
+
743
+ Returns
744
+ -------
745
+ dict or None
746
+ Dictionary containing coherence statistics:
747
+ - mean_coherence: Average coherence across all THOL nodes
748
+ - min_coherence: Lowest coherence value observed
749
+ - max_coherence: Highest coherence value observed
750
+ - nodes_below_threshold: Count of nodes with coherence < 0.3
751
+ - total_thol_nodes: Total nodes with sub-EPIs
752
+ Returns None if no THOL bifurcations exist in the network.
753
+
754
+ Notes
755
+ -----
756
+ TNFR Principle: Collective coherence measures the structural alignment
757
+ of emergent sub-EPIs. Low coherence may indicate chaotic fragmentation
758
+ rather than controlled emergence.
759
+
760
+ This metric is particularly useful for:
761
+ - Detecting pathological bifurcation patterns
762
+ - Monitoring network-wide self-organization quality
763
+ - Identifying nodes requiring stabilization
764
+
765
+ Examples
766
+ --------
767
+ >>> analyzer = SequenceHealthAnalyzer()
768
+ >>> # After running THOL operations on graph G
769
+ >>> coherence_stats = analyzer.analyze_thol_coherence(G)
770
+ >>> if coherence_stats:
771
+ ... print(f"Mean coherence: {coherence_stats['mean_coherence']:.3f}")
772
+ ... print(f"Nodes below threshold: {coherence_stats['nodes_below_threshold']}")
773
+ """
774
+ # Find all nodes with sub-EPIs (THOL bifurcation occurred)
775
+ thol_nodes = []
776
+ for node in G.nodes():
777
+ if G.nodes[node].get("sub_epis"):
778
+ thol_nodes.append(node)
779
+
780
+ if not thol_nodes:
781
+ return None
782
+
783
+ # Collect coherence values
784
+ coherences = []
785
+ for node in thol_nodes:
786
+ coh = G.nodes[node].get("_thol_collective_coherence")
787
+ if coh is not None:
788
+ coherences.append(coh)
789
+
790
+ if not coherences:
791
+ return None
792
+
793
+ # Compute statistics
794
+ mean_coherence = sum(coherences) / len(coherences)
795
+ min_coherence = min(coherences)
796
+ max_coherence = max(coherences)
797
+
798
+ # Get threshold from graph config
799
+ threshold = float(G.graph.get("THOL_MIN_COLLECTIVE_COHERENCE", 0.3))
800
+ nodes_below_threshold = sum(1 for c in coherences if c < threshold)
801
+
802
+ return {
803
+ "mean_coherence": mean_coherence,
804
+ "min_coherence": min_coherence,
805
+ "max_coherence": max_coherence,
806
+ "nodes_below_threshold": nodes_below_threshold,
807
+ "total_thol_nodes": len(thol_nodes),
808
+ "threshold": threshold,
809
+ }