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,27 @@
1
+ """Network analysis utilities for TNFR structural operators.
2
+
3
+ This module provides network-level analysis tools for structural operators,
4
+ particularly for detecting and analyzing emission sources, phase compatibility,
5
+ and coherence flow patterns.
6
+
7
+ TNFR Context
8
+ ------------
9
+ According to TNFR.pdf §2.2.1 (EN - Recepción estructural), Reception (EN) is
10
+ not passive absorption but active reorganization that requires:
11
+
12
+ - Detection of compatible emission sources in the network
13
+ - Phase compatibility validation (θᵢ ≈ θⱼ for coupling)
14
+ - Integration efficiency measurement (coherence received vs. integrated)
15
+ - Source traceability (which nodes contribute to EPI)
16
+
17
+ This module implements these capabilities for the Reception operator and other
18
+ operators that require network-level coherence analysis.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ __all__ = [
24
+ "detect_emission_sources",
25
+ ]
26
+
27
+ from .source_detection import detect_emission_sources
@@ -0,0 +1,186 @@
1
+ """Source detection for Reception (EN) operator.
2
+
3
+ This module implements emission source detection for the Reception operator,
4
+ enabling active reorganization through identification of compatible coherence
5
+ sources in the network.
6
+
7
+ TNFR Context
8
+ ------------
9
+ According to TNFR.pdf §2.2.1, EN (Reception) requires:
10
+
11
+ 1. **Source Detection**: Identify nodes emitting coherence (active EPI)
12
+ 2. **Phase Compatibility**: Validate θᵢ ≈ θⱼ for effective coupling
13
+ 3. **Coherence Strength**: Measure available coherence (EPI × νf)
14
+ 4. **Network Distance**: Respect structural proximity in network
15
+
16
+ These functions enable Reception to operate as "active reorganization from
17
+ the exterior" rather than passive data absorption.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ from typing import TYPE_CHECKING, Any
23
+
24
+ if TYPE_CHECKING:
25
+ from ...types import TNFRGraph
26
+
27
+ import math
28
+
29
+ try:
30
+ import networkx as nx
31
+ except ImportError:
32
+ nx = None # Fallback to neighbor-only detection if networkx unavailable
33
+
34
+ __all__ = [
35
+ "detect_emission_sources",
36
+ ]
37
+
38
+ # Active emission threshold: minimum EPI for node to be considered emission source
39
+ # Below this threshold, structural form is too weak to contribute coherence
40
+ ACTIVE_EMISSION_THRESHOLD = 0.2
41
+
42
+
43
+ def detect_emission_sources(
44
+ G: TNFRGraph,
45
+ receiver_node: Any,
46
+ max_distance: int = 2,
47
+ ) -> list[tuple[Any, float, float]]:
48
+ """Detect potential emission sources for EN receiver node.
49
+
50
+ Identifies nodes in the network that can serve as coherence sources for
51
+ the receiving node, ranked by phase compatibility. This implements the
52
+ "active reception" principle from TNFR.pdf §2.2.1 where EN must detect
53
+ and validate compatible emission sources before integrating external
54
+ coherence.
55
+
56
+ Parameters
57
+ ----------
58
+ G : TNFRGraph
59
+ Network graph containing TNFR nodes
60
+ receiver_node : Any
61
+ Node applying EN (Reception) that needs to detect sources
62
+ max_distance : int, optional
63
+ Maximum network distance to search for sources (default: 2)
64
+ Respects structural locality principle - distant nodes have
65
+ negligible coupling
66
+
67
+ Returns
68
+ -------
69
+ list[tuple[Any, float, float]]
70
+ List of (source_node, phase_compatibility, coherence_strength) tuples,
71
+ sorted by phase compatibility (most compatible first).
72
+
73
+ - source_node: Node identifier
74
+ - phase_compatibility: 0.0 (incompatible) to 1.0 (perfect sync)
75
+ - coherence_strength: Available coherence (EPI × νf)
76
+
77
+ TNFR Structural Logic
78
+ ---------------------
79
+ **Phase Compatibility Calculation:**
80
+
81
+ Given receiver phase θ_r and source phase θ_s:
82
+
83
+ .. code-block:: text
84
+
85
+ phase_diff = |θ_r - θ_s|
86
+ normalized_diff = min(phase_diff / π, 1.0) # Normalize to [0, 1]
87
+ compatibility = 1.0 - normalized_diff
88
+
89
+ Phase values are normalized to [0, π] range before comparison to respect
90
+ phase periodicity in TNFR.
91
+
92
+ **Coherence Strength:**
93
+
94
+ Coherence strength represents the emission capacity of the source:
95
+
96
+ .. code-block:: text
97
+
98
+ coherence_strength = EPI × νf
99
+
100
+ Higher values indicate stronger emission that can be more effectively
101
+ integrated by the receiver.
102
+
103
+ **Active Emission Threshold:**
104
+
105
+ Only nodes with EPI ≥ 0.2 are considered active emission sources.
106
+ Below this threshold, the node's structural form is too weak to
107
+ effectively contribute coherence.
108
+
109
+ Examples
110
+ --------
111
+ >>> from tnfr.structural import create_nfr
112
+ >>> import networkx as nx
113
+ >>> # Create network with emitter and receiver
114
+ >>> G = nx.Graph()
115
+ >>> G, emitter = create_nfr("teacher", epi=0.5, vf=1.0, theta=0.3, G=G)
116
+ >>> _, receiver = create_nfr("student", epi=0.25, vf=0.9, theta=0.35, G=G)
117
+ >>> G.add_edge(emitter, receiver)
118
+ >>> # Detect sources
119
+ >>> sources = detect_emission_sources(G, receiver)
120
+ >>> len(sources)
121
+ 1
122
+ >>> source_node, compatibility, strength = sources[0]
123
+ >>> source_node == emitter
124
+ True
125
+ >>> 0.9 <= compatibility <= 1.0 # High phase compatibility
126
+ True
127
+ >>> strength > 0.4 # Strong coherence (0.5 * 1.0)
128
+ True
129
+
130
+ See Also
131
+ --------
132
+ Reception : Operator that uses source detection
133
+ """
134
+ from ...alias import get_attr
135
+ from ...constants.aliases import ALIAS_THETA, ALIAS_EPI, ALIAS_VF
136
+
137
+ # Get receiver phase
138
+ receiver_theta = float(get_attr(G.nodes[receiver_node], ALIAS_THETA, 0.0))
139
+ # Normalize to [0, π] range for phase comparison
140
+ receiver_theta = abs(receiver_theta) % math.pi
141
+
142
+ sources = []
143
+
144
+ # Scan network for potential sources
145
+ for source in G.nodes():
146
+ if source == receiver_node:
147
+ continue
148
+
149
+ # Check network distance
150
+ if nx is not None:
151
+ try:
152
+ distance = nx.shortest_path_length(G, source, receiver_node)
153
+ if distance > max_distance:
154
+ continue
155
+ except nx.NetworkXNoPath:
156
+ continue
157
+ else:
158
+ # Fallback: only check immediate neighbors
159
+ if source not in G.neighbors(receiver_node):
160
+ continue
161
+
162
+ # Check if source is active (has coherent EPI)
163
+ source_epi = float(get_attr(G.nodes[source], ALIAS_EPI, 0.0))
164
+ if source_epi < ACTIVE_EMISSION_THRESHOLD:
165
+ continue
166
+
167
+ # Calculate phase compatibility
168
+ source_theta = float(get_attr(G.nodes[source], ALIAS_THETA, 0.0))
169
+ # Normalize to [0, π] range
170
+ source_theta = abs(source_theta) % math.pi
171
+
172
+ # Phase difference normalized to [0, 1] scale
173
+ phase_diff = abs(receiver_theta - source_theta)
174
+ normalized_diff = min(phase_diff / math.pi, 1.0)
175
+ phase_compatibility = 1.0 - normalized_diff
176
+
177
+ # Coherence strength (EPI × νf)
178
+ source_vf = float(get_attr(G.nodes[source], ALIAS_VF, 0.0))
179
+ coherence_strength = source_epi * source_vf
180
+
181
+ sources.append((source, phase_compatibility, coherence_strength))
182
+
183
+ # Sort by phase compatibility (most compatible first)
184
+ sources.sort(key=lambda x: x[1], reverse=True)
185
+
186
+ return sources
@@ -0,0 +1,395 @@
1
+ """Nodal equation validation for TNFR structural operators.
2
+
3
+ This module provides validation for the fundamental TNFR nodal equation:
4
+
5
+ ∂EPI/∂t = νf · ΔNFR(t)
6
+
7
+ This equation governs how the Primary Information Structure (EPI) evolves
8
+ over time based on the structural frequency (νf) and internal reorganization
9
+ operator (ΔNFR). All structural operator applications must respect this
10
+ canonical relationship to maintain TNFR theoretical fidelity.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from typing import TYPE_CHECKING, Any
16
+
17
+ if TYPE_CHECKING:
18
+ from ..types import NodeId, TNFRGraph
19
+
20
+ from ..alias import get_attr, set_attr
21
+ from ..constants.aliases import ALIAS_DNFR, ALIAS_EPI, ALIAS_VF, ALIAS_D2EPI
22
+
23
+ __all__ = [
24
+ "NodalEquationViolation",
25
+ "validate_nodal_equation",
26
+ "compute_expected_depi_dt",
27
+ "compute_d2epi_dt2",
28
+ ]
29
+
30
+ # Default tolerance for nodal equation validation
31
+ DEFAULT_NODAL_EQUATION_TOLERANCE = 1e-3
32
+ DEFAULT_NODAL_EQUATION_CLIP_AWARE = True
33
+
34
+
35
+ class NodalEquationViolation(Exception):
36
+ """Raised when operator application violates the nodal equation.
37
+
38
+ The nodal equation ∂EPI/∂t = νf · ΔNFR(t) is the fundamental equation
39
+ governing node evolution in TNFR. Violations indicate non-canonical
40
+ structural transformations.
41
+ """
42
+
43
+ def __init__(
44
+ self,
45
+ operator: str,
46
+ measured_depi_dt: float,
47
+ expected_depi_dt: float,
48
+ tolerance: float,
49
+ details: dict[str, Any] | None = None,
50
+ ) -> None:
51
+ """Initialize nodal equation violation.
52
+
53
+ Parameters
54
+ ----------
55
+ operator : str
56
+ Name of the operator that caused the violation
57
+ measured_depi_dt : float
58
+ Measured ∂EPI/∂t from before/after states
59
+ expected_depi_dt : float
60
+ Expected ∂EPI/∂t from νf · ΔNFR(t)
61
+ tolerance : float
62
+ Tolerance threshold that was exceeded
63
+ details : dict, optional
64
+ Additional diagnostic information
65
+ """
66
+ self.operator = operator
67
+ self.measured_depi_dt = measured_depi_dt
68
+ self.expected_depi_dt = expected_depi_dt
69
+ self.tolerance = tolerance
70
+ self.details = details or {}
71
+
72
+ error = abs(measured_depi_dt - expected_depi_dt)
73
+ super().__init__(
74
+ f"Nodal equation violation in {operator}: "
75
+ f"|∂EPI/∂t_measured - νf·ΔNFR| = {error:.3e} > {tolerance:.3e}\n"
76
+ f" Measured: {measured_depi_dt:.6f}\n"
77
+ f" Expected: {expected_depi_dt:.6f}"
78
+ )
79
+
80
+
81
+ def _get_node_attr(
82
+ G: TNFRGraph, node: NodeId, aliases: tuple[str, ...], default: float = 0.0
83
+ ) -> float:
84
+ """Get node attribute using alias fallback."""
85
+ return float(get_attr(G.nodes[node], aliases, default))
86
+
87
+
88
+ def compute_expected_depi_dt(G: TNFRGraph, node: NodeId) -> float:
89
+ """Compute expected ∂EPI/∂t from current νf and ΔNFR values.
90
+
91
+ Implements the canonical TNFR nodal equation:
92
+ ∂EPI/∂t = νf · ΔNFR(t)
93
+
94
+ Parameters
95
+ ----------
96
+ G : TNFRGraph
97
+ Graph containing the node
98
+ node : NodeId
99
+ Node to compute expected rate for
100
+
101
+ Returns
102
+ -------
103
+ float
104
+ Expected rate of EPI change (∂EPI/∂t)
105
+
106
+ Notes
107
+ -----
108
+ The structural frequency (νf) is in Hz_str (structural hertz) units,
109
+ and ΔNFR is the dimensionless internal reorganization operator.
110
+ Their product gives the rate of structural reorganization.
111
+ """
112
+ vf = _get_node_attr(G, node, ALIAS_VF)
113
+ dnfr = _get_node_attr(G, node, ALIAS_DNFR)
114
+ return vf * dnfr
115
+
116
+
117
+ def validate_nodal_equation(
118
+ G: TNFRGraph,
119
+ node: NodeId,
120
+ epi_before: float,
121
+ epi_after: float,
122
+ dt: float,
123
+ *,
124
+ operator_name: str = "unknown",
125
+ tolerance: float | None = None,
126
+ strict: bool = False,
127
+ clip_aware: bool | None = None,
128
+ ) -> bool:
129
+ """Validate that EPI change respects the nodal equation.
130
+
131
+ Verifies that the change in EPI between before and after states
132
+ matches the prediction from the nodal equation:
133
+
134
+ ∂EPI/∂t = νf · ΔNFR(t)
135
+
136
+ Parameters
137
+ ----------
138
+ G : TNFRGraph
139
+ Graph containing the node
140
+ node : NodeId
141
+ Node that underwent transformation
142
+ epi_before : float
143
+ EPI value before operator application
144
+ epi_after : float
145
+ EPI value after operator application
146
+ dt : float
147
+ Time step (typically 1.0 for discrete operator applications)
148
+ operator_name : str, optional
149
+ Name of the operator for error reporting
150
+ tolerance : float, optional
151
+ Absolute tolerance for equation validation.
152
+ If None, uses graph configuration or default (1e-3).
153
+ strict : bool, default False
154
+ If True, raises NodalEquationViolation on failure.
155
+ If False, returns validation result without raising.
156
+ clip_aware : bool, optional
157
+ If True, validates using structural_clip to account for boundary
158
+ preservation: EPI_expected = structural_clip(EPI_theoretical).
159
+ If False, uses classic mode without clip adjustment.
160
+ If None, uses graph configuration or default (True).
161
+
162
+ Returns
163
+ -------
164
+ bool
165
+ True if equation is satisfied within tolerance, False otherwise
166
+
167
+ Raises
168
+ ------
169
+ NodalEquationViolation
170
+ If strict=True and validation fails
171
+
172
+ Notes
173
+ -----
174
+ The nodal equation is validated using the post-transformation νf and ΔNFR
175
+ values, as these represent the structural state after the operator effect.
176
+
177
+ For discrete operator applications, dt is typically 1.0, making the
178
+ validation equivalent to: (epi_after - epi_before) ≈ νf_after · ΔNFR_after
179
+
180
+ **Clip-aware mode** (default): When structural_clip intervenes to preserve
181
+ boundaries, the actual EPI differs from the theoretical prediction. This
182
+ mode accounts for boundary preservation by applying structural_clip to the
183
+ theoretical value before comparison:
184
+
185
+ EPI_expected = structural_clip(EPI_before + νf · ΔNFR · dt)
186
+
187
+ This ensures validation passes when clip interventions are legitimate parts
188
+ of the operator's structural boundary preservation.
189
+
190
+ **Classic mode** (clip_aware=False): Validates without clip adjustment,
191
+ useful for detecting when unexpected clipping occurs.
192
+
193
+ Examples
194
+ --------
195
+ >>> from tnfr.structural import create_nfr
196
+ >>> G, node = create_nfr("test", epi=0.5, vf=1.0, dnfr=0.1)
197
+ >>> epi_before = G.nodes[node]["EPI"]
198
+ >>> # Apply some transformation...
199
+ >>> epi_after = G.nodes[node]["EPI"]
200
+ >>> is_valid = validate_nodal_equation(G, node, epi_before, epi_after, dt=1.0)
201
+ """
202
+ if tolerance is None:
203
+ # Try graph configuration first, then use default constant
204
+ tolerance = float(
205
+ G.graph.get("NODAL_EQUATION_TOLERANCE", DEFAULT_NODAL_EQUATION_TOLERANCE)
206
+ )
207
+
208
+ if clip_aware is None:
209
+ # Try graph configuration first, then use default
210
+ clip_aware = G.graph.get(
211
+ "NODAL_EQUATION_CLIP_AWARE", DEFAULT_NODAL_EQUATION_CLIP_AWARE
212
+ )
213
+
214
+ # Measured rate of EPI change
215
+ measured_depi_dt = (epi_after - epi_before) / dt if dt > 0 else 0.0
216
+
217
+ # Expected rate from nodal equation: νf · ΔNFR
218
+ # Use post-transformation values as they represent the new structural state
219
+ expected_depi_dt = compute_expected_depi_dt(G, node)
220
+
221
+ if clip_aware:
222
+ # Clip-aware mode: apply structural_clip to theoretical EPI before comparison
223
+ from ..dynamics.structural_clip import structural_clip
224
+
225
+ # Get structural boundaries from graph configuration
226
+ epi_min = float(G.graph.get("EPI_MIN", -1.0))
227
+ epi_max = float(G.graph.get("EPI_MAX", 1.0))
228
+ clip_mode = G.graph.get("CLIP_MODE", "hard")
229
+
230
+ # Compute theoretical EPI based on nodal equation
231
+ epi_theoretical = epi_before + (expected_depi_dt * dt)
232
+
233
+ # Validate and normalize clip_mode
234
+ clip_mode_str = str(clip_mode).lower()
235
+ if clip_mode_str not in ("hard", "soft"):
236
+ clip_mode_str = "hard" # Default to safe fallback
237
+
238
+ # Apply structural_clip to get expected EPI (what the operator should produce)
239
+ epi_expected = structural_clip(
240
+ epi_theoretical, lo=epi_min, hi=epi_max, mode=clip_mode_str # type: ignore[arg-type]
241
+ )
242
+
243
+ # Validate against clipped expected value
244
+ error = abs(epi_after - epi_expected)
245
+ is_valid = error <= tolerance
246
+
247
+ if not is_valid and strict:
248
+ vf = _get_node_attr(G, node, ALIAS_VF)
249
+ dnfr = _get_node_attr(G, node, ALIAS_DNFR)
250
+
251
+ raise NodalEquationViolation(
252
+ operator=operator_name,
253
+ measured_depi_dt=measured_depi_dt,
254
+ expected_depi_dt=expected_depi_dt,
255
+ tolerance=tolerance,
256
+ details={
257
+ "epi_before": epi_before,
258
+ "epi_after": epi_after,
259
+ "epi_theoretical": epi_theoretical,
260
+ "epi_expected": epi_expected,
261
+ "dt": dt,
262
+ "vf": vf,
263
+ "dnfr": dnfr,
264
+ "error": error,
265
+ "clip_aware": True,
266
+ "clip_intervened": abs(epi_theoretical - epi_expected) > 1e-10,
267
+ },
268
+ )
269
+ else:
270
+ # Classic mode: validate rate of change directly
271
+ error = abs(measured_depi_dt - expected_depi_dt)
272
+ is_valid = error <= tolerance
273
+
274
+ if not is_valid and strict:
275
+ vf = _get_node_attr(G, node, ALIAS_VF)
276
+ dnfr = _get_node_attr(G, node, ALIAS_DNFR)
277
+
278
+ raise NodalEquationViolation(
279
+ operator=operator_name,
280
+ measured_depi_dt=measured_depi_dt,
281
+ expected_depi_dt=expected_depi_dt,
282
+ tolerance=tolerance,
283
+ details={
284
+ "epi_before": epi_before,
285
+ "epi_after": epi_after,
286
+ "dt": dt,
287
+ "vf": vf,
288
+ "dnfr": dnfr,
289
+ "error": error,
290
+ "clip_aware": False,
291
+ },
292
+ )
293
+
294
+ return is_valid
295
+
296
+
297
+ def compute_d2epi_dt2(G: "TNFRGraph", node: "NodeId") -> float:
298
+ """Compute ∂²EPI/∂t² (structural acceleration).
299
+
300
+ According to TNFR canonical theory (§2.3.3, R4), bifurcation occurs when
301
+ structural acceleration exceeds threshold τ:
302
+ |∂²EPI/∂t²| > τ → multiple reorganization paths viable
303
+
304
+ This function computes the second-order time derivative of EPI using
305
+ finite differences from the node's EPI history. The acceleration indicates
306
+ how rapidly the rate of structural change is itself changing, which is
307
+ the key indicator of bifurcation readiness.
308
+
309
+ Parameters
310
+ ----------
311
+ G : TNFRGraph
312
+ Graph containing the node
313
+ node : NodeId
314
+ Node identifier to compute acceleration for
315
+
316
+ Returns
317
+ -------
318
+ float
319
+ Structural acceleration ∂²EPI/∂t². Positive values indicate accelerating
320
+ growth, negative values indicate accelerating contraction. Magnitude
321
+ indicates bifurcation potential.
322
+
323
+ Notes
324
+ -----
325
+ **Computation method:**
326
+
327
+ Uses second-order finite difference approximation:
328
+ ∂²EPI/∂t² ≈ (EPI_t - 2·EPI_{t-1} + EPI_{t-2}) / Δt²
329
+
330
+ For discrete operator applications with Δt=1:
331
+ ∂²EPI/∂t² ≈ EPI_t - 2·EPI_{t-1} + EPI_{t-2}
332
+
333
+ **History requirements:**
334
+
335
+ Requires at least 3 historical EPI values stored in node's `_epi_history`
336
+ attribute. If insufficient history exists, returns 0.0 (no acceleration).
337
+
338
+ The computed value is automatically stored in the node's `D2_EPI` attribute
339
+ (using ALIAS_D2EPI aliases) for telemetry and metrics collection.
340
+
341
+ **Physical interpretation:**
342
+
343
+ - **d2epi ≈ 0**: Steady structural evolution (constant rate)
344
+ - **d2epi > τ**: Positive acceleration, expanding reorganization
345
+ - **d2epi < -τ**: Negative acceleration, collapsing reorganization
346
+ - **|d2epi| > τ**: Bifurcation active, multiple paths viable
347
+
348
+ Examples
349
+ --------
350
+ >>> from tnfr.structural import create_nfr
351
+ >>> from tnfr.operators.definitions import Emission, Dissonance
352
+ >>> from tnfr.operators.nodal_equation import compute_d2epi_dt2
353
+ >>>
354
+ >>> G, node = create_nfr("test", epi=0.2, vf=1.0)
355
+ >>>
356
+ >>> # Build EPI history through operator applications
357
+ >>> Emission()(G, node) # EPI increases
358
+ >>> Emission()(G, node) # EPI increases more
359
+ >>> Dissonance()(G, node) # Introduce instability
360
+ >>>
361
+ >>> # Compute acceleration
362
+ >>> d2epi = compute_d2epi_dt2(G, node)
363
+ >>>
364
+ >>> # Check if bifurcation threshold exceeded
365
+ >>> tau = G.graph.get("OZ_BIFURCATION_THRESHOLD", 0.5)
366
+ >>> bifurcation_active = abs(d2epi) > tau
367
+
368
+ See Also
369
+ --------
370
+ tnfr.dynamics.bifurcation.compute_bifurcation_score : Uses d2epi for scoring
371
+ tnfr.operators.metrics.dissonance_metrics : Reports d2epi in OZ metrics
372
+ tnfr.operators.preconditions.validate_dissonance : Checks d2epi for bifurcation
373
+ """
374
+ # Get EPI history from node
375
+ history = G.nodes[node].get("_epi_history", [])
376
+
377
+ if len(history) < 3:
378
+ # Insufficient history for second derivative
379
+ # Need at least 3 points: t-2, t-1, t
380
+ return 0.0
381
+
382
+ # Extract last 3 EPI values
383
+ epi_t = history[-1] # Current (most recent)
384
+ epi_t1 = history[-2] # One step ago
385
+ epi_t2 = history[-3] # Two steps ago
386
+
387
+ # Second-order finite difference (assuming dt=1 for discrete operators)
388
+ # ∂²EPI/∂t² ≈ (EPI_t - 2·EPI_{t-1} + EPI_{t-2}) / dt²
389
+ # For dt=1: ∂²EPI/∂t² ≈ EPI_t - 2·EPI_{t-1} + EPI_{t-2}
390
+ d2epi = epi_t - 2.0 * epi_t1 + epi_t2
391
+
392
+ # Store in node for telemetry (using set_attr to handle aliases)
393
+ set_attr(G.nodes[node], ALIAS_D2EPI, d2epi)
394
+
395
+ return float(d2epi)