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

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

Potentially problematic release.


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

Files changed (365) hide show
  1. tnfr/__init__.py +334 -50
  2. tnfr/__init__.pyi +33 -0
  3. tnfr/_compat.py +10 -0
  4. tnfr/_generated_version.py +34 -0
  5. tnfr/_version.py +49 -0
  6. tnfr/_version.pyi +7 -0
  7. tnfr/alias.py +214 -37
  8. tnfr/alias.pyi +108 -0
  9. tnfr/backends/__init__.py +354 -0
  10. tnfr/backends/jax_backend.py +173 -0
  11. tnfr/backends/numpy_backend.py +238 -0
  12. tnfr/backends/optimized_numpy.py +420 -0
  13. tnfr/backends/torch_backend.py +408 -0
  14. tnfr/cache.py +149 -556
  15. tnfr/cache.pyi +13 -0
  16. tnfr/cli/__init__.py +51 -16
  17. tnfr/cli/__init__.pyi +26 -0
  18. tnfr/cli/arguments.py +344 -32
  19. tnfr/cli/arguments.pyi +29 -0
  20. tnfr/cli/execution.py +676 -50
  21. tnfr/cli/execution.pyi +70 -0
  22. tnfr/cli/interactive_validator.py +614 -0
  23. tnfr/cli/utils.py +18 -3
  24. tnfr/cli/utils.pyi +7 -0
  25. tnfr/cli/validate.py +236 -0
  26. tnfr/compat/__init__.py +85 -0
  27. tnfr/compat/dataclass.py +136 -0
  28. tnfr/compat/jsonschema_stub.py +61 -0
  29. tnfr/compat/matplotlib_stub.py +73 -0
  30. tnfr/compat/numpy_stub.py +155 -0
  31. tnfr/config/__init__.py +224 -0
  32. tnfr/config/__init__.pyi +10 -0
  33. tnfr/{constants_glyphs.py → config/constants.py} +26 -20
  34. tnfr/config/constants.pyi +12 -0
  35. tnfr/config/defaults.py +54 -0
  36. tnfr/{constants/core.py → config/defaults_core.py} +59 -6
  37. tnfr/config/defaults_init.py +33 -0
  38. tnfr/config/defaults_metric.py +104 -0
  39. tnfr/config/feature_flags.py +81 -0
  40. tnfr/config/feature_flags.pyi +16 -0
  41. tnfr/config/glyph_constants.py +31 -0
  42. tnfr/config/init.py +77 -0
  43. tnfr/config/init.pyi +8 -0
  44. tnfr/config/operator_names.py +254 -0
  45. tnfr/config/operator_names.pyi +36 -0
  46. tnfr/config/physics_derivation.py +354 -0
  47. tnfr/config/presets.py +83 -0
  48. tnfr/config/presets.pyi +7 -0
  49. tnfr/config/security.py +927 -0
  50. tnfr/config/thresholds.py +114 -0
  51. tnfr/config/tnfr_config.py +498 -0
  52. tnfr/constants/__init__.py +51 -133
  53. tnfr/constants/__init__.pyi +92 -0
  54. tnfr/constants/aliases.py +33 -0
  55. tnfr/constants/aliases.pyi +27 -0
  56. tnfr/constants/init.py +3 -1
  57. tnfr/constants/init.pyi +12 -0
  58. tnfr/constants/metric.py +9 -15
  59. tnfr/constants/metric.pyi +19 -0
  60. tnfr/core/__init__.py +33 -0
  61. tnfr/core/container.py +226 -0
  62. tnfr/core/default_implementations.py +329 -0
  63. tnfr/core/interfaces.py +279 -0
  64. tnfr/dynamics/__init__.py +213 -633
  65. tnfr/dynamics/__init__.pyi +83 -0
  66. tnfr/dynamics/adaptation.py +267 -0
  67. tnfr/dynamics/adaptation.pyi +7 -0
  68. tnfr/dynamics/adaptive_sequences.py +189 -0
  69. tnfr/dynamics/adaptive_sequences.pyi +14 -0
  70. tnfr/dynamics/aliases.py +23 -0
  71. tnfr/dynamics/aliases.pyi +19 -0
  72. tnfr/dynamics/bifurcation.py +232 -0
  73. tnfr/dynamics/canonical.py +229 -0
  74. tnfr/dynamics/canonical.pyi +48 -0
  75. tnfr/dynamics/coordination.py +385 -0
  76. tnfr/dynamics/coordination.pyi +25 -0
  77. tnfr/dynamics/dnfr.py +2699 -398
  78. tnfr/dynamics/dnfr.pyi +26 -0
  79. tnfr/dynamics/dynamic_limits.py +225 -0
  80. tnfr/dynamics/feedback.py +252 -0
  81. tnfr/dynamics/feedback.pyi +24 -0
  82. tnfr/dynamics/fused_dnfr.py +454 -0
  83. tnfr/dynamics/homeostasis.py +157 -0
  84. tnfr/dynamics/homeostasis.pyi +14 -0
  85. tnfr/dynamics/integrators.py +496 -102
  86. tnfr/dynamics/integrators.pyi +36 -0
  87. tnfr/dynamics/learning.py +310 -0
  88. tnfr/dynamics/learning.pyi +33 -0
  89. tnfr/dynamics/metabolism.py +254 -0
  90. tnfr/dynamics/nbody.py +796 -0
  91. tnfr/dynamics/nbody_tnfr.py +783 -0
  92. tnfr/dynamics/propagation.py +326 -0
  93. tnfr/dynamics/runtime.py +908 -0
  94. tnfr/dynamics/runtime.pyi +77 -0
  95. tnfr/dynamics/sampling.py +10 -5
  96. tnfr/dynamics/sampling.pyi +7 -0
  97. tnfr/dynamics/selectors.py +711 -0
  98. tnfr/dynamics/selectors.pyi +85 -0
  99. tnfr/dynamics/structural_clip.py +207 -0
  100. tnfr/errors/__init__.py +37 -0
  101. tnfr/errors/contextual.py +492 -0
  102. tnfr/execution.py +77 -55
  103. tnfr/execution.pyi +45 -0
  104. tnfr/extensions/__init__.py +205 -0
  105. tnfr/extensions/__init__.pyi +18 -0
  106. tnfr/extensions/base.py +173 -0
  107. tnfr/extensions/base.pyi +35 -0
  108. tnfr/extensions/business/__init__.py +71 -0
  109. tnfr/extensions/business/__init__.pyi +11 -0
  110. tnfr/extensions/business/cookbook.py +88 -0
  111. tnfr/extensions/business/cookbook.pyi +8 -0
  112. tnfr/extensions/business/health_analyzers.py +202 -0
  113. tnfr/extensions/business/health_analyzers.pyi +9 -0
  114. tnfr/extensions/business/patterns.py +183 -0
  115. tnfr/extensions/business/patterns.pyi +8 -0
  116. tnfr/extensions/medical/__init__.py +73 -0
  117. tnfr/extensions/medical/__init__.pyi +11 -0
  118. tnfr/extensions/medical/cookbook.py +88 -0
  119. tnfr/extensions/medical/cookbook.pyi +8 -0
  120. tnfr/extensions/medical/health_analyzers.py +181 -0
  121. tnfr/extensions/medical/health_analyzers.pyi +9 -0
  122. tnfr/extensions/medical/patterns.py +163 -0
  123. tnfr/extensions/medical/patterns.pyi +8 -0
  124. tnfr/flatten.py +29 -50
  125. tnfr/flatten.pyi +21 -0
  126. tnfr/gamma.py +66 -53
  127. tnfr/gamma.pyi +36 -0
  128. tnfr/glyph_history.py +144 -57
  129. tnfr/glyph_history.pyi +35 -0
  130. tnfr/glyph_runtime.py +19 -0
  131. tnfr/glyph_runtime.pyi +8 -0
  132. tnfr/immutable.py +70 -30
  133. tnfr/immutable.pyi +36 -0
  134. tnfr/initialization.py +22 -16
  135. tnfr/initialization.pyi +65 -0
  136. tnfr/io.py +5 -241
  137. tnfr/io.pyi +13 -0
  138. tnfr/locking.pyi +7 -0
  139. tnfr/mathematics/__init__.py +79 -0
  140. tnfr/mathematics/backend.py +453 -0
  141. tnfr/mathematics/backend.pyi +99 -0
  142. tnfr/mathematics/dynamics.py +408 -0
  143. tnfr/mathematics/dynamics.pyi +90 -0
  144. tnfr/mathematics/epi.py +391 -0
  145. tnfr/mathematics/epi.pyi +65 -0
  146. tnfr/mathematics/generators.py +242 -0
  147. tnfr/mathematics/generators.pyi +29 -0
  148. tnfr/mathematics/metrics.py +119 -0
  149. tnfr/mathematics/metrics.pyi +16 -0
  150. tnfr/mathematics/operators.py +239 -0
  151. tnfr/mathematics/operators.pyi +59 -0
  152. tnfr/mathematics/operators_factory.py +124 -0
  153. tnfr/mathematics/operators_factory.pyi +11 -0
  154. tnfr/mathematics/projection.py +87 -0
  155. tnfr/mathematics/projection.pyi +33 -0
  156. tnfr/mathematics/runtime.py +182 -0
  157. tnfr/mathematics/runtime.pyi +64 -0
  158. tnfr/mathematics/spaces.py +256 -0
  159. tnfr/mathematics/spaces.pyi +83 -0
  160. tnfr/mathematics/transforms.py +305 -0
  161. tnfr/mathematics/transforms.pyi +62 -0
  162. tnfr/metrics/__init__.py +47 -9
  163. tnfr/metrics/__init__.pyi +20 -0
  164. tnfr/metrics/buffer_cache.py +163 -0
  165. tnfr/metrics/buffer_cache.pyi +24 -0
  166. tnfr/metrics/cache_utils.py +214 -0
  167. tnfr/metrics/coherence.py +1510 -330
  168. tnfr/metrics/coherence.pyi +129 -0
  169. tnfr/metrics/common.py +23 -16
  170. tnfr/metrics/common.pyi +35 -0
  171. tnfr/metrics/core.py +251 -36
  172. tnfr/metrics/core.pyi +13 -0
  173. tnfr/metrics/diagnosis.py +709 -110
  174. tnfr/metrics/diagnosis.pyi +86 -0
  175. tnfr/metrics/emergence.py +245 -0
  176. tnfr/metrics/export.py +60 -18
  177. tnfr/metrics/export.pyi +7 -0
  178. tnfr/metrics/glyph_timing.py +233 -43
  179. tnfr/metrics/glyph_timing.pyi +81 -0
  180. tnfr/metrics/learning_metrics.py +280 -0
  181. tnfr/metrics/learning_metrics.pyi +21 -0
  182. tnfr/metrics/phase_coherence.py +351 -0
  183. tnfr/metrics/phase_compatibility.py +349 -0
  184. tnfr/metrics/reporting.py +63 -28
  185. tnfr/metrics/reporting.pyi +25 -0
  186. tnfr/metrics/sense_index.py +1126 -43
  187. tnfr/metrics/sense_index.pyi +9 -0
  188. tnfr/metrics/trig.py +215 -23
  189. tnfr/metrics/trig.pyi +13 -0
  190. tnfr/metrics/trig_cache.py +148 -24
  191. tnfr/metrics/trig_cache.pyi +10 -0
  192. tnfr/multiscale/__init__.py +32 -0
  193. tnfr/multiscale/hierarchical.py +517 -0
  194. tnfr/node.py +646 -140
  195. tnfr/node.pyi +139 -0
  196. tnfr/observers.py +160 -45
  197. tnfr/observers.pyi +31 -0
  198. tnfr/ontosim.py +23 -19
  199. tnfr/ontosim.pyi +28 -0
  200. tnfr/operators/__init__.py +1358 -106
  201. tnfr/operators/__init__.pyi +31 -0
  202. tnfr/operators/algebra.py +277 -0
  203. tnfr/operators/canonical_patterns.py +420 -0
  204. tnfr/operators/cascade.py +267 -0
  205. tnfr/operators/cycle_detection.py +358 -0
  206. tnfr/operators/definitions.py +4108 -0
  207. tnfr/operators/definitions.pyi +78 -0
  208. tnfr/operators/grammar.py +1164 -0
  209. tnfr/operators/grammar.pyi +140 -0
  210. tnfr/operators/hamiltonian.py +710 -0
  211. tnfr/operators/health_analyzer.py +809 -0
  212. tnfr/operators/jitter.py +107 -38
  213. tnfr/operators/jitter.pyi +11 -0
  214. tnfr/operators/lifecycle.py +314 -0
  215. tnfr/operators/metabolism.py +618 -0
  216. tnfr/operators/metrics.py +2138 -0
  217. tnfr/operators/network_analysis/__init__.py +27 -0
  218. tnfr/operators/network_analysis/source_detection.py +186 -0
  219. tnfr/operators/nodal_equation.py +395 -0
  220. tnfr/operators/pattern_detection.py +660 -0
  221. tnfr/operators/patterns.py +669 -0
  222. tnfr/operators/postconditions/__init__.py +38 -0
  223. tnfr/operators/postconditions/mutation.py +236 -0
  224. tnfr/operators/preconditions/__init__.py +1226 -0
  225. tnfr/operators/preconditions/coherence.py +305 -0
  226. tnfr/operators/preconditions/dissonance.py +236 -0
  227. tnfr/operators/preconditions/emission.py +128 -0
  228. tnfr/operators/preconditions/mutation.py +580 -0
  229. tnfr/operators/preconditions/reception.py +125 -0
  230. tnfr/operators/preconditions/resonance.py +364 -0
  231. tnfr/operators/registry.py +74 -0
  232. tnfr/operators/registry.pyi +9 -0
  233. tnfr/operators/remesh.py +1415 -91
  234. tnfr/operators/remesh.pyi +26 -0
  235. tnfr/operators/structural_units.py +268 -0
  236. tnfr/operators/unified_grammar.py +105 -0
  237. tnfr/parallel/__init__.py +54 -0
  238. tnfr/parallel/auto_scaler.py +234 -0
  239. tnfr/parallel/distributed.py +384 -0
  240. tnfr/parallel/engine.py +238 -0
  241. tnfr/parallel/gpu_engine.py +420 -0
  242. tnfr/parallel/monitoring.py +248 -0
  243. tnfr/parallel/partitioner.py +459 -0
  244. tnfr/py.typed +0 -0
  245. tnfr/recipes/__init__.py +22 -0
  246. tnfr/recipes/cookbook.py +743 -0
  247. tnfr/rng.py +75 -151
  248. tnfr/rng.pyi +26 -0
  249. tnfr/schemas/__init__.py +8 -0
  250. tnfr/schemas/grammar.json +94 -0
  251. tnfr/sdk/__init__.py +107 -0
  252. tnfr/sdk/__init__.pyi +19 -0
  253. tnfr/sdk/adaptive_system.py +173 -0
  254. tnfr/sdk/adaptive_system.pyi +21 -0
  255. tnfr/sdk/builders.py +370 -0
  256. tnfr/sdk/builders.pyi +51 -0
  257. tnfr/sdk/fluent.py +1121 -0
  258. tnfr/sdk/fluent.pyi +74 -0
  259. tnfr/sdk/templates.py +342 -0
  260. tnfr/sdk/templates.pyi +41 -0
  261. tnfr/sdk/utils.py +341 -0
  262. tnfr/secure_config.py +46 -0
  263. tnfr/security/__init__.py +70 -0
  264. tnfr/security/database.py +514 -0
  265. tnfr/security/subprocess.py +503 -0
  266. tnfr/security/validation.py +290 -0
  267. tnfr/selector.py +59 -22
  268. tnfr/selector.pyi +19 -0
  269. tnfr/sense.py +92 -67
  270. tnfr/sense.pyi +23 -0
  271. tnfr/services/__init__.py +17 -0
  272. tnfr/services/orchestrator.py +325 -0
  273. tnfr/sparse/__init__.py +39 -0
  274. tnfr/sparse/representations.py +492 -0
  275. tnfr/structural.py +639 -263
  276. tnfr/structural.pyi +83 -0
  277. tnfr/telemetry/__init__.py +35 -0
  278. tnfr/telemetry/cache_metrics.py +226 -0
  279. tnfr/telemetry/cache_metrics.pyi +64 -0
  280. tnfr/telemetry/nu_f.py +422 -0
  281. tnfr/telemetry/nu_f.pyi +108 -0
  282. tnfr/telemetry/verbosity.py +36 -0
  283. tnfr/telemetry/verbosity.pyi +15 -0
  284. tnfr/tokens.py +2 -4
  285. tnfr/tokens.pyi +36 -0
  286. tnfr/tools/__init__.py +20 -0
  287. tnfr/tools/domain_templates.py +478 -0
  288. tnfr/tools/sequence_generator.py +846 -0
  289. tnfr/topology/__init__.py +13 -0
  290. tnfr/topology/asymmetry.py +151 -0
  291. tnfr/trace.py +300 -126
  292. tnfr/trace.pyi +42 -0
  293. tnfr/tutorials/__init__.py +38 -0
  294. tnfr/tutorials/autonomous_evolution.py +285 -0
  295. tnfr/tutorials/interactive.py +1576 -0
  296. tnfr/tutorials/structural_metabolism.py +238 -0
  297. tnfr/types.py +743 -12
  298. tnfr/types.pyi +357 -0
  299. tnfr/units.py +68 -0
  300. tnfr/units.pyi +13 -0
  301. tnfr/utils/__init__.py +282 -0
  302. tnfr/utils/__init__.pyi +215 -0
  303. tnfr/utils/cache.py +4223 -0
  304. tnfr/utils/cache.pyi +470 -0
  305. tnfr/{callback_utils.py → utils/callbacks.py} +26 -39
  306. tnfr/utils/callbacks.pyi +49 -0
  307. tnfr/utils/chunks.py +108 -0
  308. tnfr/utils/chunks.pyi +22 -0
  309. tnfr/utils/data.py +428 -0
  310. tnfr/utils/data.pyi +74 -0
  311. tnfr/utils/graph.py +85 -0
  312. tnfr/utils/graph.pyi +10 -0
  313. tnfr/utils/init.py +821 -0
  314. tnfr/utils/init.pyi +80 -0
  315. tnfr/utils/io.py +559 -0
  316. tnfr/utils/io.pyi +66 -0
  317. tnfr/{helpers → utils}/numeric.py +51 -24
  318. tnfr/utils/numeric.pyi +21 -0
  319. tnfr/validation/__init__.py +257 -0
  320. tnfr/validation/__init__.pyi +85 -0
  321. tnfr/validation/compatibility.py +460 -0
  322. tnfr/validation/compatibility.pyi +6 -0
  323. tnfr/validation/config.py +73 -0
  324. tnfr/validation/graph.py +139 -0
  325. tnfr/validation/graph.pyi +18 -0
  326. tnfr/validation/input_validation.py +755 -0
  327. tnfr/validation/invariants.py +712 -0
  328. tnfr/validation/rules.py +253 -0
  329. tnfr/validation/rules.pyi +44 -0
  330. tnfr/validation/runtime.py +279 -0
  331. tnfr/validation/runtime.pyi +28 -0
  332. tnfr/validation/sequence_validator.py +162 -0
  333. tnfr/validation/soft_filters.py +170 -0
  334. tnfr/validation/soft_filters.pyi +32 -0
  335. tnfr/validation/spectral.py +164 -0
  336. tnfr/validation/spectral.pyi +42 -0
  337. tnfr/validation/validator.py +1266 -0
  338. tnfr/validation/window.py +39 -0
  339. tnfr/validation/window.pyi +1 -0
  340. tnfr/visualization/__init__.py +98 -0
  341. tnfr/visualization/cascade_viz.py +256 -0
  342. tnfr/visualization/hierarchy.py +284 -0
  343. tnfr/visualization/sequence_plotter.py +784 -0
  344. tnfr/viz/__init__.py +60 -0
  345. tnfr/viz/matplotlib.py +278 -0
  346. tnfr/viz/matplotlib.pyi +35 -0
  347. tnfr-8.5.0.dist-info/METADATA +573 -0
  348. tnfr-8.5.0.dist-info/RECORD +353 -0
  349. {tnfr-4.5.2.dist-info → tnfr-8.5.0.dist-info}/entry_points.txt +1 -0
  350. {tnfr-4.5.2.dist-info → tnfr-8.5.0.dist-info}/licenses/LICENSE.md +1 -1
  351. tnfr/collections_utils.py +0 -300
  352. tnfr/config.py +0 -32
  353. tnfr/grammar.py +0 -344
  354. tnfr/graph_utils.py +0 -84
  355. tnfr/helpers/__init__.py +0 -71
  356. tnfr/import_utils.py +0 -228
  357. tnfr/json_utils.py +0 -162
  358. tnfr/logging_utils.py +0 -116
  359. tnfr/presets.py +0 -60
  360. tnfr/validators.py +0 -84
  361. tnfr/value_utils.py +0 -59
  362. tnfr-4.5.2.dist-info/METADATA +0 -379
  363. tnfr-4.5.2.dist-info/RECORD +0 -67
  364. {tnfr-4.5.2.dist-info → tnfr-8.5.0.dist-info}/WHEEL +0 -0
  365. {tnfr-4.5.2.dist-info → tnfr-8.5.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,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 CHANGED
@@ -1,39 +1,47 @@
1
- """Utilities to select glyphs based on structural metrics.
1
+ """Utilities to select structural operator symbols based on structural metrics.
2
2
 
3
3
  This module normalises thresholds, computes selection scores and applies
4
- hysteresis when assigning glyphs to nodes.
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.
5
9
  """
6
10
 
7
11
  from __future__ import annotations
8
12
 
9
13
  import threading
10
14
  from operator import itemgetter
11
- from typing import Any, Mapping, TYPE_CHECKING
15
+ from typing import TYPE_CHECKING, Any, Mapping, cast
12
16
  from weakref import WeakKeyDictionary
13
17
 
14
18
  if TYPE_CHECKING: # pragma: no cover
15
- import networkx as nx # type: ignore[import-untyped]
19
+ import networkx as nx
16
20
 
17
21
  from .constants import DEFAULTS
18
- from .constants.core import SELECTOR_THRESHOLD_DEFAULTS
19
- from .helpers.numeric import clamp01
22
+ from .config.defaults_core import SELECTOR_THRESHOLD_DEFAULTS
23
+ from .utils import clamp01
20
24
  from .metrics.common import compute_dnfr_accel_max
21
- from .collections_utils import is_non_string_sequence
25
+ from .types import SelectorNorms, SelectorThresholds, SelectorWeights
26
+ from .utils import is_non_string_sequence
22
27
 
28
+ if TYPE_CHECKING: # pragma: no cover
29
+ from .types import TNFRGraph
23
30
 
24
31
  HYSTERESIS_GLYPHS: set[str] = {"IL", "OZ", "ZHIR", "THOL", "NAV", "RA"}
25
32
 
26
33
  __all__ = (
27
34
  "_selector_thresholds",
28
- "_norms_para_selector",
35
+ "_selector_norms",
29
36
  "_calc_selector_score",
30
37
  "_apply_selector_hysteresis",
38
+ "_selector_parallel_jobs",
31
39
  )
32
40
 
33
-
41
+ _SelectorThresholdItems = tuple[tuple[str, float], ...]
34
42
  _SelectorThresholdCacheEntry = tuple[
35
- tuple[tuple[str, float], ...],
36
- dict[str, float],
43
+ _SelectorThresholdItems,
44
+ SelectorThresholds,
37
45
  ]
38
46
  _SELECTOR_THRESHOLD_CACHE: WeakKeyDictionary[
39
47
  "nx.Graph",
@@ -42,7 +50,7 @@ _SELECTOR_THRESHOLD_CACHE: WeakKeyDictionary[
42
50
  _SELECTOR_THRESHOLD_CACHE_LOCK = threading.Lock()
43
51
 
44
52
 
45
- def _sorted_items(mapping: Mapping[str, float]) -> tuple[tuple[str, float], ...]:
53
+ def _sorted_items(mapping: Mapping[str, float]) -> _SelectorThresholdItems:
46
54
  """Return mapping items sorted by key.
47
55
 
48
56
  Parameters
@@ -59,8 +67,8 @@ def _sorted_items(mapping: Mapping[str, float]) -> tuple[tuple[str, float], ...]
59
67
 
60
68
 
61
69
  def _compute_selector_thresholds(
62
- thr_sel_items: tuple[tuple[str, float], ...],
63
- ) -> dict[str, float]:
70
+ thr_sel_items: _SelectorThresholdItems,
71
+ ) -> SelectorThresholds:
64
72
  """Construct selector thresholds for a graph.
65
73
 
66
74
  Parameters
@@ -79,10 +87,10 @@ def _compute_selector_thresholds(
79
87
  for key, default in SELECTOR_THRESHOLD_DEFAULTS.items():
80
88
  val = thr_sel.get(key, default)
81
89
  out[key] = clamp01(float(val))
82
- return out
90
+ return cast(SelectorThresholds, out)
83
91
 
84
92
 
85
- def _selector_thresholds(G: "nx.Graph") -> dict[str, float]:
93
+ def _selector_thresholds(G: "nx.Graph") -> SelectorThresholds:
86
94
  """Return normalised thresholds for Si, ΔNFR and acceleration.
