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