tnfr 3.0.3__py3-none-any.whl → 8.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of tnfr might be problematic. Click here for more details.

Files changed (360) hide show
  1. tnfr/__init__.py +375 -56
  2. tnfr/__init__.pyi +33 -0
  3. tnfr/_compat.py +10 -0
  4. tnfr/_generated_version.py +34 -0
  5. tnfr/_version.py +49 -0
  6. tnfr/_version.pyi +7 -0
  7. tnfr/alias.py +723 -0
  8. tnfr/alias.pyi +108 -0
  9. tnfr/backends/__init__.py +354 -0
  10. tnfr/backends/jax_backend.py +173 -0
  11. tnfr/backends/numpy_backend.py +238 -0
  12. tnfr/backends/optimized_numpy.py +420 -0
  13. tnfr/backends/torch_backend.py +408 -0
  14. tnfr/cache.py +171 -0
  15. tnfr/cache.pyi +13 -0
  16. tnfr/cli/__init__.py +110 -0
  17. tnfr/cli/__init__.pyi +26 -0
  18. tnfr/cli/arguments.py +489 -0
  19. tnfr/cli/arguments.pyi +29 -0
  20. tnfr/cli/execution.py +914 -0
  21. tnfr/cli/execution.pyi +70 -0
  22. tnfr/cli/interactive_validator.py +614 -0
  23. tnfr/cli/utils.py +51 -0
  24. tnfr/cli/utils.pyi +7 -0
  25. tnfr/cli/validate.py +236 -0
  26. tnfr/compat/__init__.py +85 -0
  27. tnfr/compat/dataclass.py +136 -0
  28. tnfr/compat/jsonschema_stub.py +61 -0
  29. tnfr/compat/matplotlib_stub.py +73 -0
  30. tnfr/compat/numpy_stub.py +155 -0
  31. tnfr/config/__init__.py +224 -0
  32. tnfr/config/__init__.pyi +10 -0
  33. tnfr/config/constants.py +104 -0
  34. tnfr/config/constants.pyi +12 -0
  35. tnfr/config/defaults.py +54 -0
  36. tnfr/config/defaults_core.py +212 -0
  37. tnfr/config/defaults_init.py +33 -0
  38. tnfr/config/defaults_metric.py +104 -0
  39. tnfr/config/feature_flags.py +81 -0
  40. tnfr/config/feature_flags.pyi +16 -0
  41. tnfr/config/glyph_constants.py +31 -0
  42. tnfr/config/init.py +77 -0
  43. tnfr/config/init.pyi +8 -0
  44. tnfr/config/operator_names.py +254 -0
  45. tnfr/config/operator_names.pyi +36 -0
  46. tnfr/config/physics_derivation.py +354 -0
  47. tnfr/config/presets.py +83 -0
  48. tnfr/config/presets.pyi +7 -0
  49. tnfr/config/security.py +927 -0
  50. tnfr/config/thresholds.py +114 -0
  51. tnfr/config/tnfr_config.py +498 -0
  52. tnfr/constants/__init__.py +92 -0
  53. tnfr/constants/__init__.pyi +92 -0
  54. tnfr/constants/aliases.py +33 -0
  55. tnfr/constants/aliases.pyi +27 -0
  56. tnfr/constants/init.py +33 -0
  57. tnfr/constants/init.pyi +12 -0
  58. tnfr/constants/metric.py +104 -0
  59. tnfr/constants/metric.pyi +19 -0
  60. tnfr/core/__init__.py +33 -0
  61. tnfr/core/container.py +226 -0
  62. tnfr/core/default_implementations.py +329 -0
  63. tnfr/core/interfaces.py +279 -0
  64. tnfr/dynamics/__init__.py +238 -0
  65. tnfr/dynamics/__init__.pyi +83 -0
  66. tnfr/dynamics/adaptation.py +267 -0
  67. tnfr/dynamics/adaptation.pyi +7 -0
  68. tnfr/dynamics/adaptive_sequences.py +189 -0
  69. tnfr/dynamics/adaptive_sequences.pyi +14 -0
  70. tnfr/dynamics/aliases.py +23 -0
  71. tnfr/dynamics/aliases.pyi +19 -0
  72. tnfr/dynamics/bifurcation.py +232 -0
  73. tnfr/dynamics/canonical.py +229 -0
  74. tnfr/dynamics/canonical.pyi +48 -0
  75. tnfr/dynamics/coordination.py +385 -0
  76. tnfr/dynamics/coordination.pyi +25 -0
  77. tnfr/dynamics/dnfr.py +3034 -0
  78. tnfr/dynamics/dnfr.pyi +26 -0
  79. tnfr/dynamics/dynamic_limits.py +225 -0
  80. tnfr/dynamics/feedback.py +252 -0
  81. tnfr/dynamics/feedback.pyi +24 -0
  82. tnfr/dynamics/fused_dnfr.py +454 -0
  83. tnfr/dynamics/homeostasis.py +157 -0
  84. tnfr/dynamics/homeostasis.pyi +14 -0
  85. tnfr/dynamics/integrators.py +661 -0
  86. tnfr/dynamics/integrators.pyi +36 -0
  87. tnfr/dynamics/learning.py +310 -0
  88. tnfr/dynamics/learning.pyi +33 -0
  89. tnfr/dynamics/metabolism.py +254 -0
  90. tnfr/dynamics/nbody.py +796 -0
  91. tnfr/dynamics/nbody_tnfr.py +783 -0
  92. tnfr/dynamics/propagation.py +326 -0
  93. tnfr/dynamics/runtime.py +908 -0
  94. tnfr/dynamics/runtime.pyi +77 -0
  95. tnfr/dynamics/sampling.py +36 -0
  96. tnfr/dynamics/sampling.pyi +7 -0
  97. tnfr/dynamics/selectors.py +711 -0
  98. tnfr/dynamics/selectors.pyi +85 -0
  99. tnfr/dynamics/structural_clip.py +207 -0
  100. tnfr/errors/__init__.py +37 -0
  101. tnfr/errors/contextual.py +492 -0
  102. tnfr/execution.py +223 -0
  103. tnfr/execution.pyi +45 -0
  104. tnfr/extensions/__init__.py +205 -0
  105. tnfr/extensions/__init__.pyi +18 -0
  106. tnfr/extensions/base.py +173 -0
  107. tnfr/extensions/base.pyi +35 -0
  108. tnfr/extensions/business/__init__.py +71 -0
  109. tnfr/extensions/business/__init__.pyi +11 -0
  110. tnfr/extensions/business/cookbook.py +88 -0
  111. tnfr/extensions/business/cookbook.pyi +8 -0
  112. tnfr/extensions/business/health_analyzers.py +202 -0
  113. tnfr/extensions/business/health_analyzers.pyi +9 -0
  114. tnfr/extensions/business/patterns.py +183 -0
  115. tnfr/extensions/business/patterns.pyi +8 -0
  116. tnfr/extensions/medical/__init__.py +73 -0
  117. tnfr/extensions/medical/__init__.pyi +11 -0
  118. tnfr/extensions/medical/cookbook.py +88 -0
  119. tnfr/extensions/medical/cookbook.pyi +8 -0
  120. tnfr/extensions/medical/health_analyzers.py +181 -0
  121. tnfr/extensions/medical/health_analyzers.pyi +9 -0
  122. tnfr/extensions/medical/patterns.py +163 -0
  123. tnfr/extensions/medical/patterns.pyi +8 -0
  124. tnfr/flatten.py +262 -0
  125. tnfr/flatten.pyi +21 -0
  126. tnfr/gamma.py +354 -0
  127. tnfr/gamma.pyi +36 -0
  128. tnfr/glyph_history.py +377 -0
  129. tnfr/glyph_history.pyi +35 -0
  130. tnfr/glyph_runtime.py +19 -0
  131. tnfr/glyph_runtime.pyi +8 -0
  132. tnfr/immutable.py +218 -0
  133. tnfr/immutable.pyi +36 -0
  134. tnfr/initialization.py +203 -0
  135. tnfr/initialization.pyi +65 -0
  136. tnfr/io.py +10 -0
  137. tnfr/io.pyi +13 -0
  138. tnfr/locking.py +37 -0
  139. tnfr/locking.pyi +7 -0
  140. tnfr/mathematics/__init__.py +79 -0
  141. tnfr/mathematics/backend.py +453 -0
  142. tnfr/mathematics/backend.pyi +99 -0
  143. tnfr/mathematics/dynamics.py +408 -0
  144. tnfr/mathematics/dynamics.pyi +90 -0
  145. tnfr/mathematics/epi.py +391 -0
  146. tnfr/mathematics/epi.pyi +65 -0
  147. tnfr/mathematics/generators.py +242 -0
  148. tnfr/mathematics/generators.pyi +29 -0
  149. tnfr/mathematics/metrics.py +119 -0
  150. tnfr/mathematics/metrics.pyi +16 -0
  151. tnfr/mathematics/operators.py +239 -0
  152. tnfr/mathematics/operators.pyi +59 -0
  153. tnfr/mathematics/operators_factory.py +124 -0
  154. tnfr/mathematics/operators_factory.pyi +11 -0
  155. tnfr/mathematics/projection.py +87 -0
  156. tnfr/mathematics/projection.pyi +33 -0
  157. tnfr/mathematics/runtime.py +182 -0
  158. tnfr/mathematics/runtime.pyi +64 -0
  159. tnfr/mathematics/spaces.py +256 -0
  160. tnfr/mathematics/spaces.pyi +83 -0
  161. tnfr/mathematics/transforms.py +305 -0
  162. tnfr/mathematics/transforms.pyi +62 -0
  163. tnfr/metrics/__init__.py +79 -0
  164. tnfr/metrics/__init__.pyi +20 -0
  165. tnfr/metrics/buffer_cache.py +163 -0
  166. tnfr/metrics/buffer_cache.pyi +24 -0
  167. tnfr/metrics/cache_utils.py +214 -0
  168. tnfr/metrics/coherence.py +2009 -0
  169. tnfr/metrics/coherence.pyi +129 -0
  170. tnfr/metrics/common.py +158 -0
  171. tnfr/metrics/common.pyi +35 -0
  172. tnfr/metrics/core.py +316 -0
  173. tnfr/metrics/core.pyi +13 -0
  174. tnfr/metrics/diagnosis.py +833 -0
  175. tnfr/metrics/diagnosis.pyi +86 -0
  176. tnfr/metrics/emergence.py +245 -0
  177. tnfr/metrics/export.py +179 -0
  178. tnfr/metrics/export.pyi +7 -0
  179. tnfr/metrics/glyph_timing.py +379 -0
  180. tnfr/metrics/glyph_timing.pyi +81 -0
  181. tnfr/metrics/learning_metrics.py +280 -0
  182. tnfr/metrics/learning_metrics.pyi +21 -0
  183. tnfr/metrics/phase_coherence.py +351 -0
  184. tnfr/metrics/phase_compatibility.py +349 -0
  185. tnfr/metrics/reporting.py +183 -0
  186. tnfr/metrics/reporting.pyi +25 -0
  187. tnfr/metrics/sense_index.py +1203 -0
  188. tnfr/metrics/sense_index.pyi +9 -0
  189. tnfr/metrics/trig.py +373 -0
  190. tnfr/metrics/trig.pyi +13 -0
  191. tnfr/metrics/trig_cache.py +233 -0
  192. tnfr/metrics/trig_cache.pyi +10 -0
  193. tnfr/multiscale/__init__.py +32 -0
  194. tnfr/multiscale/hierarchical.py +517 -0
  195. tnfr/node.py +763 -0
  196. tnfr/node.pyi +139 -0
  197. tnfr/observers.py +255 -130
  198. tnfr/observers.pyi +31 -0
  199. tnfr/ontosim.py +144 -137
  200. tnfr/ontosim.pyi +28 -0
  201. tnfr/operators/__init__.py +1672 -0
  202. tnfr/operators/__init__.pyi +31 -0
  203. tnfr/operators/algebra.py +277 -0
  204. tnfr/operators/canonical_patterns.py +420 -0
  205. tnfr/operators/cascade.py +267 -0
  206. tnfr/operators/cycle_detection.py +358 -0
  207. tnfr/operators/definitions.py +4108 -0
  208. tnfr/operators/definitions.pyi +78 -0
  209. tnfr/operators/grammar.py +1164 -0
  210. tnfr/operators/grammar.pyi +140 -0
  211. tnfr/operators/hamiltonian.py +710 -0
  212. tnfr/operators/health_analyzer.py +809 -0
  213. tnfr/operators/jitter.py +272 -0
  214. tnfr/operators/jitter.pyi +11 -0
  215. tnfr/operators/lifecycle.py +314 -0
  216. tnfr/operators/metabolism.py +618 -0
  217. tnfr/operators/metrics.py +2138 -0
  218. tnfr/operators/network_analysis/__init__.py +27 -0
  219. tnfr/operators/network_analysis/source_detection.py +186 -0
  220. tnfr/operators/nodal_equation.py +395 -0
  221. tnfr/operators/pattern_detection.py +660 -0
  222. tnfr/operators/patterns.py +669 -0
  223. tnfr/operators/postconditions/__init__.py +38 -0
  224. tnfr/operators/postconditions/mutation.py +236 -0
  225. tnfr/operators/preconditions/__init__.py +1226 -0
  226. tnfr/operators/preconditions/coherence.py +305 -0
  227. tnfr/operators/preconditions/dissonance.py +236 -0
  228. tnfr/operators/preconditions/emission.py +128 -0
  229. tnfr/operators/preconditions/mutation.py +580 -0
  230. tnfr/operators/preconditions/reception.py +125 -0
  231. tnfr/operators/preconditions/resonance.py +364 -0
  232. tnfr/operators/registry.py +74 -0
  233. tnfr/operators/registry.pyi +9 -0
  234. tnfr/operators/remesh.py +1809 -0
  235. tnfr/operators/remesh.pyi +26 -0
  236. tnfr/operators/structural_units.py +268 -0
  237. tnfr/operators/unified_grammar.py +105 -0
  238. tnfr/parallel/__init__.py +54 -0
  239. tnfr/parallel/auto_scaler.py +234 -0
  240. tnfr/parallel/distributed.py +384 -0
  241. tnfr/parallel/engine.py +238 -0
  242. tnfr/parallel/gpu_engine.py +420 -0
  243. tnfr/parallel/monitoring.py +248 -0
  244. tnfr/parallel/partitioner.py +459 -0
  245. tnfr/py.typed +0 -0
  246. tnfr/recipes/__init__.py +22 -0
  247. tnfr/recipes/cookbook.py +743 -0
  248. tnfr/rng.py +178 -0
  249. tnfr/rng.pyi +26 -0
  250. tnfr/schemas/__init__.py +8 -0
  251. tnfr/schemas/grammar.json +94 -0
  252. tnfr/sdk/__init__.py +107 -0
  253. tnfr/sdk/__init__.pyi +19 -0
  254. tnfr/sdk/adaptive_system.py +173 -0
  255. tnfr/sdk/adaptive_system.pyi +21 -0
  256. tnfr/sdk/builders.py +370 -0
  257. tnfr/sdk/builders.pyi +51 -0
  258. tnfr/sdk/fluent.py +1121 -0
  259. tnfr/sdk/fluent.pyi +74 -0
  260. tnfr/sdk/templates.py +342 -0
  261. tnfr/sdk/templates.pyi +41 -0
  262. tnfr/sdk/utils.py +341 -0
  263. tnfr/secure_config.py +46 -0
  264. tnfr/security/__init__.py +70 -0
  265. tnfr/security/database.py +514 -0
  266. tnfr/security/subprocess.py +503 -0
  267. tnfr/security/validation.py +290 -0
  268. tnfr/selector.py +247 -0
  269. tnfr/selector.pyi +19 -0
  270. tnfr/sense.py +378 -0
  271. tnfr/sense.pyi +23 -0
  272. tnfr/services/__init__.py +17 -0
  273. tnfr/services/orchestrator.py +325 -0
  274. tnfr/sparse/__init__.py +39 -0
  275. tnfr/sparse/representations.py +492 -0
  276. tnfr/structural.py +705 -0
  277. tnfr/structural.pyi +83 -0
  278. tnfr/telemetry/__init__.py +35 -0
  279. tnfr/telemetry/cache_metrics.py +226 -0
  280. tnfr/telemetry/cache_metrics.pyi +64 -0
  281. tnfr/telemetry/nu_f.py +422 -0
  282. tnfr/telemetry/nu_f.pyi +108 -0
  283. tnfr/telemetry/verbosity.py +36 -0
  284. tnfr/telemetry/verbosity.pyi +15 -0
  285. tnfr/tokens.py +58 -0
  286. tnfr/tokens.pyi +36 -0
  287. tnfr/tools/__init__.py +20 -0
  288. tnfr/tools/domain_templates.py +478 -0
  289. tnfr/tools/sequence_generator.py +846 -0
  290. tnfr/topology/__init__.py +13 -0
  291. tnfr/topology/asymmetry.py +151 -0
  292. tnfr/trace.py +543 -0
  293. tnfr/trace.pyi +42 -0
  294. tnfr/tutorials/__init__.py +38 -0
  295. tnfr/tutorials/autonomous_evolution.py +285 -0
  296. tnfr/tutorials/interactive.py +1576 -0
  297. tnfr/tutorials/structural_metabolism.py +238 -0
  298. tnfr/types.py +775 -0
  299. tnfr/types.pyi +357 -0
  300. tnfr/units.py +68 -0
  301. tnfr/units.pyi +13 -0
  302. tnfr/utils/__init__.py +282 -0
  303. tnfr/utils/__init__.pyi +215 -0
  304. tnfr/utils/cache.py +4223 -0
  305. tnfr/utils/cache.pyi +470 -0
  306. tnfr/utils/callbacks.py +375 -0
  307. tnfr/utils/callbacks.pyi +49 -0
  308. tnfr/utils/chunks.py +108 -0
  309. tnfr/utils/chunks.pyi +22 -0
  310. tnfr/utils/data.py +428 -0
  311. tnfr/utils/data.pyi +74 -0
  312. tnfr/utils/graph.py +85 -0
  313. tnfr/utils/graph.pyi +10 -0
  314. tnfr/utils/init.py +821 -0
  315. tnfr/utils/init.pyi +80 -0
  316. tnfr/utils/io.py +559 -0
  317. tnfr/utils/io.pyi +66 -0
  318. tnfr/utils/numeric.py +114 -0
  319. tnfr/utils/numeric.pyi +21 -0
  320. tnfr/validation/__init__.py +257 -0
  321. tnfr/validation/__init__.pyi +85 -0
  322. tnfr/validation/compatibility.py +460 -0
  323. tnfr/validation/compatibility.pyi +6 -0
  324. tnfr/validation/config.py +73 -0
  325. tnfr/validation/graph.py +139 -0
  326. tnfr/validation/graph.pyi +18 -0
  327. tnfr/validation/input_validation.py +755 -0
  328. tnfr/validation/invariants.py +712 -0
  329. tnfr/validation/rules.py +253 -0
  330. tnfr/validation/rules.pyi +44 -0
  331. tnfr/validation/runtime.py +279 -0
  332. tnfr/validation/runtime.pyi +28 -0
  333. tnfr/validation/sequence_validator.py +162 -0
  334. tnfr/validation/soft_filters.py +170 -0
  335. tnfr/validation/soft_filters.pyi +32 -0
  336. tnfr/validation/spectral.py +164 -0
  337. tnfr/validation/spectral.pyi +42 -0
  338. tnfr/validation/validator.py +1266 -0
  339. tnfr/validation/window.py +39 -0
  340. tnfr/validation/window.pyi +1 -0
  341. tnfr/visualization/__init__.py +98 -0
  342. tnfr/visualization/cascade_viz.py +256 -0
  343. tnfr/visualization/hierarchy.py +284 -0
  344. tnfr/visualization/sequence_plotter.py +784 -0
  345. tnfr/viz/__init__.py +60 -0
  346. tnfr/viz/matplotlib.py +278 -0
  347. tnfr/viz/matplotlib.pyi +35 -0
  348. tnfr-8.5.0.dist-info/METADATA +573 -0
  349. tnfr-8.5.0.dist-info/RECORD +353 -0
  350. tnfr-8.5.0.dist-info/entry_points.txt +3 -0
  351. tnfr-3.0.3.dist-info/licenses/LICENSE.txt → tnfr-8.5.0.dist-info/licenses/LICENSE.md +1 -1
  352. tnfr/constants.py +0 -183
  353. tnfr/dynamics.py +0 -543
  354. tnfr/helpers.py +0 -198
  355. tnfr/main.py +0 -37
  356. tnfr/operators.py +0 -296
  357. tnfr-3.0.3.dist-info/METADATA +0 -35
  358. tnfr-3.0.3.dist-info/RECORD +0 -13
  359. {tnfr-3.0.3.dist-info → tnfr-8.5.0.dist-info}/WHEEL +0 -0
  360. {tnfr-3.0.3.dist-info → tnfr-8.5.0.dist-info}/top_level.txt +0 -0
