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,517 @@
1
+ """Hierarchical multi-scale TNFR network implementation.
2
+
3
+ Implements operational fractality by managing TNFR networks at multiple scales
4
+ with cross-scale coupling, preserving canonical TNFR invariants.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
10
+ from dataclasses import dataclass
11
+ from typing import Any, Dict, List, Optional, Sequence
12
+
13
+ import networkx as nx
14
+ import numpy as np
15
+
16
+ from ..types import DeltaNFR, NodeId, TNFRGraph
17
+ from ..structural import create_nfr
18
+ from ..dynamics import set_delta_nfr_hook, dnfr_epi_vf_mixed
19
+ from ..utils import get_logger
20
+
21
+ logger = get_logger(__name__)
22
+
23
+
24
+ @dataclass(frozen=True)
25
+ class ScaleDefinition:
26
+ """Definition of a single scale in a hierarchical TNFR network.
27
+
28
+ Parameters
29
+ ----------
30
+ name : str
31
+ Identifier for this scale (e.g., "quantum", "molecular", "cellular")
32
+ node_count : int
33
+ Number of nodes at this scale
34
+ coupling_strength : float
35
+ Base coupling strength for nodes within this scale (0.0 to 1.0)
36
+ edge_probability : float, optional
37
+ Probability of edge creation in Erdős-Rényi graph generation
38
+ """
39
+
40
+ name: str
41
+ node_count: int
42
+ coupling_strength: float
43
+ edge_probability: float = 0.1
44
+
45
+
46
+ @dataclass
47
+ class EvolutionResult:
48
+ """Results from multi-scale evolution.
49
+
50
+ Attributes
51
+ ----------
52
+ scale_results : Dict[str, Any]
53
+ Results indexed by scale name
54
+ total_coherence : float
55
+ Aggregated coherence across all scales
56
+ cross_scale_coupling : float
57
+ Measure of cross-scale synchronization
58
+ """
59
+
60
+ scale_results: Dict[str, Any]
61
+ total_coherence: float = 0.0
62
+ cross_scale_coupling: float = 0.0
63
+
64
+
65
+ class HierarchicalTNFRNetwork:
66
+ """Multi-scale TNFR network supporting operational fractality (§3.7).
67
+
68
+ Manages multiple TNFR networks at different scales with cross-scale
69
+ coupling, enabling simultaneous evolution while preserving structural
70
+ coherence.
71
+
72
+ This implementation maintains all TNFR canonical invariants:
73
+ - Nodal equation: ∂EPI/∂t = νf · ΔNFR(t)
74
+ - Operator closure: all transformations yield valid TNFR states
75
+ - Phase verification: explicit synchrony checks for coupling
76
+ - Determinism: reproducible evolution with fixed seeds
77
+
78
+ Parameters
79
+ ----------
80
+ scales : Sequence[ScaleDefinition]
81
+ Definitions of each scale in the hierarchy
82
+ seed : int, optional
83
+ Random seed for reproducible network generation
84
+ parallel : bool, optional
85
+ Enable parallel evolution of scales (default: True)
86
+ max_workers : int, optional
87
+ Maximum worker threads/processes for parallel execution
88
+
89
+ Examples
90
+ --------
91
+ Create a two-scale network and evolve it:
92
+
93
+ >>> from tnfr.multiscale import HierarchicalTNFRNetwork, ScaleDefinition
94
+ >>> scales = [
95
+ ... ScaleDefinition("micro", 100, 0.8),
96
+ ... ScaleDefinition("macro", 50, 0.5),
97
+ ... ]
98
+ >>> network = HierarchicalTNFRNetwork(scales, seed=42)
99
+ >>> result = network.evolve_multiscale(dt=0.1, steps=10)
100
+ >>> result.total_coherence # doctest: +SKIP
101
+ 0.65...
102
+
103
+ Notes
104
+ -----
105
+ Cross-scale coupling is computed as:
106
+ ΔNFR_total = ΔNFR_scale + Σ(coupling_ij * ΔNFR_other_scale)
107
+ where coupling_ij represents the strength of influence from scale j to i.
108
+ """
109
+
110
+ def __init__(
111
+ self,
112
+ scales: Sequence[ScaleDefinition],
113
+ seed: Optional[int] = None,
114
+ parallel: bool = True,
115
+ max_workers: Optional[int] = None,
116
+ ):
117
+ if not scales:
118
+ raise ValueError("At least one scale definition required")
119
+
120
+ self.scales = list(scales)
121
+ self.seed = seed
122
+ self.parallel = parallel
123
+ self.max_workers = max_workers
124
+
125
+ # Initialize networks for each scale
126
+ self.networks_by_scale: Dict[str, TNFRGraph] = {}
127
+ self._initialize_scales()
128
+
129
+ # Cross-scale coupling matrix (scale x scale)
130
+ self.cross_scale_couplings: Dict[tuple[str, str], float] = {}
131
+ self._initialize_cross_scale_couplings()
132
+
133
+ logger.info(
134
+ f"Initialized hierarchical network with {len(scales)} scales, "
135
+ f"total {sum(s.node_count for s in scales)} nodes"
136
+ )
137
+
138
+ def _initialize_scales(self) -> None:
139
+ """Initialize TNFR network for each scale."""
140
+ rng = np.random.RandomState(self.seed)
141
+
142
+ for scale in self.scales:
143
+ # Create Erdős-Rényi graph for this scale
144
+ G = nx.erdos_renyi_graph(
145
+ scale.node_count, scale.edge_probability, seed=rng.randint(0, 2**31)
146
+ )
147
+
148
+ # Initialize each node with TNFR attributes
149
+ for node in G.nodes():
150
+ node_id = f"{scale.name}_{node}"
151
+ G.nodes[node]["epi"] = rng.uniform(0.0, 1.0)
152
+ G.nodes[node]["vf"] = rng.uniform(0.5, 1.5)
153
+ G.nodes[node]["phase"] = rng.uniform(0.0, 2 * np.pi)
154
+ G.nodes[node]["delta_nfr"] = 0.0
155
+ G.nodes[node]["si"] = 0.0
156
+
157
+ # Set base coupling weights
158
+ for u, v in G.edges():
159
+ G[u][v]["weight"] = scale.coupling_strength * rng.uniform(0.8, 1.2)
160
+
161
+ # Install ΔNFR hook
162
+ set_delta_nfr_hook(G, dnfr_epi_vf_mixed)
163
+
164
+ self.networks_by_scale[scale.name] = G
165
+
166
+ def _initialize_cross_scale_couplings(self) -> None:
167
+ """Initialize coupling strengths between scales.
168
+
169
+ Default: Adjacent scales couple more strongly than distant scales.
170
+ """
171
+ scale_names = [s.name for s in self.scales]
172
+ n_scales = len(scale_names)
173
+
174
+ for i, scale_i in enumerate(scale_names):
175
+ for j, scale_j in enumerate(scale_names):
176
+ if i == j:
177
+ continue # No self-coupling
178
+
179
+ # Distance-based coupling: closer scales couple more
180
+ distance = abs(i - j)
181
+ coupling_strength = 0.3 / distance if distance > 0 else 0.0
182
+
183
+ self.cross_scale_couplings[(scale_i, scale_j)] = coupling_strength
184
+
185
+ def set_cross_scale_coupling(
186
+ self, from_scale: str, to_scale: str, strength: float
187
+ ) -> None:
188
+ """Set explicit cross-scale coupling strength.
189
+
190
+ Parameters
191
+ ----------
192
+ from_scale : str
193
+ Source scale name
194
+ to_scale : str
195
+ Target scale name
196
+ strength : float
197
+ Coupling strength (0.0 to 1.0)
198
+ """
199
+ if from_scale not in self.networks_by_scale:
200
+ raise ValueError(f"Unknown scale: {from_scale}")
201
+ if to_scale not in self.networks_by_scale:
202
+ raise ValueError(f"Unknown scale: {to_scale}")
203
+ if strength < 0.0 or strength > 1.0:
204
+ raise ValueError("Coupling strength must be in [0.0, 1.0]")
205
+
206
+ self.cross_scale_couplings[(from_scale, to_scale)] = strength
207
+
208
+ def compute_multiscale_dnfr(self, node_id: NodeId, target_scale: str) -> DeltaNFR:
209
+ """Compute ΔNFR considering all relevant scales.
210
+
211
+ Implements cross-scale ΔNFR computation:
212
+ ΔNFR_total = ΔNFR_base + Σ(coupling * ΔNFR_other)
213
+
214
+ Parameters
215
+ ----------
216
+ node_id : NodeId
217
+ Node identifier within the target scale
218
+ target_scale : str
219
+ Scale where the node resides
220
+
221
+ Returns
222
+ -------
223
+ DeltaNFR
224
+ Multi-scale ΔNFR value
225
+ """
226
+ if target_scale not in self.networks_by_scale:
227
+ raise ValueError(f"Unknown scale: {target_scale}")
228
+
229
+ G = self.networks_by_scale[target_scale]
230
+
231
+ # Base ΔNFR at target scale (simplified computation)
232
+ base_dnfr = G.nodes[node_id].get("delta_nfr", 0.0)
233
+
234
+ # Cross-scale contributions
235
+ cross_scale_contribution = 0.0
236
+ for other_scale in self.networks_by_scale:
237
+ if other_scale == target_scale:
238
+ continue
239
+
240
+ coupling = self.cross_scale_couplings.get((target_scale, other_scale), 0.0)
241
+ if coupling > 0:
242
+ # Aggregate ΔNFR from other scale
243
+ other_G = self.networks_by_scale[other_scale]
244
+ other_dnfr_values = [
245
+ other_G.nodes[n].get("delta_nfr", 0.0) for n in other_G.nodes()
246
+ ]
247
+ mean_other_dnfr = (
248
+ np.mean(other_dnfr_values) if other_dnfr_values else 0.0
249
+ )
250
+ cross_scale_contribution += coupling * mean_other_dnfr
251
+
252
+ return base_dnfr + cross_scale_contribution
253
+
254
+ def compute_total_coherence(self) -> float:
255
+ """Compute aggregated coherence across all scales.
256
+
257
+ Returns
258
+ -------
259
+ float
260
+ Total coherence C(t) aggregated across scales
261
+ """
262
+ total_c = 0.0
263
+ total_nodes = 0
264
+
265
+ for scale_name, G in self.networks_by_scale.items():
266
+ # Simplified coherence: 1 - mean(|ΔNFR|)
267
+ dnfr_values = [abs(G.nodes[n].get("delta_nfr", 0.0)) for n in G.nodes()]
268
+ mean_abs_dnfr = np.mean(dnfr_values) if dnfr_values else 0.0
269
+ scale_coherence = 1.0 / (1.0 + mean_abs_dnfr)
270
+
271
+ # Weight by node count
272
+ node_count = G.number_of_nodes()
273
+ total_c += scale_coherence * node_count
274
+ total_nodes += node_count
275
+
276
+ return total_c / total_nodes if total_nodes > 0 else 0.0
277
+
278
+ def evolve_multiscale(
279
+ self,
280
+ dt: float = 0.1,
281
+ steps: int = 10,
282
+ operators: Optional[Sequence[str]] = None,
283
+ ) -> EvolutionResult:
284
+ """Evolve all scales simultaneously with cross-coupling.
285
+
286
+ Parameters
287
+ ----------
288
+ dt : float
289
+ Time step for evolution
290
+ steps : int
291
+ Number of evolution steps
292
+ operators : Sequence[str], optional
293
+ Structural operators to apply (e.g., ["A'L", "THOL"])
294
+
295
+ Returns
296
+ -------
297
+ EvolutionResult
298
+ Results containing scale-specific and aggregated metrics
299
+ """
300
+ if operators is None:
301
+ operators = ["THOL"] # Default: Coherence operator
302
+
303
+ results = {}
304
+
305
+ for step in range(steps):
306
+ if self.parallel and self.max_workers != 1:
307
+ # Parallel evolution
308
+ results = self._evolve_parallel(dt, operators)
309
+ else:
310
+ # Sequential evolution
311
+ results = self._evolve_sequential(dt, operators)
312
+
313
+ # Apply cross-scale coupling effects
314
+ self._apply_cross_scale_coupling(dt)
315
+
316
+ # Compute final metrics
317
+ total_coherence = self.compute_total_coherence()
318
+ cross_coupling = self._compute_cross_scale_synchrony()
319
+
320
+ return EvolutionResult(
321
+ scale_results=results,
322
+ total_coherence=total_coherence,
323
+ cross_scale_coupling=cross_coupling,
324
+ )
325
+
326
+ def _evolve_sequential(self, dt: float, operators: Sequence[str]) -> Dict[str, Any]:
327
+ """Evolve scales sequentially."""
328
+ results = {}
329
+
330
+ for scale_name, G in self.networks_by_scale.items():
331
+ # Simple evolution: update ΔNFR for all nodes
332
+ for node in G.nodes():
333
+ phase = G.nodes[node]["phase"]
334
+ vf = G.nodes[node]["vf"]
335
+
336
+ # Compute neighbor phase difference contribution
337
+ neighbors = list(G.neighbors(node))
338
+ if neighbors:
339
+ phase_diffs = [
340
+ np.sin(phase - G.nodes[n]["phase"]) for n in neighbors
341
+ ]
342
+ dnfr = np.mean(phase_diffs)
343
+ else:
344
+ dnfr = 0.0
345
+
346
+ G.nodes[node]["delta_nfr"] = dnfr
347
+
348
+ # Update EPI according to nodal equation: ∂EPI/∂t = νf · ΔNFR
349
+ G.nodes[node]["epi"] += vf * dnfr * dt
350
+
351
+ results[scale_name] = {"coherence": self._scale_coherence(G)}
352
+
353
+ return results
354
+
355
+ def _evolve_parallel(self, dt: float, operators: Sequence[str]) -> Dict[str, Any]:
356
+ """Evolve scales in parallel using ThreadPoolExecutor.
357
+
358
+ Note: ThreadPoolExecutor is used instead of ProcessPoolExecutor because:
359
+ 1. NetworkX graphs are not easily picklable (required for multiprocessing)
360
+ 2. The overhead of serializing/deserializing graphs would negate benefits
361
+ 3. Thread-based parallelism still provides speedup for I/O and NumPy ops
362
+
363
+ For CPU-intensive workloads on very large scales, consider using
364
+ ProcessPoolExecutor with custom serialization or shared memory.
365
+ """
366
+ results = {}
367
+
368
+ # Use ThreadPoolExecutor for GIL-safe parallel evolution
369
+ # (ProcessPoolExecutor would require pickling networkx graphs)
370
+ with ThreadPoolExecutor(max_workers=self.max_workers) as executor:
371
+ futures = {
372
+ scale_name: executor.submit(
373
+ self._evolve_single_scale, scale_name, dt, operators
374
+ )
375
+ for scale_name in self.networks_by_scale
376
+ }
377
+
378
+ for scale_name, future in futures.items():
379
+ results[scale_name] = future.result()
380
+
381
+ return results
382
+
383
+ def _evolve_single_scale(
384
+ self, scale_name: str, dt: float, operators: Sequence[str]
385
+ ) -> Dict[str, Any]:
386
+ """Evolve a single scale (helper for parallel execution)."""
387
+ G = self.networks_by_scale[scale_name]
388
+
389
+ # Same logic as _evolve_sequential but for one scale
390
+ for node in G.nodes():
391
+ phase = G.nodes[node]["phase"]
392
+ vf = G.nodes[node]["vf"]
393
+
394
+ neighbors = list(G.neighbors(node))
395
+ if neighbors:
396
+ phase_diffs = [np.sin(phase - G.nodes[n]["phase"]) for n in neighbors]
397
+ dnfr = np.mean(phase_diffs)
398
+ else:
399
+ dnfr = 0.0
400
+
401
+ G.nodes[node]["delta_nfr"] = dnfr
402
+ G.nodes[node]["epi"] += vf * dnfr * dt
403
+
404
+ return {"coherence": self._scale_coherence(G)}
405
+
406
+ def _apply_cross_scale_coupling(self, dt: float) -> None:
407
+ """Apply cross-scale coupling effects after evolution step."""
408
+ # For each scale, add cross-scale ΔNFR contributions
409
+ for target_scale in self.networks_by_scale:
410
+ G_target = self.networks_by_scale[target_scale]
411
+
412
+ for node in G_target.nodes():
413
+ cross_contribution = 0.0
414
+
415
+ for source_scale in self.networks_by_scale:
416
+ if source_scale == target_scale:
417
+ continue
418
+
419
+ coupling = self.cross_scale_couplings.get(
420
+ (target_scale, source_scale), 0.0
421
+ )
422
+
423
+ if coupling > 0:
424
+ G_source = self.networks_by_scale[source_scale]
425
+ source_dnfr_values = [
426
+ G_source.nodes[n].get("delta_nfr", 0.0)
427
+ for n in G_source.nodes()
428
+ ]
429
+ mean_source_dnfr = (
430
+ np.mean(source_dnfr_values) if source_dnfr_values else 0.0
431
+ )
432
+ cross_contribution += coupling * mean_source_dnfr
433
+
434
+ # Apply cross-scale effect to EPI
435
+ if cross_contribution != 0.0:
436
+ vf = G_target.nodes[node]["vf"]
437
+ G_target.nodes[node]["epi"] += vf * cross_contribution * dt
438
+
439
+ def _scale_coherence(self, G: TNFRGraph) -> float:
440
+ """Compute coherence for a single scale."""
441
+ dnfr_values = [abs(G.nodes[n].get("delta_nfr", 0.0)) for n in G.nodes()]
442
+ mean_abs_dnfr = np.mean(dnfr_values) if dnfr_values else 0.0
443
+ return 1.0 / (1.0 + mean_abs_dnfr)
444
+
445
+ def _compute_cross_scale_synchrony(self) -> float:
446
+ """Compute cross-scale phase synchronization."""
447
+ if len(self.networks_by_scale) < 2:
448
+ return 0.0
449
+
450
+ # Simplified: compare mean phases across scales
451
+ scale_mean_phases = []
452
+ for G in self.networks_by_scale.values():
453
+ phases = [G.nodes[n]["phase"] for n in G.nodes()]
454
+ if phases:
455
+ # Use circular mean for phases
456
+ mean_phase = np.angle(np.mean(np.exp(1j * np.array(phases))))
457
+ scale_mean_phases.append(mean_phase)
458
+
459
+ if len(scale_mean_phases) < 2:
460
+ return 0.0
461
+
462
+ # Compute phase coherence between scales
463
+ phase_diffs = []
464
+ for i in range(len(scale_mean_phases)):
465
+ for j in range(i + 1, len(scale_mean_phases)):
466
+ phase_diff = abs(scale_mean_phases[i] - scale_mean_phases[j])
467
+ # Normalize to [0, π]
468
+ phase_diff = min(phase_diff, 2 * np.pi - phase_diff)
469
+ phase_diffs.append(phase_diff)
470
+
471
+ mean_phase_diff = np.mean(phase_diffs) if phase_diffs else 0.0
472
+ # Convert to synchrony metric (0 = no sync, 1 = perfect sync)
473
+ synchrony = 1.0 - (mean_phase_diff / np.pi)
474
+
475
+ return max(0.0, synchrony)
476
+
477
+ def get_scale_network(self, scale_name: str) -> TNFRGraph:
478
+ """Get the network graph for a specific scale.
479
+
480
+ Parameters
481
+ ----------
482
+ scale_name : str
483
+ Name of the scale
484
+
485
+ Returns
486
+ -------
487
+ TNFRGraph
488
+ NetworkX graph for the specified scale
489
+ """
490
+ if scale_name not in self.networks_by_scale:
491
+ raise ValueError(f"Unknown scale: {scale_name}")
492
+ return self.networks_by_scale[scale_name]
493
+
494
+ def memory_footprint(self) -> Dict[str, float]:
495
+ """Estimate memory usage per scale.
496
+
497
+ Returns
498
+ -------
499
+ Dict[str, float]
500
+ Memory usage in MB for each scale
501
+ """
502
+ footprint = {}
503
+ for scale_name, G in self.networks_by_scale.items():
504
+ # Rough estimate: graph structure + node attributes
505
+ n_nodes = G.number_of_nodes()
506
+ n_edges = G.number_of_edges()
507
+
508
+ # NetworkX overhead + node dict + edge dict + attributes
509
+ # Each node: ~200 bytes (dict overhead) + 4 attributes * 8 bytes
510
+ # Each edge: ~100 bytes (dict overhead) + 1 attribute * 8 bytes
511
+ estimate_bytes = n_nodes * 232 + n_edges * 108
512
+ estimate_mb = estimate_bytes / (1024 * 1024)
513
+
514
+ footprint[scale_name] = estimate_mb
515
+
516
+ footprint["total"] = sum(v for k, v in footprint.items() if k != "total")
517
+ return footprint