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,229 @@
1
+ """Canonical TNFR nodal equation implementation.
2
+
3
+ This module provides the explicit, canonical implementation of the fundamental
4
+ TNFR nodal equation as specified in the theory:
5
+
6
+ ∂EPI/∂t = νf · ΔNFR(t)
7
+
8
+ Where:
9
+ - EPI: Primary Information Structure (coherent form)
10
+ - νf: Structural frequency in Hz_str (structural hertz)
11
+ - ΔNFR: Nodal gradient (reorganization operator)
12
+ - t: Structural time (not chronological time)
13
+
14
+ This implementation ensures theoretical fidelity to the TNFR paradigm by:
15
+ 1. Making the canonical equation explicit in code
16
+ 2. Validating dimensional consistency (Hz_str units)
17
+ 3. Providing clear mapping between theory and implementation
18
+ 4. Maintaining reproducibility and traceability
19
+
20
+ TNFR Invariants (from AGENTS.md):
21
+ - EPI as coherent form: changes only via structural operators
22
+ - Structural units: νf expressed in Hz_str (structural hertz)
23
+ - ΔNFR semantics: sign and magnitude modulate reorganization rate
24
+ - Operator closure: composition yields valid TNFR states
25
+
26
+ References:
27
+ - TNFR.pdf: Canonical nodal equation specification
28
+ - AGENTS.md: Section 3 (Canonical invariants)
29
+ """
30
+
31
+ from __future__ import annotations
32
+
33
+ import math
34
+ from typing import TYPE_CHECKING, NamedTuple
35
+
36
+ if TYPE_CHECKING:
37
+ from ..types import GraphLike
38
+
39
+ __all__ = (
40
+ "NodalEquationResult",
41
+ "compute_canonical_nodal_derivative",
42
+ "validate_structural_frequency",
43
+ "validate_nodal_gradient",
44
+ )
45
+
46
+
47
+ class NodalEquationResult(NamedTuple):
48
+ """Result of canonical nodal equation evaluation.
49
+
50
+ Attributes:
51
+ derivative: ∂EPI/∂t computed from νf · ΔNFR(t)
52
+ nu_f: Structural frequency (Hz_str) used in computation
53
+ delta_nfr: Nodal gradient (ΔNFR) used in computation
54
+ validated: Whether units and bounds were validated
55
+ """
56
+
57
+ derivative: float
58
+ nu_f: float
59
+ delta_nfr: float
60
+ validated: bool
61
+
62
+
63
+ def compute_canonical_nodal_derivative(
64
+ nu_f: float,
65
+ delta_nfr: float,
66
+ *,
67
+ validate_units: bool = True,
68
+ graph: GraphLike | None = None,
69
+ ) -> NodalEquationResult:
70
+ """Compute ∂EPI/∂t using the canonical TNFR nodal equation.
71
+
72
+ This is the explicit implementation of the fundamental equation:
73
+ ∂EPI/∂t = νf · ΔNFR(t)
74
+
75
+ The function computes the time derivative of the Primary Information
76
+ Structure (EPI) as the product of:
77
+ - νf: structural frequency (reorganization rate in Hz_str)
78
+ - ΔNFR: nodal gradient (reorganization need/operator)
79
+
80
+ Args:
81
+ nu_f: Structural frequency in Hz_str (must be non-negative)
82
+ delta_nfr: Nodal gradient (reorganization operator)
83
+ validate_units: If True, validates that inputs are in valid ranges
84
+ graph: Optional graph for context-aware validation
85
+
86
+ Returns:
87
+ NodalEquationResult containing the computed derivative and metadata
88
+
89
+ Raises:
90
+ ValueError: If validation is enabled and inputs are invalid
91
+
92
+ Notes:
93
+ - This function is the canonical reference implementation
94
+ - The result represents the instantaneous rate of EPI evolution
95
+ - Units: [∂EPI/∂t] = Hz_str (structural reorganization rate)
96
+ - The product νf·ΔNFR must preserve TNFR operator closure
97
+
98
+ Examples:
99
+ >>> # Basic computation
100
+ >>> result = compute_canonical_nodal_derivative(1.0, 0.5)
101
+ >>> result.derivative
102
+ 0.5
103
+
104
+ >>> # With explicit validation
105
+ >>> result = compute_canonical_nodal_derivative(
106
+ ... nu_f=1.2,
107
+ ... delta_nfr=-0.3,
108
+ ... validate_units=True
109
+ ... )
110
+ >>> result.validated
111
+ True
112
+ """
113
+ validated = False
114
+
115
+ if validate_units:
116
+ nu_f = validate_structural_frequency(nu_f, graph=graph)
117
+ delta_nfr = validate_nodal_gradient(delta_nfr, graph=graph)
118
+ validated = True
119
+
120
+ # Canonical TNFR nodal equation: ∂EPI/∂t = νf · ΔNFR(t)
121
+ derivative = float(nu_f) * float(delta_nfr)
122
+
123
+ return NodalEquationResult(
124
+ derivative=derivative,
125
+ nu_f=nu_f,
126
+ delta_nfr=delta_nfr,
127
+ validated=validated,
128
+ )
129
+
130
+
131
+ def validate_structural_frequency(
132
+ nu_f: float,
133
+ *,
134
+ graph: GraphLike | None = None,
135
+ ) -> float:
136
+ """Validate that structural frequency is in valid range.
137
+
138
+ Structural frequency (νf) must satisfy TNFR constraints:
139
+ - Non-negative (νf ≥ 0)
140
+ - Expressed in Hz_str (structural hertz)
141
+ - Finite and well-defined
142
+
143
+ Args:
144
+ nu_f: Structural frequency to validate
145
+ graph: Optional graph for context-aware bounds checking
146
+
147
+ Returns:
148
+ Validated structural frequency value
149
+
150
+ Raises:
151
+ ValueError: If nu_f is negative, infinite, or NaN
152
+ TypeError: If nu_f cannot be converted to float
153
+
154
+ Notes:
155
+ - νf = 0 is valid and represents structural silence
156
+ - Units must be Hz_str (not classical Hz)
157
+ - For Hz↔Hz_str conversion, use tnfr.units module
158
+ """
159
+ try:
160
+ value = float(nu_f)
161
+ except TypeError as exc:
162
+ # Non-convertible type (e.g., None, object())
163
+ raise TypeError(
164
+ f"Structural frequency must be numeric, got {type(nu_f).__name__}"
165
+ ) from exc
166
+ except ValueError as exc:
167
+ # Invalid string value (e.g., "invalid")
168
+ raise ValueError(
169
+ f"Structural frequency must be a valid number, got {nu_f!r}"
170
+ ) from exc
171
+
172
+ # Check for NaN or infinity using math.isfinite
173
+ if not math.isfinite(value):
174
+ raise ValueError(f"Structural frequency must be finite, got νf={value}")
175
+
176
+ if value < 0:
177
+ raise ValueError(f"Structural frequency must be non-negative, got νf={value}")
178
+
179
+ return value
180
+
181
+
182
+ def validate_nodal_gradient(
183
+ delta_nfr: float,
184
+ *,
185
+ graph: GraphLike | None = None,
186
+ ) -> float:
187
+ """Validate that nodal gradient is well-defined.
188
+
189
+ The nodal gradient (ΔNFR) represents the internal reorganization
190
+ operator and must be:
191
+ - Finite and well-defined
192
+ - Sign indicates reorganization direction
193
+ - Magnitude indicates reorganization intensity
194
+
195
+ Args:
196
+ delta_nfr: Nodal gradient to validate
197
+ graph: Optional graph for context-aware validation
198
+
199
+ Returns:
200
+ Validated nodal gradient value
201
+
202
+ Raises:
203
+ ValueError: If delta_nfr is infinite or NaN
204
+ TypeError: If delta_nfr cannot be converted to float
205
+
206
+ Notes:
207
+ - ΔNFR can be positive (expansion) or negative (contraction)
208
+ - ΔNFR = 0 indicates equilibrium (no reorganization)
209
+ - Do NOT reinterpret as classical "error gradient"
210
+ - Semantics: operator over EPI, not optimization target
211
+ """
212
+ try:
213
+ value = float(delta_nfr)
214
+ except TypeError as exc:
215
+ # Non-convertible type (e.g., None, object())
216
+ raise TypeError(
217
+ f"Nodal gradient must be numeric, got {type(delta_nfr).__name__}"
218
+ ) from exc
219
+ except ValueError as exc:
220
+ # Invalid string value (e.g., "invalid")
221
+ raise ValueError(
222
+ f"Nodal gradient must be a valid number, got {delta_nfr!r}"
223
+ ) from exc
224
+
225
+ # Check for NaN or infinity using math.isfinite
226
+ if not math.isfinite(value):
227
+ raise ValueError(f"Nodal gradient must be finite, got ΔNFR={value}")
228
+
229
+ return value
@@ -0,0 +1,48 @@
1
+ """Type stubs for canonical TNFR nodal equation implementation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import NamedTuple
6
+
7
+ from ..types import GraphLike
8
+
9
+ __all__ = (
10
+ "NodalEquationResult",
11
+ "compute_canonical_nodal_derivative",
12
+ "validate_structural_frequency",
13
+ "validate_nodal_gradient",
14
+ )
15
+
16
+ class NodalEquationResult(NamedTuple):
17
+ """Result of canonical nodal equation evaluation."""
18
+
19
+ derivative: float
20
+ nu_f: float
21
+ delta_nfr: float
22
+ validated: bool
23
+
24
+ def compute_canonical_nodal_derivative(
25
+ nu_f: float,
26
+ delta_nfr: float,
27
+ *,
28
+ validate_units: bool = True,
29
+ graph: GraphLike | None = None,
30
+ ) -> NodalEquationResult:
31
+ """Compute ∂EPI/∂t using the canonical TNFR nodal equation."""
32
+ ...
33
+
34
+ def validate_structural_frequency(
35
+ nu_f: float,
36
+ *,
37
+ graph: GraphLike | None = None,
38
+ ) -> float:
39
+ """Validate that structural frequency is in valid range."""
40
+ ...
41
+
42
+ def validate_nodal_gradient(
43
+ delta_nfr: float,
44
+ *,
45
+ graph: GraphLike | None = None,
46
+ ) -> float:
47
+ """Validate that nodal gradient is well-defined."""
48
+ ...
@@ -0,0 +1,385 @@
1
+ """Phase coordination helpers for TNFR dynamics."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ from collections import deque
7
+ from collections.abc import Mapping, MutableMapping, Sequence
8
+ from concurrent.futures import ProcessPoolExecutor
9
+ from typing import Any, TypeVar, cast
10
+ from ..alias import get_theta_attr, set_theta
11
+ from ..constants import (
12
+ DEFAULTS,
13
+ METRIC_DEFAULTS,
14
+ STATE_DISSONANT,
15
+ STATE_STABLE,
16
+ STATE_TRANSITION,
17
+ normalise_state_token,
18
+ )
19
+ from ..glyph_history import append_metric
20
+ from ..utils import angle_diff, resolve_chunk_size
21
+ from ..metrics.common import ensure_neighbors_map
22
+ from ..metrics.trig import neighbor_phase_mean_list
23
+ from ..metrics.trig_cache import get_trig_cache
24
+ from ..observers import DEFAULT_GLYPH_LOAD_SPAN, glyph_load, kuramoto_order
25
+ from ..types import FloatArray, NodeId, Phase, TNFRGraph
26
+ from ..utils import get_numpy
27
+
28
+ _DequeT = TypeVar("_DequeT")
29
+
30
+ ChunkArgs = tuple[
31
+ Sequence[NodeId],
32
+ Mapping[NodeId, Phase],
33
+ Mapping[NodeId, float],
34
+ Mapping[NodeId, float],
35
+ Mapping[NodeId, Sequence[NodeId]],
36
+ float,
37
+ float,
38
+ float,
39
+ ]
40
+
41
+ __all__ = ("coordinate_global_local_phase",)
42
+
43
+
44
+ def _ensure_hist_deque(
45
+ hist: MutableMapping[str, Any], key: str, maxlen: int
46
+ ) -> deque[_DequeT]:
47
+ """Ensure history entry ``key`` is a deque with ``maxlen``."""
48
+
49
+ dq = hist.setdefault(key, deque(maxlen=maxlen))
50
+ if not isinstance(dq, deque):
51
+ dq = deque(dq, maxlen=maxlen)
52
+ hist[key] = dq
53
+ return cast("deque[_DequeT]", dq)
54
+
55
+
56
+ def _read_adaptive_params(
57
+ g: Mapping[str, Any],
58
+ ) -> tuple[Mapping[str, Any], float, float]:
59
+ """Obtain configuration and current values for phase adaptation."""
60
+
61
+ cfg = g.get("PHASE_ADAPT", DEFAULTS.get("PHASE_ADAPT", {}))
62
+ kG = float(g.get("PHASE_K_GLOBAL", DEFAULTS["PHASE_K_GLOBAL"]))
63
+ kL = float(g.get("PHASE_K_LOCAL", DEFAULTS["PHASE_K_LOCAL"]))
64
+ return cast(Mapping[str, Any], cfg), kG, kL
65
+
66
+
67
+ def _compute_state(G: TNFRGraph, cfg: Mapping[str, Any]) -> tuple[str, float, float]:
68
+ """Return the canonical network state and supporting metrics."""
69
+
70
+ R = kuramoto_order(G)
71
+ dist = glyph_load(G, window=DEFAULT_GLYPH_LOAD_SPAN)
72
+ disr = float(dist.get("_disruptors", 0.0)) if dist else 0.0
73
+
74
+ R_hi = float(cfg.get("R_hi", 0.90))
75
+ R_lo = float(cfg.get("R_lo", 0.60))
76
+ disr_hi = float(cfg.get("disr_hi", 0.50))
77
+ disr_lo = float(cfg.get("disr_lo", 0.25))
78
+ if (R >= R_hi) and (disr <= disr_lo):
79
+ state = STATE_STABLE
80
+ elif (R <= R_lo) or (disr >= disr_hi):
81
+ state = STATE_DISSONANT
82
+ else:
83
+ state = STATE_TRANSITION
84
+ return state, float(R), disr
85
+
86
+
87
+ def _smooth_adjust_k(
88
+ kG: float, kL: float, state: str, cfg: Mapping[str, Any]
89
+ ) -> tuple[float, float]:
90
+ """Smoothly update kG/kL toward targets according to state."""
91
+
92
+ kG_min = float(cfg.get("kG_min", 0.01))
93
+ kG_max = float(cfg.get("kG_max", 0.20))
94
+ kL_min = float(cfg.get("kL_min", 0.05))
95
+ kL_max = float(cfg.get("kL_max", 0.25))
96
+
97
+ state = normalise_state_token(state)
98
+
99
+ if state == STATE_DISSONANT:
100
+ kG_t = kG_max
101
+ kL_t = 0.5 * (kL_min + kL_max) # keep kL mid-range to preserve local plasticity
102
+ elif state == STATE_STABLE:
103
+ kG_t = kG_min
104
+ kL_t = kL_min
105
+ else:
106
+ kG_t = 0.5 * (kG_min + kG_max)
107
+ kL_t = 0.5 * (kL_min + kL_max)
108
+
109
+ up = float(cfg.get("up", 0.10))
110
+ down = float(cfg.get("down", 0.07))
111
+
112
+ def _step(curr: float, target: float, mn: float, mx: float) -> float:
113
+ gain = up if target > curr else down
114
+ nxt = curr + gain * (target - curr)
115
+ return max(mn, min(mx, nxt))
116
+
117
+ return _step(kG, kG_t, kG_min, kG_max), _step(kL, kL_t, kL_min, kL_max)
118
+
119
+
120
+ def _phase_adjust_chunk(args: ChunkArgs) -> list[tuple[NodeId, Phase]]:
121
+ """Return coordinated phase updates for the provided chunk."""
122
+
123
+ (
124
+ nodes,
125
+ theta_map,
126
+ cos_map,
127
+ sin_map,
128
+ neighbors_map,
129
+ thG,
130
+ kG,
131
+ kL,
132
+ ) = args
133
+ updates: list[tuple[NodeId, Phase]] = []
134
+ for node in nodes:
135
+ th = float(theta_map.get(node, 0.0))
136
+ neigh = neighbors_map.get(node, ())
137
+ if neigh:
138
+ thL = neighbor_phase_mean_list(
139
+ neigh,
140
+ cos_map,
141
+ sin_map,
142
+ np=None,
143
+ fallback=th,
144
+ )
145
+ else:
146
+ thL = th
147
+ dG = angle_diff(thG, th)
148
+ dL = angle_diff(thL, th)
149
+ updates.append((node, cast(Phase, th + kG * dG + kL * dL)))
150
+ return updates
151
+
152
+
153
+ def coordinate_global_local_phase(
154
+ G: TNFRGraph,
155
+ global_force: float | None = None,
156
+ local_force: float | None = None,
157
+ *,
158
+ n_jobs: int | None = None,
159
+ ) -> None:
160
+ """Coordinate phase using a blend of global and neighbour coupling.
161
+
162
+ This operator harmonises a TNFR graph by iteratively nudging each node's
163
+ phase toward the global Kuramoto mean while respecting the local
164
+ neighbourhood attractor. The global (``kG``) and local (``kL``) coupling
165
+ gains reshape phase coherence by modulating how strongly nodes follow the
166
+ network-wide synchrony versus immediate neighbours. When explicit coupling
167
+ overrides are not supplied, the gains adapt based on current ΔNFR telemetry
168
+ and the structural state recorded in the graph history. Adaptive updates
169
+ mutate the ``history`` buffers for phase state, order parameter, disruptor
170
+ load, and the stored coupling gains.
171
+
172
+ Parameters
173
+ ----------
174
+ G : TNFRGraph
175
+ Graph whose nodes expose TNFR phase attributes and ΔNFR telemetry. The
176
+ graph's ``history`` mapping is updated in-place when adaptive gain
177
+ smoothing is active.
178
+ global_force : float, optional
179
+ Override for the global coupling gain ``kG``. When provided, adaptive
180
+ gain estimation is skipped and the global history buffers are left
181
+ untouched.
182
+ local_force : float, optional
183
+ Override for the local coupling gain ``kL``. Analogous to
184
+ ``global_force``, the adaptive pathway is bypassed when supplied.
185
+ n_jobs : int, optional
186
+ Maximum number of worker processes for distributing local updates.
187
+ Values of ``None`` or ``<=1`` perform updates sequentially. NumPy
188
+ availability forces sequential execution because vectorised updates are
189
+ faster than multiprocess handoffs.
190
+
191
+ Returns
192
+ -------
193
+ None
194
+ This operator updates node phases in-place and does not allocate a new
195
+ graph structure.
196
+
197
+ Examples
198
+ --------
199
+ Coordinate phase on a minimal TNFR network while inspecting ΔNFR telemetry
200
+ and history traces::
201
+
202
+ >>> import networkx as nx
203
+ >>> from tnfr.dynamics.coordination import coordinate_global_local_phase
204
+ >>> G = nx.Graph()
205
+ >>> G.add_nodes_from(("a", {"theta": 0.0, "ΔNFR": 0.08}),
206
+ ... ("b", {"theta": 1.2, "ΔNFR": -0.05}))
207
+ >>> G.add_edge("a", "b")
208
+ >>> G.graph["history"] = {}
209
+ >>> coordinate_global_local_phase(G)
210
+ >>> list(round(G.nodes[n]["theta"], 3) for n in G)
211
+ [0.578, 0.622]
212
+ >>> history = G.graph["history"]
213
+ >>> sorted(history)
214
+ ['phase_R', 'phase_disr', 'phase_kG', 'phase_kL', 'phase_state']
215
+ >>> history["phase_kG"][-1] <= history["phase_kL"][-1]
216
+ True
217
+
218
+ The resulting history buffers allow downstream observers to correlate
219
+ ΔNFR adjustments with phase telemetry snapshots.
220
+ """
221
+
222
+ g = cast(dict[str, Any], G.graph)
223
+ hist = cast(dict[str, Any], g.setdefault("history", {}))
224
+ maxlen = int(g.get("PHASE_HISTORY_MAXLEN", METRIC_DEFAULTS["PHASE_HISTORY_MAXLEN"]))
225
+ hist_state = cast(deque[str], _ensure_hist_deque(hist, "phase_state", maxlen))
226
+ if hist_state:
227
+ normalised_states = [normalise_state_token(item) for item in hist_state]
228
+ if normalised_states != list(hist_state):
229
+ hist_state.clear()
230
+ hist_state.extend(normalised_states)
231
+ hist_R = cast(deque[float], _ensure_hist_deque(hist, "phase_R", maxlen))
232
+ hist_disr = cast(deque[float], _ensure_hist_deque(hist, "phase_disr", maxlen))
233
+
234
+ if (global_force is not None) or (local_force is not None):
235
+ kG = float(
236
+ global_force
237
+ if global_force is not None
238
+ else g.get("PHASE_K_GLOBAL", DEFAULTS["PHASE_K_GLOBAL"])
239
+ )
240
+ kL = float(
241
+ local_force
242
+ if local_force is not None
243
+ else g.get("PHASE_K_LOCAL", DEFAULTS["PHASE_K_LOCAL"])
244
+ )
245
+ else:
246
+ cfg, kG, kL = _read_adaptive_params(g)
247
+
248
+ if bool(cfg.get("enabled", False)):
249
+ state, R, disr = _compute_state(G, cfg)
250
+ kG, kL = _smooth_adjust_k(kG, kL, state, cfg)
251
+
252
+ hist_state.append(state)
253
+ hist_R.append(float(R))
254
+ hist_disr.append(float(disr))
255
+
256
+ g["PHASE_K_GLOBAL"] = kG
257
+ g["PHASE_K_LOCAL"] = kL
258
+ append_metric(hist, "phase_kG", float(kG))
259
+ append_metric(hist, "phase_kL", float(kL))
260
+
261
+ jobs: int | None
262
+ try:
263
+ jobs = None if n_jobs is None else int(n_jobs)
264
+ except (TypeError, ValueError):
265
+ jobs = None
266
+ if jobs is not None and jobs <= 1:
267
+ jobs = None
268
+
269
+ np = get_numpy()
270
+ if np is not None:
271
+ jobs = None
272
+
273
+ nodes: list[NodeId] = [cast(NodeId, node) for node in G.nodes()]
274
+ num_nodes = len(nodes)
275
+ if not num_nodes:
276
+ return
277
+
278
+ trig = get_trig_cache(G, np=np)
279
+ theta_map = cast(dict[NodeId, Phase], trig.theta)
280
+ cos_map = cast(dict[NodeId, float], trig.cos)
281
+ sin_map = cast(dict[NodeId, float], trig.sin)
282
+
283
+ neighbors_proxy = ensure_neighbors_map(G)
284
+ neighbors_map: dict[NodeId, tuple[NodeId, ...]] = {}
285
+ for n in nodes:
286
+ try:
287
+ neighbors_map[n] = tuple(cast(Sequence[NodeId], neighbors_proxy[n]))
288
+ except KeyError:
289
+ neighbors_map[n] = ()
290
+
291
+ def _theta_value(node: NodeId) -> float:
292
+ cached = theta_map.get(node)
293
+ if cached is not None:
294
+ return float(cached)
295
+ attr_val = get_theta_attr(G.nodes[node], 0.0)
296
+ return float(attr_val if attr_val is not None else 0.0)
297
+
298
+ theta_vals = [_theta_value(n) for n in nodes]
299
+ cos_vals = [
300
+ float(cos_map.get(n, math.cos(theta_vals[idx]))) for idx, n in enumerate(nodes)
301
+ ]
302
+ sin_vals = [
303
+ float(sin_map.get(n, math.sin(theta_vals[idx]))) for idx, n in enumerate(nodes)
304
+ ]
305
+
306
+ if np is not None:
307
+ theta_arr = cast(FloatArray, np.fromiter(theta_vals, dtype=float))
308
+ cos_arr = cast(FloatArray, np.fromiter(cos_vals, dtype=float))
309
+ sin_arr = cast(FloatArray, np.fromiter(sin_vals, dtype=float))
310
+ if cos_arr.size:
311
+ mean_cos = float(np.mean(cos_arr))
312
+ mean_sin = float(np.mean(sin_arr))
313
+ thG = float(np.arctan2(mean_sin, mean_cos))
314
+ else:
315
+ thG = 0.0
316
+ neighbor_means = [
317
+ neighbor_phase_mean_list(
318
+ neighbors_map.get(n, ()),
319
+ cos_map,
320
+ sin_map,
321
+ np=np,
322
+ fallback=theta_vals[idx],
323
+ )
324
+ for idx, n in enumerate(nodes)
325
+ ]
326
+ neighbor_arr = cast(FloatArray, np.fromiter(neighbor_means, dtype=float))
327
+ theta_updates = (
328
+ theta_arr + kG * (thG - theta_arr) + kL * (neighbor_arr - theta_arr)
329
+ )
330
+ for idx, node in enumerate(nodes):
331
+ set_theta(G, node, float(theta_updates[int(idx)]))
332
+ return
333
+
334
+ mean_cos = math.fsum(cos_vals) / num_nodes
335
+ mean_sin = math.fsum(sin_vals) / num_nodes
336
+ thG = math.atan2(mean_sin, mean_cos)
337
+
338
+ if jobs is None:
339
+ for node in nodes:
340
+ th = float(theta_map.get(node, 0.0))
341
+ neigh = neighbors_map.get(node, ())
342
+ if neigh:
343
+ thL = neighbor_phase_mean_list(
344
+ neigh,
345
+ cos_map,
346
+ sin_map,
347
+ np=None,
348
+ fallback=th,
349
+ )
350
+ else:
351
+ thL = th
352
+ dG = angle_diff(thG, th)
353
+ dL = angle_diff(thL, th)
354
+ set_theta(G, node, float(th + kG * dG + kL * dL))
355
+ return
356
+
357
+ approx_chunk = math.ceil(len(nodes) / jobs) if jobs else None
358
+ chunk_size = resolve_chunk_size(
359
+ approx_chunk,
360
+ len(nodes),
361
+ minimum=1,
362
+ )
363
+ chunks = [nodes[idx : idx + chunk_size] for idx in range(0, len(nodes), chunk_size)]
364
+ args: list[ChunkArgs] = [
365
+ (
366
+ chunk,
367
+ theta_map,
368
+ cos_map,
369
+ sin_map,
370
+ neighbors_map,
371
+ thG,
372
+ kG,
373
+ kL,
374
+ )
375
+ for chunk in chunks
376
+ ]
377
+ results: dict[NodeId, Phase] = {}
378
+ with ProcessPoolExecutor(max_workers=jobs) as executor:
379
+ for res in executor.map(_phase_adjust_chunk, args):
380
+ for node, value in res:
381
+ results[node] = value
382
+ for node in nodes:
383
+ new_theta = results.get(node)
384
+ base_theta = theta_map.get(node, 0.0)
385
+ set_theta(G, node, float(new_theta if new_theta is not None else base_theta))
@@ -0,0 +1,25 @@
1
+ from __future__ import annotations
2
+
3
+ from ..types import NodeId, Phase, TNFRGraph
4
+ from collections.abc import Mapping, Sequence
5
+
6
+ __all__ = ["coordinate_global_local_phase"]
7
+
8
+ ChunkArgs = tuple[
9
+ Sequence[NodeId],
10
+ Mapping[NodeId, Phase],
11
+ Mapping[NodeId, float],
12
+ Mapping[NodeId, float],
13
+ Mapping[NodeId, Sequence[NodeId]],
14
+ float,
15
+ float,
16
+ float,
17
+ ]
18
+
19
+ def coordinate_global_local_phase(
20
+ G: TNFRGraph,
21
+ global_force: float | None = None,
22
+ local_force: float | None = None,
23
+ *,
24
+ n_jobs: int | None = None,
25
+ ) -> None: ...