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

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

Potentially problematic release.


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

Files changed (360) hide show
  1. tnfr/__init__.py +375 -56
  2. tnfr/__init__.pyi +33 -0
  3. tnfr/_compat.py +10 -0
  4. tnfr/_generated_version.py +34 -0
  5. tnfr/_version.py +49 -0
  6. tnfr/_version.pyi +7 -0
  7. tnfr/alias.py +723 -0
  8. tnfr/alias.pyi +108 -0
  9. tnfr/backends/__init__.py +354 -0
  10. tnfr/backends/jax_backend.py +173 -0
  11. tnfr/backends/numpy_backend.py +238 -0
  12. tnfr/backends/optimized_numpy.py +420 -0
  13. tnfr/backends/torch_backend.py +408 -0
  14. tnfr/cache.py +171 -0
  15. tnfr/cache.pyi +13 -0
  16. tnfr/cli/__init__.py +110 -0
  17. tnfr/cli/__init__.pyi +26 -0
  18. tnfr/cli/arguments.py +489 -0
  19. tnfr/cli/arguments.pyi +29 -0
  20. tnfr/cli/execution.py +914 -0
  21. tnfr/cli/execution.pyi +70 -0
  22. tnfr/cli/interactive_validator.py +614 -0
  23. tnfr/cli/utils.py +51 -0
  24. tnfr/cli/utils.pyi +7 -0
  25. tnfr/cli/validate.py +236 -0
  26. tnfr/compat/__init__.py +85 -0
  27. tnfr/compat/dataclass.py +136 -0
  28. tnfr/compat/jsonschema_stub.py +61 -0
  29. tnfr/compat/matplotlib_stub.py +73 -0
  30. tnfr/compat/numpy_stub.py +155 -0
  31. tnfr/config/__init__.py +224 -0
  32. tnfr/config/__init__.pyi +10 -0
  33. tnfr/config/constants.py +104 -0
  34. tnfr/config/constants.pyi +12 -0
  35. tnfr/config/defaults.py +54 -0
  36. tnfr/config/defaults_core.py +212 -0
  37. tnfr/config/defaults_init.py +33 -0
  38. tnfr/config/defaults_metric.py +104 -0
  39. tnfr/config/feature_flags.py +81 -0
  40. tnfr/config/feature_flags.pyi +16 -0
  41. tnfr/config/glyph_constants.py +31 -0
  42. tnfr/config/init.py +77 -0
  43. tnfr/config/init.pyi +8 -0
  44. tnfr/config/operator_names.py +254 -0
  45. tnfr/config/operator_names.pyi +36 -0
  46. tnfr/config/physics_derivation.py +354 -0
  47. tnfr/config/presets.py +83 -0
  48. tnfr/config/presets.pyi +7 -0
  49. tnfr/config/security.py +927 -0
  50. tnfr/config/thresholds.py +114 -0
  51. tnfr/config/tnfr_config.py +498 -0
  52. tnfr/constants/__init__.py +92 -0
  53. tnfr/constants/__init__.pyi +92 -0
  54. tnfr/constants/aliases.py +33 -0
  55. tnfr/constants/aliases.pyi +27 -0
  56. tnfr/constants/init.py +33 -0
  57. tnfr/constants/init.pyi +12 -0
  58. tnfr/constants/metric.py +104 -0
  59. tnfr/constants/metric.pyi +19 -0
  60. tnfr/core/__init__.py +33 -0
  61. tnfr/core/container.py +226 -0
  62. tnfr/core/default_implementations.py +329 -0
  63. tnfr/core/interfaces.py +279 -0
  64. tnfr/dynamics/__init__.py +238 -0
  65. tnfr/dynamics/__init__.pyi +83 -0
  66. tnfr/dynamics/adaptation.py +267 -0
  67. tnfr/dynamics/adaptation.pyi +7 -0
  68. tnfr/dynamics/adaptive_sequences.py +189 -0
  69. tnfr/dynamics/adaptive_sequences.pyi +14 -0
  70. tnfr/dynamics/aliases.py +23 -0
  71. tnfr/dynamics/aliases.pyi +19 -0
  72. tnfr/dynamics/bifurcation.py +232 -0
  73. tnfr/dynamics/canonical.py +229 -0
  74. tnfr/dynamics/canonical.pyi +48 -0
  75. tnfr/dynamics/coordination.py +385 -0
  76. tnfr/dynamics/coordination.pyi +25 -0
  77. tnfr/dynamics/dnfr.py +3034 -0
  78. tnfr/dynamics/dnfr.pyi +26 -0
  79. tnfr/dynamics/dynamic_limits.py +225 -0
  80. tnfr/dynamics/feedback.py +252 -0
  81. tnfr/dynamics/feedback.pyi +24 -0
  82. tnfr/dynamics/fused_dnfr.py +454 -0
  83. tnfr/dynamics/homeostasis.py +157 -0
  84. tnfr/dynamics/homeostasis.pyi +14 -0
  85. tnfr/dynamics/integrators.py +661 -0
  86. tnfr/dynamics/integrators.pyi +36 -0
  87. tnfr/dynamics/learning.py +310 -0
  88. tnfr/dynamics/learning.pyi +33 -0
  89. tnfr/dynamics/metabolism.py +254 -0
  90. tnfr/dynamics/nbody.py +796 -0
  91. tnfr/dynamics/nbody_tnfr.py +783 -0
  92. tnfr/dynamics/propagation.py +326 -0
  93. tnfr/dynamics/runtime.py +908 -0
  94. tnfr/dynamics/runtime.pyi +77 -0
  95. tnfr/dynamics/sampling.py +36 -0
  96. tnfr/dynamics/sampling.pyi +7 -0
  97. tnfr/dynamics/selectors.py +711 -0
  98. tnfr/dynamics/selectors.pyi +85 -0
  99. tnfr/dynamics/structural_clip.py +207 -0
  100. tnfr/errors/__init__.py +37 -0
  101. tnfr/errors/contextual.py +492 -0
  102. tnfr/execution.py +223 -0
  103. tnfr/execution.pyi +45 -0
  104. tnfr/extensions/__init__.py +205 -0
  105. tnfr/extensions/__init__.pyi +18 -0
  106. tnfr/extensions/base.py +173 -0
  107. tnfr/extensions/base.pyi +35 -0
  108. tnfr/extensions/business/__init__.py +71 -0
  109. tnfr/extensions/business/__init__.pyi +11 -0
  110. tnfr/extensions/business/cookbook.py +88 -0
  111. tnfr/extensions/business/cookbook.pyi +8 -0
  112. tnfr/extensions/business/health_analyzers.py +202 -0
  113. tnfr/extensions/business/health_analyzers.pyi +9 -0
  114. tnfr/extensions/business/patterns.py +183 -0
  115. tnfr/extensions/business/patterns.pyi +8 -0
  116. tnfr/extensions/medical/__init__.py +73 -0
  117. tnfr/extensions/medical/__init__.pyi +11 -0
  118. tnfr/extensions/medical/cookbook.py +88 -0
  119. tnfr/extensions/medical/cookbook.pyi +8 -0
  120. tnfr/extensions/medical/health_analyzers.py +181 -0
  121. tnfr/extensions/medical/health_analyzers.pyi +9 -0
  122. tnfr/extensions/medical/patterns.py +163 -0
  123. tnfr/extensions/medical/patterns.pyi +8 -0
  124. tnfr/flatten.py +262 -0
  125. tnfr/flatten.pyi +21 -0
  126. tnfr/gamma.py +354 -0
  127. tnfr/gamma.pyi +36 -0
  128. tnfr/glyph_history.py +377 -0
  129. tnfr/glyph_history.pyi +35 -0
  130. tnfr/glyph_runtime.py +19 -0
  131. tnfr/glyph_runtime.pyi +8 -0
  132. tnfr/immutable.py +218 -0
  133. tnfr/immutable.pyi +36 -0
  134. tnfr/initialization.py +203 -0
  135. tnfr/initialization.pyi +65 -0
  136. tnfr/io.py +10 -0
  137. tnfr/io.pyi +13 -0
  138. tnfr/locking.py +37 -0
  139. tnfr/locking.pyi +7 -0
  140. tnfr/mathematics/__init__.py +79 -0
  141. tnfr/mathematics/backend.py +453 -0
  142. tnfr/mathematics/backend.pyi +99 -0
  143. tnfr/mathematics/dynamics.py +408 -0
  144. tnfr/mathematics/dynamics.pyi +90 -0
  145. tnfr/mathematics/epi.py +391 -0
  146. tnfr/mathematics/epi.pyi +65 -0
  147. tnfr/mathematics/generators.py +242 -0
  148. tnfr/mathematics/generators.pyi +29 -0
  149. tnfr/mathematics/metrics.py +119 -0
  150. tnfr/mathematics/metrics.pyi +16 -0
  151. tnfr/mathematics/operators.py +239 -0
  152. tnfr/mathematics/operators.pyi +59 -0
  153. tnfr/mathematics/operators_factory.py +124 -0
  154. tnfr/mathematics/operators_factory.pyi +11 -0
  155. tnfr/mathematics/projection.py +87 -0
  156. tnfr/mathematics/projection.pyi +33 -0
  157. tnfr/mathematics/runtime.py +182 -0
  158. tnfr/mathematics/runtime.pyi +64 -0
  159. tnfr/mathematics/spaces.py +256 -0
  160. tnfr/mathematics/spaces.pyi +83 -0
  161. tnfr/mathematics/transforms.py +305 -0
  162. tnfr/mathematics/transforms.pyi +62 -0
  163. tnfr/metrics/__init__.py +79 -0
  164. tnfr/metrics/__init__.pyi +20 -0
  165. tnfr/metrics/buffer_cache.py +163 -0
  166. tnfr/metrics/buffer_cache.pyi +24 -0
  167. tnfr/metrics/cache_utils.py +214 -0
  168. tnfr/metrics/coherence.py +2009 -0
  169. tnfr/metrics/coherence.pyi +129 -0
  170. tnfr/metrics/common.py +158 -0
  171. tnfr/metrics/common.pyi +35 -0
  172. tnfr/metrics/core.py +316 -0
  173. tnfr/metrics/core.pyi +13 -0
  174. tnfr/metrics/diagnosis.py +833 -0
  175. tnfr/metrics/diagnosis.pyi +86 -0
  176. tnfr/metrics/emergence.py +245 -0
  177. tnfr/metrics/export.py +179 -0
  178. tnfr/metrics/export.pyi +7 -0
  179. tnfr/metrics/glyph_timing.py +379 -0
  180. tnfr/metrics/glyph_timing.pyi +81 -0
  181. tnfr/metrics/learning_metrics.py +280 -0
  182. tnfr/metrics/learning_metrics.pyi +21 -0
  183. tnfr/metrics/phase_coherence.py +351 -0
  184. tnfr/metrics/phase_compatibility.py +349 -0
  185. tnfr/metrics/reporting.py +183 -0
  186. tnfr/metrics/reporting.pyi +25 -0
  187. tnfr/metrics/sense_index.py +1203 -0
  188. tnfr/metrics/sense_index.pyi +9 -0
  189. tnfr/metrics/trig.py +373 -0
  190. tnfr/metrics/trig.pyi +13 -0
  191. tnfr/metrics/trig_cache.py +233 -0
  192. tnfr/metrics/trig_cache.pyi +10 -0
  193. tnfr/multiscale/__init__.py +32 -0
  194. tnfr/multiscale/hierarchical.py +517 -0
  195. tnfr/node.py +763 -0
  196. tnfr/node.pyi +139 -0
  197. tnfr/observers.py +255 -130
  198. tnfr/observers.pyi +31 -0
  199. tnfr/ontosim.py +144 -137
  200. tnfr/ontosim.pyi +28 -0
  201. tnfr/operators/__init__.py +1672 -0
  202. tnfr/operators/__init__.pyi +31 -0
  203. tnfr/operators/algebra.py +277 -0
  204. tnfr/operators/canonical_patterns.py +420 -0
  205. tnfr/operators/cascade.py +267 -0
  206. tnfr/operators/cycle_detection.py +358 -0
  207. tnfr/operators/definitions.py +4108 -0
  208. tnfr/operators/definitions.pyi +78 -0
  209. tnfr/operators/grammar.py +1164 -0
  210. tnfr/operators/grammar.pyi +140 -0
  211. tnfr/operators/hamiltonian.py +710 -0
  212. tnfr/operators/health_analyzer.py +809 -0
  213. tnfr/operators/jitter.py +272 -0
  214. tnfr/operators/jitter.pyi +11 -0
  215. tnfr/operators/lifecycle.py +314 -0
  216. tnfr/operators/metabolism.py +618 -0
  217. tnfr/operators/metrics.py +2138 -0
  218. tnfr/operators/network_analysis/__init__.py +27 -0
  219. tnfr/operators/network_analysis/source_detection.py +186 -0
  220. tnfr/operators/nodal_equation.py +395 -0
  221. tnfr/operators/pattern_detection.py +660 -0
  222. tnfr/operators/patterns.py +669 -0
  223. tnfr/operators/postconditions/__init__.py +38 -0
  224. tnfr/operators/postconditions/mutation.py +236 -0
  225. tnfr/operators/preconditions/__init__.py +1226 -0
  226. tnfr/operators/preconditions/coherence.py +305 -0
  227. tnfr/operators/preconditions/dissonance.py +236 -0
  228. tnfr/operators/preconditions/emission.py +128 -0
  229. tnfr/operators/preconditions/mutation.py +580 -0
  230. tnfr/operators/preconditions/reception.py +125 -0
  231. tnfr/operators/preconditions/resonance.py +364 -0
  232. tnfr/operators/registry.py +74 -0
  233. tnfr/operators/registry.pyi +9 -0
  234. tnfr/operators/remesh.py +1809 -0
  235. tnfr/operators/remesh.pyi +26 -0
  236. tnfr/operators/structural_units.py +268 -0
  237. tnfr/operators/unified_grammar.py +105 -0
  238. tnfr/parallel/__init__.py +54 -0
  239. tnfr/parallel/auto_scaler.py +234 -0
  240. tnfr/parallel/distributed.py +384 -0
  241. tnfr/parallel/engine.py +238 -0
  242. tnfr/parallel/gpu_engine.py +420 -0
  243. tnfr/parallel/monitoring.py +248 -0
  244. tnfr/parallel/partitioner.py +459 -0
  245. tnfr/py.typed +0 -0
  246. tnfr/recipes/__init__.py +22 -0
  247. tnfr/recipes/cookbook.py +743 -0
  248. tnfr/rng.py +178 -0
  249. tnfr/rng.pyi +26 -0
  250. tnfr/schemas/__init__.py +8 -0
  251. tnfr/schemas/grammar.json +94 -0
  252. tnfr/sdk/__init__.py +107 -0
  253. tnfr/sdk/__init__.pyi +19 -0
  254. tnfr/sdk/adaptive_system.py +173 -0
  255. tnfr/sdk/adaptive_system.pyi +21 -0
  256. tnfr/sdk/builders.py +370 -0
  257. tnfr/sdk/builders.pyi +51 -0
  258. tnfr/sdk/fluent.py +1121 -0
  259. tnfr/sdk/fluent.pyi +74 -0
  260. tnfr/sdk/templates.py +342 -0
  261. tnfr/sdk/templates.pyi +41 -0
  262. tnfr/sdk/utils.py +341 -0
  263. tnfr/secure_config.py +46 -0
  264. tnfr/security/__init__.py +70 -0
  265. tnfr/security/database.py +514 -0
  266. tnfr/security/subprocess.py +503 -0
  267. tnfr/security/validation.py +290 -0
  268. tnfr/selector.py +247 -0
  269. tnfr/selector.pyi +19 -0
  270. tnfr/sense.py +378 -0
  271. tnfr/sense.pyi +23 -0
  272. tnfr/services/__init__.py +17 -0
  273. tnfr/services/orchestrator.py +325 -0
  274. tnfr/sparse/__init__.py +39 -0
  275. tnfr/sparse/representations.py +492 -0
  276. tnfr/structural.py +705 -0
  277. tnfr/structural.pyi +83 -0
  278. tnfr/telemetry/__init__.py +35 -0
  279. tnfr/telemetry/cache_metrics.py +226 -0
  280. tnfr/telemetry/cache_metrics.pyi +64 -0
  281. tnfr/telemetry/nu_f.py +422 -0
  282. tnfr/telemetry/nu_f.pyi +108 -0
  283. tnfr/telemetry/verbosity.py +36 -0
  284. tnfr/telemetry/verbosity.pyi +15 -0
  285. tnfr/tokens.py +58 -0
  286. tnfr/tokens.pyi +36 -0
  287. tnfr/tools/__init__.py +20 -0
  288. tnfr/tools/domain_templates.py +478 -0
  289. tnfr/tools/sequence_generator.py +846 -0
  290. tnfr/topology/__init__.py +13 -0
  291. tnfr/topology/asymmetry.py +151 -0
  292. tnfr/trace.py +543 -0
  293. tnfr/trace.pyi +42 -0
  294. tnfr/tutorials/__init__.py +38 -0
  295. tnfr/tutorials/autonomous_evolution.py +285 -0
  296. tnfr/tutorials/interactive.py +1576 -0
  297. tnfr/tutorials/structural_metabolism.py +238 -0
  298. tnfr/types.py +775 -0
  299. tnfr/types.pyi +357 -0
  300. tnfr/units.py +68 -0
  301. tnfr/units.pyi +13 -0
  302. tnfr/utils/__init__.py +282 -0
  303. tnfr/utils/__init__.pyi +215 -0
  304. tnfr/utils/cache.py +4223 -0
  305. tnfr/utils/cache.pyi +470 -0
  306. tnfr/utils/callbacks.py +375 -0
  307. tnfr/utils/callbacks.pyi +49 -0
  308. tnfr/utils/chunks.py +108 -0
  309. tnfr/utils/chunks.pyi +22 -0
  310. tnfr/utils/data.py +428 -0
  311. tnfr/utils/data.pyi +74 -0
  312. tnfr/utils/graph.py +85 -0
  313. tnfr/utils/graph.pyi +10 -0
  314. tnfr/utils/init.py +821 -0
  315. tnfr/utils/init.pyi +80 -0
  316. tnfr/utils/io.py +559 -0
  317. tnfr/utils/io.pyi +66 -0
  318. tnfr/utils/numeric.py +114 -0
  319. tnfr/utils/numeric.pyi +21 -0
  320. tnfr/validation/__init__.py +257 -0
  321. tnfr/validation/__init__.pyi +85 -0
  322. tnfr/validation/compatibility.py +460 -0
  323. tnfr/validation/compatibility.pyi +6 -0
  324. tnfr/validation/config.py +73 -0
  325. tnfr/validation/graph.py +139 -0
  326. tnfr/validation/graph.pyi +18 -0
  327. tnfr/validation/input_validation.py +755 -0
  328. tnfr/validation/invariants.py +712 -0
  329. tnfr/validation/rules.py +253 -0
  330. tnfr/validation/rules.pyi +44 -0
  331. tnfr/validation/runtime.py +279 -0
  332. tnfr/validation/runtime.pyi +28 -0
  333. tnfr/validation/sequence_validator.py +162 -0
  334. tnfr/validation/soft_filters.py +170 -0
  335. tnfr/validation/soft_filters.pyi +32 -0
  336. tnfr/validation/spectral.py +164 -0
  337. tnfr/validation/spectral.pyi +42 -0
  338. tnfr/validation/validator.py +1266 -0
  339. tnfr/validation/window.py +39 -0
  340. tnfr/validation/window.pyi +1 -0
  341. tnfr/visualization/__init__.py +98 -0
  342. tnfr/visualization/cascade_viz.py +256 -0
  343. tnfr/visualization/hierarchy.py +284 -0
  344. tnfr/visualization/sequence_plotter.py +784 -0
  345. tnfr/viz/__init__.py +60 -0
  346. tnfr/viz/matplotlib.py +278 -0
  347. tnfr/viz/matplotlib.pyi +35 -0
  348. tnfr-8.5.0.dist-info/METADATA +573 -0
  349. tnfr-8.5.0.dist-info/RECORD +353 -0
  350. tnfr-8.5.0.dist-info/entry_points.txt +3 -0
  351. tnfr-3.0.3.dist-info/licenses/LICENSE.txt → tnfr-8.5.0.dist-info/licenses/LICENSE.md +1 -1
  352. tnfr/constants.py +0 -183
  353. tnfr/dynamics.py +0 -543
  354. tnfr/helpers.py +0 -198
  355. tnfr/main.py +0 -37
  356. tnfr/operators.py +0 -296
  357. tnfr-3.0.3.dist-info/METADATA +0 -35
  358. tnfr-3.0.3.dist-info/RECORD +0 -13
  359. {tnfr-3.0.3.dist-info → tnfr-8.5.0.dist-info}/WHEEL +0 -0
  360. {tnfr-3.0.3.dist-info → tnfr-8.5.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,9 @@
1
+ from typing import Any
2
+
3
+ __all__: Any
4
+
5
+ def __getattr__(name: str) -> Any: ...
6
+
7
+ compute_Si: Any
8
+ compute_Si_node: Any
9
+ get_Si_weights: Any
tnfr/metrics/trig.py ADDED
@@ -0,0 +1,373 @@
1
+ """Trigonometric helpers shared across metrics and helpers.
2
+
3
+ This module focuses on mathematical utilities (means, compensated sums, etc.).
4
+ Caching of cosine/sine values lives in :mod:`tnfr.metrics.trig_cache`.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import math
10
+ from collections.abc import Iterable, Iterator, Sequence
11
+ from itertools import tee
12
+ from typing import TYPE_CHECKING, Any, cast, overload
13
+
14
+ from ..utils import kahan_sum_nd
15
+ from ..types import NodeId, Phase, TNFRGraph
16
+ from ..utils import cached_import, get_numpy
17
+
18
+ if TYPE_CHECKING: # pragma: no cover - typing only
19
+ from ..node import NodeProtocol
20
+
21
+ __all__ = (
22
+ "accumulate_cos_sin",
23
+ "_phase_mean_from_iter",
24
+ "_neighbor_phase_mean_core",
25
+ "_neighbor_phase_mean_generic",
26
+ "neighbor_phase_mean_bulk",
27
+ "neighbor_phase_mean_list",
28
+ "neighbor_phase_mean",
29
+ )
30
+
31
+
32
+ def accumulate_cos_sin(
33
+ it: Iterable[tuple[float, float] | None],
34
+ ) -> tuple[float, float, bool]:
35
+ """Accumulate cosine and sine pairs with compensated summation.
36
+
37
+ ``it`` yields optional ``(cos, sin)`` tuples. Entries with ``None``
38
+ components are ignored. The returned values are the compensated sums of
39
+ cosines and sines along with a flag indicating whether any pair was
40
+ processed.
41
+ """
42
+
43
+ processed = False
44
+
45
+ def iter_real_pairs() -> Iterator[tuple[float, float]]:
46
+ nonlocal processed
47
+ for cs in it:
48
+ if cs is None:
49
+ continue
50
+ c, s = cs
51
+ if c is None or s is None:
52
+ continue
53
+ try:
54
+ c_val = float(c)
55
+ s_val = float(s)
56
+ except (TypeError, ValueError):
57
+ continue
58
+ if not (math.isfinite(c_val) and math.isfinite(s_val)):
59
+ continue
60
+ processed = True
61
+ yield (c_val, s_val)
62
+
63
+ sum_cos, sum_sin = kahan_sum_nd(iter_real_pairs(), dims=2)
64
+
65
+ if not processed:
66
+ return 0.0, 0.0, False
67
+
68
+ return sum_cos, sum_sin, True
69
+
70
+
71
+ def _phase_mean_from_iter(
72
+ it: Iterable[tuple[float, float] | None], fallback: float
73
+ ) -> float:
74
+ """Return circular mean from an iterator of cosine/sine pairs.
75
+
76
+ ``it`` yields optional ``(cos, sin)`` tuples. ``fallback`` is returned if
77
+ no valid pairs are processed.
78
+ """
79
+
80
+ sum_cos, sum_sin, processed = accumulate_cos_sin(it)
81
+ if not processed:
82
+ return fallback
83
+ return math.atan2(sum_sin, sum_cos)
84
+
85
+
86
+ def _neighbor_phase_mean_core(
87
+ neigh: Sequence[Any],
88
+ cos_map: dict[Any, float],
89
+ sin_map: dict[Any, float],
90
+ np: Any | None,
91
+ fallback: float,
92
+ ) -> float:
93
+ """Return circular mean of neighbour phases given trig mappings."""
94
+
95
+ def _iter_pairs() -> Iterator[tuple[float, float]]:
96
+ for v in neigh:
97
+ c = cos_map.get(v)
98
+ s = sin_map.get(v)
99
+ if c is not None and s is not None:
100
+ yield c, s
101
+
102
+ pairs = _iter_pairs()
103
+
104
+ if np is not None:
105
+ cos_iter, sin_iter = tee(pairs, 2)
106
+ cos_arr = np.fromiter((c for c, _ in cos_iter), dtype=float)
107
+ sin_arr = np.fromiter((s for _, s in sin_iter), dtype=float)
108
+ if cos_arr.size:
109
+ mean_cos = float(np.mean(cos_arr))
110
+ mean_sin = float(np.mean(sin_arr))
111
+ return float(np.arctan2(mean_sin, mean_cos))
112
+ return fallback
113
+
114
+ sum_cos, sum_sin, processed = accumulate_cos_sin(pairs)
115
+ if not processed:
116
+ return fallback
117
+ return math.atan2(sum_sin, sum_cos)
118
+
119
+
120
+ def _neighbor_phase_mean_generic(
121
+ obj: "NodeProtocol" | Sequence[Any],
122
+ cos_map: dict[Any, float] | None = None,
123
+ sin_map: dict[Any, float] | None = None,
124
+ np: Any | None = None,
125
+ fallback: float = 0.0,
126
+ ) -> float:
127
+ """Compute the neighbour phase mean via :func:`_neighbor_phase_mean_core`.
128
+
129
+ ``obj`` may be either a node bound to a graph or a sequence of neighbours.
130
+ When ``cos_map`` and ``sin_map`` are ``None`` the function assumes ``obj`` is
131
+ a node and obtains the required trigonometric mappings from the cached
132
+ structures. Otherwise ``obj`` is treated as an explicit neighbour
133
+ sequence and ``cos_map``/``sin_map`` must be provided.
134
+ """
135
+
136
+ if np is None:
137
+ np = get_numpy()
138
+
139
+ if cos_map is None or sin_map is None:
140
+ node = cast("NodeProtocol", obj)
141
+ if getattr(node, "G", None) is None:
142
+ raise TypeError("neighbor_phase_mean requires nodes bound to a graph")
143
+ from .trig_cache import get_trig_cache
144
+
145
+ trig = get_trig_cache(node.G)
146
+ fallback = trig.theta.get(node.n, fallback)
147
+ cos_map = trig.cos
148
+ sin_map = trig.sin
149
+ neigh = node.G[node.n]
150
+ else:
151
+ neigh = cast(Sequence[Any], obj)
152
+
153
+ return _neighbor_phase_mean_core(neigh, cos_map, sin_map, np, fallback)
154
+
155
+
156
+ def neighbor_phase_mean_list(
157
+ neigh: Sequence[Any],
158
+ cos_th: dict[Any, float],
159
+ sin_th: dict[Any, float],
160
+ np: Any | None = None,
161
+ fallback: float = 0.0,
162
+ ) -> float:
163
+ """Return circular mean of neighbour phases from cosine/sine mappings.
164
+
165
+ This is a thin wrapper over :func:`_neighbor_phase_mean_generic` that
166
+ operates on explicit neighbour lists.
167
+ """
168
+
169
+ return _neighbor_phase_mean_generic(
170
+ neigh, cos_map=cos_th, sin_map=sin_th, np=np, fallback=fallback
171
+ )
172
+
173
+
174
+ def neighbor_phase_mean_bulk(
175
+ edge_src: Any,
176
+ edge_dst: Any,
177
+ *,
178
+ cos_values: Any,
179
+ sin_values: Any,
180
+ theta_values: Any,
181
+ node_count: int,
182
+ np: Any,
183
+ neighbor_cos_sum: Any | None = None,
184
+ neighbor_sin_sum: Any | None = None,
185
+ neighbor_counts: Any | None = None,
186
+ mean_cos: Any | None = None,
187
+ mean_sin: Any | None = None,
188
+ ) -> tuple[Any, Any]:
189
+ """Vectorised neighbour phase means for all nodes in a graph.
190
+
191
+ Parameters
192
+ ----------
193
+ edge_src, edge_dst:
194
+ Arrays describing the source (neighbour) and destination (node) indices
195
+ for each edge contribution. They must have matching shapes.
196
+ cos_values, sin_values:
197
+ Arrays containing the cosine and sine values of each node's phase. The
198
+ arrays must be indexed using the same positional indices referenced by
199
+ ``edge_src``.
200
+ theta_values:
201
+ Array with the baseline phase for each node. Positions that do not have
202
+ neighbours reuse this baseline as their mean phase.
203
+ node_count:
204
+ Total number of nodes represented in ``theta_values``.
205
+ np:
206
+ Numpy module used to materialise the vectorised operations.
207
+
208
+ Optional buffers
209
+ -----------------
210
+ neighbor_cos_sum, neighbor_sin_sum, neighbor_counts, mean_cos, mean_sin:
211
+ Preallocated arrays sized ``node_count`` reused to accumulate the
212
+ neighbour cosine/sine sums, neighbour sample counts, and the averaged
213
+ cosine/sine vectors. When omitted, the helper materialises fresh
214
+ buffers that match the previous semantics.
215
+
216
+ Returns
217
+ -------
218
+ tuple[Any, Any]
219
+ Tuple ``(mean_theta, has_neighbors)`` where ``mean_theta`` contains the
220
+ circular mean of neighbour phases for every node and ``has_neighbors``
221
+ is a boolean mask identifying which nodes contributed at least one
222
+ neighbour sample.
223
+ """
224
+
225
+ if node_count <= 0:
226
+ empty_mean = np.zeros(0, dtype=float)
227
+ return empty_mean, empty_mean.astype(bool)
228
+
229
+ edge_src_arr = np.asarray(edge_src, dtype=np.intp)
230
+ edge_dst_arr = np.asarray(edge_dst, dtype=np.intp)
231
+
232
+ if edge_src_arr.shape != edge_dst_arr.shape:
233
+ raise ValueError("edge_src and edge_dst must share the same shape")
234
+
235
+ theta_arr = np.asarray(theta_values, dtype=float)
236
+ if theta_arr.ndim != 1 or theta_arr.size != node_count:
237
+ raise ValueError("theta_values must be a 1-D array matching node_count")
238
+
239
+ cos_arr = np.asarray(cos_values, dtype=float)
240
+ sin_arr = np.asarray(sin_values, dtype=float)
241
+ if cos_arr.ndim != 1 or cos_arr.size != node_count:
242
+ raise ValueError("cos_values must be a 1-D array matching node_count")
243
+ if sin_arr.ndim != 1 or sin_arr.size != node_count:
244
+ raise ValueError("sin_values must be a 1-D array matching node_count")
245
+
246
+ edge_count = edge_dst_arr.size
247
+
248
+ def _coerce_buffer(buffer: Any | None, *, name: str) -> tuple[Any, bool]:
249
+ if buffer is None:
250
+ return None, False
251
+ arr = np.array(buffer, dtype=float, copy=False)
252
+ if arr.ndim != 1 or arr.size != node_count:
253
+ raise ValueError(f"{name} must be a 1-D array sized node_count")
254
+ arr.fill(0.0)
255
+ return arr, True
256
+
257
+ neighbor_cos_sum, has_cos_buffer = _coerce_buffer(
258
+ neighbor_cos_sum, name="neighbor_cos_sum"
259
+ )
260
+ neighbor_sin_sum, has_sin_buffer = _coerce_buffer(
261
+ neighbor_sin_sum, name="neighbor_sin_sum"
262
+ )
263
+ neighbor_counts, has_count_buffer = _coerce_buffer(
264
+ neighbor_counts, name="neighbor_counts"
265
+ )
266
+
267
+ if edge_count:
268
+ cos_bincount = np.bincount(
269
+ edge_dst_arr,
270
+ weights=cos_arr[edge_src_arr],
271
+ minlength=node_count,
272
+ )
273
+ sin_bincount = np.bincount(
274
+ edge_dst_arr,
275
+ weights=sin_arr[edge_src_arr],
276
+ minlength=node_count,
277
+ )
278
+ count_bincount = np.bincount(
279
+ edge_dst_arr,
280
+ minlength=node_count,
281
+ ).astype(float, copy=False)
282
+
283
+ if not has_cos_buffer:
284
+ neighbor_cos_sum = cos_bincount
285
+ else:
286
+ np.copyto(neighbor_cos_sum, cos_bincount)
287
+
288
+ if not has_sin_buffer:
289
+ neighbor_sin_sum = sin_bincount
290
+ else:
291
+ np.copyto(neighbor_sin_sum, sin_bincount)
292
+
293
+ if not has_count_buffer:
294
+ neighbor_counts = count_bincount
295
+ else:
296
+ np.copyto(neighbor_counts, count_bincount)
297
+ else:
298
+ if neighbor_cos_sum is None:
299
+ neighbor_cos_sum = np.zeros(node_count, dtype=float)
300
+ if neighbor_sin_sum is None:
301
+ neighbor_sin_sum = np.zeros(node_count, dtype=float)
302
+ if neighbor_counts is None:
303
+ neighbor_counts = np.zeros(node_count, dtype=float)
304
+
305
+ has_neighbors = neighbor_counts > 0.0
306
+
307
+ mean_cos, _ = _coerce_buffer(mean_cos, name="mean_cos")
308
+ mean_sin, _ = _coerce_buffer(mean_sin, name="mean_sin")
309
+
310
+ if mean_cos is None:
311
+ mean_cos = np.zeros(node_count, dtype=float)
312
+ if mean_sin is None:
313
+ mean_sin = np.zeros(node_count, dtype=float)
314
+
315
+ if edge_count:
316
+ with np.errstate(divide="ignore", invalid="ignore"):
317
+ np.divide(
318
+ neighbor_cos_sum,
319
+ neighbor_counts,
320
+ out=mean_cos,
321
+ where=has_neighbors,
322
+ )
323
+ np.divide(
324
+ neighbor_sin_sum,
325
+ neighbor_counts,
326
+ out=mean_sin,
327
+ where=has_neighbors,
328
+ )
329
+
330
+ mean_theta = np.where(has_neighbors, np.arctan2(mean_sin, mean_cos), theta_arr)
331
+ return mean_theta, has_neighbors
332
+
333
+
334
+ @overload
335
+ def neighbor_phase_mean(obj: "NodeProtocol", n: None = ...) -> Phase: ...
336
+
337
+
338
+ @overload
339
+ def neighbor_phase_mean(obj: TNFRGraph, n: NodeId) -> Phase: ...
340
+
341
+
342
+ def neighbor_phase_mean(
343
+ obj: "NodeProtocol" | TNFRGraph, n: NodeId | None = None
344
+ ) -> Phase:
345
+ """Circular mean of neighbour phases for ``obj``.
346
+
347
+ Parameters
348
+ ----------
349
+ obj:
350
+ Either a :class:`~tnfr.node.NodeProtocol` instance bound to a graph or a
351
+ :class:`~tnfr.types.TNFRGraph` from which the node ``n`` will be wrapped.
352
+ n:
353
+ Optional node identifier. Required when ``obj`` is a graph. Providing a
354
+ node identifier for a node object raises :class:`TypeError`.
355
+ """
356
+
357
+ NodeNX = cached_import("tnfr.node", "NodeNX")
358
+ if NodeNX is None:
359
+ raise ImportError("NodeNX is unavailable")
360
+ if n is None:
361
+ if hasattr(obj, "nodes"):
362
+ raise TypeError(
363
+ "neighbor_phase_mean requires a node identifier when passing a graph"
364
+ )
365
+ node = obj
366
+ else:
367
+ if hasattr(obj, "nodes"):
368
+ node = NodeNX(obj, n)
369
+ else:
370
+ raise TypeError(
371
+ "neighbor_phase_mean received a node and an explicit identifier"
372
+ )
373
+ return _neighbor_phase_mean_generic(node)
tnfr/metrics/trig.pyi ADDED
@@ -0,0 +1,13 @@
1
+ from typing import Any
2
+
3
+ __all__: Any
4
+
5
+ def __getattr__(name: str) -> Any: ...
6
+
7
+ _neighbor_phase_mean_core: Any
8
+ _neighbor_phase_mean_generic: Any
9
+ _phase_mean_from_iter: Any
10
+ accumulate_cos_sin: Any
11
+ neighbor_phase_mean: Any
12
+ neighbor_phase_mean_bulk: Any
13
+ neighbor_phase_mean_list: Any
@@ -0,0 +1,233 @@
1
+ """Trigonometric caches for TNFR metrics.
2
+
3
+ The cosine/sine storage helpers live here to keep :mod:`tnfr.metrics.trig`
4
+ focused on pure mathematical utilities (phase means, compensated sums, etc.).
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from ..compat.dataclass import dataclass
10
+ import hashlib
11
+ import math
12
+ import struct
13
+ from typing import Any, Iterable, Mapping
14
+
15
+ from ..alias import get_theta_attr
16
+ from ..types import GraphLike, NodeAttrMap
17
+ from ..utils import edge_version_cache, get_numpy
18
+
19
+ __all__ = ("TrigCache", "compute_theta_trig", "get_trig_cache", "_compute_trig_python")
20
+
21
+
22
+ @dataclass(slots=True)
23
+ class TrigCache:
24
+ """Container for cached trigonometric values per node."""
25
+
26
+ cos: dict[Any, float]
27
+ sin: dict[Any, float]
28
+ theta: dict[Any, float]
29
+ theta_checksums: dict[Any, bytes]
30
+ order: tuple[Any, ...]
31
+ cos_values: Any
32
+ sin_values: Any
33
+ theta_values: Any
34
+ index: dict[Any, int]
35
+ edge_src: Any | None = None
36
+ edge_dst: Any | None = None
37
+
38
+
39
+ def _iter_theta_pairs(
40
+ nodes: Iterable[tuple[Any, NodeAttrMap | float]],
41
+ ) -> Iterable[tuple[Any, float]]:
42
+ """Yield ``(node, θ)`` pairs from ``nodes``."""
43
+
44
+ for n, data in nodes:
45
+ if isinstance(data, Mapping):
46
+ yield n, get_theta_attr(data, 0.0) or 0.0
47
+ else:
48
+ yield n, float(data)
49
+
50
+
51
+ def _compute_trig_python(
52
+ nodes: Iterable[tuple[Any, NodeAttrMap | float]],
53
+ ) -> TrigCache:
54
+ """Compute trigonometric mappings using pure Python."""
55
+
56
+ pairs = list(_iter_theta_pairs(nodes))
57
+
58
+ cos_th: dict[Any, float] = {}
59
+ sin_th: dict[Any, float] = {}
60
+ thetas: dict[Any, float] = {}
61
+ theta_checksums: dict[Any, bytes] = {}
62
+ order_list: list[Any] = []
63
+
64
+ for n, th in pairs:
65
+ order_list.append(n)
66
+ thetas[n] = th
67
+ cos_th[n] = math.cos(th)
68
+ sin_th[n] = math.sin(th)
69
+ theta_checksums[n] = _theta_checksum(th)
70
+
71
+ order = tuple(order_list)
72
+ cos_values = tuple(cos_th[n] for n in order)
73
+ sin_values = tuple(sin_th[n] for n in order)
74
+ theta_values = tuple(thetas[n] for n in order)
75
+ index = {n: i for i, n in enumerate(order)}
76
+
77
+ return TrigCache(
78
+ cos=cos_th,
79
+ sin=sin_th,
80
+ theta=thetas,
81
+ theta_checksums=theta_checksums,
82
+ order=order,
83
+ cos_values=cos_values,
84
+ sin_values=sin_values,
85
+ theta_values=theta_values,
86
+ index=index,
87
+ edge_src=None,
88
+ edge_dst=None,
89
+ )
90
+
91
+
92
+ def compute_theta_trig(
93
+ nodes: Iterable[tuple[Any, NodeAttrMap | float]],
94
+ np: Any | None = None,
95
+ ) -> TrigCache:
96
+ """Return trigonometric mappings of ``θ`` per node."""
97
+
98
+ if np is None:
99
+ np = get_numpy()
100
+ if np is None or not all(hasattr(np, attr) for attr in ("fromiter", "cos", "sin")):
101
+ return _compute_trig_python(nodes)
102
+
103
+ pairs = list(_iter_theta_pairs(nodes))
104
+ if not pairs:
105
+ return TrigCache(
106
+ cos={},
107
+ sin={},
108
+ theta={},
109
+ theta_checksums={},
110
+ order=(),
111
+ cos_values=(),
112
+ sin_values=(),
113
+ theta_values=(),
114
+ index={},
115
+ edge_src=None,
116
+ edge_dst=None,
117
+ )
118
+
119
+ node_list, theta_vals = zip(*pairs)
120
+ node_list = tuple(node_list)
121
+ theta_arr = np.fromiter(theta_vals, dtype=float)
122
+ cos_arr = np.cos(theta_arr)
123
+ sin_arr = np.sin(theta_arr)
124
+
125
+ cos_th = dict(zip(node_list, map(float, cos_arr)))
126
+ sin_th = dict(zip(node_list, map(float, sin_arr)))
127
+ thetas = dict(zip(node_list, map(float, theta_arr)))
128
+ theta_checksums = {node: _theta_checksum(float(theta)) for node, theta in pairs}
129
+ index = {n: i for i, n in enumerate(node_list)}
130
+ return TrigCache(
131
+ cos=cos_th,
132
+ sin=sin_th,
133
+ theta=thetas,
134
+ theta_checksums=theta_checksums,
135
+ order=node_list,
136
+ cos_values=cos_arr,
137
+ sin_values=sin_arr,
138
+ theta_values=theta_arr,
139
+ index=index,
140
+ edge_src=None,
141
+ edge_dst=None,
142
+ )
143
+
144
+
145
+ def _build_trig_cache(G: GraphLike, np: Any | None = None) -> TrigCache:
146
+ """Construct trigonometric cache for ``G``."""
147
+
148
+ return compute_theta_trig(G.nodes(data=True), np=np)
149
+
150
+
151
+ def get_trig_cache(
152
+ G: GraphLike,
153
+ *,
154
+ np: Any | None = None,
155
+ cache_size: int | None = 128,
156
+ ) -> TrigCache:
157
+ """Return cached cosines and sines of ``θ`` per node.
158
+
159
+ This function maintains a cache of trigonometric values to avoid repeated
160
+ cos(θ) and sin(θ) computations across Si, coherence, and ΔNFR calculations.
161
+ The cache uses version-based invalidation triggered by theta attribute changes.
162
+
163
+ Cache Strategy
164
+ --------------
165
+ - **Key**: ``("_trig", version)`` where version increments on theta changes
166
+ - **Invalidation**: Checksum-based detection of theta attribute updates
167
+ - **Capacity**: Controlled by ``cache_size`` parameter (default: 128)
168
+ - **Scope**: Graph-wide, shared across all metrics computations
169
+
170
+ The cache maintains both dict (for sparse access) and array (for vectorized
171
+ operations) representations of the trigonometric values.
172
+
173
+ Parameters
174
+ ----------
175
+ G : GraphLike
176
+ Graph whose node theta attributes are cached.
177
+ np : Any or None, optional
178
+ NumPy module for array-based storage. Falls back to dict if None.
179
+ cache_size : int or None, optional
180
+ Maximum cache entries. Default: 128. None for unlimited.
181
+
182
+ Returns
183
+ -------
184
+ TrigCache
185
+ Container with cos/sin mappings and optional array representations.
186
+ See TrigCache dataclass for field documentation.
187
+ """
188
+
189
+ if np is None:
190
+ np = get_numpy()
191
+ graph = G.graph
192
+ version = graph.setdefault("_trig_version", 0)
193
+ key = ("_trig", version)
194
+
195
+ def builder() -> TrigCache:
196
+ return _build_trig_cache(G, np=np)
197
+
198
+ trig = edge_version_cache(G, key, builder, max_entries=cache_size)
199
+ current_checksums = _graph_theta_checksums(G)
200
+ trig_checksums = getattr(trig, "theta_checksums", None)
201
+ if trig_checksums is None:
202
+ trig_checksums = {}
203
+
204
+ # Checksum-based invalidation: detect theta attribute changes
205
+ if trig_checksums != current_checksums:
206
+ version = version + 1
207
+ graph["_trig_version"] = version
208
+ key = ("_trig", version)
209
+ trig = edge_version_cache(G, key, builder, max_entries=cache_size)
210
+ trig_checksums = getattr(trig, "theta_checksums", None)
211
+ if trig_checksums is None:
212
+ trig_checksums = {}
213
+ if trig_checksums != current_checksums:
214
+ current_checksums = _graph_theta_checksums(G)
215
+ if trig_checksums != current_checksums:
216
+ return trig
217
+ return trig
218
+
219
+
220
+ def _theta_checksum(theta: float) -> bytes:
221
+ """Return a deterministic checksum for ``theta``."""
222
+
223
+ packed = struct.pack("!d", float(theta))
224
+ return hashlib.blake2b(packed, digest_size=8).digest()
225
+
226
+
227
+ def _graph_theta_checksums(G: GraphLike) -> dict[Any, bytes]:
228
+ """Return checksum snapshot of the graph's current ``θ`` values."""
229
+
230
+ checksums: dict[Any, bytes] = {}
231
+ for node, theta in _iter_theta_pairs(G.nodes(data=True)):
232
+ checksums[node] = _theta_checksum(theta)
233
+ return checksums
@@ -0,0 +1,10 @@
1
+ from typing import Any
2
+
3
+ __all__: Any
4
+
5
+ def __getattr__(name: str) -> Any: ...
6
+
7
+ TrigCache: Any
8
+ _compute_trig_python: Any
9
+ compute_theta_trig: Any
10
+ get_trig_cache: Any
@@ -0,0 +1,32 @@
1
+ """Multi-scale hierarchical TNFR network support.
2
+
3
+ This module implements operational fractality (§3.7) by enabling TNFR networks
4
+ to operate recursively at multiple scales simultaneously, preserving structural
5
+ coherence across scale transitions.
6
+
7
+ Canonical Invariants Preserved
8
+ ------------------------------
9
+ 1. EPI operational fractality - nested EPIs maintain functional identity
10
+ 2. Cross-scale ΔNFR - coherence propagates between scales
11
+ 3. Phase synchrony - maintained within and across scales
12
+ 4. Deterministic evolution - reproducible multi-scale dynamics
13
+
14
+ Examples
15
+ --------
16
+ Create a hierarchical network spanning quantum to organism scales:
17
+
18
+ >>> from tnfr.multiscale import HierarchicalTNFRNetwork, ScaleDefinition
19
+ >>> scales = [
20
+ ... ScaleDefinition("quantum", node_count=1000, coupling_strength=0.8),
21
+ ... ScaleDefinition("molecular", node_count=500, coupling_strength=0.6),
22
+ ... ScaleDefinition("cellular", node_count=100, coupling_strength=0.4),
23
+ ... ]
24
+ >>> network = HierarchicalTNFRNetwork(scales)
25
+ >>> # Network supports simultaneous evolution at all scales
26
+ """
27
+
28
+ from __future__ import annotations
29
+
30
+ from .hierarchical import HierarchicalTNFRNetwork, ScaleDefinition
31
+
32
+ __all__ = ["HierarchicalTNFRNetwork", "ScaleDefinition"]