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,661 @@
1
+ """Canonical ΔNFR integrators driving TNFR runtime evolution.
2
+
3
+ This module implements numerical integration of the canonical TNFR nodal equation:
4
+
5
+ ∂EPI/∂t = νf · ΔNFR(t) + Γi(R)
6
+
7
+ The extended equation includes:
8
+ - Base term: νf · ΔNFR(t) - canonical structural evolution
9
+ - Network term: Γi(R) - optional Kuramoto coupling
10
+
11
+ Integration respects TNFR invariants:
12
+ - Structural units (Hz_str for νf)
13
+ - Operator closure (valid ΔNFR semantics)
14
+ - Phase coherence (network synchronization)
15
+ - Reproducibility (deterministic with seeds)
16
+
17
+ The canonical base term is computed explicitly in _collect_nodal_increments()
18
+ at line 321 and 342 as: base = vf * dnfr, implementing ∂EPI/∂t = νf·ΔNFR(t).
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import math
24
+ from abc import ABC, abstractmethod
25
+ from collections.abc import Iterable, Mapping
26
+ from concurrent.futures import ProcessPoolExecutor
27
+ from multiprocessing import get_context
28
+ from typing import Any, Literal, cast
29
+
30
+ import networkx as nx
31
+
32
+ from .._compat import TypeAlias
33
+ from ..alias import collect_attr, get_attr, get_attr_str, set_attr, set_attr_str
34
+ from ..constants import DEFAULTS
35
+ from ..constants.aliases import (
36
+ ALIAS_D2EPI,
37
+ ALIAS_DEPI,
38
+ ALIAS_DNFR,
39
+ ALIAS_EPI,
40
+ ALIAS_EPI_KIND,
41
+ ALIAS_VF,
42
+ )
43
+ from ..gamma import _get_gamma_spec, eval_gamma
44
+ from ..types import NodeId, TNFRGraph
45
+ from ..utils import get_numpy, resolve_chunk_size
46
+ from .canonical import compute_canonical_nodal_derivative
47
+ from .structural_clip import structural_clip
48
+
49
+ __all__ = (
50
+ "AbstractIntegrator",
51
+ "DefaultIntegrator",
52
+ "prepare_integration_params",
53
+ "update_epi_via_nodal_equation",
54
+ )
55
+
56
+ GammaMap: TypeAlias = dict[NodeId, float]
57
+ """Γ evaluation cache keyed by node identifier."""
58
+
59
+ NodeIncrements: TypeAlias = dict[NodeId, tuple[float, ...]]
60
+ """Mapping of nodes to staged integration increments."""
61
+
62
+ NodalUpdate: TypeAlias = dict[NodeId, tuple[float, float, float]]
63
+ """Mapping of nodes to ``(EPI, dEPI/dt, ∂²EPI/∂t²)`` tuples."""
64
+
65
+ IntegratorMethod: TypeAlias = Literal["euler", "rk4"]
66
+ """Supported explicit integration schemes for nodal updates."""
67
+
68
+ _PARALLEL_GRAPH: TNFRGraph | None = None
69
+
70
+
71
+ def _gamma_worker_init(graph: TNFRGraph) -> None:
72
+ """Initialise process-local graph reference for Γ evaluation."""
73
+
74
+ global _PARALLEL_GRAPH
75
+ _PARALLEL_GRAPH = graph
76
+
77
+
78
+ def _gamma_worker(task: tuple[list[NodeId], float]) -> list[tuple[NodeId, float]]:
79
+ """Evaluate Γ for ``task`` chunk using process-local graph."""
80
+
81
+ chunk, t = task
82
+ if _PARALLEL_GRAPH is None:
83
+ raise RuntimeError("Parallel Γ worker initialised without graph reference")
84
+ return [(node, float(eval_gamma(_PARALLEL_GRAPH, node, t))) for node in chunk]
85
+
86
+
87
+ def _normalise_jobs(n_jobs: int | None, total: int) -> int | None:
88
+ """Return an effective worker count respecting serial fallbacks."""
89
+
90
+ if n_jobs is None:
91
+ return None
92
+ try:
93
+ workers = int(n_jobs)
94
+ except (TypeError, ValueError):
95
+ return None
96
+ if workers <= 1 or total <= 1:
97
+ return None
98
+ return max(1, min(workers, total))
99
+
100
+
101
+ def _chunk_nodes(nodes: list[NodeId], chunk_size: int) -> Iterable[list[NodeId]]:
102
+ """Yield deterministic chunks from ``nodes`` respecting insertion order."""
103
+
104
+ for idx in range(0, len(nodes), chunk_size):
105
+ yield nodes[idx : idx + chunk_size]
106
+
107
+
108
+ def _apply_increment_chunk(
109
+ chunk: list[tuple[NodeId, float, float, tuple[float, ...]]],
110
+ dt_step: float,
111
+ method: str,
112
+ ) -> list[tuple[NodeId, tuple[float, float, float]]]:
113
+ """Compute updated states for ``chunk`` using scalar arithmetic."""
114
+
115
+ results: list[tuple[NodeId, tuple[float, float, float]]] = []
116
+ dt_nonzero = dt_step != 0
117
+
118
+ for node, epi_i, dEPI_prev, ks in chunk:
119
+ if method == "rk4":
120
+ k1, k2, k3, k4 = ks
121
+ epi = epi_i + (dt_step / 6.0) * (k1 + 2 * k2 + 2 * k3 + k4)
122
+ dEPI_dt = k4
123
+ else:
124
+ (k1,) = ks
125
+ epi = epi_i + dt_step * k1
126
+ dEPI_dt = k1
127
+ d2epi = (dEPI_dt - dEPI_prev) / dt_step if dt_nonzero else 0.0
128
+ results.append((node, (float(epi), float(dEPI_dt), float(d2epi))))
129
+
130
+ return results
131
+
132
+
133
+ def _evaluate_gamma_map(
134
+ G: TNFRGraph,
135
+ nodes: list[NodeId],
136
+ t: float,
137
+ *,
138
+ n_jobs: int | None = None,
139
+ ) -> GammaMap:
140
+ """Return Γ evaluations for ``nodes`` at time ``t`` respecting parallelism."""
141
+
142
+ workers = _normalise_jobs(n_jobs, len(nodes))
143
+ if workers is None:
144
+ return {n: float(eval_gamma(G, n, t)) for n in nodes}
145
+
146
+ approx_chunk = math.ceil(len(nodes) / (workers * 4)) if workers > 0 else None
147
+ chunk_size = resolve_chunk_size(
148
+ approx_chunk,
149
+ len(nodes),
150
+ minimum=1,
151
+ )
152
+ mp_ctx = get_context("spawn")
153
+ tasks = ((chunk, t) for chunk in _chunk_nodes(nodes, chunk_size))
154
+
155
+ results: GammaMap = {}
156
+ with ProcessPoolExecutor(
157
+ max_workers=workers,
158
+ mp_context=mp_ctx,
159
+ initializer=_gamma_worker_init,
160
+ initargs=(G,),
161
+ ) as executor:
162
+ futures = [executor.submit(_gamma_worker, task) for task in tasks]
163
+ for fut in futures:
164
+ for node, value in fut.result():
165
+ results[node] = value
166
+ return results
167
+
168
+
169
+ def prepare_integration_params(
170
+ G: TNFRGraph,
171
+ dt: float | None = None,
172
+ t: float | None = None,
173
+ method: Literal["euler", "rk4"] | None = None,
174
+ ) -> tuple[float, int, float, Literal["euler", "rk4"]]:
175
+ """Validate and normalise ``dt``, ``t`` and ``method`` for integration.
176
+
177
+ The function raises :class:`TypeError` when ``dt`` cannot be coerced to a
178
+ number, :class:`ValueError` if ``dt`` is negative, and another
179
+ :class:`ValueError` when an unsupported method is requested. When ``dt``
180
+ exceeds a positive ``DT_MIN`` stored on ``G`` the span is deterministically
181
+ subdivided into integer steps so that the resulting ``dt_step`` never falls
182
+ below that minimum threshold.
183
+
184
+ Returns ``(dt_step, steps, t0, method)`` where ``dt_step`` is the effective
185
+ step, ``steps`` the number of substeps and ``t0`` the prepared initial
186
+ time.
187
+ """
188
+ if dt is None:
189
+ dt = float(G.graph.get("DT", DEFAULTS["DT"]))
190
+ else:
191
+ if not isinstance(dt, (int, float)):
192
+ raise TypeError("dt must be a number")
193
+ if dt < 0:
194
+ raise ValueError("dt must be non-negative")
195
+ dt = float(dt)
196
+
197
+ if t is None:
198
+ t = float(G.graph.get("_t", 0.0))
199
+ else:
200
+ t = float(t)
201
+
202
+ method_value = (
203
+ method
204
+ or G.graph.get("INTEGRATOR_METHOD", DEFAULTS.get("INTEGRATOR_METHOD", "euler"))
205
+ ).lower()
206
+ if method_value not in ("euler", "rk4"):
207
+ raise ValueError("method must be 'euler' or 'rk4'")
208
+
209
+ dt_min = float(G.graph.get("DT_MIN", DEFAULTS.get("DT_MIN", 0.0)))
210
+ steps = 1
211
+ if dt_min > 0 and dt > dt_min:
212
+ ratio = dt / dt_min
213
+ steps = max(1, int(math.floor(ratio + 1e-12)))
214
+ if dt / steps < dt_min:
215
+ steps = int(math.ceil(ratio))
216
+ dt_step = dt / steps if steps else 0.0
217
+
218
+ return dt_step, steps, t, cast(Literal["euler", "rk4"], method_value)
219
+
220
+
221
+ def _apply_increments(
222
+ G: TNFRGraph,
223
+ dt_step: float,
224
+ increments: NodeIncrements,
225
+ *,
226
+ method: str,
227
+ n_jobs: int | None = None,
228
+ ) -> NodalUpdate:
229
+ """Combine precomputed increments to update node states."""
230
+
231
+ nodes: list[NodeId] = list(G.nodes)
232
+ if not nodes:
233
+ return {}
234
+
235
+ np = get_numpy()
236
+
237
+ epi_initial: list[float] = []
238
+ dEPI_prev: list[float] = []
239
+ ordered_increments: list[tuple[float, ...]] = []
240
+
241
+ for node in nodes:
242
+ nd = G.nodes[node]
243
+ _, _, dEPI_dt_prev, epi_i = _node_state(nd)
244
+ epi_initial.append(float(epi_i))
245
+ dEPI_prev.append(float(dEPI_dt_prev))
246
+ ordered_increments.append(increments[node])
247
+
248
+ if np is not None:
249
+ epi_arr = np.asarray(epi_initial, dtype=float)
250
+ dEPI_prev_arr = np.asarray(dEPI_prev, dtype=float)
251
+ k_arr = np.asarray(ordered_increments, dtype=float)
252
+
253
+ if method == "rk4":
254
+ if k_arr.ndim != 2 or k_arr.shape[1] != 4:
255
+ raise ValueError("rk4 increments require four staged values")
256
+ dt_factor = dt_step / 6.0
257
+ k1 = k_arr[:, 0]
258
+ k2 = k_arr[:, 1]
259
+ k3 = k_arr[:, 2]
260
+ k4 = k_arr[:, 3]
261
+ epi = epi_arr + dt_factor * (k1 + 2 * k2 + 2 * k3 + k4)
262
+ dEPI_dt = k4
263
+ else:
264
+ if k_arr.ndim == 1:
265
+ k1 = k_arr
266
+ else:
267
+ k1 = k_arr[:, 0]
268
+ epi = epi_arr + dt_step * k1
269
+ dEPI_dt = k1
270
+
271
+ if dt_step != 0:
272
+ d2epi = (dEPI_dt - dEPI_prev_arr) / dt_step
273
+ else:
274
+ d2epi = np.zeros_like(dEPI_dt)
275
+
276
+ results: NodalUpdate = {}
277
+ for idx, node in enumerate(nodes):
278
+ results[node] = (
279
+ float(epi[idx]),
280
+ float(dEPI_dt[idx]),
281
+ float(d2epi[idx]),
282
+ )
283
+ return results
284
+
285
+ payload: list[tuple[NodeId, float, float, tuple[float, ...]]] = list(
286
+ zip(nodes, epi_initial, dEPI_prev, ordered_increments)
287
+ )
288
+
289
+ workers = _normalise_jobs(n_jobs, len(nodes))
290
+ if workers is None:
291
+ return dict(_apply_increment_chunk(payload, dt_step, method))
292
+
293
+ approx_chunk = math.ceil(len(nodes) / (workers * 4)) if workers > 0 else None
294
+ chunk_size = resolve_chunk_size(
295
+ approx_chunk,
296
+ len(nodes),
297
+ minimum=1,
298
+ )
299
+ mp_ctx = get_context("spawn")
300
+
301
+ results: NodalUpdate = {}
302
+ with ProcessPoolExecutor(max_workers=workers, mp_context=mp_ctx) as executor:
303
+ futures = [
304
+ executor.submit(
305
+ _apply_increment_chunk,
306
+ chunk,
307
+ dt_step,
308
+ method,
309
+ )
310
+ for chunk in _chunk_nodes(payload, chunk_size)
311
+ ]
312
+ for fut in futures:
313
+ for node, value in fut.result():
314
+ results[node] = value
315
+
316
+ return {node: results[node] for node in nodes}
317
+
318
+
319
+ def _collect_nodal_increments(
320
+ G: TNFRGraph,
321
+ gamma_maps: tuple[GammaMap, ...],
322
+ *,
323
+ method: str,
324
+ ) -> NodeIncrements:
325
+ """Combine node base state with staged Γ contributions.
326
+
327
+ Implements the canonical TNFR nodal equation in two parts:
328
+
329
+ 1. **Base term** (canonical equation):
330
+ base = vf * dnfr → ∂EPI/∂t = νf · ΔNFR(t)
331
+
332
+ This is the fundamental TNFR equation where:
333
+ - vf (νf): structural frequency in Hz_str
334
+ - dnfr (ΔNFR): nodal gradient (reorganization operator)
335
+ - base: instantaneous rate of EPI evolution
336
+
337
+ 2. **Network coupling term**:
338
+ Γi(R) from gamma_maps - optional Kuramoto order parameter
339
+
340
+ The full extended equation is: ∂EPI/∂t = νf·ΔNFR(t) + Γi(R)
341
+
342
+ Args:
343
+ G: TNFR graph with node attributes vf and dnfr
344
+ gamma_maps: Staged Γ evaluations (1 for Euler, 4 for RK4)
345
+ method: Integration method ('euler' or 'rk4')
346
+
347
+ Returns:
348
+ Mapping of nodes to staged integration increments
349
+
350
+ Notes:
351
+ - Line 321 implements the canonical nodal equation explicitly
352
+ - Units: vf in Hz_str, dnfr dimensionless, base in Hz_str
353
+ - Preserves TNFR operator closure and structural semantics
354
+ """
355
+
356
+ nodes: list[NodeId] = list(G.nodes())
357
+ if not nodes:
358
+ return {}
359
+
360
+ if method == "rk4":
361
+ expected_maps = 4
362
+ elif method == "euler":
363
+ expected_maps = 1
364
+ else:
365
+ raise ValueError("method must be 'euler' or 'rk4'")
366
+
367
+ if len(gamma_maps) != expected_maps:
368
+ raise ValueError(f"{method} integration requires {expected_maps} gamma maps")
369
+
370
+ np = get_numpy()
371
+ if np is not None:
372
+ vf = collect_attr(G, nodes, ALIAS_VF, 0.0, np=np)
373
+ dnfr = collect_attr(G, nodes, ALIAS_DNFR, 0.0, np=np)
374
+ # CANONICAL TNFR EQUATION: ∂EPI/∂t = νf · ΔNFR(t)
375
+ # This implements the fundamental nodal equation explicitly
376
+ base = vf * dnfr
377
+
378
+ gamma_arrays = [
379
+ np.fromiter((gm.get(n, 0.0) for n in nodes), float, count=len(nodes))
380
+ for gm in gamma_maps
381
+ ]
382
+ if gamma_arrays:
383
+ gamma_stack = np.stack(gamma_arrays, axis=1)
384
+ combined = base[:, None] + gamma_stack
385
+ else:
386
+ combined = base[:, None]
387
+
388
+ return {
389
+ node: tuple(float(value) for value in combined[idx])
390
+ for idx, node in enumerate(nodes)
391
+ }
392
+
393
+ increments: NodeIncrements = {}
394
+ for node in nodes:
395
+ nd = G.nodes[node]
396
+ vf, dnfr, *_ = _node_state(nd)
397
+ # CANONICAL TNFR EQUATION: ∂EPI/∂t = νf · ΔNFR(t)
398
+ # Scalar implementation of the fundamental nodal equation
399
+ base = vf * dnfr
400
+ gammas = [gm.get(node, 0.0) for gm in gamma_maps]
401
+
402
+ if method == "rk4":
403
+ k1, k2, k3, k4 = gammas
404
+ increments[node] = (
405
+ base + k1,
406
+ base + k2,
407
+ base + k3,
408
+ base + k4,
409
+ )
410
+ else:
411
+ (k1,) = gammas
412
+ increments[node] = (base + k1,)
413
+
414
+ return increments
415
+
416
+
417
+ def _build_gamma_increments(
418
+ G: TNFRGraph,
419
+ dt_step: float,
420
+ t_local: float,
421
+ *,
422
+ method: str,
423
+ n_jobs: int | None = None,
424
+ ) -> NodeIncrements:
425
+ """Evaluate Γ contributions and merge them with ``νf·ΔNFR`` base terms."""
426
+
427
+ if method == "rk4":
428
+ gamma_count = 4
429
+ elif method == "euler":
430
+ gamma_count = 1
431
+ else:
432
+ raise ValueError("method must be 'euler' or 'rk4'")
433
+
434
+ gamma_spec = G.graph.get("_gamma_spec")
435
+ if gamma_spec is None:
436
+ gamma_spec = _get_gamma_spec(G)
437
+
438
+ gamma_type = ""
439
+ if isinstance(gamma_spec, Mapping):
440
+ gamma_type = str(gamma_spec.get("type", "")).lower()
441
+
442
+ if gamma_type == "none":
443
+ gamma_maps: tuple[GammaMap, ...] = tuple(
444
+ cast(GammaMap, {}) for _ in range(gamma_count)
445
+ )
446
+ return _collect_nodal_increments(G, gamma_maps, method=method)
447
+
448
+ nodes: list[NodeId] = list(G.nodes)
449
+ if not nodes:
450
+ gamma_maps = tuple(cast(GammaMap, {}) for _ in range(gamma_count))
451
+ return _collect_nodal_increments(G, gamma_maps, method=method)
452
+
453
+ if method == "rk4":
454
+ t_mid = t_local + dt_step / 2.0
455
+ t_end = t_local + dt_step
456
+ g1_map = _evaluate_gamma_map(G, nodes, t_local, n_jobs=n_jobs)
457
+ g_mid_map = _evaluate_gamma_map(G, nodes, t_mid, n_jobs=n_jobs)
458
+ g4_map = _evaluate_gamma_map(G, nodes, t_end, n_jobs=n_jobs)
459
+ gamma_maps = (g1_map, g_mid_map, g_mid_map, g4_map)
460
+ else: # method == "euler"
461
+ gamma_maps = (_evaluate_gamma_map(G, nodes, t_local, n_jobs=n_jobs),)
462
+
463
+ return _collect_nodal_increments(G, gamma_maps, method=method)
464
+
465
+
466
+ def _integrate_euler(
467
+ G: TNFRGraph,
468
+ dt_step: float,
469
+ t_local: float,
470
+ *,
471
+ n_jobs: int | None = None,
472
+ ) -> NodalUpdate:
473
+ """One explicit Euler integration step."""
474
+ increments = _build_gamma_increments(
475
+ G,
476
+ dt_step,
477
+ t_local,
478
+ method="euler",
479
+ n_jobs=n_jobs,
480
+ )
481
+ return _apply_increments(
482
+ G,
483
+ dt_step,
484
+ increments,
485
+ method="euler",
486
+ n_jobs=n_jobs,
487
+ )
488
+
489
+
490
+ def _integrate_rk4(
491
+ G: TNFRGraph,
492
+ dt_step: float,
493
+ t_local: float,
494
+ *,
495
+ n_jobs: int | None = None,
496
+ ) -> NodalUpdate:
497
+ """One Runge–Kutta order-4 integration step."""
498
+ increments = _build_gamma_increments(
499
+ G,
500
+ dt_step,
501
+ t_local,
502
+ method="rk4",
503
+ n_jobs=n_jobs,
504
+ )
505
+ return _apply_increments(
506
+ G,
507
+ dt_step,
508
+ increments,
509
+ method="rk4",
510
+ n_jobs=n_jobs,
511
+ )
512
+
513
+
514
+ class AbstractIntegrator(ABC):
515
+ """Abstract base class encapsulating nodal equation integration."""
516
+
517
+ @abstractmethod
518
+ def integrate(
519
+ self,
520
+ graph: TNFRGraph,
521
+ *,
522
+ dt: float | None,
523
+ t: float | None,
524
+ method: str | None,
525
+ n_jobs: int | None,
526
+ ) -> None:
527
+ """Advance ``graph`` coherence states according to the nodal equation."""
528
+
529
+
530
+ class DefaultIntegrator(AbstractIntegrator):
531
+ """Explicit integrator combining Euler and RK4 step implementations."""
532
+
533
+ def integrate(
534
+ self,
535
+ graph: TNFRGraph,
536
+ *,
537
+ dt: float | None,
538
+ t: float | None,
539
+ method: str | None,
540
+ n_jobs: int | None,
541
+ ) -> None:
542
+ """Integrate the nodal equation updating EPI, ΔEPI and Δ²EPI."""
543
+
544
+ if not isinstance(
545
+ graph, (nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph)
546
+ ):
547
+ raise TypeError("G must be a networkx graph instance")
548
+
549
+ dt_step, steps, t0, resolved_method = prepare_integration_params(
550
+ graph, dt, t, cast(IntegratorMethod | None, method)
551
+ )
552
+
553
+ t_local = t0
554
+ for _ in range(steps):
555
+ if resolved_method == "rk4":
556
+ updates: NodalUpdate = _integrate_rk4(
557
+ graph, dt_step, t_local, n_jobs=n_jobs
558
+ )
559
+ else:
560
+ updates = _integrate_euler(graph, dt_step, t_local, n_jobs=n_jobs)
561
+
562
+ for n, (epi, dEPI_dt, d2epi) in updates.items():
563
+ nd = graph.nodes[n]
564
+ epi_kind = get_attr_str(nd, ALIAS_EPI_KIND, "")
565
+
566
+ # Apply structural boundary preservation
567
+ epi_min = float(
568
+ graph.graph.get("EPI_MIN", DEFAULTS.get("EPI_MIN", -1.0))
569
+ )
570
+ epi_max = float(
571
+ graph.graph.get("EPI_MAX", DEFAULTS.get("EPI_MAX", 1.0))
572
+ )
573
+ clip_mode_str = str(graph.graph.get("CLIP_MODE", "hard"))
574
+ # Validate clip mode and cast to proper type
575
+ if clip_mode_str not in ("hard", "soft"):
576
+ clip_mode_str = "hard"
577
+ clip_mode: Literal["hard", "soft"] = clip_mode_str # type: ignore[assignment]
578
+ clip_k = float(graph.graph.get("CLIP_SOFT_K", 3.0))
579
+
580
+ epi_clipped = structural_clip(
581
+ epi,
582
+ lo=epi_min,
583
+ hi=epi_max,
584
+ mode=clip_mode,
585
+ k=clip_k,
586
+ record_stats=False,
587
+ )
588
+
589
+ set_attr(nd, ALIAS_EPI, epi_clipped)
590
+ if epi_kind:
591
+ set_attr_str(nd, ALIAS_EPI_KIND, epi_kind)
592
+ set_attr(nd, ALIAS_DEPI, dEPI_dt)
593
+ set_attr(nd, ALIAS_D2EPI, d2epi)
594
+
595
+ t_local += dt_step
596
+
597
+ graph.graph["_t"] = t_local
598
+
599
+
600
+ def update_epi_via_nodal_equation(
601
+ G: TNFRGraph,
602
+ *,
603
+ dt: float | None = None,
604
+ t: float | None = None,
605
+ method: Literal["euler", "rk4"] | None = None,
606
+ n_jobs: int | None = None,
607
+ ) -> None:
608
+ """TNFR nodal equation.
609
+
610
+ Implements the extended nodal equation:
611
+ ∂EPI/∂t = νf · ΔNFR(t) + Γi(R)
612
+
613
+ Where:
614
+ - EPI is the node's Primary Information Structure.
615
+ - νf is the node's structural frequency (Hz_str).
616
+ - ΔNFR(t) is the nodal gradient (reorganisation need), typically a mix
617
+ of components (e.g. phase θ, EPI, νf).
618
+ - Γi(R) is the optional network coupling as a function of Kuramoto order
619
+ ``R`` (see :mod:`gamma`), used to modulate network integration.
620
+
621
+ TNFR references: nodal equation (manual), νf/ΔNFR/EPI glossary, Γ operator.
622
+ Side effects: caches dEPI and updates EPI via explicit integration.
623
+ """
624
+ DefaultIntegrator().integrate(
625
+ G,
626
+ dt=dt,
627
+ t=t,
628
+ method=method,
629
+ n_jobs=n_jobs,
630
+ )
631
+
632
+
633
+ def _node_state(nd: dict[str, Any]) -> tuple[float, float, float, float]:
634
+ """Return common node state attributes for canonical equation evaluation.
635
+
636
+ Extracts the fundamental TNFR variables from node data:
637
+ - νf (vf): Structural frequency in Hz_str
638
+ - ΔNFR (dnfr): Nodal gradient (reorganization operator)
639
+ - dEPI/dt (previous): Last computed EPI derivative
640
+ - EPI (current): Current Primary Information Structure
641
+
642
+ These variables are used in the canonical nodal equation:
643
+ ∂EPI/∂t = νf · ΔNFR(t)
644
+
645
+ Args:
646
+ nd: Node data dictionary containing TNFR attributes
647
+
648
+ Returns:
649
+ Tuple of (vf, dnfr, dEPI_dt_prev, epi_i) with 0.0 defaults
650
+
651
+ Notes:
652
+ - vf alias maps to VF, frequency, or structural_frequency
653
+ - dnfr alias maps to DNFR, delta_nfr, or reorganization_gradient
654
+ - All values are coerced to float for numerical stability
655
+ """
656
+
657
+ vf = get_attr(nd, ALIAS_VF, 0.0)
658
+ dnfr = get_attr(nd, ALIAS_DNFR, 0.0)
659
+ dEPI_dt_prev = get_attr(nd, ALIAS_DEPI, 0.0)
660
+ epi_i = get_attr(nd, ALIAS_EPI, 0.0)
661
+ return vf, dnfr, dEPI_dt_prev, epi_i
@@ -0,0 +1,36 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Literal
4
+
5
+ from tnfr.types import TNFRGraph
6
+
7
+ __all__: tuple[str, ...]
8
+
9
+ class AbstractIntegrator:
10
+ def integrate(
11
+ self,
12
+ graph: TNFRGraph,
13
+ *,
14
+ dt: float | None = ...,
15
+ t: float | None = ...,
16
+ method: str | None = ...,
17
+ n_jobs: int | None = ...,
18
+ ) -> None: ...
19
+
20
+ class DefaultIntegrator(AbstractIntegrator):
21
+ def __init__(self) -> None: ...
22
+
23
+ def prepare_integration_params(
24
+ G: TNFRGraph,
25
+ dt: float | None = ...,
26
+ t: float | None = ...,
27
+ method: Literal["euler", "rk4"] | None = ...,
28
+ ) -> tuple[float, int, float, Literal["euler", "rk4"]]: ...
29
+ def update_epi_via_nodal_equation(
30
+ G: TNFRGraph,
31
+ *,
32
+ dt: float | None = ...,
33
+ t: float | None = ...,
34
+ method: Literal["euler", "rk4"] | None = ...,
35
+ n_jobs: int | None = ...,
36
+ ) -> None: ...