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
tnfr/cli/utils.py CHANGED
@@ -2,8 +2,11 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ from collections.abc import Iterable
5
6
  from typing import Any
6
7
 
8
+ from ..utils import normalize_optional_int
9
+
7
10
 
8
11
  def spec(opt: str, /, **kwargs: Any) -> tuple[str, dict[str, Any]]:
9
12
  """Create an argument specification pair.
@@ -27,10 +30,22 @@ def spec(opt: str, /, **kwargs: Any) -> tuple[str, dict[str, Any]]:
27
30
  """
28
31
 
29
32
  kwargs = dict(kwargs)
30
- kwargs.setdefault(
31
- "dest", opt.lstrip("-").replace("-", "_").replace(".", "_")
32
- )
33
+ kwargs.setdefault("dest", opt.lstrip("-").replace("-", "_").replace(".", "_"))
33
34
  kwargs.setdefault("default", None)
34
35
  return opt, kwargs
35
36
 
36
37
 
38
+ def _parse_cli_variants(values: Iterable[Any] | None) -> list[int | None]:
39
+ """Return a stable list of integer/``None`` variants for the CLI options."""
40
+
41
+ if values is None:
42
+ return [None]
43
+ parsed: list[int | None] = []
44
+ seen: set[int | None] = set()
45
+ for raw in values:
46
+ coerced = normalize_optional_int(raw, strict=True)
47
+ if coerced in seen:
48
+ continue
49
+ seen.add(coerced)
50
+ parsed.append(coerced)
51
+ return parsed or [None]
tnfr/cli/utils.pyi ADDED
@@ -0,0 +1,7 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Iterable
4
+ from typing import Any
5
+
6
+ def spec(opt: str, /, **kwargs: Any) -> tuple[str, dict[str, Any]]: ...
7
+ def _parse_cli_variants(values: Iterable[Any] | None) -> list[int | None]: ...
tnfr/cli/validate.py ADDED
@@ -0,0 +1,236 @@
1
+ #!/usr/bin/env python
2
+ """CLI tool for TNFR validation.
3
+
4
+ This tool provides two modes:
5
+ 1. Graph validation: Validate TNFR graphs from files (original behavior)
6
+ 2. Interactive sequence validation: User-friendly sequence validator (new)
7
+ """
8
+
9
+ import argparse
10
+ import json
11
+ import sys
12
+ from pathlib import Path
13
+ from typing import Optional
14
+
15
+ import networkx as nx
16
+
17
+ from ..validation import (
18
+ InvariantSeverity,
19
+ TNFRValidator,
20
+ configure_validation,
21
+ )
22
+
23
+
24
+ def load_graph(filepath: str) -> nx.Graph:
25
+ """Load a graph from file.
26
+
27
+ Parameters
28
+ ----------
29
+ filepath : str
30
+ Path to graph file (supports .graphml, .gml, .json).
31
+
32
+ Returns
33
+ -------
34
+ nx.Graph
35
+ Loaded graph.
36
+ """
37
+ filepath_obj = Path(filepath)
38
+
39
+ if not filepath_obj.exists():
40
+ raise FileNotFoundError(f"Graph file not found: {filepath}")
41
+
42
+ # Determine format from extension
43
+ ext = filepath_obj.suffix.lower()
44
+
45
+ if ext == ".graphml":
46
+ return nx.read_graphml(filepath)
47
+ elif ext == ".gml":
48
+ return nx.read_gml(filepath)
49
+ elif ext == ".json":
50
+ with open(filepath, "r") as f:
51
+ data = json.load(f)
52
+ return nx.node_link_graph(data)
53
+ else:
54
+ raise ValueError(
55
+ f"Unsupported graph format: {ext}. Use .graphml, .gml, or .json"
56
+ )
57
+
58
+
59
+ def main():
60
+ """Main CLI entry point."""
61
+ parser = argparse.ArgumentParser(
62
+ description="TNFR Validation Tool - Validate graphs or sequences",
63
+ formatter_class=argparse.RawDescriptionHelpFormatter,
64
+ epilog="""
65
+ Examples:
66
+ # Interactive sequence validator (NEW!)
67
+ tnfr-validate --interactive
68
+ tnfr-validate -i
69
+
70
+ # Validate a graph file
71
+ tnfr-validate graph.graphml
72
+
73
+ # Export to JSON
74
+ tnfr-validate graph.graphml --format json --output report.json
75
+
76
+ # Export to HTML
77
+ tnfr-validate graph.graphml --format html --output report.html
78
+
79
+ # Set minimum severity to WARNING
80
+ tnfr-validate graph.graphml --min-severity warning
81
+
82
+ # Enable caching for multiple validations
83
+ tnfr-validate graph.graphml --cache
84
+ """,
85
+ )
86
+
87
+ parser.add_argument(
88
+ "--interactive",
89
+ "-i",
90
+ action="store_true",
91
+ help="Launch interactive sequence validator",
92
+ )
93
+
94
+ parser.add_argument(
95
+ "graph_file",
96
+ nargs="?",
97
+ help="Path to graph file (.graphml, .gml, or .json). Required unless using --interactive mode.",
98
+ )
99
+
100
+ parser.add_argument(
101
+ "--format",
102
+ choices=["text", "json", "html"],
103
+ default="text",
104
+ help="Output format (default: text)",
105
+ )
106
+
107
+ parser.add_argument(
108
+ "--output",
109
+ "-o",
110
+ help="Output file (default: stdout for text, or auto-generated for json/html)",
111
+ )
112
+
113
+ parser.add_argument(
114
+ "--min-severity",
115
+ choices=["info", "warning", "error", "critical"],
116
+ default="error",
117
+ help="Minimum severity to report (default: error)",
118
+ )
119
+
120
+ parser.add_argument(
121
+ "--cache", action="store_true", help="Enable validation result caching"
122
+ )
123
+
124
+ parser.add_argument(
125
+ "--no-semantic",
126
+ action="store_true",
127
+ help="Disable semantic sequence validation",
128
+ )
129
+
130
+ parser.add_argument(
131
+ "--phase-threshold",
132
+ type=float,
133
+ help="Phase coupling threshold in radians (default: π/2)",
134
+ )
135
+
136
+ parser.add_argument("--verbose", "-v", action="store_true", help="Verbose output")
137
+
138
+ parser.add_argument(
139
+ "--seed",
140
+ type=int,
141
+ help="Random seed for interactive validator (deterministic generation)",
142
+ )
143
+
144
+ args = parser.parse_args()
145
+
146
+ # Handle interactive mode
147
+ if args.interactive:
148
+ from .interactive_validator import run_interactive_validator
149
+
150
+ return run_interactive_validator(seed=args.seed)
151
+
152
+ # Require graph_file for non-interactive mode
153
+ if not args.graph_file:
154
+ parser.error("graph_file is required when not using --interactive mode")
155
+
156
+ # Configure validation
157
+ severity_map = {
158
+ "info": InvariantSeverity.INFO,
159
+ "warning": InvariantSeverity.WARNING,
160
+ "error": InvariantSeverity.ERROR,
161
+ "critical": InvariantSeverity.CRITICAL,
162
+ }
163
+
164
+ configure_validation(
165
+ validate_invariants=True,
166
+ enable_semantic_validation=not args.no_semantic,
167
+ min_severity=severity_map[args.min_severity],
168
+ )
169
+
170
+ if args.verbose:
171
+ print(f"Loading graph from: {args.graph_file}")
172
+
173
+ try:
174
+ # Load graph
175
+ graph = load_graph(args.graph_file)
176
+
177
+ if args.verbose:
178
+ print(
179
+ f"Graph loaded: {graph.number_of_nodes()} nodes, {graph.number_of_edges()} edges"
180
+ )
181
+
182
+ # Create validator
183
+ if args.phase_threshold:
184
+ validator = TNFRValidator(phase_coupling_threshold=args.phase_threshold)
185
+ else:
186
+ validator = TNFRValidator()
187
+
188
+ if args.cache:
189
+ validator.enable_cache(True)
190
+
191
+ # Validate
192
+ if args.verbose:
193
+ print("Running TNFR validation...")
194
+
195
+ violations = validator.validate_graph(graph)
196
+
197
+ # Generate output
198
+ if args.format == "text":
199
+ output = validator.generate_report(violations)
200
+ elif args.format == "json":
201
+ output = validator.export_to_json(violations)
202
+ elif args.format == "html":
203
+ output = validator.export_to_html(violations)
204
+
205
+ # Write output
206
+ if args.output:
207
+ output_path = Path(args.output)
208
+ with open(output_path, "w") as f:
209
+ f.write(output)
210
+ print(f"Report written to: {output_path}")
211
+ else:
212
+ # Print to stdout
213
+ print(output)
214
+
215
+ # Exit code based on violations
216
+ if violations:
217
+ # Check if there are any ERROR or CRITICAL violations
218
+ has_errors = any(
219
+ v.severity in [InvariantSeverity.ERROR, InvariantSeverity.CRITICAL]
220
+ for v in violations
221
+ )
222
+ sys.exit(1 if has_errors else 0)
223
+ else:
224
+ sys.exit(0)
225
+
226
+ except Exception as e:
227
+ print(f"Error: {e}", file=sys.stderr)
228
+ if args.verbose:
229
+ import traceback
230
+
231
+ traceback.print_exc()
232
+ sys.exit(2)
233
+
234
+
235
+ if __name__ == "__main__":
236
+ main()
@@ -0,0 +1,85 @@
1
+ """Compatibility layer for optional dependencies.
2
+
3
+ This module provides lightweight stubs and fallback interfaces for optional
4
+ dependencies like numpy, matplotlib, and jsonschema. When these packages are
5
+ not installed, the stubs allow type checking and imports to succeed without
6
+ runtime errors, while maintaining TNFR semantic clarity.
7
+
8
+ TYPE_CHECKING guards
9
+ --------------------
10
+ Use ``if TYPE_CHECKING:`` blocks to import optional typing-only dependencies.
11
+ At runtime, fallback stubs are used instead.
12
+
13
+ Example::
14
+
15
+ from typing import TYPE_CHECKING
16
+ if TYPE_CHECKING:
17
+ import numpy as np
18
+ else:
19
+ from tnfr.compat import numpy_stub as np
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ __all__ = [
25
+ "get_numpy_or_stub",
26
+ "get_matplotlib_or_stub",
27
+ "get_jsonschema_or_stub",
28
+ "numpy_stub",
29
+ "matplotlib_stub",
30
+ "jsonschema_stub",
31
+ ]
32
+
33
+ from typing import Any
34
+
35
+ # Import stubs so they're available as module attributes
36
+ from . import numpy_stub, matplotlib_stub, jsonschema_stub
37
+
38
+
39
+ def get_numpy_or_stub() -> Any:
40
+ """Return numpy module if available, otherwise return a stub.
41
+
42
+ Returns
43
+ -------
44
+ module or stub
45
+ The actual numpy module when installed, or a minimal stub providing
46
+ basic type compatibility for type checking.
47
+ """
48
+ try:
49
+ import numpy
50
+
51
+ return numpy
52
+ except ImportError:
53
+ return numpy_stub
54
+
55
+
56
+ def get_matplotlib_or_stub() -> Any:
57
+ """Return matplotlib module if available, otherwise return a stub.
58
+
59
+ Returns
60
+ -------
61
+ module or stub
62
+ The actual matplotlib module when installed, or a minimal stub.
63
+ """
64
+ try:
65
+ import matplotlib
66
+
67
+ return matplotlib
68
+ except ImportError:
69
+ return matplotlib_stub
70
+
71
+
72
+ def get_jsonschema_or_stub() -> Any:
73
+ """Return jsonschema module if available, otherwise return a stub.
74
+
75
+ Returns
76
+ -------
77
+ module or stub
78
+ The actual jsonschema module when installed, or a minimal stub.
79
+ """
80
+ try:
81
+ import jsonschema
82
+
83
+ return jsonschema
84
+ except ImportError:
85
+ return jsonschema_stub
@@ -0,0 +1,136 @@
1
+ """Dataclass compatibility for Python 3.9+.
2
+
3
+ Provides a wrapper for @dataclass that conditionally applies ``slots=True``
4
+ only on Python 3.10+ where it is supported, maintaining compatibility with
5
+ Python 3.9.
6
+ """
7
+
8
+ import sys
9
+ from dataclasses import dataclass as _dataclass
10
+ from typing import Type, TypeVar, overload
11
+
12
+ _T = TypeVar("_T")
13
+
14
+ # Check if slots parameter is supported (Python 3.10+)
15
+ _SLOTS_SUPPORTED = sys.version_info >= (3, 10)
16
+
17
+
18
+ @overload
19
+ def dataclass(
20
+ cls: Type[_T],
21
+ /,
22
+ *,
23
+ init: bool = True,
24
+ repr: bool = True,
25
+ eq: bool = True,
26
+ order: bool = False,
27
+ unsafe_hash: bool = False,
28
+ frozen: bool = False,
29
+ match_args: bool = True,
30
+ kw_only: bool = False,
31
+ slots: bool = False,
32
+ ) -> Type[_T]: ...
33
+
34
+
35
+ @overload
36
+ def dataclass(
37
+ cls: None = None,
38
+ /,
39
+ *,
40
+ init: bool = True,
41
+ repr: bool = True,
42
+ eq: bool = True,
43
+ order: bool = False,
44
+ unsafe_hash: bool = False,
45
+ frozen: bool = False,
46
+ match_args: bool = True,
47
+ kw_only: bool = False,
48
+ slots: bool = False,
49
+ ) -> Type[_T]: ...
50
+
51
+
52
+ def dataclass(
53
+ cls=None,
54
+ /,
55
+ *,
56
+ init=True,
57
+ repr=True,
58
+ eq=True,
59
+ order=False,
60
+ unsafe_hash=False,
61
+ frozen=False,
62
+ match_args=True,
63
+ kw_only=False,
64
+ slots=False,
65
+ ):
66
+ """Compatibility wrapper for @dataclass supporting Python 3.9+.
67
+
68
+ On Python 3.10+, passes all parameters including ``slots`` to dataclass.
69
+ On Python 3.9, silently ignores ``slots`` parameter since it's not supported.
70
+
71
+ This allows code to use ``slots=True`` for performance benefits on newer
72
+ Python versions while maintaining backward compatibility with 3.9.
73
+
74
+ Parameters
75
+ ----------
76
+ cls : type, optional
77
+ Class to decorate (when used without parentheses).
78
+ init : bool, default True
79
+ Generate __init__ method.
80
+ repr : bool, default True
81
+ Generate __repr__ method.
82
+ eq : bool, default True
83
+ Generate __eq__ method.
84
+ order : bool, default False
85
+ Generate ordering methods.
86
+ unsafe_hash : bool, default False
87
+ Force generation of __hash__ method.
88
+ frozen : bool, default False
89
+ Make instances immutable.
90
+ match_args : bool, default True
91
+ Generate __match_args__ for pattern matching (Python 3.10+).
92
+ kw_only : bool, default False
93
+ Make all fields keyword-only.
94
+ slots : bool, default False
95
+ Generate __slots__ (Python 3.10+ only, ignored on 3.9).
96
+
97
+ Returns
98
+ -------
99
+ type
100
+ Decorated dataclass.
101
+
102
+ Examples
103
+ --------
104
+ >>> from tnfr.compat.dataclass import dataclass
105
+ >>> @dataclass(slots=True) # Works on both 3.9 and 3.10+
106
+ ... class Point:
107
+ ... x: float
108
+ ... y: float
109
+ """
110
+
111
+ # Build kwargs based on Python version
112
+ kwargs = {
113
+ "init": init,
114
+ "repr": repr,
115
+ "eq": eq,
116
+ "order": order,
117
+ "unsafe_hash": unsafe_hash,
118
+ "frozen": frozen,
119
+ }
120
+
121
+ # Add parameters supported in Python 3.10+
122
+ if sys.version_info >= (3, 10):
123
+ kwargs["match_args"] = match_args
124
+ kwargs["kw_only"] = kw_only
125
+ kwargs["slots"] = slots
126
+
127
+ # Handle decorator with and without parentheses
128
+ def wrap(c):
129
+ return _dataclass(c, **kwargs)
130
+
131
+ if cls is None:
132
+ # Called with parentheses: @dataclass(...)
133
+ return wrap
134
+ else:
135
+ # Called without parentheses: @dataclass
136
+ return wrap(cls)
@@ -0,0 +1,61 @@
1
+ """Lightweight stub for jsonschema when it's not installed.
2
+
3
+ This stub provides minimal type compatibility for jsonschema when it's not
4
+ installed, allowing type checking to succeed.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Any
10
+
11
+ __all__ = ["Draft7Validator", "exceptions"]
12
+
13
+
14
+ class _NotInstalledError(RuntimeError):
15
+ """Raised when trying to use jsonschema operations without jsonschema installed."""
16
+
17
+ def __init__(self, operation: str = "jsonschema operation") -> None:
18
+ super().__init__(
19
+ f"Cannot perform {operation}: jsonschema is not installed. "
20
+ "Install it with: pip install jsonschema"
21
+ )
22
+
23
+
24
+ class SchemaError(Exception):
25
+ """Stub for jsonschema.exceptions.SchemaError."""
26
+
27
+ pass
28
+
29
+
30
+ class ValidationError(Exception):
31
+ """Stub for jsonschema.exceptions.ValidationError."""
32
+
33
+ pass
34
+
35
+
36
+ class _ExceptionsStub:
37
+ """Stub for jsonschema.exceptions module."""
38
+
39
+ SchemaError = SchemaError
40
+ ValidationError = ValidationError
41
+
42
+
43
+ class Draft7Validator:
44
+ """Stub for jsonschema.Draft7Validator."""
45
+
46
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
47
+ raise _NotInstalledError("Draft7Validator creation")
48
+
49
+ @classmethod
50
+ def check_schema(cls, schema: Any) -> None:
51
+ raise _NotInstalledError("Draft7Validator.check_schema")
52
+
53
+ def validate(self, instance: Any) -> None:
54
+ raise _NotInstalledError("Draft7Validator.validate")
55
+
56
+ def iter_errors(self, instance: Any) -> Any:
57
+ raise _NotInstalledError("Draft7Validator.iter_errors")
58
+
59
+
60
+ # Module-level stubs
61
+ exceptions = _ExceptionsStub()
@@ -0,0 +1,73 @@
1
+ """Lightweight stub for matplotlib when it's not installed.
2
+
3
+ This stub provides minimal type compatibility for matplotlib when it's not
4
+ installed, allowing type checking to succeed.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Any
10
+
11
+ __all__ = ["pyplot", "axes", "figure"]
12
+
13
+
14
+ class _NotInstalledError(RuntimeError):
15
+ """Raised when trying to use matplotlib operations without matplotlib installed."""
16
+
17
+ def __init__(self, operation: str = "matplotlib operation") -> None:
18
+ super().__init__(
19
+ f"Cannot perform {operation}: matplotlib is not installed. "
20
+ "Install it with: pip install tnfr[viz] or pip install matplotlib"
21
+ )
22
+
23
+
24
+ class Axes:
25
+ """Stub for matplotlib.axes.Axes."""
26
+
27
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
28
+ raise _NotInstalledError("Axes creation")
29
+
30
+
31
+ class Figure:
32
+ """Stub for matplotlib.figure.Figure."""
33
+
34
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
35
+ raise _NotInstalledError("Figure creation")
36
+
37
+
38
+ class _PyPlotStub:
39
+ """Stub for matplotlib.pyplot module."""
40
+
41
+ @staticmethod
42
+ def subplots(*args: Any, **kwargs: Any) -> tuple[Figure, Axes]:
43
+ raise _NotInstalledError("pyplot.subplots")
44
+
45
+ @staticmethod
46
+ def savefig(*args: Any, **kwargs: Any) -> None:
47
+ raise _NotInstalledError("pyplot.savefig")
48
+
49
+ @staticmethod
50
+ def show(*args: Any, **kwargs: Any) -> None:
51
+ raise _NotInstalledError("pyplot.show")
52
+
53
+ @staticmethod
54
+ def figure(*args: Any, **kwargs: Any) -> Figure:
55
+ raise _NotInstalledError("pyplot.figure")
56
+
57
+
58
+ class _AxesStub:
59
+ """Stub for matplotlib.axes module."""
60
+
61
+ Axes = Axes
62
+
63
+
64
+ class _FigureStub:
65
+ """Stub for matplotlib.figure module."""
66
+
67
+ Figure = Figure
68
+
69
+
70
+ # Module-level stubs
71
+ pyplot = _PyPlotStub()
72
+ axes = _AxesStub()
73
+ figure = _FigureStub()