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,290 @@
1
+ """Input validation utilities for TNFR structural data.
2
+
3
+ This module provides validation functions for TNFR-specific data types
4
+ to ensure structural coherence when persisting or querying data.
5
+
6
+ TNFR Structural Invariants
7
+ ---------------------------
8
+ These validators enforce TNFR canonical invariants:
9
+ 1. Structural frequency (νf) in Hz_str units
10
+ 2. Phase (φ) in valid range [0, 2π]
11
+ 3. Coherence C(t) as non-negative value
12
+ 4. Sense index Si validation
13
+
14
+ Example
15
+ -------
16
+ >>> validate_structural_frequency(0.5) # Valid
17
+ 0.5
18
+ >>> validate_phase_value(3.14) # Valid
19
+ 3.14
20
+ >>> validate_structural_frequency(-1.0) # doctest: +SKIP
21
+ Traceback (most recent call last):
22
+ ...
23
+ ValueError: Structural frequency must be non-negative
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ import math
29
+ from typing import Any
30
+
31
+
32
+ def validate_structural_frequency(nu_f: float) -> float:
33
+ """Validate structural frequency (νf) value.
34
+
35
+ Structural frequency must be non-negative and expressed in Hz_str
36
+ (structural hertz), representing the nodal reorganization rate.
37
+
38
+ Parameters
39
+ ----------
40
+ nu_f : float
41
+ Structural frequency value to validate
42
+
43
+ Returns
44
+ -------
45
+ float
46
+ The validated frequency value
47
+
48
+ Raises
49
+ ------
50
+ ValueError
51
+ If the frequency is negative, NaN, or infinite
52
+
53
+ Example
54
+ -------
55
+ >>> validate_structural_frequency(0.5)
56
+ 0.5
57
+ >>> validate_structural_frequency(0.0) # Silence operator
58
+ 0.0
59
+ >>> validate_structural_frequency(-0.1) # doctest: +SKIP
60
+ Traceback (most recent call last):
61
+ ...
62
+ ValueError: Structural frequency must be non-negative, got -0.1
63
+ """
64
+ if not isinstance(nu_f, (int, float)):
65
+ raise ValueError(
66
+ f"Structural frequency must be numeric, got {type(nu_f).__name__}"
67
+ )
68
+
69
+ if math.isnan(nu_f):
70
+ raise ValueError("Structural frequency cannot be NaN")
71
+
72
+ if math.isinf(nu_f):
73
+ raise ValueError("Structural frequency cannot be infinite")
74
+
75
+ if nu_f < 0:
76
+ raise ValueError(f"Structural frequency must be non-negative, got {nu_f}")
77
+
78
+ return float(nu_f)
79
+
80
+
81
+ def validate_phase_value(phase: float, *, allow_wrap: bool = True) -> float:
82
+ """Validate phase (φ) value.
83
+
84
+ Phase represents synchronization in the network and should be in the
85
+ range [0, 2π]. If allow_wrap is True, values outside this range are
86
+ automatically wrapped.
87
+
88
+ Parameters
89
+ ----------
90
+ phase : float
91
+ Phase value to validate
92
+ allow_wrap : bool, optional
93
+ If True, wrap phase to [0, 2π] range (default: True)
94
+
95
+ Returns
96
+ -------
97
+ float
98
+ The validated (and possibly wrapped) phase value
99
+
100
+ Raises
101
+ ------
102
+ ValueError
103
+ If phase is NaN, infinite, or outside valid range (when allow_wrap=False)
104
+
105
+ Example
106
+ -------
107
+ >>> validate_phase_value(1.57) # π/2
108
+ 1.57
109
+ >>> result = validate_phase_value(7.0) # Wrapped to [0, 2π]
110
+ >>> 0.0 <= result <= 6.3
111
+ True
112
+ >>> validate_phase_value(7.0, allow_wrap=False) # doctest: +SKIP
113
+ Traceback (most recent call last):
114
+ ...
115
+ ValueError: Phase must be in range [0, 2π], got 7.0
116
+ """
117
+ if not isinstance(phase, (int, float)):
118
+ raise ValueError(f"Phase must be numeric, got {type(phase).__name__}")
119
+
120
+ if math.isnan(phase):
121
+ raise ValueError("Phase cannot be NaN")
122
+
123
+ if math.isinf(phase):
124
+ raise ValueError("Phase cannot be infinite")
125
+
126
+ two_pi = 2 * math.pi
127
+
128
+ if allow_wrap:
129
+ # Wrap phase to [0, 2π] range
130
+ phase = phase % two_pi
131
+ else:
132
+ if not 0 <= phase <= two_pi:
133
+ raise ValueError(f"Phase must be in range [0, 2π], got {phase}")
134
+
135
+ return float(phase)
136
+
137
+
138
+ def validate_coherence_value(coherence: float) -> float:
139
+ """Validate coherence C(t) value.
140
+
141
+ Coherence represents the total structural stability and must be
142
+ non-negative.
143
+
144
+ Parameters
145
+ ----------
146
+ coherence : float
147
+ Coherence value to validate
148
+
149
+ Returns
150
+ -------
151
+ float
152
+ The validated coherence value
153
+
154
+ Raises
155
+ ------
156
+ ValueError
157
+ If coherence is negative, NaN, or infinite
158
+
159
+ Example
160
+ -------
161
+ >>> validate_coherence_value(0.8)
162
+ 0.8
163
+ >>> validate_coherence_value(0.0) # Minimum coherence
164
+ 0.0
165
+ >>> validate_coherence_value(-0.1) # doctest: +SKIP
166
+ Traceback (most recent call last):
167
+ ...
168
+ ValueError: Coherence must be non-negative, got -0.1
169
+ """
170
+ if not isinstance(coherence, (int, float)):
171
+ raise ValueError(f"Coherence must be numeric, got {type(coherence).__name__}")
172
+
173
+ if math.isnan(coherence):
174
+ raise ValueError("Coherence cannot be NaN")
175
+
176
+ if math.isinf(coherence):
177
+ raise ValueError("Coherence cannot be infinite")
178
+
179
+ if coherence < 0:
180
+ raise ValueError(f"Coherence must be non-negative, got {coherence}")
181
+
182
+ return float(coherence)
183
+
184
+
185
+ def validate_sense_index(si: float) -> float:
186
+ """Validate sense index (Si) value.
187
+
188
+ Sense index represents the capacity to generate stable reorganization.
189
+ Valid range is typically [0, 1] but can exceed 1 in high-coherence networks.
190
+
191
+ Parameters
192
+ ----------
193
+ si : float
194
+ Sense index value to validate
195
+
196
+ Returns
197
+ -------
198
+ float
199
+ The validated sense index value
200
+
201
+ Raises
202
+ ------
203
+ ValueError
204
+ If Si is negative, NaN, or infinite
205
+
206
+ Example
207
+ -------
208
+ >>> validate_sense_index(0.7)
209
+ 0.7
210
+ >>> validate_sense_index(1.2) # High coherence
211
+ 1.2
212
+ >>> validate_sense_index(-0.1) # doctest: +SKIP
213
+ Traceback (most recent call last):
214
+ ...
215
+ ValueError: Sense index must be non-negative, got -0.1
216
+ """
217
+ if not isinstance(si, (int, float)):
218
+ raise ValueError(f"Sense index must be numeric, got {type(si).__name__}")
219
+
220
+ if math.isnan(si):
221
+ raise ValueError("Sense index cannot be NaN")
222
+
223
+ if math.isinf(si):
224
+ raise ValueError("Sense index cannot be infinite")
225
+
226
+ if si < 0:
227
+ raise ValueError(f"Sense index must be non-negative, got {si}")
228
+
229
+ return float(si)
230
+
231
+
232
+ def validate_nodal_input(data: dict[str, Any]) -> dict[str, Any]:
233
+ """Validate a complete nodal data structure.
234
+
235
+ This function validates all common NFR node attributes to ensure
236
+ structural coherence before database persistence.
237
+
238
+ Parameters
239
+ ----------
240
+ data : dict
241
+ Dictionary containing nodal attributes
242
+
243
+ Returns
244
+ -------
245
+ dict
246
+ The validated data dictionary
247
+
248
+ Raises
249
+ ------
250
+ ValueError
251
+ If any attribute fails validation
252
+
253
+ Example
254
+ -------
255
+ >>> data = {"nu_f": 0.5, "phase": 1.57, "delta_nfr": 0.1}
256
+ >>> validated = validate_nodal_input(data)
257
+ >>> validated["nu_f"]
258
+ 0.5
259
+ """
260
+ validated = {}
261
+
262
+ if "nu_f" in data:
263
+ validated["nu_f"] = validate_structural_frequency(data["nu_f"])
264
+
265
+ if "phase" in data:
266
+ validated["phase"] = validate_phase_value(data["phase"])
267
+
268
+ if "coherence" in data:
269
+ validated["coherence"] = validate_coherence_value(data["coherence"])
270
+
271
+ if "si" in data or "sense_index" in data:
272
+ si_key = "si" if "si" in data else "sense_index"
273
+ validated[si_key] = validate_sense_index(data[si_key])
274
+
275
+ # Pass through other fields without validation
276
+ # (e.g., node_id, epi arrays, etc.)
277
+ for key, value in data.items():
278
+ if key not in validated:
279
+ validated[key] = value
280
+
281
+ return validated
282
+
283
+
284
+ __all__ = (
285
+ "validate_coherence_value",
286
+ "validate_nodal_input",
287
+ "validate_phase_value",
288
+ "validate_sense_index",
289
+ "validate_structural_frequency",
290
+ )
tnfr/selector.py ADDED
@@ -0,0 +1,247 @@
1
+ """Utilities to select structural operator symbols based on structural metrics.
2
+
3
+ This module normalises thresholds, computes selection scores and applies
4
+ hysteresis when assigning structural operator symbols (glyphs) to nodes.
5
+
6
+ Each structural operator (Emission, Reception, Coherence, etc.) is represented
7
+ by a glyph symbol (AL, EN, IL, etc.) that this module selects based on the
8
+ node's current structural state.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import threading
14
+ from operator import itemgetter
15
+ from typing import TYPE_CHECKING, Any, Mapping, cast
16
+ from weakref import WeakKeyDictionary
17
+
18
+ if TYPE_CHECKING: # pragma: no cover
19
+ import networkx as nx
20
+
21
+ from .constants import DEFAULTS
22
+ from .config.defaults_core import SELECTOR_THRESHOLD_DEFAULTS
23
+ from .utils import clamp01
24
+ from .metrics.common import compute_dnfr_accel_max
25
+ from .types import SelectorNorms, SelectorThresholds, SelectorWeights
26
+ from .utils import is_non_string_sequence
27
+
28
+ if TYPE_CHECKING: # pragma: no cover
29
+ from .types import TNFRGraph
30
+
31
+ HYSTERESIS_GLYPHS: set[str] = {"IL", "OZ", "ZHIR", "THOL", "NAV", "RA"}
32
+
33
+ __all__ = (
34
+ "_selector_thresholds",
35
+ "_selector_norms",
36
+ "_calc_selector_score",
37
+ "_apply_selector_hysteresis",
38
+ "_selector_parallel_jobs",
39
+ )
40
+
41
+ _SelectorThresholdItems = tuple[tuple[str, float], ...]
42
+ _SelectorThresholdCacheEntry = tuple[
43
+ _SelectorThresholdItems,
44
+ SelectorThresholds,
45
+ ]
46
+ _SELECTOR_THRESHOLD_CACHE: WeakKeyDictionary[
47
+ "nx.Graph",
48
+ _SelectorThresholdCacheEntry,
49
+ ] = WeakKeyDictionary()
50
+ _SELECTOR_THRESHOLD_CACHE_LOCK = threading.Lock()
51
+
52
+
53
+ def _sorted_items(mapping: Mapping[str, float]) -> _SelectorThresholdItems:
54
+ """Return mapping items sorted by key.
55
+
56
+ Parameters
57
+ ----------
58
+ mapping : Mapping[str, float]
59
+ Mapping whose items will be sorted.
60
+
61
+ Returns
62
+ -------
63
+ tuple[tuple[str, float], ...]
64
+ Key-sorted items providing a hashable representation for memoisation.
65
+ """
66
+ return tuple(sorted(mapping.items()))
67
+
68
+
69
+ def _compute_selector_thresholds(
70
+ thr_sel_items: _SelectorThresholdItems,
71
+ ) -> SelectorThresholds:
72
+ """Construct selector thresholds for a graph.
73
+
74
+ Parameters
75
+ ----------
76
+ thr_sel_items : tuple[tuple[str, float], ...]
77
+ Selector threshold items as ``(key, value)`` pairs.
78
+
79
+ Returns
80
+ -------
81
+ dict[str, float]
82
+ Normalised thresholds for selector metrics.
83
+ """
84
+ thr_sel = dict(thr_sel_items)
85
+
86
+ out: dict[str, float] = {}
87
+ for key, default in SELECTOR_THRESHOLD_DEFAULTS.items():
88
+ val = thr_sel.get(key, default)
89
+ out[key] = clamp01(float(val))
90
+ return cast(SelectorThresholds, out)
91
+
92
+
93
+ def _selector_thresholds(G: "nx.Graph") -> SelectorThresholds:
94
+ """Return normalised thresholds for Si, ΔNFR and acceleration.
95
+
96
+ Parameters
97
+ ----------
98
+ G : nx.Graph
99
+ Graph whose configuration stores selector thresholds.
100
+
101
+ Returns
102
+ -------
103
+ dict[str, float]
104
+ Dictionary with clamped hi/lo thresholds, memoised per graph.
105
+ """
106
+ sel_defaults = DEFAULTS.get("SELECTOR_THRESHOLDS", {})
107
+ thr_sel = {**sel_defaults, **G.graph.get("SELECTOR_THRESHOLDS", {})}
108
+ thr_sel_items = _sorted_items(thr_sel)
109
+
110
+ with _SELECTOR_THRESHOLD_CACHE_LOCK:
111
+ cached = _SELECTOR_THRESHOLD_CACHE.get(G)
112
+ if cached is not None and cached[0] == thr_sel_items:
113
+ return cached[1]
114
+
115
+ thresholds = _compute_selector_thresholds(thr_sel_items)
116
+
117
+ with _SELECTOR_THRESHOLD_CACHE_LOCK:
118
+ cached = _SELECTOR_THRESHOLD_CACHE.get(G)
119
+ if cached is not None and cached[0] == thr_sel_items:
120
+ return cached[1]
121
+ _SELECTOR_THRESHOLD_CACHE[G] = (thr_sel_items, thresholds)
122
+ return thresholds
123
+
124
+
125
+ def _selector_norms(G: "nx.Graph") -> SelectorNorms:
126
+ """Compute and cache selector norms for ΔNFR and acceleration.
127
+
128
+ Parameters
129
+ ----------
130
+ G : nx.Graph
131
+ Graph for which to compute maxima. Results are stored in ``G.graph``
132
+ under ``"_sel_norms"``.
133
+
134
+ Returns
135
+ -------
136
+ dict
137
+ Mapping with normalisation maxima for ``dnfr`` and ``accel``.
138
+ """
139
+ norms = compute_dnfr_accel_max(G)
140
+ G.graph["_sel_norms"] = norms
141
+ return norms
142
+
143
+
144
+ def _calc_selector_score(
145
+ Si: float, dnfr: float, accel: float, weights: SelectorWeights
146
+ ) -> float:
147
+ """Compute weighted selector score.
148
+
149
+ Parameters
150
+ ----------
151
+ Si : float
152
+ Normalised sense index.
153
+ dnfr : float
154
+ Normalised absolute ΔNFR value.
155
+ accel : float
156
+ Normalised acceleration (|d²EPI/dt²|).
157
+ weights : dict[str, float]
158
+ Normalised weights for ``"w_si"``, ``"w_dnfr"`` and ``"w_accel"``.
159
+
160
+ Returns
161
+ -------
162
+ float
163
+ Final weighted score.
164
+ """
165
+ return (
166
+ weights["w_si"] * Si
167
+ + weights["w_dnfr"] * (1.0 - dnfr)
168
+ + weights["w_accel"] * (1.0 - accel)
169
+ )
170
+
171
+
172
+ def _apply_selector_hysteresis(
173
+ nd: dict[str, Any],
174
+ Si: float,
175
+ dnfr: float,
176
+ accel: float,
177
+ thr: dict[str, float],
178
+ margin: float | None,
179
+ ) -> str | None:
180
+ """Apply hysteresis when values are near thresholds.
181
+
182
+ Parameters
183
+ ----------
184
+ nd : dict[str, Any]
185
+ Node attribute dictionary containing glyph history.
186
+ Si : float
187
+ Normalised sense index.
188
+ dnfr : float
189
+ Normalised absolute ΔNFR value.
190
+ accel : float
191
+ Normalised acceleration.
192
+ thr : dict[str, float]
193
+ Thresholds returned by :func:`_selector_thresholds`.
194
+ margin : float or None
195
+ When positive, distance from thresholds below which the previous
196
+ glyph is reused. Falsy margins disable hysteresis entirely, letting
197
+ selectors bypass the reuse logic.
198
+
199
+ Returns
200
+ -------
201
+ str or None
202
+ Previous glyph if hysteresis applies, otherwise ``None``.
203
+ """
204
+ # Batch extraction reduces dictionary lookups inside loops.
205
+ if not margin:
206
+ return None
207
+
208
+ si_hi, si_lo, dnfr_hi, dnfr_lo, accel_hi, accel_lo = itemgetter(
209
+ "si_hi", "si_lo", "dnfr_hi", "dnfr_lo", "accel_hi", "accel_lo"
210
+ )(thr)
211
+
212
+ d_si = min(abs(Si - si_hi), abs(Si - si_lo))
213
+ d_dn = min(abs(dnfr - dnfr_hi), abs(dnfr - dnfr_lo))
214
+ d_ac = min(abs(accel - accel_hi), abs(accel - accel_lo))
215
+ certeza = min(d_si, d_dn, d_ac)
216
+ if certeza < margin:
217
+ hist = nd.get("glyph_history")
218
+ if not is_non_string_sequence(hist) or not hist:
219
+ return None
220
+ prev = hist[-1]
221
+ if isinstance(prev, str) and prev in HYSTERESIS_GLYPHS:
222
+ return prev
223
+ return None
224
+
225
+
226
+ def _selector_parallel_jobs(G: "TNFRGraph") -> int | None:
227
+ """Return worker count for selector helpers when parallelism is enabled.
228
+
229
+ Parameters
230
+ ----------
231
+ G : TNFRGraph
232
+ Graph containing selector configuration.
233
+
234
+ Returns
235
+ -------
236
+ int | None
237
+ Number of parallel jobs to use, or None if parallelism is disabled
238
+ or invalid configuration is provided.
239
+ """
240
+ raw_jobs = G.graph.get("GLYPH_SELECTOR_N_JOBS")
241
+ try:
242
+ n_jobs = None if raw_jobs is None else int(raw_jobs)
243
+ except (TypeError, ValueError):
244
+ return None
245
+ if n_jobs is None or n_jobs <= 1:
246
+ return None
247
+ return n_jobs
tnfr/selector.pyi ADDED
@@ -0,0 +1,19 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Mapping
4
+
5
+ __all__: Any
6
+
7
+ def __getattr__(name: str) -> Any: ...
8
+ def _apply_selector_hysteresis(
9
+ nd: dict[str, Any],
10
+ Si: float,
11
+ dnfr: float,
12
+ accel: float,
13
+ thr: Mapping[str, float],
14
+ margin: float | None,
15
+ ) -> str | None: ...
16
+
17
+ _calc_selector_score: Any
18
+ _selector_norms: Any
19
+ _selector_thresholds: Any