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/sense.py ADDED
@@ -0,0 +1,378 @@
1
+ """Sense calculations and structural operator symbol vector analysis.
2
+
3
+ This module implements the sense index (Si) calculation and related vector
4
+ operations for analyzing the distribution of structural operator applications.
5
+
6
+ The 'glyph rose' visualization represents the distribution of structural operator
7
+ symbols in a circular format, where each glyph corresponds to an angle representing
8
+ the associated structural operator.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import math
14
+ from collections import Counter
15
+ from collections.abc import Iterable, Iterator, Mapping
16
+ from itertools import tee
17
+ from typing import Any, Callable, TypeVar
18
+
19
+ import networkx as nx
20
+
21
+ from .alias import get_attr
22
+ from .utils import CallbackEvent, callback_manager
23
+ from .config.constants import (
24
+ ANGLE_MAP,
25
+ GLYPHS_CANONICAL,
26
+ )
27
+ from .constants import get_graph_param
28
+ from .constants.aliases import ALIAS_EPI, ALIAS_SI
29
+ from .glyph_history import append_metric, count_glyphs, ensure_history
30
+ from .glyph_runtime import last_glyph
31
+ from .utils import clamp01, kahan_sum_nd
32
+ from .types import NodeId, SigmaVector, TNFRGraph
33
+ from .utils import get_numpy
34
+
35
+ # -------------------------
36
+ # Canon: circular glyph order and angles
37
+ # -------------------------
38
+
39
+ GLYPH_UNITS: dict[str, complex] = {
40
+ g: complex(math.cos(a), math.sin(a)) for g, a in ANGLE_MAP.items()
41
+ }
42
+
43
+ __all__ = (
44
+ "GLYPH_UNITS",
45
+ "glyph_angle",
46
+ "glyph_unit",
47
+ "sigma_vector_node",
48
+ "sigma_vector",
49
+ "sigma_vector_from_graph",
50
+ "push_sigma_snapshot",
51
+ "register_sigma_callback",
52
+ "sigma_rose",
53
+ )
54
+
55
+ # -------------------------
56
+ # Basic utilities
57
+ # -------------------------
58
+
59
+ T = TypeVar("T")
60
+
61
+
62
+ def _resolve_glyph(g: str, mapping: Mapping[str, T]) -> T:
63
+ """Return ``mapping[g]`` or raise ``KeyError`` with a standard message."""
64
+
65
+ try:
66
+ return mapping[g]
67
+ except KeyError as e: # pragma: no cover - small helper
68
+ raise KeyError(f"Unknown glyph: {g}") from e
69
+
70
+
71
+ def glyph_angle(g: str) -> float:
72
+ """Return the canonical angle for structural operator symbol ``g``.
73
+
74
+ Each structural operator symbol (glyph) is mapped to a specific angle
75
+ in the circular representation used for sense vector calculations.
76
+ """
77
+
78
+ return float(_resolve_glyph(g, ANGLE_MAP))
79
+
80
+
81
+ def glyph_unit(g: str) -> complex:
82
+ """Return the unit vector for structural operator symbol ``g``.
83
+
84
+ Each structural operator symbol (glyph) corresponds to a unit vector
85
+ in the complex plane, used for aggregating operator applications.
86
+ """
87
+
88
+ return _resolve_glyph(g, GLYPH_UNITS)
89
+
90
+
91
+ MODE_FUNCS: dict[str, Callable[[Mapping[str, Any]], float]] = {
92
+ "Si": lambda nd: clamp01(get_attr(nd, ALIAS_SI, 0.5)),
93
+ "EPI": lambda nd: max(0.0, get_attr(nd, ALIAS_EPI, 0.0)),
94
+ }
95
+
96
+
97
+ def _weight(nd: Mapping[str, Any], mode: str) -> float:
98
+ return MODE_FUNCS.get(mode, lambda _: 1.0)(nd)
99
+
100
+
101
+ def _node_weight(
102
+ nd: Mapping[str, Any], weight_mode: str
103
+ ) -> tuple[str, float, complex] | None:
104
+ """Return ``(glyph, weight, weighted_unit)`` or ``None`` if no glyph."""
105
+ g = last_glyph(nd)
106
+ if not g:
107
+ return None
108
+ w = _weight(nd, weight_mode)
109
+ z = glyph_unit(g) * w # precompute weighted unit vector
110
+ return g, w, z
111
+
112
+
113
+ def _sigma_cfg(G: TNFRGraph) -> dict[str, Any]:
114
+ return get_graph_param(G, "SIGMA", dict)
115
+
116
+
117
+ def _to_complex(val: complex | float | int) -> complex:
118
+ """Return ``val`` as complex, promoting real numbers."""
119
+
120
+ if isinstance(val, complex):
121
+ return val
122
+ if isinstance(val, (int, float)):
123
+ return complex(val, 0.0)
124
+ raise TypeError("values must be an iterable of real or complex numbers")
125
+
126
+
127
+ def _empty_sigma(fallback_angle: float) -> SigmaVector:
128
+ """Return an empty σ-vector with ``fallback_angle``.
129
+
130
+ Helps centralise the default structure returned when no values are
131
+ available for σ calculations.
132
+ """
133
+
134
+ return {
135
+ "x": 0.0,
136
+ "y": 0.0,
137
+ "mag": 0.0,
138
+ "angle": float(fallback_angle),
139
+ "n": 0,
140
+ }
141
+
142
+
143
+ # -------------------------
144
+ # σ per node and global σ
145
+ # -------------------------
146
+
147
+
148
+ def _sigma_from_iterable(
149
+ values: Iterable[complex | float | int] | complex | float | int,
150
+ fallback_angle: float = 0.0,
151
+ ) -> SigmaVector:
152
+ """Normalise vectors in the σ-plane.
153
+
154
+ ``values`` may contain complex or real numbers; real inputs are promoted to
155
+ complex with zero imaginary part. The returned dictionary includes the
156
+ number of processed values under the ``"n"`` key.
157
+ """
158
+
159
+ if isinstance(values, Iterable) and not isinstance(
160
+ values, (str, bytes, bytearray, Mapping)
161
+ ):
162
+ iterator = iter(values)
163
+ else:
164
+ iterator = iter((values,))
165
+
166
+ np = get_numpy()
167
+ if np is not None:
168
+ iterator, np_iter = tee(iterator)
169
+ arr = np.fromiter((_to_complex(v) for v in np_iter), dtype=np.complex128)
170
+ cnt = int(arr.size)
171
+ if cnt == 0:
172
+ return _empty_sigma(fallback_angle)
173
+ x = float(np.mean(arr.real))
174
+ y = float(np.mean(arr.imag))
175
+ mag = float(np.hypot(x, y))
176
+ ang = float(np.arctan2(y, x)) if mag > 0 else float(fallback_angle)
177
+ return {
178
+ "x": float(x),
179
+ "y": float(y),
180
+ "mag": float(mag),
181
+ "angle": float(ang),
182
+ "n": int(cnt),
183
+ }
184
+ cnt = 0
185
+
186
+ def pair_iter() -> Iterator[tuple[float, float]]:
187
+ nonlocal cnt
188
+ for val in iterator:
189
+ z = _to_complex(val)
190
+ cnt += 1
191
+ yield (z.real, z.imag)
192
+
193
+ sum_x, sum_y = kahan_sum_nd(pair_iter(), dims=2)
194
+
195
+ if cnt == 0:
196
+ return _empty_sigma(fallback_angle)
197
+
198
+ x = sum_x / cnt
199
+ y = sum_y / cnt
200
+ mag = math.hypot(x, y)
201
+ ang = math.atan2(y, x) if mag > 0 else float(fallback_angle)
202
+ return {
203
+ "x": float(x),
204
+ "y": float(y),
205
+ "mag": float(mag),
206
+ "angle": float(ang),
207
+ "n": int(cnt),
208
+ }
209
+
210
+
211
+ def _ema_update(prev: SigmaVector, current: SigmaVector, alpha: float) -> SigmaVector:
212
+ """Exponential moving average update for σ vectors."""
213
+ x = (1 - alpha) * prev["x"] + alpha * current["x"]
214
+ y = (1 - alpha) * prev["y"] + alpha * current["y"]
215
+ mag = math.hypot(x, y)
216
+ ang = math.atan2(y, x)
217
+ return {
218
+ "x": float(x),
219
+ "y": float(y),
220
+ "mag": float(mag),
221
+ "angle": float(ang),
222
+ "n": int(current["n"]),
223
+ }
224
+
225
+
226
+ def _sigma_from_nodes(
227
+ nodes: Iterable[Mapping[str, Any]],
228
+ weight_mode: str,
229
+ fallback_angle: float = 0.0,
230
+ ) -> tuple[SigmaVector, list[tuple[str, float, complex]]]:
231
+ """Aggregate weighted glyph vectors for ``nodes``.
232
+
233
+ Returns the aggregated σ vector and the list of ``(glyph, weight, vector)``
234
+ triples used in the calculation.
235
+ """
236
+
237
+ nws = [nw for nd in nodes if (nw := _node_weight(nd, weight_mode))]
238
+ sv = _sigma_from_iterable((nw[2] for nw in nws), fallback_angle)
239
+ return sv, nws
240
+
241
+
242
+ def sigma_vector_node(
243
+ G: TNFRGraph, n: NodeId, weight_mode: str | None = None
244
+ ) -> SigmaVector | None:
245
+ """Return the σ vector for node ``n`` using the configured weighting."""
246
+
247
+ cfg = _sigma_cfg(G)
248
+ nd = G.nodes[n]
249
+ weight_mode = weight_mode or cfg.get("weight", "Si")
250
+ sv, nws = _sigma_from_nodes([nd], weight_mode)
251
+ if not nws:
252
+ return None
253
+ g, w, _ = nws[0]
254
+ if sv["mag"] == 0:
255
+ sv["angle"] = glyph_angle(g)
256
+ sv["glyph"] = g
257
+ sv["w"] = float(w)
258
+ return sv
259
+
260
+
261
+ def sigma_vector(dist: Mapping[str, float]) -> SigmaVector:
262
+ """Compute Σ⃗ from a glyph distribution.
263
+
264
+ ``dist`` may contain raw counts or proportions. All ``(glyph, weight)``
265
+ pairs are converted to vectors and passed to :func:`_sigma_from_iterable`.
266
+ The resulting vector includes the number of processed pairs under ``n``.
267
+ """
268
+
269
+ vectors = (glyph_unit(g) * float(w) for g, w in dist.items())
270
+ return _sigma_from_iterable(vectors)
271
+
272
+
273
+ def sigma_vector_from_graph(
274
+ G: TNFRGraph, weight_mode: str | None = None
275
+ ) -> SigmaVector:
276
+ """Global vector in the σ sense plane for a graph.
277
+
278
+ Parameters
279
+ ----------
280
+ G:
281
+ NetworkX graph with per-node states.
282
+ weight_mode:
283
+ How to weight each node ("Si", "EPI" or ``None`` for unit weight).
284
+
285
+ Returns
286
+ -------
287
+ dict[str, float]
288
+ Cartesian components, magnitude and angle of the average vector.
289
+ """
290
+
291
+ if not isinstance(G, nx.Graph):
292
+ raise TypeError("sigma_vector_from_graph requires a networkx.Graph")
293
+
294
+ cfg = _sigma_cfg(G)
295
+ weight_mode = weight_mode or cfg.get("weight", "Si")
296
+ sv, _ = _sigma_from_nodes((nd for _, nd in G.nodes(data=True)), weight_mode)
297
+ return sv
298
+
299
+
300
+ # -------------------------
301
+ # History / series
302
+ # -------------------------
303
+
304
+
305
+ def push_sigma_snapshot(G: TNFRGraph, t: float | None = None) -> None:
306
+ """Record a global σ snapshot (and optional per-node traces) for ``G``."""
307
+
308
+ cfg = _sigma_cfg(G)
309
+ if not cfg.get("enabled", True):
310
+ return
311
+
312
+ # Local history cache to avoid repeated lookups
313
+ hist = ensure_history(G)
314
+ key = cfg.get("history_key", "sigma_global")
315
+
316
+ weight_mode = cfg.get("weight", "Si")
317
+ sv = sigma_vector_from_graph(G, weight_mode)
318
+
319
+ # Optional exponential smoothing (EMA)
320
+ alpha = float(cfg.get("smooth", 0.0))
321
+ if alpha > 0 and hist.get(key):
322
+ sv = _ema_update(hist[key][-1], sv, alpha)
323
+
324
+ current_t = float(G.graph.get("_t", 0.0) if t is None else t)
325
+ sv["t"] = current_t
326
+
327
+ append_metric(hist, key, sv)
328
+
329
+ # Glyph count per step (useful for the glyph rose)
330
+ counts = count_glyphs(G, last_only=True)
331
+ append_metric(hist, "sigma_counts", {"t": current_t, **counts})
332
+
333
+ # Optional per-node trajectory
334
+ if cfg.get("per_node", False):
335
+ per = hist.setdefault("sigma_per_node", {})
336
+ for n, nd in G.nodes(data=True):
337
+ g = last_glyph(nd)
338
+ if not g:
339
+ continue
340
+ d = per.setdefault(n, [])
341
+ d.append({"t": current_t, "g": g, "angle": glyph_angle(g)})
342
+
343
+
344
+ # -------------------------
345
+ # Register as an automatic callback (after_step)
346
+ # -------------------------
347
+
348
+
349
+ def register_sigma_callback(G: TNFRGraph) -> None:
350
+ """Attach :func:`push_sigma_snapshot` to the ``AFTER_STEP`` callback bus."""
351
+
352
+ callback_manager.register_callback(
353
+ G,
354
+ event=CallbackEvent.AFTER_STEP.value,
355
+ func=push_sigma_snapshot,
356
+ name="sigma_snapshot",
357
+ )
358
+
359
+
360
+ def sigma_rose(G: TNFRGraph, steps: int | None = None) -> dict[str, int]:
361
+ """Histogram of glyphs in the last ``steps`` steps (or all)."""
362
+ hist = ensure_history(G)
363
+ counts = hist.get("sigma_counts", [])
364
+ if not counts:
365
+ return {g: 0 for g in GLYPHS_CANONICAL}
366
+ if steps is not None:
367
+ steps = int(steps)
368
+ if steps < 0:
369
+ raise ValueError("steps must be non-negative")
370
+ rows = counts if steps >= len(counts) else counts[-steps:] # noqa: E203
371
+ else:
372
+ rows = counts
373
+ counter = Counter()
374
+ for row in rows:
375
+ for k, v in row.items():
376
+ if k != "t":
377
+ counter[k] += int(v)
378
+ return {g: int(counter.get(g, 0)) for g in GLYPHS_CANONICAL}
tnfr/sense.pyi ADDED
@@ -0,0 +1,23 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Mapping
4
+ from typing import Optional
5
+
6
+ from .types import NodeId, SigmaVector, TNFRGraph
7
+
8
+ __all__: tuple[str, ...]
9
+
10
+ GLYPH_UNITS: dict[str, complex]
11
+
12
+ def glyph_angle(g: str) -> float: ...
13
+ def glyph_unit(g: str) -> complex: ...
14
+ def push_sigma_snapshot(G: TNFRGraph, t: Optional[float] = None) -> None: ...
15
+ def register_sigma_callback(G: TNFRGraph) -> None: ...
16
+ def sigma_rose(G: TNFRGraph, steps: Optional[int] = None) -> dict[str, int]: ...
17
+ def sigma_vector(dist: Mapping[str, float]) -> SigmaVector: ...
18
+ def sigma_vector_from_graph(
19
+ G: TNFRGraph, weight_mode: Optional[str] = None
20
+ ) -> SigmaVector: ...
21
+ def sigma_vector_node(
22
+ G: TNFRGraph, n: NodeId, weight_mode: Optional[str] = None
23
+ ) -> Optional[SigmaVector]: ...
@@ -0,0 +1,17 @@
1
+ """Service layer for TNFR orchestration.
2
+
3
+ This package provides the service layer that coordinates execution of TNFR
4
+ operator sequences while maintaining clean separation of responsibilities
5
+ across validation, execution, dynamics, and telemetry concerns.
6
+
7
+ Public API
8
+ ----------
9
+ TNFROrchestrator
10
+ Main orchestration service coordinating sequence execution.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from .orchestrator import TNFROrchestrator
16
+
17
+ __all__ = ("TNFROrchestrator",)