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,1266 @@
1
+ """Unified TNFR Validation Pipeline.
2
+
3
+ This module provides the TNFRValidator class which serves as the canonical
4
+ entry point for all TNFR validation operations. It integrates:
5
+ - Invariant validation (10 canonical TNFR invariants)
6
+ - Input validation (parameters, types, bounds)
7
+ - Graph validation (structure, coherence)
8
+ - Runtime validation (canonical clamps, contracts)
9
+ - Security validation (injection prevention, type safety)
10
+ - Operator precondition validation
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import warnings
16
+ from typing import Any, Mapping, Optional
17
+
18
+ from .invariants import (
19
+ InvariantSeverity,
20
+ InvariantViolation,
21
+ Invariant1_EPIOnlyThroughOperators,
22
+ Invariant2_VfInHzStr,
23
+ Invariant3_DNFRSemantics,
24
+ Invariant4_OperatorClosure,
25
+ Invariant5_ExplicitPhaseChecks,
26
+ Invariant6_NodeBirthCollapse,
27
+ Invariant7_OperationalFractality,
28
+ Invariant8_ControlledDeterminism,
29
+ Invariant9_StructuralMetrics,
30
+ Invariant10_DomainNeutrality,
31
+ TNFRInvariant,
32
+ )
33
+ from ..types import Glyph, NodeId, TNFRGraph
34
+
35
+ __all__ = [
36
+ "TNFRValidator",
37
+ "TNFRValidationError",
38
+ ]
39
+
40
+
41
+ class TNFRValidator:
42
+ """Unified TNFR Validation Pipeline.
43
+
44
+ This class serves as the single entry point for all TNFR validation operations,
45
+ consolidating scattered validation logic into a coherent pipeline that enforces
46
+ all canonical TNFR invariants.
47
+
48
+ Features
49
+ --------
50
+ - Validates 10 canonical TNFR invariants
51
+ - Input validation with security checks
52
+ - Graph structure and coherence validation
53
+ - Runtime canonical validation
54
+ - Operator precondition checking
55
+ - Comprehensive reporting (text, JSON, HTML)
56
+ - Optional result caching for performance
57
+
58
+ Examples
59
+ --------
60
+ >>> validator = TNFRValidator()
61
+ >>> violations = validator.validate_graph(graph)
62
+ >>> if violations:
63
+ ... print(validator.generate_report(violations))
64
+
65
+ >>> # Validate inputs before operator application
66
+ >>> validator.validate_inputs(epi=0.5, vf=1.0, theta=0.0, config=G.graph)
67
+
68
+ >>> # Validate operator preconditions
69
+ >>> validator.validate_operator_preconditions(G, node, "emission")
70
+ """
71
+
72
+ def __init__(
73
+ self,
74
+ phase_coupling_threshold: float | None = None,
75
+ enable_input_validation: bool = True,
76
+ enable_graph_validation: bool = True,
77
+ enable_runtime_validation: bool = True,
78
+ ) -> None:
79
+ """Initialize unified TNFR validator.
80
+
81
+ Parameters
82
+ ----------
83
+ phase_coupling_threshold : float, optional
84
+ Threshold for phase difference in coupled nodes (default: π/2).
85
+ enable_input_validation : bool, optional
86
+ Enable input validation checks (default: True).
87
+ enable_graph_validation : bool, optional
88
+ Enable graph structure validation (default: True).
89
+ enable_runtime_validation : bool, optional
90
+ Enable runtime canonical validation (default: True).
91
+ """
92
+ # Initialize core invariant validators
93
+ self._invariant_validators: list[TNFRInvariant] = [
94
+ Invariant1_EPIOnlyThroughOperators(),
95
+ Invariant2_VfInHzStr(),
96
+ Invariant3_DNFRSemantics(),
97
+ Invariant4_OperatorClosure(),
98
+ Invariant6_NodeBirthCollapse(),
99
+ Invariant7_OperationalFractality(),
100
+ Invariant8_ControlledDeterminism(),
101
+ Invariant9_StructuralMetrics(),
102
+ Invariant10_DomainNeutrality(),
103
+ ]
104
+
105
+ # Initialize phase validator with custom threshold if provided
106
+ if phase_coupling_threshold is not None:
107
+ self._invariant_validators.append(
108
+ Invariant5_ExplicitPhaseChecks(phase_coupling_threshold)
109
+ )
110
+ else:
111
+ self._invariant_validators.append(Invariant5_ExplicitPhaseChecks())
112
+
113
+ self._custom_validators: list[TNFRInvariant] = []
114
+
115
+ # Validation pipeline configuration
116
+ self._enable_input_validation = enable_input_validation
117
+ self._enable_graph_validation = enable_graph_validation
118
+ self._enable_runtime_validation = enable_runtime_validation
119
+
120
+ # Cache for validation results (graph_id -> violations)
121
+ self._validation_cache: dict[int, list[InvariantViolation]] = {}
122
+ self._cache_enabled = False
123
+
124
+ def add_custom_validator(self, validator: TNFRInvariant) -> None:
125
+ """Add custom invariant validator.
126
+
127
+ Parameters
128
+ ----------
129
+ validator : TNFRInvariant
130
+ Custom validator implementing TNFRInvariant interface.
131
+ """
132
+ self._custom_validators.append(validator)
133
+
134
+ def enable_cache(self, enabled: bool = True) -> None:
135
+ """Enable or disable validation result caching.
136
+
137
+ Parameters
138
+ ----------
139
+ enabled : bool
140
+ Whether to enable caching (default: True).
141
+ """
142
+ self._cache_enabled = enabled
143
+ if not enabled:
144
+ self._validation_cache.clear()
145
+
146
+ def clear_cache(self) -> None:
147
+ """Clear the validation result cache."""
148
+ self._validation_cache.clear()
149
+
150
+ def validate(
151
+ self,
152
+ graph: TNFRGraph | None = None,
153
+ *,
154
+ epi: Any = None,
155
+ vf: Any = None,
156
+ theta: Any = None,
157
+ dnfr: Any = None,
158
+ node_id: NodeId | None = None,
159
+ operator: str | None = None,
160
+ include_invariants: bool = True,
161
+ include_graph_structure: bool = True,
162
+ include_runtime: bool = False,
163
+ raise_on_error: bool = False,
164
+ ) -> dict[str, Any]:
165
+ """Comprehensive unified validation pipeline (single entry point).
166
+
167
+ This method provides a single entry point for all TNFR validation needs,
168
+ consolidating input validation, graph validation, invariant checking,
169
+ and operator preconditions into one call.
170
+
171
+ Parameters
172
+ ----------
173
+ graph : TNFRGraph, optional
174
+ Graph to validate (required for graph/invariant validation).
175
+ epi : Any, optional
176
+ EPI value to validate.
177
+ vf : Any, optional
178
+ Structural frequency (νf) to validate.
179
+ theta : Any, optional
180
+ Phase (θ) to validate.
181
+ dnfr : Any, optional
182
+ ΔNFR value to validate.
183
+ node_id : NodeId, optional
184
+ Node ID to validate (required for operator preconditions).
185
+ operator : str, optional
186
+ Operator name to validate preconditions for.
187
+ include_invariants : bool, optional
188
+ Include invariant validation (default: True).
189
+ include_graph_structure : bool, optional
190
+ Include graph structure validation (default: True).
191
+ include_runtime : bool, optional
192
+ Include runtime canonical validation (default: False).
193
+ raise_on_error : bool, optional
194
+ Whether to raise on first error (default: False).
195
+
196
+ Returns
197
+ -------
198
+ dict[str, Any]
199
+ Comprehensive validation results including:
200
+ - 'passed': bool - Overall validation status
201
+ - 'inputs': dict - Input validation results
202
+ - 'graph_structure': dict - Graph structure validation results
203
+ - 'runtime': dict - Runtime validation results
204
+ - 'invariants': list - Invariant violations
205
+ - 'operator_preconditions': bool - Operator precondition status
206
+ - 'errors': list - Any errors encountered
207
+
208
+ Examples
209
+ --------
210
+ >>> validator = TNFRValidator()
211
+ >>> # Validate graph with inputs
212
+ >>> result = validator.validate(
213
+ ... graph=G,
214
+ ... epi=0.5,
215
+ ... vf=1.0,
216
+ ... include_invariants=True
217
+ ... )
218
+ >>> if not result['passed']:
219
+ ... print(f"Validation failed: {result['errors']}")
220
+
221
+ >>> # Validate operator preconditions
222
+ >>> result = validator.validate(
223
+ ... graph=G,
224
+ ... node_id="node_1",
225
+ ... operator="emission"
226
+ ... )
227
+ >>> if result['operator_preconditions']:
228
+ ... # Apply operator
229
+ ... pass
230
+ """
231
+ results: dict[str, Any] = {
232
+ "passed": True,
233
+ "inputs": {},
234
+ "graph_structure": None,
235
+ "runtime": None,
236
+ "invariants": [],
237
+ "operator_preconditions": None,
238
+ "errors": [],
239
+ }
240
+
241
+ config = graph.graph if graph is not None else None
242
+
243
+ # Input validation
244
+ if epi is not None or vf is not None or theta is not None or dnfr is not None:
245
+ try:
246
+ results["inputs"] = self.validate_inputs(
247
+ epi=epi,
248
+ vf=vf,
249
+ theta=theta,
250
+ dnfr=dnfr,
251
+ node_id=node_id,
252
+ config=config,
253
+ raise_on_error=raise_on_error,
254
+ )
255
+ if "error" in results["inputs"]:
256
+ results["passed"] = False
257
+ results["errors"].append(
258
+ f"Input validation: {results['inputs']['error']}"
259
+ )
260
+ except Exception as e:
261
+ results["passed"] = False
262
+ results["errors"].append(f"Input validation failed: {str(e)}")
263
+ if raise_on_error:
264
+ raise
265
+
266
+ # Graph validation
267
+ if graph is not None:
268
+ # Graph structure validation
269
+ if include_graph_structure:
270
+ try:
271
+ results["graph_structure"] = self.validate_graph_structure(
272
+ graph,
273
+ raise_on_error=raise_on_error,
274
+ )
275
+ if not results["graph_structure"].get("passed", False):
276
+ results["passed"] = False
277
+ results["errors"].append(
278
+ f"Graph structure: {results['graph_structure'].get('error', 'Failed')}"
279
+ )
280
+ except Exception as e:
281
+ results["passed"] = False
282
+ results["errors"].append(
283
+ f"Graph structure validation failed: {str(e)}"
284
+ )
285
+ if raise_on_error:
286
+ raise
287
+
288
+ # Runtime canonical validation
289
+ if include_runtime:
290
+ try:
291
+ results["runtime"] = self.validate_runtime_canonical(
292
+ graph,
293
+ raise_on_error=raise_on_error,
294
+ )
295
+ if not results["runtime"].get("passed", False):
296
+ results["passed"] = False
297
+ results["errors"].append(
298
+ f"Runtime validation: {results['runtime'].get('error', 'Failed')}"
299
+ )
300
+ except Exception as e:
301
+ results["passed"] = False
302
+ results["errors"].append(f"Runtime validation failed: {str(e)}")
303
+ if raise_on_error:
304
+ raise
305
+
306
+ # Invariant validation
307
+ if include_invariants:
308
+ try:
309
+ violations = self.validate_graph(
310
+ graph,
311
+ include_graph_validation=False, # Already done above
312
+ include_runtime_validation=False, # Already done above
313
+ )
314
+ results["invariants"] = violations
315
+ if violations:
316
+ # Check if there are any ERROR or CRITICAL violations
317
+ critical_violations = [
318
+ v
319
+ for v in violations
320
+ if v.severity
321
+ in (InvariantSeverity.ERROR, InvariantSeverity.CRITICAL)
322
+ ]
323
+ if critical_violations:
324
+ results["passed"] = False
325
+ results["errors"].append(
326
+ f"{len(critical_violations)} critical invariant violations found"
327
+ )
328
+ except Exception as e:
329
+ results["passed"] = False
330
+ results["errors"].append(f"Invariant validation failed: {str(e)}")
331
+ if raise_on_error:
332
+ raise
333
+
334
+ # Operator preconditions validation
335
+ if operator is not None and node_id is not None:
336
+ try:
337
+ results["operator_preconditions"] = (
338
+ self.validate_operator_preconditions(
339
+ graph,
340
+ node_id,
341
+ operator,
342
+ raise_on_error=raise_on_error,
343
+ )
344
+ )
345
+ if not results["operator_preconditions"]:
346
+ results["passed"] = False
347
+ results["errors"].append(
348
+ f"Operator '{operator}' preconditions not met for node {node_id}"
349
+ )
350
+ except Exception as e:
351
+ results["passed"] = False
352
+ results["errors"].append(
353
+ f"Operator precondition validation failed: {str(e)}"
354
+ )
355
+ if raise_on_error:
356
+ raise
357
+
358
+ return results
359
+
360
+ def validate_inputs(
361
+ self,
362
+ *,
363
+ epi: Any = None,
364
+ vf: Any = None,
365
+ theta: Any = None,
366
+ dnfr: Any = None,
367
+ node_id: Any = None,
368
+ glyph: Any = None,
369
+ graph: Any = None,
370
+ config: Mapping[str, Any] | None = None,
371
+ raise_on_error: bool = True,
372
+ ) -> dict[str, Any]:
373
+ """Validate structural operator inputs.
374
+
375
+ This method consolidates input validation for all TNFR structural parameters,
376
+ enforcing type safety, bounds checking, and security constraints.
377
+
378
+ Parameters
379
+ ----------
380
+ epi : Any, optional
381
+ EPI (Primary Information Structure) value to validate.
382
+ vf : Any, optional
383
+ νf (structural frequency) value to validate.
384
+ theta : Any, optional
385
+ θ (phase) value to validate.
386
+ dnfr : Any, optional
387
+ ΔNFR (reorganization operator) value to validate.
388
+ node_id : Any, optional
389
+ Node identifier to validate.
390
+ glyph : Any, optional
391
+ Glyph enumeration to validate.
392
+ graph : Any, optional
393
+ TNFRGraph to validate.
394
+ config : Mapping[str, Any], optional
395
+ Configuration for bounds checking.
396
+ raise_on_error : bool, optional
397
+ Whether to raise exception on validation failure (default: True).
398
+
399
+ Returns
400
+ -------
401
+ dict[str, Any]
402
+ Dictionary with validation results for each parameter.
403
+ Keys: parameter names, Values: validation status or validated values.
404
+
405
+ Raises
406
+ ------
407
+ ValidationError
408
+ If any validation fails and raise_on_error is True.
409
+
410
+ Examples
411
+ --------
412
+ >>> validator = TNFRValidator()
413
+ >>> validator.validate_inputs(epi=0.5, vf=1.0, theta=0.0)
414
+ {'epi': 0.5, 'vf': 1.0, 'theta': 0.0}
415
+ """
416
+ if not self._enable_input_validation:
417
+ return {}
418
+
419
+ from .input_validation import (
420
+ validate_epi_value,
421
+ validate_vf_value,
422
+ validate_theta_value,
423
+ validate_dnfr_value,
424
+ validate_node_id,
425
+ validate_glyph,
426
+ validate_tnfr_graph,
427
+ )
428
+
429
+ results = {}
430
+
431
+ try:
432
+ if epi is not None:
433
+ results["epi"] = validate_epi_value(epi, config=config)
434
+
435
+ if vf is not None:
436
+ results["vf"] = validate_vf_value(vf, config=config)
437
+
438
+ if theta is not None:
439
+ results["theta"] = validate_theta_value(theta)
440
+
441
+ if dnfr is not None:
442
+ results["dnfr"] = validate_dnfr_value(dnfr, config=config)
443
+
444
+ if node_id is not None:
445
+ results["node_id"] = validate_node_id(node_id)
446
+
447
+ if glyph is not None:
448
+ results["glyph"] = validate_glyph(glyph)
449
+
450
+ if graph is not None:
451
+ results["graph"] = validate_tnfr_graph(graph)
452
+
453
+ except Exception as e:
454
+ if raise_on_error:
455
+ raise
456
+ results["error"] = str(e)
457
+
458
+ return results
459
+
460
+ def validate_operator_preconditions(
461
+ self,
462
+ graph: TNFRGraph,
463
+ node: NodeId,
464
+ operator: str,
465
+ raise_on_error: bool = True,
466
+ ) -> bool:
467
+ """Validate operator preconditions before application.
468
+
469
+ Each TNFR structural operator has specific requirements that must be met
470
+ before execution to maintain structural invariants.
471
+
472
+ Parameters
473
+ ----------
474
+ graph : TNFRGraph
475
+ Graph containing the target node.
476
+ node : NodeId
477
+ Target node for operator application.
478
+ operator : str
479
+ Name of the operator to validate (e.g., "emission", "coherence").
480
+ raise_on_error : bool, optional
481
+ Whether to raise exception on failure (default: True).
482
+
483
+ Returns
484
+ -------
485
+ bool
486
+ True if preconditions are met, False otherwise.
487
+
488
+ Raises
489
+ ------
490
+ OperatorPreconditionError
491
+ If preconditions are not met and raise_on_error is True.
492
+
493
+ Examples
494
+ --------
495
+ >>> validator = TNFRValidator()
496
+ >>> if validator.validate_operator_preconditions(G, node, "emission"):
497
+ ... # Apply emission operator
498
+ ... pass
499
+ """
500
+ from ..operators import preconditions
501
+
502
+ validator_map = {
503
+ "emission": preconditions.validate_emission,
504
+ "reception": preconditions.validate_reception,
505
+ "coherence": preconditions.validate_coherence,
506
+ "dissonance": preconditions.validate_dissonance,
507
+ "coupling": preconditions.validate_coupling,
508
+ "resonance": preconditions.validate_resonance,
509
+ "silence": preconditions.validate_silence,
510
+ "expansion": preconditions.validate_expansion,
511
+ "contraction": preconditions.validate_contraction,
512
+ "self_organization": preconditions.validate_self_organization,
513
+ "mutation": preconditions.validate_mutation,
514
+ "transition": preconditions.validate_transition,
515
+ "recursivity": preconditions.validate_recursivity,
516
+ }
517
+
518
+ validator_func = validator_map.get(operator.lower())
519
+ if validator_func is None:
520
+ if raise_on_error:
521
+ raise ValueError(f"Unknown operator: {operator}")
522
+ return False
523
+
524
+ try:
525
+ validator_func(graph, node)
526
+ return True
527
+ except Exception as e:
528
+ if raise_on_error:
529
+ raise
530
+ return False
531
+
532
+ def validate_graph_structure(
533
+ self,
534
+ graph: TNFRGraph,
535
+ raise_on_error: bool = True,
536
+ ) -> dict[str, Any]:
537
+ """Validate graph structure and coherence.
538
+
539
+ Performs structural validation including:
540
+ - Node attribute completeness
541
+ - EPI bounds and grid uniformity
542
+ - Structural frequency ranges
543
+ - Coherence metrics
544
+
545
+ Parameters
546
+ ----------
547
+ graph : TNFRGraph
548
+ Graph to validate.
549
+ raise_on_error : bool, optional
550
+ Whether to raise exception on failure (default: True).
551
+
552
+ Returns
553
+ -------
554
+ dict[str, Any]
555
+ Validation results including passed checks and any errors.
556
+
557
+ Raises
558
+ ------
559
+ ValueError
560
+ If structural validation fails and raise_on_error is True.
561
+ """
562
+ if not self._enable_graph_validation:
563
+ return {"passed": True, "message": "Graph validation disabled"}
564
+
565
+ from .graph import run_validators
566
+
567
+ try:
568
+ run_validators(graph)
569
+ return {"passed": True, "message": "Graph structure valid"}
570
+ except Exception as e:
571
+ if raise_on_error:
572
+ raise
573
+ return {"passed": False, "error": str(e)}
574
+
575
+ def validate_runtime_canonical(
576
+ self,
577
+ graph: TNFRGraph,
578
+ raise_on_error: bool = True,
579
+ ) -> dict[str, Any]:
580
+ """Validate runtime canonical constraints.
581
+
582
+ Applies canonical clamps and validates graph contracts at runtime.
583
+
584
+ Parameters
585
+ ----------
586
+ graph : TNFRGraph
587
+ Graph to validate.
588
+ raise_on_error : bool, optional
589
+ Whether to raise exception on failure (default: True).
590
+
591
+ Returns
592
+ -------
593
+ dict[str, Any]
594
+ Validation results.
595
+
596
+ Raises
597
+ ------
598
+ Exception
599
+ If runtime validation fails and raise_on_error is True.
600
+ """
601
+ if not self._enable_runtime_validation:
602
+ return {"passed": True, "message": "Runtime validation disabled"}
603
+
604
+ from .runtime import validate_canon
605
+
606
+ try:
607
+ outcome = validate_canon(graph)
608
+ return {
609
+ "passed": outcome.passed,
610
+ "summary": outcome.summary,
611
+ "artifacts": outcome.artifacts,
612
+ }
613
+ except Exception as e:
614
+ if raise_on_error:
615
+ raise
616
+ return {"passed": False, "error": str(e)}
617
+
618
+ def validate_graph(
619
+ self,
620
+ graph: TNFRGraph,
621
+ severity_filter: Optional[InvariantSeverity] = None,
622
+ use_cache: bool = True,
623
+ include_graph_validation: bool = True,
624
+ include_runtime_validation: bool = False,
625
+ ) -> list[InvariantViolation]:
626
+ """Validate graph against all TNFR invariants (unified pipeline).
627
+
628
+ This is the main entry point for comprehensive graph validation,
629
+ integrating all validation layers:
630
+ - Invariant validation (10 canonical TNFR invariants)
631
+ - Optional graph structure validation
632
+ - Optional runtime canonical validation
633
+
634
+ Parameters
635
+ ----------
636
+ graph : TNFRGraph
637
+ Graph to validate against TNFR invariants.
638
+ severity_filter : InvariantSeverity, optional
639
+ Only return violations of this severity level.
640
+ use_cache : bool, optional
641
+ Whether to use cached results if available (default: True).
642
+ include_graph_validation : bool, optional
643
+ Include graph structure validation (default: True).
644
+ include_runtime_validation : bool, optional
645
+ Include runtime canonical validation (default: False).
646
+
647
+ Returns
648
+ -------
649
+ list[InvariantViolation]
650
+ List of detected violations.
651
+
652
+ Examples
653
+ --------
654
+ >>> validator = TNFRValidator()
655
+ >>> violations = validator.validate_graph(graph)
656
+ >>> if violations:
657
+ ... print(validator.generate_report(violations))
658
+ """
659
+ # Check cache if enabled
660
+ if self._cache_enabled and use_cache:
661
+ graph_id = id(graph)
662
+ if graph_id in self._validation_cache:
663
+ all_violations = self._validation_cache[graph_id]
664
+ # Apply severity filter if specified
665
+ if severity_filter:
666
+ return [v for v in all_violations if v.severity == severity_filter]
667
+ return all_violations
668
+
669
+ all_violations: list[InvariantViolation] = []
670
+
671
+ # Run graph structure validation if enabled
672
+ if include_graph_validation and self._enable_graph_validation:
673
+ try:
674
+ result = self.validate_graph_structure(graph, raise_on_error=False)
675
+ if not result.get("passed", False):
676
+ all_violations.append(
677
+ InvariantViolation(
678
+ invariant_id=4, # Operator closure
679
+ severity=InvariantSeverity.ERROR,
680
+ description=f"Graph structure validation failed: {result.get('error', 'Unknown error')}",
681
+ suggestion="Check graph structure and node attributes",
682
+ )
683
+ )
684
+ except Exception as e:
685
+ all_violations.append(
686
+ InvariantViolation(
687
+ invariant_id=4,
688
+ severity=InvariantSeverity.CRITICAL,
689
+ description=f"Graph structure validator failed: {str(e)}",
690
+ suggestion="Check graph structure validator implementation",
691
+ )
692
+ )
693
+
694
+ # Run runtime canonical validation if enabled
695
+ if include_runtime_validation and self._enable_runtime_validation:
696
+ try:
697
+ result = self.validate_runtime_canonical(graph, raise_on_error=False)
698
+ if not result.get("passed", False):
699
+ all_violations.append(
700
+ InvariantViolation(
701
+ invariant_id=8, # Controlled determinism
702
+ severity=InvariantSeverity.WARNING,
703
+ description=f"Runtime canonical validation failed: {result.get('error', 'Unknown error')}",
704
+ suggestion="Check canonical clamps and runtime contracts",
705
+ )
706
+ )
707
+ except Exception as e:
708
+ all_violations.append(
709
+ InvariantViolation(
710
+ invariant_id=8,
711
+ severity=InvariantSeverity.WARNING,
712
+ description=f"Runtime validator failed: {str(e)}",
713
+ suggestion="Check runtime validator implementation",
714
+ )
715
+ )
716
+
717
+ # Run invariant validators
718
+ for validator in self._invariant_validators + self._custom_validators:
719
+ try:
720
+ violations = validator.validate(graph)
721
+ all_violations.extend(violations)
722
+ except Exception as e:
723
+ # If validator fails, it's a critical error
724
+ all_violations.append(
725
+ InvariantViolation(
726
+ invariant_id=validator.invariant_id,
727
+ severity=InvariantSeverity.CRITICAL,
728
+ description=f"Validator execution failed: {str(e)}",
729
+ suggestion="Check validator implementation",
730
+ )
731
+ )
732
+
733
+ # Cache results if enabled
734
+ if self._cache_enabled:
735
+ graph_id = id(graph)
736
+ self._validation_cache[graph_id] = all_violations.copy()
737
+
738
+ # Filtrar por severidad si se especifica
739
+ if severity_filter:
740
+ all_violations = [
741
+ v for v in all_violations if v.severity == severity_filter
742
+ ]
743
+
744
+ return all_violations
745
+
746
+ def validate_and_raise(
747
+ self,
748
+ graph: TNFRGraph,
749
+ min_severity: InvariantSeverity = InvariantSeverity.ERROR,
750
+ ) -> None:
751
+ """Validates and raises exception if violations of minimum severity are found.
752
+
753
+ Parameters
754
+ ----------
755
+ graph : TNFRGraph
756
+ Graph to validate.
757
+ min_severity : InvariantSeverity
758
+ Minimum severity level to trigger exception (default: ERROR).
759
+
760
+ Raises
761
+ ------
762
+ TNFRValidationError
763
+ If violations of minimum severity or higher are found.
764
+ """
765
+ violations = self.validate_graph(graph)
766
+
767
+ # Filter violations by minimum severity
768
+ severity_order = {
769
+ InvariantSeverity.INFO: -1,
770
+ InvariantSeverity.WARNING: 0,
771
+ InvariantSeverity.ERROR: 1,
772
+ InvariantSeverity.CRITICAL: 2,
773
+ }
774
+
775
+ critical_violations = [
776
+ v
777
+ for v in violations
778
+ if severity_order[v.severity] >= severity_order[min_severity]
779
+ ]
780
+
781
+ if critical_violations:
782
+ raise TNFRValidationError(critical_violations)
783
+
784
+ def generate_report(self, violations: list[InvariantViolation]) -> str:
785
+ """Genera reporte human-readable de violaciones.
786
+
787
+ Parameters
788
+ ----------
789
+ violations : list[InvariantViolation]
790
+ List of violations to report.
791
+
792
+ Returns
793
+ -------
794
+ str
795
+ Human-readable report.
796
+ """
797
+ if not violations:
798
+ return "✅ No TNFR invariant violations found."
799
+
800
+ report_lines = ["\n🚨 TNFR Invariant Violations Detected:\n"]
801
+
802
+ # Agrupar por severidad
803
+ by_severity: dict[InvariantSeverity, list[InvariantViolation]] = {}
804
+ for v in violations:
805
+ if v.severity not in by_severity:
806
+ by_severity[v.severity] = []
807
+ by_severity[v.severity].append(v)
808
+
809
+ # Reporte por severidad
810
+ severity_icons = {
811
+ InvariantSeverity.INFO: "ℹ️",
812
+ InvariantSeverity.WARNING: "⚠️",
813
+ InvariantSeverity.ERROR: "❌",
814
+ InvariantSeverity.CRITICAL: "💥",
815
+ }
816
+
817
+ for severity in [
818
+ InvariantSeverity.CRITICAL,
819
+ InvariantSeverity.ERROR,
820
+ InvariantSeverity.WARNING,
821
+ InvariantSeverity.INFO,
822
+ ]:
823
+ if severity in by_severity:
824
+ report_lines.append(
825
+ f"\n{severity_icons[severity]} {severity.value.upper()} "
826
+ f"({len(by_severity[severity])}):\n"
827
+ )
828
+
829
+ for violation in by_severity[severity]:
830
+ report_lines.append(
831
+ f" Invariant #{violation.invariant_id}: {violation.description}"
832
+ )
833
+ if violation.node_id:
834
+ report_lines.append(f" Node: {violation.node_id}")
835
+ if violation.expected_value and violation.actual_value:
836
+ report_lines.append(f" Expected: {violation.expected_value}")
837
+ report_lines.append(f" Actual: {violation.actual_value}")
838
+ if violation.suggestion:
839
+ report_lines.append(
840
+ f" 💡 Suggestion: {violation.suggestion}"
841
+ )
842
+ report_lines.append("")
843
+
844
+ return "\n".join(report_lines)
845
+
846
+ def export_to_json(self, violations: list[InvariantViolation]) -> str:
847
+ """Export violations to JSON format.
848
+
849
+ Parameters
850
+ ----------
851
+ violations : list[InvariantViolation]
852
+ List of violations to export.
853
+
854
+ Returns
855
+ -------
856
+ str
857
+ JSON-formatted string of violations.
858
+ """
859
+ import json
860
+
861
+ violations_data = []
862
+ for v in violations:
863
+ violations_data.append(
864
+ {
865
+ "invariant_id": v.invariant_id,
866
+ "severity": v.severity.value,
867
+ "description": v.description,
868
+ "node_id": v.node_id,
869
+ "expected_value": (
870
+ str(v.expected_value) if v.expected_value else None
871
+ ),
872
+ "actual_value": str(v.actual_value) if v.actual_value else None,
873
+ "suggestion": v.suggestion,
874
+ }
875
+ )
876
+
877
+ return json.dumps(
878
+ {
879
+ "total_violations": len(violations),
880
+ "by_severity": {
881
+ InvariantSeverity.CRITICAL.value: len(
882
+ [
883
+ v
884
+ for v in violations
885
+ if v.severity == InvariantSeverity.CRITICAL
886
+ ]
887
+ ),
888
+ InvariantSeverity.ERROR.value: len(
889
+ [v for v in violations if v.severity == InvariantSeverity.ERROR]
890
+ ),
891
+ InvariantSeverity.WARNING.value: len(
892
+ [
893
+ v
894
+ for v in violations
895
+ if v.severity == InvariantSeverity.WARNING
896
+ ]
897
+ ),
898
+ InvariantSeverity.INFO.value: len(
899
+ [v for v in violations if v.severity == InvariantSeverity.INFO]
900
+ ),
901
+ },
902
+ "violations": violations_data,
903
+ },
904
+ indent=2,
905
+ )
906
+
907
+ def export_to_html(self, violations: list[InvariantViolation]) -> str:
908
+ """Export violations to HTML format.
909
+
910
+ Parameters
911
+ ----------
912
+ violations : list[InvariantViolation]
913
+ List of violations to export.
914
+
915
+ Returns
916
+ -------
917
+ str
918
+ HTML-formatted string of violations.
919
+ """
920
+ if not violations:
921
+ return """
922
+ <!DOCTYPE html>
923
+ <html>
924
+ <head>
925
+ <title>TNFR Validation Report</title>
926
+ <style>
927
+ body { font-family: Arial, sans-serif; margin: 40px; }
928
+ .success { color: green; font-size: 24px; }
929
+ </style>
930
+ </head>
931
+ <body>
932
+ <h1>TNFR Validation Report</h1>
933
+ <p class="success">✅ No TNFR invariant violations found.</p>
934
+ </body>
935
+ </html>
936
+ """
937
+
938
+ # Group by severity
939
+ by_severity: dict[InvariantSeverity, list[InvariantViolation]] = {}
940
+ for v in violations:
941
+ if v.severity not in by_severity:
942
+ by_severity[v.severity] = []
943
+ by_severity[v.severity].append(v)
944
+
945
+ severity_colors = {
946
+ InvariantSeverity.INFO: "#17a2b8",
947
+ InvariantSeverity.WARNING: "#ffc107",
948
+ InvariantSeverity.ERROR: "#dc3545",
949
+ InvariantSeverity.CRITICAL: "#6f42c1",
950
+ }
951
+
952
+ html_parts = [
953
+ """
954
+ <!DOCTYPE html>
955
+ <html>
956
+ <head>
957
+ <title>TNFR Validation Report</title>
958
+ <style>
959
+ body {{ font-family: Arial, sans-serif; margin: 40px; background-color: #f5f5f5; }}
960
+ h1 {{ color: #333; }}
961
+ .summary {{ background: white; padding: 20px; border-radius: 5px; margin-bottom: 20px; }}
962
+ .severity-section {{ background: white; padding: 20px; border-radius: 5px; margin-bottom: 20px; }}
963
+ .severity-header {{ font-size: 20px; font-weight: bold; margin-bottom: 15px; }}
964
+ .violation {{ background: #f9f9f9; padding: 15px; margin-bottom: 10px; border-left: 4px solid; border-radius: 3px; }}
965
+ .violation-title {{ font-weight: bold; margin-bottom: 5px; }}
966
+ .violation-detail {{ margin-left: 20px; color: #666; }}
967
+ .suggestion {{ background: #e7f5ff; padding: 10px; margin-top: 10px; border-radius: 3px; }}
968
+ </style>
969
+ </head>
970
+ <body>
971
+ <h1>🚨 TNFR Validation Report</h1>
972
+ <div class="summary">
973
+ <h2>Summary</h2>
974
+ <p><strong>Total Violations:</strong> {{}}</p>
975
+ """.format(
976
+ len(violations)
977
+ )
978
+ ]
979
+
980
+ for severity in [
981
+ InvariantSeverity.CRITICAL,
982
+ InvariantSeverity.ERROR,
983
+ InvariantSeverity.WARNING,
984
+ InvariantSeverity.INFO,
985
+ ]:
986
+ count = len(by_severity.get(severity, []))
987
+ if count > 0:
988
+ html_parts.append(
989
+ f"<p><strong>{severity.value.upper()}:</strong> {count}</p>"
990
+ )
991
+
992
+ html_parts.append("</div>")
993
+
994
+ for severity in [
995
+ InvariantSeverity.CRITICAL,
996
+ InvariantSeverity.ERROR,
997
+ InvariantSeverity.WARNING,
998
+ InvariantSeverity.INFO,
999
+ ]:
1000
+ if severity in by_severity:
1001
+ color = severity_colors[severity]
1002
+ html_parts.append(
1003
+ f"""
1004
+ <div class="severity-section">
1005
+ <div class="severity-header" style="color: {color};">
1006
+ {severity.value.upper()} ({len(by_severity[severity])})
1007
+ </div>
1008
+ """
1009
+ )
1010
+
1011
+ for violation in by_severity[severity]:
1012
+ html_parts.append(
1013
+ f"""
1014
+ <div class="violation" style="border-left-color: {color};">
1015
+ <div class="violation-title">
1016
+ Invariant #{violation.invariant_id}: {violation.description}
1017
+ </div>
1018
+ """
1019
+ )
1020
+
1021
+ if violation.node_id:
1022
+ html_parts.append(
1023
+ f'<div class="violation-detail"><strong>Node:</strong> {violation.node_id}</div>'
1024
+ )
1025
+
1026
+ if violation.expected_value and violation.actual_value:
1027
+ html_parts.append(
1028
+ f'<div class="violation-detail"><strong>Expected:</strong> {violation.expected_value}</div>'
1029
+ )
1030
+ html_parts.append(
1031
+ f'<div class="violation-detail"><strong>Actual:</strong> {violation.actual_value}</div>'
1032
+ )
1033
+
1034
+ if violation.suggestion:
1035
+ html_parts.append(
1036
+ f'<div class="suggestion">💡 <strong>Suggestion:</strong> {violation.suggestion}</div>'
1037
+ )
1038
+
1039
+ html_parts.append("</div>")
1040
+
1041
+ html_parts.append("</div>")
1042
+
1043
+ html_parts.append(
1044
+ """
1045
+ </body>
1046
+ </html>
1047
+ """
1048
+ )
1049
+
1050
+ return "".join(html_parts)
1051
+
1052
+
1053
+ class TNFRValidationError(Exception):
1054
+ """Exception raised when TNFR invariant violations are detected."""
1055
+
1056
+ def __init__(self, violations: list[InvariantViolation]) -> None:
1057
+ self.violations = violations
1058
+ validator = TNFRValidator()
1059
+ self.report = validator.generate_report(violations)
1060
+ super().__init__(self.report)
1061
+
1062
+ def export_to_json(self, violations: list[InvariantViolation]) -> str:
1063
+ """Export violations to JSON format.
1064
+
1065
+ Parameters
1066
+ ----------
1067
+ violations : list[InvariantViolation]
1068
+ List of violations to export.
1069
+
1070
+ Returns
1071
+ -------
1072
+ str
1073
+ JSON-formatted string of violations.
1074
+ """
1075
+ import json
1076
+
1077
+ violations_data = []
1078
+ for v in violations:
1079
+ violations_data.append(
1080
+ {
1081
+ "invariant_id": v.invariant_id,
1082
+ "severity": v.severity.value,
1083
+ "description": v.description,
1084
+ "node_id": v.node_id,
1085
+ "expected_value": (
1086
+ str(v.expected_value) if v.expected_value else None
1087
+ ),
1088
+ "actual_value": str(v.actual_value) if v.actual_value else None,
1089
+ "suggestion": v.suggestion,
1090
+ }
1091
+ )
1092
+
1093
+ return json.dumps(
1094
+ {
1095
+ "total_violations": len(violations),
1096
+ "by_severity": {
1097
+ InvariantSeverity.CRITICAL.value: len(
1098
+ [
1099
+ v
1100
+ for v in violations
1101
+ if v.severity == InvariantSeverity.CRITICAL
1102
+ ]
1103
+ ),
1104
+ InvariantSeverity.ERROR.value: len(
1105
+ [v for v in violations if v.severity == InvariantSeverity.ERROR]
1106
+ ),
1107
+ InvariantSeverity.WARNING.value: len(
1108
+ [
1109
+ v
1110
+ for v in violations
1111
+ if v.severity == InvariantSeverity.WARNING
1112
+ ]
1113
+ ),
1114
+ InvariantSeverity.INFO.value: len(
1115
+ [v for v in violations if v.severity == InvariantSeverity.INFO]
1116
+ ),
1117
+ },
1118
+ "violations": violations_data,
1119
+ },
1120
+ indent=2,
1121
+ )
1122
+
1123
+ def export_to_html(self, violations: list[InvariantViolation]) -> str:
1124
+ """Export violations to HTML format.
1125
+
1126
+ Parameters
1127
+ ----------
1128
+ violations : list[InvariantViolation]
1129
+ List of violations to export.
1130
+
1131
+ Returns
1132
+ -------
1133
+ str
1134
+ HTML-formatted string of violations.
1135
+ """
1136
+ if not violations:
1137
+ return """
1138
+ <!DOCTYPE html>
1139
+ <html>
1140
+ <head>
1141
+ <title>TNFR Validation Report</title>
1142
+ <style>
1143
+ body { font-family: Arial, sans-serif; margin: 40px; }
1144
+ .success { color: green; font-size: 24px; }
1145
+ </style>
1146
+ </head>
1147
+ <body>
1148
+ <h1>TNFR Validation Report</h1>
1149
+ <p class="success">✅ No TNFR invariant violations found.</p>
1150
+ </body>
1151
+ </html>
1152
+ """
1153
+
1154
+ # Group by severity
1155
+ by_severity: dict[InvariantSeverity, list[InvariantViolation]] = {}
1156
+ for v in violations:
1157
+ if v.severity not in by_severity:
1158
+ by_severity[v.severity] = []
1159
+ by_severity[v.severity].append(v)
1160
+
1161
+ severity_colors = {
1162
+ InvariantSeverity.INFO: "#17a2b8",
1163
+ InvariantSeverity.WARNING: "#ffc107",
1164
+ InvariantSeverity.ERROR: "#dc3545",
1165
+ InvariantSeverity.CRITICAL: "#6f42c1",
1166
+ }
1167
+
1168
+ html_parts = [
1169
+ """
1170
+ <!DOCTYPE html>
1171
+ <html>
1172
+ <head>
1173
+ <title>TNFR Validation Report</title>
1174
+ <style>
1175
+ body { font-family: Arial, sans-serif; margin: 40px; background-color: #f5f5f5; }
1176
+ h1 { color: #333; }
1177
+ .summary { background: white; padding: 20px; border-radius: 5px; margin-bottom: 20px; }
1178
+ .severity-section { background: white; padding: 20px; border-radius: 5px; margin-bottom: 20px; }
1179
+ .severity-header { font-size: 20px; font-weight: bold; margin-bottom: 15px; }
1180
+ .violation { background: #f9f9f9; padding: 15px; margin-bottom: 10px; border-left: 4px solid; border-radius: 3px; }
1181
+ .violation-title { font-weight: bold; margin-bottom: 5px; }
1182
+ .violation-detail { margin-left: 20px; color: #666; }
1183
+ .suggestion { background: #e7f5ff; padding: 10px; margin-top: 10px; border-radius: 3px; }
1184
+ </style>
1185
+ </head>
1186
+ <body>
1187
+ <h1>🚨 TNFR Validation Report</h1>
1188
+ <div class="summary">
1189
+ <h2>Summary</h2>
1190
+ <p><strong>Total Violations:</strong> {}</p>
1191
+ """.format(
1192
+ len(violations)
1193
+ )
1194
+ ]
1195
+
1196
+ for severity in [
1197
+ InvariantSeverity.CRITICAL,
1198
+ InvariantSeverity.ERROR,
1199
+ InvariantSeverity.WARNING,
1200
+ InvariantSeverity.INFO,
1201
+ ]:
1202
+ count = len(by_severity.get(severity, []))
1203
+ if count > 0:
1204
+ html_parts.append(
1205
+ f"<p><strong>{severity.value.upper()}:</strong> {count}</p>"
1206
+ )
1207
+
1208
+ html_parts.append("</div>")
1209
+
1210
+ for severity in [
1211
+ InvariantSeverity.CRITICAL,
1212
+ InvariantSeverity.ERROR,
1213
+ InvariantSeverity.WARNING,
1214
+ InvariantSeverity.INFO,
1215
+ ]:
1216
+ if severity in by_severity:
1217
+ color = severity_colors[severity]
1218
+ html_parts.append(
1219
+ f"""
1220
+ <div class="severity-section">
1221
+ <div class="severity-header" style="color: {color};">
1222
+ {severity.value.upper()} ({len(by_severity[severity])})
1223
+ </div>
1224
+ """
1225
+ )
1226
+
1227
+ for violation in by_severity[severity]:
1228
+ html_parts.append(
1229
+ f"""
1230
+ <div class="violation" style="border-left-color: {color};">
1231
+ <div class="violation-title">
1232
+ Invariant #{violation.invariant_id}: {violation.description}
1233
+ </div>
1234
+ """
1235
+ )
1236
+
1237
+ if violation.node_id:
1238
+ html_parts.append(
1239
+ f'<div class="violation-detail"><strong>Node:</strong> {violation.node_id}</div>'
1240
+ )
1241
+
1242
+ if violation.expected_value and violation.actual_value:
1243
+ html_parts.append(
1244
+ f'<div class="violation-detail"><strong>Expected:</strong> {violation.expected_value}</div>'
1245
+ )
1246
+ html_parts.append(
1247
+ f'<div class="violation-detail"><strong>Actual:</strong> {violation.actual_value}</div>'
1248
+ )
1249
+
1250
+ if violation.suggestion:
1251
+ html_parts.append(
1252
+ f'<div class="suggestion">💡 <strong>Suggestion:</strong> {violation.suggestion}</div>'
1253
+ )
1254
+
1255
+ html_parts.append("</div>")
1256
+
1257
+ html_parts.append("</div>")
1258
+
1259
+ html_parts.append(
1260
+ """
1261
+ </body>
1262
+ </html>
1263
+ """
1264
+ )
1265
+
1266
+ return "".join(html_parts)