tnfr/gamma.py ADDED
@@ -0,0 +1,354 @@
1
+ """Gamma registry."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import logging
7
+ import math
8
+ from collections.abc import Mapping
9
+ from functools import lru_cache
10
+ from types import MappingProxyType
11
+ from typing import Any, Callable, NamedTuple
12
+
13
+ from .alias import get_theta_attr
14
+ from .constants import DEFAULTS
15
+ from .utils import json_dumps
16
+ from .metrics.trig_cache import get_trig_cache
17
+ from .types import GammaSpec, NodeId, TNFRGraph
18
+ from .utils import (
19
+ edge_version_cache,
20
+ get_graph_mapping,
21
+ get_logger,
22
+ node_set_checksum,
23
+ )
24
+
25
+ logger = get_logger(__name__)
26
+
27
+ DEFAULT_GAMMA: Mapping[str, Any] = MappingProxyType(dict(DEFAULTS["GAMMA"]))
28
+
29
+ __all__ = (
30
+ "kuramoto_R_psi",
31
+ "gamma_none",
32
+ "gamma_kuramoto_linear",
33
+ "gamma_kuramoto_bandpass",
34
+ "gamma_kuramoto_tanh",
35
+ "gamma_harmonic",
36
+ "GammaEntry",
37
+ "GAMMA_REGISTRY",
38
+ "eval_gamma",
39
+ )
40
+
41
+
42
+ @lru_cache(maxsize=1)
43
+ def _default_gamma_spec() -> tuple[bytes, str]:
44
+ dumped = json_dumps(dict(DEFAULT_GAMMA), sort_keys=True, to_bytes=True)
45
+ hash_ = hashlib.blake2b(dumped, digest_size=16).hexdigest()
46
+ return dumped, hash_
47
+
48
+
49
+ def _ensure_kuramoto_cache(G: TNFRGraph, t: float | int) -> None:
50
+ """Cache ``(R, ψ)`` for the current step ``t`` using ``edge_version_cache``."""
51
+ checksum = G.graph.get("_dnfr_nodes_checksum")
52
+ if checksum is None:
53
+ # reuse checksum from cached_nodes_and_A when available
54
+ checksum = node_set_checksum(G)
55
+ nodes_sig = (len(G), checksum)
56
+ max_steps = int(G.graph.get("KURAMOTO_CACHE_STEPS", 1))
57
+
58
+ def builder() -> dict[str, float]:
59
+ R, psi = kuramoto_R_psi(G)
60
+ return {"R": R, "psi": psi}
61
+
62
+ key = (t, nodes_sig)
63
+ entry = edge_version_cache(G, key, builder, max_entries=max_steps)
64
+ G.graph["_kuramoto_cache"] = entry
65
+
66
+
67
+ def kuramoto_R_psi(G: TNFRGraph) -> tuple[float, float]:
68
+ """Return ``(R, ψ)`` for Kuramoto order using θ from all nodes."""
69
+ max_steps = int(G.graph.get("KURAMOTO_CACHE_STEPS", 1))
70
+ trig = get_trig_cache(G, cache_size=max_steps)
71
+ n = len(trig.theta)
72
+ if n == 0:
73
+ return 0.0, 0.0
74
+
75
+ cos_sum = sum(trig.cos.values())
76
+ sin_sum = sum(trig.sin.values())
77
+ R = math.hypot(cos_sum, sin_sum) / n
78
+ psi = math.atan2(sin_sum, cos_sum)
79
+ return R, psi
80
+
81
+
82
+ def _kuramoto_common(
83
+ G: TNFRGraph, node: NodeId, _cfg: GammaSpec
84
+ ) -> tuple[float, float, float]:
85
+ """Return ``(θ_i, R, ψ)`` for Kuramoto-based Γ functions.
86
+
87
+ Reads cached global order ``R`` and mean phase ``ψ`` and obtains node
88
+ phase ``θ_i``. ``_cfg`` is accepted only to keep a homogeneous signature
89
+ with Γ evaluators.
90
+ """
91
+ cache = G.graph.get("_kuramoto_cache", {})
92
+ R = float(cache.get("R", 0.0))
93
+ psi = float(cache.get("psi", 0.0))
94
+ th_val = get_theta_attr(G.nodes[node], 0.0)
95
+ th_i = float(th_val if th_val is not None else 0.0)
96
+ return th_i, R, psi
97
+
98
+
99
+ def _read_gamma_raw(G: TNFRGraph) -> GammaSpec | None:
100
+ """Return raw Γ specification from ``G.graph['GAMMA']``.
101
+
102
+ The returned value is the direct contents of ``G.graph['GAMMA']`` when
103
+ it is a mapping or the result of :func:`get_graph_mapping` if a path is
104
+ provided. Final validation and caching are handled elsewhere.
105
+ """
106
+
107
+ raw = G.graph.get("GAMMA")
108
+ if raw is None or isinstance(raw, Mapping):
109
+ return raw
110
+ return get_graph_mapping(
111
+ G,
112
+ "GAMMA",
113
+ "G.graph['GAMMA'] is not a mapping; using {'type': 'none'}",
114
+ )
115
+
116
+
117
+ def _get_gamma_spec(G: TNFRGraph) -> GammaSpec:
118
+ """Return validated Γ specification caching results.
119
+
120
+ The raw value from ``G.graph['GAMMA']`` is cached together with the
121
+ normalized specification and its hash. When the raw value is unchanged,
122
+ the cached spec is returned without re-reading or re-validating,
123
+ preventing repeated warnings or costly hashing.
124
+ """
125
+
126
+ raw = G.graph.get("GAMMA")
127
+ cached_raw = G.graph.get("_gamma_raw")
128
+ cached_spec = G.graph.get("_gamma_spec")
129
+ cached_hash = G.graph.get("_gamma_spec_hash")
130
+
131
+ def _hash_mapping(mapping: GammaSpec) -> str:
132
+ dumped = json_dumps(mapping, sort_keys=True, to_bytes=True)
133
+ return hashlib.blake2b(dumped, digest_size=16).hexdigest()
134
+
135
+ mapping_hash: str | None = None
136
+ if isinstance(raw, Mapping):
137
+ mapping_hash = _hash_mapping(raw)
138
+ if (
139
+ raw is cached_raw
140
+ and cached_spec is not None
141
+ and cached_hash == mapping_hash
142
+ ):
143
+ return cached_spec
144
+ elif raw is cached_raw and cached_spec is not None and cached_hash is not None:
145
+ return cached_spec
146
+
147
+ if raw is None:
148
+ spec = DEFAULT_GAMMA
149
+ _, cur_hash = _default_gamma_spec()
150
+ elif isinstance(raw, Mapping):
151
+ spec = raw
152
+ cur_hash = mapping_hash if mapping_hash is not None else _hash_mapping(spec)
153
+ else:
154
+ spec_raw = _read_gamma_raw(G)
155
+ if isinstance(spec_raw, Mapping) and spec_raw is not None:
156
+ spec = spec_raw
157
+ cur_hash = _hash_mapping(spec)
158
+ else:
159
+ spec = DEFAULT_GAMMA
160
+ _, cur_hash = _default_gamma_spec()
161
+
162
+ # Store raw input, validated spec and its hash for future calls
163
+ G.graph["_gamma_raw"] = raw
164
+ G.graph["_gamma_spec"] = spec
165
+ G.graph["_gamma_spec_hash"] = cur_hash
166
+ return spec
167
+
168
+
169
+ # -----------------
170
+ # Helpers
171
+ # -----------------
172
+
173
+
174
+ def _gamma_params(cfg: GammaSpec, **defaults: float) -> tuple[float, ...]:
175
+ """Return normalized Γ parameters from ``cfg``.
176
+
177
+ Parameters are retrieved from ``cfg`` using the keys in ``defaults`` and
178
+ converted to ``float``. If a key is missing, its value from ``defaults`` is
179
+ used. Values convertible to ``float`` (e.g. strings) are accepted.
180
+
181
+ Example
182
+ -------
183
+ >>> beta, R0 = _gamma_params(cfg, beta=0.0, R0=0.0)
184
+ """
185
+
186
+ return tuple(float(cfg.get(name, default)) for name, default in defaults.items())
187
+
188
+
189
+ # -----------------
190
+ # Canonical Γi(R)
191
+ # -----------------
192
+
193
+
194
+ def gamma_none(G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec) -> float:
195
+ """Return ``0.0`` to disable Γ forcing for the given node."""
196
+
197
+ return 0.0
198
+
199
+
200
+ def _gamma_kuramoto(
201
+ G: TNFRGraph,
202
+ node: NodeId,
203
+ cfg: GammaSpec,
204
+ builder: Callable[..., float],
205
+ **defaults: float,
206
+ ) -> float:
207
+ """Construct a Kuramoto-based Γ function.
208
+
209
+ ``builder`` receives ``(θ_i, R, ψ, *params)`` where ``params`` are
210
+ extracted from ``cfg`` according to ``defaults``.
211
+ """
212
+
213
+ params = _gamma_params(cfg, **defaults)
214
+ th_i, R, psi = _kuramoto_common(G, node, cfg)
215
+ return builder(th_i, R, psi, *params)
216
+
217
+
218
+ def _builder_linear(th_i: float, R: float, psi: float, beta: float, R0: float) -> float:
219
+ return beta * (R - R0) * math.cos(th_i - psi)
220
+
221
+
222
+ def _builder_bandpass(th_i: float, R: float, psi: float, beta: float) -> float:
223
+ sgn = 1.0 if math.cos(th_i - psi) >= 0.0 else -1.0
224
+ return beta * R * (1.0 - R) * sgn
225
+
226
+
227
+ def _builder_tanh(
228
+ th_i: float, R: float, psi: float, beta: float, k: float, R0: float
229
+ ) -> float:
230
+ return beta * math.tanh(k * (R - R0)) * math.cos(th_i - psi)
231
+
232
+
233
+ def gamma_kuramoto_linear(
234
+ G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec
235
+ ) -> float:
236
+ """Linear Kuramoto coupling for Γi(R).
237
+
238
+ Formula: Γ = β · (R - R0) · cos(θ_i - ψ)
239
+ - R ∈ [0,1] is the global phase order.
240
+ - ψ is the mean phase (coordination direction).
241
+ - β, R0 are parameters (gain/threshold).
242
+
243
+ Use: reinforces integration when the network already shows phase
244
+ coherence (R>R0).
245
+ """
246
+
247
+ return _gamma_kuramoto(G, node, cfg, _builder_linear, beta=0.0, R0=0.0)
248
+
249
+
250
+ def gamma_kuramoto_bandpass(
251
+ G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec
252
+ ) -> float:
253
+ """Compute Γ = β · R(1-R) · sign(cos(θ_i - ψ))."""
254
+
255
+ return _gamma_kuramoto(G, node, cfg, _builder_bandpass, beta=0.0)
256
+
257
+
258
+ def gamma_kuramoto_tanh(
259
+ G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec
260
+ ) -> float:
261
+ """Saturating tanh coupling for Γi(R).
262
+
263
+ Formula: Γ = β · tanh(k·(R - R0)) · cos(θ_i - ψ)
264
+ - β: coupling gain
265
+ - k: tanh slope (how fast it saturates)
266
+ - R0: activation threshold
267
+ """
268
+
269
+ return _gamma_kuramoto(G, node, cfg, _builder_tanh, beta=0.0, k=1.0, R0=0.0)
270
+
271
+
272
+ def gamma_harmonic(G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec) -> float:
273
+ """Harmonic forcing aligned with the global phase field.
274
+
275
+ Formula: Γ = β · sin(ω·t + φ) · cos(θ_i - ψ)
276
+ - β: coupling gain
277
+ - ω: angular frequency of the forcing
278
+ - φ: initial phase of the forcing
279
+ """
280
+ beta, omega, phi = _gamma_params(cfg, beta=0.0, omega=1.0, phi=0.0)
281
+ th_i, _, psi = _kuramoto_common(G, node, cfg)
282
+ return beta * math.sin(omega * t + phi) * math.cos(th_i - psi)
283
+
284
+
285
+ class GammaEntry(NamedTuple):
286
+ """Lookup entry linking Γ evaluators with their preconditions."""
287
+
288
+ fn: Callable[[TNFRGraph, NodeId, float | int, GammaSpec], float]
289
+ needs_kuramoto: bool
290
+
291
+
292
+ # ``GAMMA_REGISTRY`` associates each coupling name with a ``GammaEntry`` where
293
+ # ``fn`` is the evaluation function and ``needs_kuramoto`` indicates whether
294
+ # the global phase order must be precomputed.
295
+ GAMMA_REGISTRY: dict[str, GammaEntry] = {
296
+ "none": GammaEntry(gamma_none, False),
297
+ "kuramoto_linear": GammaEntry(gamma_kuramoto_linear, True),
298
+ "kuramoto_bandpass": GammaEntry(gamma_kuramoto_bandpass, True),
299
+ "kuramoto_tanh": GammaEntry(gamma_kuramoto_tanh, True),
300
+ "harmonic": GammaEntry(gamma_harmonic, True),
301
+ }
302
+
303
+
304
+ def eval_gamma(
305
+ G: TNFRGraph,
306
+ node: NodeId,
307
+ t: float | int,
308
+ *,
309
+ strict: bool = False,
310
+ log_level: int | None = None,
311
+ ) -> float:
312
+ """Evaluate Γi for ``node`` using ``G.graph['GAMMA']`` specification.
313
+
314
+ If ``strict`` is ``True`` exceptions raised during evaluation are
315
+ propagated instead of returning ``0.0``. Likewise, if the specified
316
+ Γ type is not registered a warning is emitted (or ``ValueError`` in
317
+ strict mode) and ``gamma_none`` is used.
318
+
319
+ ``log_level`` controls the logging level for captured errors when
320
+ ``strict`` is ``False``. If omitted, ``logging.ERROR`` is used in
321
+ strict mode and ``logging.DEBUG`` otherwise.
322
+ """
323
+ spec = _get_gamma_spec(G)
324
+ spec_type = spec.get("type", "none")
325
+ reg_entry = GAMMA_REGISTRY.get(spec_type)
326
+ if reg_entry is None:
327
+ msg = f"Unknown GAMMA type: {spec_type}"
328
+ if strict:
329
+ raise ValueError(msg)
330
+ logger.warning(msg)
331
+ entry = GammaEntry(gamma_none, False)
332
+ else:
333
+ entry = reg_entry
334
+ if entry.needs_kuramoto:
335
+ _ensure_kuramoto_cache(G, t)
336
+ try:
337
+ return float(entry.fn(G, node, t, spec))
338
+ except (ValueError, TypeError, ArithmeticError) as exc:
339
+ level = (
340
+ log_level
341
+ if log_level is not None
342
+ else (logging.ERROR if strict else logging.DEBUG)
343
+ )
344
+ logger.log(
345
+ level,
346
+ "Failed to evaluate Γi for node %s at t=%s: %s: %s",
347
+ node,
348
+ t,
349
+ exc.__class__.__name__,
350
+ exc,
351
+ )
352
+ if strict:
353
+ raise
354
+ return 0.0
tnfr/gamma.pyi ADDED
@@ -0,0 +1,36 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Callable, NamedTuple
4
+
5
+ from .types import GammaSpec, NodeId, TNFRGraph
6
+
7
+ __all__: tuple[str, ...]
8
+
9
+ class GammaEntry(NamedTuple):
10
+ fn: Callable[[TNFRGraph, NodeId, float | int, GammaSpec], float]
11
+ needs_kuramoto: bool
12
+
13
+ GAMMA_REGISTRY: dict[str, GammaEntry]
14
+
15
+ def kuramoto_R_psi(G: TNFRGraph) -> tuple[float, float]: ...
16
+ def gamma_none(G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec) -> float: ...
17
+ def gamma_kuramoto_linear(
18
+ G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec
19
+ ) -> float: ...
20
+ def gamma_kuramoto_bandpass(
21
+ G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec
22
+ ) -> float: ...
23
+ def gamma_kuramoto_tanh(
24
+ G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec
25
+ ) -> float: ...
26
+ def gamma_harmonic(
27
+ G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec
28
+ ) -> float: ...
29
+ def eval_gamma(
30
+ G: TNFRGraph,
31
+ node: NodeId,
32
+ t: float | int,
33
+ *,
34
+ strict: bool = ...,
35
+ log_level: int | None = ...,
36
+ ) -> float: ...