87
95
 
88
96
  Parameters
@@ -114,8 +122,8 @@ def _selector_thresholds(G: "nx.Graph") -> dict[str, float]:
114
122
  return thresholds
115
123
 
116
124
 
117
- def _norms_para_selector(G: "nx.Graph") -> dict:
118
- """Compute and cache norms for ΔNFR and acceleration.
125
+ def _selector_norms(G: "nx.Graph") -> SelectorNorms:
126
+ """Compute and cache selector norms for ΔNFR and acceleration.
119
127
 
120
128
  Parameters
121
129
  ----------
@@ -134,7 +142,7 @@ def _norms_para_selector(G: "nx.Graph") -> dict:
134
142
 
135
143
 
136
144
  def _calc_selector_score(
137
- Si: float, dnfr: float, accel: float, weights: dict[str, float]
145
+ Si: float, dnfr: float, accel: float, weights: SelectorWeights
138
146
  ) -> float:
139
147
  """Compute weighted selector score.
140
148
 
@@ -167,7 +175,7 @@ def _apply_selector_hysteresis(
167
175
  dnfr: float,
168
176
  accel: float,
169
177
  thr: dict[str, float],
170
- margin: float,
178
+ margin: float | None,
171
179
  ) -> str | None:
172
180
  """Apply hysteresis when values are near thresholds.
173
181
 
@@ -183,8 +191,10 @@ def _apply_selector_hysteresis(
183
191
  Normalised acceleration.
184
192
  thr : dict[str, float]
185
193
  Thresholds returned by :func:`_selector_thresholds`.
186
- margin : float
187
- Distance from thresholds below which the previous glyph is reused.
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.
188
198
 
189
199
  Returns
190
200
  -------
@@ -192,6 +202,9 @@ def _apply_selector_hysteresis(
192
202
  Previous glyph if hysteresis applies, otherwise ``None``.
193
203
  """
194
204
  # Batch extraction reduces dictionary lookups inside loops.
205
+ if not margin:
206
+ return None
207
+
195
208
  si_hi, si_lo, dnfr_hi, dnfr_lo, accel_hi, accel_lo = itemgetter(
196
209
  "si_hi", "si_lo", "dnfr_hi", "dnfr_lo", "accel_hi", "accel_lo"
197
210
  )(thr)
@@ -208,3 +221,27 @@ def _apply_selector_hysteresis(
208
221
  if isinstance(prev, str) and prev in HYSTERESIS_GLYPHS:
209
222
  return prev
210
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