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,755 @@
1
+ """Input validation for TNFR structural operators.
2
+
3
+ This module provides security-focused input validation for structural operator
4
+ parameters to prevent injection attacks and ensure canonical TNFR invariants
5
+ are preserved. All validation functions respect TNFR structural semantics:
6
+
7
+ - EPI (Primary Information Structure) bounds
8
+ - νf (structural frequency) Hz_str units and ranges
9
+ - θ (phase) normalization to [-π, π]
10
+ - ΔNFR (reorganization operator) magnitude constraints
11
+ - Type safety for TNFRGraph, NodeId, and Glyph enumerations
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import math
17
+ import re
18
+ import warnings
19
+ from typing import Any, Mapping
20
+
21
+ import networkx as nx
22
+
23
+ from ..constants import DEFAULTS
24
+ from ..types import Glyph, TNFRGraph
25
+
26
+ # Check HzStr availability once at module level
27
+ _HZ_STR_AVAILABLE = False
28
+ try:
29
+ from ..operators.structural_units import HzStr
30
+
31
+ _HZ_STR_AVAILABLE = True
32
+ except ImportError:
33
+ HzStr = None # type: ignore[assignment, misc]
34
+
35
+ __all__ = [
36
+ "ValidationError",
37
+ "validate_epi_value",
38
+ "validate_vf_value",
39
+ "validate_theta_value",
40
+ "validate_dnfr_value",
41
+ "validate_node_id",
42
+ "validate_glyph",
43
+ "validate_tnfr_graph",
44
+ "validate_glyph_factors",
45
+ "validate_operator_parameters",
46
+ ]
47
+
48
+
49
+ class ValidationError(ValueError):
50
+ """Raised when input validation fails for structural operators."""
51
+
52
+ __slots__ = ("parameter", "value", "constraint")
53
+
54
+ def __init__(
55
+ self,
56
+ message: str,
57
+ *,
58
+ parameter: str | None = None,
59
+ value: Any = None,
60
+ constraint: str | None = None,
61
+ ) -> None:
62
+ super().__init__(message)
63
+ self.parameter = parameter
64
+ self.value = value
65
+ self.constraint = constraint
66
+
67
+
68
+ def _get_bound(config: Mapping[str, Any] | None, key: str, default: float) -> float:
69
+ """Retrieve a numeric bound from configuration or DEFAULTS.
70
+
71
+ Parameters
72
+ ----------
73
+ config : Mapping[str, Any] | None
74
+ Graph configuration mapping (typically from G.graph).
75
+ key : str
76
+ Configuration key (e.g., "EPI_MIN", "VF_MAX").
77
+ default : float
78
+ Fallback value when key is not in config or DEFAULTS.
79
+
80
+ Returns
81
+ -------
82
+ float
83
+ The configured bound or default value.
84
+
85
+ Notes
86
+ -----
87
+ DEFAULTS is a mapping defined in tnfr.constants containing canonical
88
+ TNFR parameter bounds and configuration values.
89
+ """
90
+ if config is not None and key in config:
91
+ try:
92
+ return float(config[key])
93
+ except (TypeError, ValueError):
94
+ pass
95
+ return float(DEFAULTS.get(key, default))
96
+
97
+
98
+ def validate_epi_value(
99
+ value: Any,
100
+ *,
101
+ config: Mapping[str, Any] | None = None,
102
+ allow_complex: bool = True,
103
+ ) -> float | complex:
104
+ """Validate EPI (Primary Information Structure) value.
105
+
106
+ EPI represents the coherent form of a node and must remain within
107
+ canonical bounds to preserve structural stability.
108
+
109
+ Parameters
110
+ ----------
111
+ value : Any
112
+ EPI value to validate. Must be numeric (float or complex if allowed).
113
+ config : Mapping[str, Any] | None
114
+ Graph configuration containing EPI_MIN and EPI_MAX bounds.
115
+ allow_complex : bool
116
+ Whether to allow complex EPI values (default: True).
117
+
118
+ Returns
119
+ -------
120
+ float or complex
121
+ Validated EPI value.
122
+
123
+ Raises
124
+ ------
125
+ ValidationError
126
+ If value is not numeric, contains special floats (nan, inf),
127
+ or violates bounds.
128
+
129
+ Examples
130
+ --------
131
+ >>> validate_epi_value(0.5)
132
+ 0.5
133
+ >>> validate_epi_value(1.5, config={"EPI_MAX": 1.0})
134
+ Traceback (most recent call last):
135
+ ...
136
+ ValidationError: EPI value 1.5 exceeds maximum bound 1.0
137
+ """
138
+ if not isinstance(value, (int, float, complex)):
139
+ raise ValidationError(
140
+ f"EPI must be numeric, got {type(value).__name__}",
141
+ parameter="EPI",
142
+ value=value,
143
+ constraint="numeric type",
144
+ )
145
+
146
+ if isinstance(value, complex):
147
+ if not allow_complex:
148
+ raise ValidationError(
149
+ "EPI must be real-valued in this context",
150
+ parameter="EPI",
151
+ value=value,
152
+ constraint="real-valued",
153
+ )
154
+ # Check both real and imaginary parts for special values
155
+ if not math.isfinite(value.real) or not math.isfinite(value.imag):
156
+ raise ValidationError(
157
+ f"EPI cannot contain nan or inf: {value}",
158
+ parameter="EPI",
159
+ value=value,
160
+ constraint="finite",
161
+ )
162
+ magnitude = abs(value)
163
+ else:
164
+ if not math.isfinite(float(value)):
165
+ raise ValidationError(
166
+ f"EPI cannot be nan or inf: {value}",
167
+ parameter="EPI",
168
+ value=value,
169
+ constraint="finite",
170
+ )
171
+ magnitude = abs(float(value))
172
+
173
+ epi_min = _get_bound(config, "EPI_MIN", 0.0)
174
+ epi_max = _get_bound(config, "EPI_MAX", 1.0)
175
+
176
+ if magnitude < epi_min:
177
+ raise ValidationError(
178
+ f"EPI magnitude {magnitude} below minimum bound {epi_min}",
179
+ parameter="EPI",
180
+ value=value,
181
+ constraint=f"magnitude >= {epi_min}",
182
+ )
183
+
184
+ if magnitude > epi_max:
185
+ raise ValidationError(
186
+ f"EPI magnitude {magnitude} exceeds maximum bound {epi_max}",
187
+ parameter="EPI",
188
+ value=value,
189
+ constraint=f"magnitude <= {epi_max}",
190
+ )
191
+
192
+ return value
193
+
194
+
195
+ def validate_vf_value(
196
+ value: Any,
197
+ *,
198
+ config: Mapping[str, Any] | None = None,
199
+ enforce_hz_str: bool = False,
200
+ ) -> float:
201
+ """Validate νf (structural frequency) value in Hz_str units.
202
+
203
+ Structural frequency represents the reorganization rate and must be
204
+ positive and within canonical bounds. Optionally enforces Hz_str type.
205
+
206
+ Parameters
207
+ ----------
208
+ value : Any
209
+ νf value to validate. Can be numeric or HzStr instance.
210
+ config : Mapping[str, Any] | None
211
+ Graph configuration containing VF_MIN and VF_MAX bounds.
212
+ enforce_hz_str : bool, default False
213
+ If True, warns when value is not a HzStr instance (encourages
214
+ canonical unit usage without breaking existing code).
215
+
216
+ Returns
217
+ -------
218
+ float
219
+ Validated νf value.
220
+
221
+ Raises
222
+ ------
223
+ ValidationError
224
+ If value is not numeric, not finite, negative, or violates bounds.
225
+
226
+ Examples
227
+ --------
228
+ >>> validate_vf_value(1.0)
229
+ 1.0
230
+ >>> validate_vf_value(-0.5)
231
+ Traceback (most recent call last):
232
+ ...
233
+ ValidationError: νf must be non-negative, got -0.5
234
+
235
+ Notes
236
+ -----
237
+ When enforce_hz_str is True and value is not a HzStr instance, logs a
238
+ warning to encourage canonical unit usage but still accepts the value.
239
+ """
240
+ # Accept HzStr instances (canonical form)
241
+ if _HZ_STR_AVAILABLE and HzStr is not None and isinstance(value, HzStr):
242
+ value = float(value) # Extract numeric value
243
+ elif enforce_hz_str and _HZ_STR_AVAILABLE:
244
+ # Log warning but don't raise (soft enforcement for migration)
245
+ warnings.warn(
246
+ f"νf should use Hz_str units for TNFR canonical compliance. "
247
+ f"Got {type(value).__name__} instead.",
248
+ UserWarning,
249
+ stacklevel=2,
250
+ )
251
+
252
+ if not isinstance(value, (int, float)):
253
+ raise ValidationError(
254
+ f"νf must be numeric, got {type(value).__name__}",
255
+ parameter="vf",
256
+ value=value,
257
+ constraint="numeric type",
258
+ )
259
+
260
+ value = float(value)
261
+
262
+ if not math.isfinite(value):
263
+ raise ValidationError(
264
+ f"νf cannot be nan or inf: {value}",
265
+ parameter="vf",
266
+ value=value,
267
+ constraint="finite",
268
+ )
269
+
270
+ if value < 0:
271
+ raise ValidationError(
272
+ f"νf must be non-negative, got {value}",
273
+ parameter="vf",
274
+ value=value,
275
+ constraint=">= 0",
276
+ )
277
+
278
+ vf_min = _get_bound(config, "VF_MIN", 0.0)
279
+ vf_max = _get_bound(config, "VF_MAX", 1.0) # Match DEFAULTS["VF_MAX"]
280
+
281
+ if value < vf_min:
282
+ raise ValidationError(
283
+ f"νf value {value} below minimum bound {vf_min}",
284
+ parameter="vf",
285
+ value=value,
286
+ constraint=f">= {vf_min}",
287
+ )
288
+
289
+ if value > vf_max:
290
+ raise ValidationError(
291
+ f"νf value {value} exceeds maximum bound {vf_max}",
292
+ parameter="vf",
293
+ value=value,
294
+ constraint=f"<= {vf_max}",
295
+ )
296
+
297
+ return value
298
+
299
+
300
+ def validate_theta_value(
301
+ value: Any,
302
+ *,
303
+ normalize: bool = True,
304
+ ) -> float:
305
+ """Validate θ (phase) value.
306
+
307
+ Phase represents synchrony with the network and is normalized to [-π, π].
308
+
309
+ Parameters
310
+ ----------
311
+ value : Any
312
+ θ value to validate. Must be a real number.
313
+ normalize : bool
314
+ Whether to normalize phase to [-π, π] (default: True).
315
+
316
+ Returns
317
+ -------
318
+ float
319
+ Validated (and possibly normalized) θ value.
320
+
321
+ Raises
322
+ ------
323
+ ValidationError
324
+ If value is not numeric or not finite.
325
+
326
+ Examples
327
+ --------
328
+ >>> validate_theta_value(math.pi / 2)
329
+ 1.5707963267948966
330
+ >>> validate_theta_value(4 * math.pi, normalize=True)
331
+ 0.0
332
+ """
333
+ if not isinstance(value, (int, float)):
334
+ raise ValidationError(
335
+ f"θ must be numeric, got {type(value).__name__}",
336
+ parameter="theta",
337
+ value=value,
338
+ constraint="numeric type",
339
+ )
340
+
341
+ value = float(value)
342
+
343
+ if not math.isfinite(value):
344
+ raise ValidationError(
345
+ f"θ cannot be nan or inf: {value}",
346
+ parameter="theta",
347
+ value=value,
348
+ constraint="finite",
349
+ )
350
+
351
+ if normalize:
352
+ # Normalize to [-π, π]
353
+ value = (value + math.pi) % (2 * math.pi) - math.pi
354
+
355
+ return value
356
+
357
+
358
+ def validate_dnfr_value(
359
+ value: Any,
360
+ *,
361
+ config: Mapping[str, Any] | None = None,
362
+ ) -> float:
363
+ """Validate ΔNFR (reorganization operator) magnitude.
364
+
365
+ ΔNFR represents the internal reorganization differential and should be
366
+ bounded to prevent excessive structural changes.
367
+
368
+ Parameters
369
+ ----------
370
+ value : Any
371
+ ΔNFR value to validate. Must be a real number.
372
+ config : Mapping[str, Any] | None
373
+ Graph configuration containing DNFR_MAX bound.
374
+
375
+ Returns
376
+ -------
377
+ float
378
+ Validated ΔNFR value.
379
+
380
+ Raises
381
+ ------
382
+ ValidationError
383
+ If value is not numeric, not finite, or exceeds bounds.
384
+
385
+ Examples
386
+ --------
387
+ >>> validate_dnfr_value(0.1)
388
+ 0.1
389
+ >>> validate_dnfr_value(2.0, config={"DNFR_MAX": 1.0})
390
+ Traceback (most recent call last):
391
+ ...
392
+ ValidationError: |ΔNFR| value 2.0 exceeds maximum bound 1.0
393
+ """
394
+ if not isinstance(value, (int, float)):
395
+ raise ValidationError(
396
+ f"ΔNFR must be numeric, got {type(value).__name__}",
397
+ parameter="dnfr",
398
+ value=value,
399
+ constraint="numeric type",
400
+ )
401
+
402
+ value = float(value)
403
+
404
+ if not math.isfinite(value):
405
+ raise ValidationError(
406
+ f"ΔNFR cannot be nan or inf: {value}",
407
+ parameter="dnfr",
408
+ value=value,
409
+ constraint="finite",
410
+ )
411
+
412
+ dnfr_max = _get_bound(config, "DNFR_MAX", 1.0)
413
+
414
+ if abs(value) > dnfr_max:
415
+ raise ValidationError(
416
+ f"|ΔNFR| value {abs(value)} exceeds maximum bound {dnfr_max}",
417
+ parameter="dnfr",
418
+ value=value,
419
+ constraint=f"|ΔNFR| <= {dnfr_max}",
420
+ )
421
+
422
+ return value
423
+
424
+
425
+ def validate_node_id(value: Any) -> Any:
426
+ """Validate NodeId value.
427
+
428
+ NodeId must be hashable and not contain special characters that could
429
+ enable injection attacks.
430
+
431
+ Parameters
432
+ ----------
433
+ value : Any
434
+ NodeId to validate.
435
+
436
+ Returns
437
+ -------
438
+ Any
439
+ Validated NodeId (unchanged if valid).
440
+
441
+ Raises
442
+ ------
443
+ ValidationError
444
+ If value is not hashable or contains suspicious patterns.
445
+
446
+ Examples
447
+ --------
448
+ >>> validate_node_id("node_1")
449
+ 'node_1'
450
+ >>> validate_node_id(42)
451
+ 42
452
+ >>> validate_node_id(['list'])
453
+ Traceback (most recent call last):
454
+ ...
455
+ ValidationError: NodeId must be hashable
456
+ """
457
+ try:
458
+ hash(value)
459
+ except TypeError:
460
+ raise ValidationError(
461
+ "NodeId must be hashable",
462
+ parameter="node",
463
+ value=value,
464
+ constraint="hashable",
465
+ )
466
+
467
+ # For string node IDs, check for suspicious patterns
468
+ if isinstance(value, str):
469
+ # Disallow control characters and common injection patterns
470
+ if any(ord(c) < 32 or ord(c) == 127 for c in value):
471
+ raise ValidationError(
472
+ "NodeId cannot contain control characters",
473
+ parameter="node",
474
+ value=value,
475
+ constraint="printable",
476
+ )
477
+
478
+ # Check for common injection patterns (basic protection)
479
+ suspicious_patterns = [
480
+ r"<script", # XSS attempts
481
+ r"javascript:", # JavaScript protocol
482
+ r"on\w+\s*=", # Event handlers
483
+ r"\$\{", # Template injection
484
+ r"`", # Backticks for command injection
485
+ ]
486
+
487
+ for pattern in suspicious_patterns:
488
+ if re.search(pattern, value, re.IGNORECASE):
489
+ raise ValidationError(
490
+ f"NodeId contains suspicious pattern: {pattern}",
491
+ parameter="node",
492
+ value=value,
493
+ constraint="safe string",
494
+ )
495
+
496
+ return value
497
+
498
+
499
+ def validate_glyph(value: Any) -> Glyph:
500
+ """Validate Glyph enumeration value or structural operator name.
501
+
502
+ Accepts both Glyph codes (e.g., "AL", "IL") and structural operator names
503
+ (e.g., "emission", "coherence") following TNFR canonical grammar.
504
+
505
+ Parameters
506
+ ----------
507
+ value : Any
508
+ Value to validate as a Glyph. Can be:
509
+ - A Glyph enum instance (e.g., Glyph.AL)
510
+ - A glyph code string (e.g., "AL", "IL")
511
+ - A structural operator name (e.g., "emission", "coherence")
512
+
513
+ Returns
514
+ -------
515
+ Glyph
516
+ Validated Glyph enumeration.
517
+
518
+ Raises
519
+ ------
520
+ ValidationError
521
+ If value is not a valid Glyph or structural operator name.
522
+
523
+ Examples
524
+ --------
525
+ >>> validate_glyph(Glyph.AL)
526
+ <Glyph.AL: 'AL'>
527
+ >>> validate_glyph("AL")
528
+ <Glyph.AL: 'AL'>
529
+ >>> validate_glyph("emission")
530
+ <Glyph.AL: 'AL'>
531
+ >>> validate_glyph("INVALID")
532
+ Traceback (most recent call last):
533
+ ...
534
+ ValidationError: Invalid glyph value: 'INVALID'
535
+ """
536
+ if isinstance(value, Glyph):
537
+ return value
538
+
539
+ if isinstance(value, str):
540
+ # Try direct Glyph code first
541
+ try:
542
+ return Glyph(value)
543
+ except ValueError:
544
+ pass
545
+
546
+ # Try structural operator name mapping
547
+ try:
548
+ from ..operators.grammar import function_name_to_glyph
549
+
550
+ glyph = function_name_to_glyph(value)
551
+ if glyph is not None:
552
+ return glyph
553
+ except Exception:
554
+ # If grammar module import fails, continue to error
555
+ pass
556
+
557
+ raise ValidationError(
558
+ f"Invalid glyph value: {value!r}",
559
+ parameter="glyph",
560
+ value=value,
561
+ constraint="valid Glyph enumeration or structural operator name",
562
+ )
563
+
564
+
565
+ def validate_tnfr_graph(value: Any) -> TNFRGraph:
566
+ """Validate TNFRGraph instance.
567
+
568
+ Parameters
569
+ ----------
570
+ value : Any
571
+ Value to validate as a TNFRGraph.
572
+
573
+ Returns
574
+ -------
575
+ TNFRGraph
576
+ Validated TNFRGraph instance.
577
+
578
+ Raises
579
+ ------
580
+ ValidationError
581
+ If value is not a valid TNFRGraph.
582
+
583
+ Examples
584
+ --------
585
+ >>> import networkx as nx
586
+ >>> G = nx.DiGraph()
587
+ >>> validate_tnfr_graph(G)
588
+ <networkx.classes.digraph.DiGraph object at ...>
589
+ >>> validate_tnfr_graph("not a graph")
590
+ Traceback (most recent call last):
591
+ ...
592
+ ValidationError: Expected TNFRGraph, got str
593
+ """
594
+ if not isinstance(value, (nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph)):
595
+ raise ValidationError(
596
+ f"Expected TNFRGraph, got {type(value).__name__}",
597
+ parameter="G",
598
+ value=value,
599
+ constraint="networkx.Graph instance",
600
+ )
601
+
602
+ # Ensure required graph attributes exist
603
+ if not hasattr(value, "graph"):
604
+ raise ValidationError(
605
+ "TNFRGraph must have 'graph' attribute",
606
+ parameter="G",
607
+ value=value,
608
+ constraint="graph attribute",
609
+ )
610
+
611
+ return value
612
+
613
+
614
+ def validate_glyph_factors(
615
+ factors: Any,
616
+ *,
617
+ required_keys: set[str] | None = None,
618
+ ) -> dict[str, float]:
619
+ """Validate glyph factors dictionary.
620
+
621
+ Glyph factors contain operator-specific coefficients that modulate
622
+ structural transformations.
623
+
624
+ Parameters
625
+ ----------
626
+ factors : Any
627
+ Glyph factors to validate.
628
+ required_keys : set[str] | None
629
+ Required factor keys. If None, no specific keys are required.
630
+
631
+ Returns
632
+ -------
633
+ dict[str, float]
634
+ Validated glyph factors.
635
+
636
+ Raises
637
+ ------
638
+ ValidationError
639
+ If factors is not a mapping or contains invalid values.
640
+
641
+ Examples
642
+ --------
643
+ >>> validate_glyph_factors({"AL_boost": 0.1, "EN_mix": 0.25})
644
+ {'AL_boost': 0.1, 'EN_mix': 0.25}
645
+ >>> validate_glyph_factors("not a dict")
646
+ Traceback (most recent call last):
647
+ ...
648
+ ValidationError: Glyph factors must be a mapping
649
+ """
650
+ if not isinstance(factors, Mapping):
651
+ raise ValidationError(
652
+ "Glyph factors must be a mapping",
653
+ parameter="glyph_factors",
654
+ value=factors,
655
+ constraint="mapping type",
656
+ )
657
+
658
+ validated: dict[str, float] = {}
659
+
660
+ for key, value in factors.items():
661
+ if not isinstance(key, str):
662
+ raise ValidationError(
663
+ f"Glyph factor key must be string, got {type(key).__name__}",
664
+ parameter=f"glyph_factors[{key!r}]",
665
+ value=value,
666
+ constraint="string key",
667
+ )
668
+
669
+ if not isinstance(value, (int, float)):
670
+ raise ValidationError(
671
+ f"Glyph factor value must be numeric, got {type(value).__name__}",
672
+ parameter=f"glyph_factors[{key!r}]",
673
+ value=value,
674
+ constraint="numeric value",
675
+ )
676
+
677
+ if not math.isfinite(float(value)):
678
+ raise ValidationError(
679
+ f"Glyph factor value cannot be nan or inf: {value}",
680
+ parameter=f"glyph_factors[{key!r}]",
681
+ value=value,
682
+ constraint="finite",
683
+ )
684
+
685
+ validated[key] = float(value)
686
+
687
+ if required_keys is not None:
688
+ missing = required_keys - set(validated.keys())
689
+ if missing:
690
+ raise ValidationError(
691
+ f"Missing required glyph factor keys: {sorted(missing)}",
692
+ parameter="glyph_factors",
693
+ value=factors,
694
+ constraint=f"required keys: {sorted(required_keys)}",
695
+ )
696
+
697
+ return validated
698
+
699
+
700
+ def validate_operator_parameters(
701
+ parameters: Mapping[str, Any],
702
+ *,
703
+ config: Mapping[str, Any] | None = None,
704
+ ) -> dict[str, Any]:
705
+ """Validate structural operator parameters.
706
+
707
+ This function validates common operator parameters including EPI, νf, θ,
708
+ and ΔNFR values to ensure they respect canonical bounds.
709
+
710
+ Parameters
711
+ ----------
712
+ parameters : Mapping[str, Any]
713
+ Operator parameters to validate.
714
+ config : Mapping[str, Any] | None
715
+ Graph configuration for bound checking.
716
+
717
+ Returns
718
+ -------
719
+ dict[str, Any]
720
+ Validated parameters.
721
+
722
+ Raises
723
+ ------
724
+ ValidationError
725
+ If any parameter fails validation.
726
+
727
+ Examples
728
+ --------
729
+ >>> validate_operator_parameters({"epi": 0.5, "vf": 1.0, "theta": 0.0})
730
+ {'epi': 0.5, 'vf': 1.0, 'theta': 0.0}
731
+ """
732
+ validated: dict[str, Any] = {}
733
+
734
+ for key, value in parameters.items():
735
+ if key in ("epi", "EPI"):
736
+ validated[key] = validate_epi_value(value, config=config)
737
+ elif key in ("vf", "VF", "nu_f"):
738
+ validated[key] = validate_vf_value(value, config=config)
739
+ elif key in ("theta", "THETA", "phase"):
740
+ validated[key] = validate_theta_value(value)
741
+ elif key in ("dnfr", "DNFR", "delta_nfr"):
742
+ validated[key] = validate_dnfr_value(value, config=config)
743
+ elif key == "node":
744
+ validated[key] = validate_node_id(value)
745
+ elif key == "glyph":
746
+ validated[key] = validate_glyph(value)
747
+ elif key == "G" or key == "graph":
748
+ validated[key] = validate_tnfr_graph(value)
749
+ elif key == "glyph_factors":
750
+ validated[key] = validate_glyph_factors(value)
751
+ else:
752
+ # Pass through other parameters unchanged
753
+ validated[key] = value
754
+
755
+ return validated