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,253 @@
1
+ """Validation helpers grouped by rule type.
2
+
3
+ These utilities implement the canonical checks required by
4
+ :mod:`tnfr.validation`. They are organised here to make it
5
+ explicit which pieces enforce repetition control, transition
6
+ compatibility or stabilisation thresholds.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from functools import lru_cache
12
+ from typing import TYPE_CHECKING, Any, Mapping
13
+
14
+ from ..alias import get_attr
15
+ from ..constants.aliases import ALIAS_SI
16
+ from ..config.operator_names import (
17
+ CONTRACTION,
18
+ DISSONANCE,
19
+ MUTATION,
20
+ SELF_ORGANIZATION,
21
+ SILENCE,
22
+ canonical_operator_name,
23
+ operator_display_name,
24
+ )
25
+ from ..utils import clamp01
26
+ from ..metrics.common import normalize_dnfr
27
+ from ..types import Glyph
28
+
29
+ if TYPE_CHECKING: # pragma: no cover - only for typing
30
+ from ..operators.grammar import GrammarContext
31
+
32
+ __all__ = [
33
+ "coerce_glyph",
34
+ "glyph_fallback",
35
+ "get_norm",
36
+ "normalized_dnfr",
37
+ "_norm_attr",
38
+ "_si",
39
+ "_check_oz_to_zhir",
40
+ "_check_thol_closure",
41
+ "_check_compatibility",
42
+ ]
43
+
44
+
45
+ def coerce_glyph(val: Any) -> Glyph | Any:
46
+ """Return ``val`` coerced to :class:`Glyph` when possible."""
47
+
48
+ try:
49
+ return Glyph(val)
50
+ except (ValueError, TypeError):
51
+ if isinstance(val, str) and val.startswith("Glyph."):
52
+ _, _, candidate = val.partition(".")
53
+ if candidate:
54
+ try:
55
+ return Glyph(candidate)
56
+ except ValueError:
57
+ pass # Invalid glyph candidate, return as-is
58
+ return val
59
+
60
+
61
+ def glyph_fallback(cand_key: str, fallbacks: Mapping[str, Any]) -> Glyph | str:
62
+ """Determine fallback glyph for ``cand_key``.
63
+
64
+ Note: Compatibility table fallbacks have been deprecated.
65
+ Only explicit fallback overrides are now supported.
66
+ Grammar rules emerge naturally from TNFR structural dynamics.
67
+ """
68
+
69
+ glyph_key = coerce_glyph(cand_key)
70
+ fb_override = fallbacks.get(cand_key)
71
+ if fb_override is not None:
72
+ return coerce_glyph(fb_override)
73
+
74
+ # No automatic fallback - let frequency validation handle compatibility
75
+ return coerce_glyph(cand_key)
76
+
77
+
78
+ # -------------------------
79
+ # Normalisation helpers
80
+ # -------------------------
81
+
82
+
83
+ def get_norm(ctx: "GrammarContext", key: str) -> float:
84
+ """Retrieve a global normalisation value from ``ctx.norms``."""
85
+
86
+ return float(ctx.norms.get(key, 1.0)) or 1.0
87
+
88
+
89
+ def _norm_attr(ctx: "GrammarContext", nd, attr_alias: str, norm_key: str) -> float:
90
+ """Normalise ``attr_alias`` using the global maximum ``norm_key``."""
91
+
92
+ max_val = get_norm(ctx, norm_key)
93
+ return clamp01(abs(get_attr(nd, attr_alias, 0.0)) / max_val)
94
+
95
+
96
+ def _si(nd) -> float:
97
+ """Return the structural sense index for ``nd`` clamped to ``[0, 1]``."""
98
+
99
+ return clamp01(get_attr(nd, ALIAS_SI, 0.5))
100
+
101
+
102
+ def normalized_dnfr(ctx: "GrammarContext", nd) -> float:
103
+ """Normalise |ΔNFR| using the configured global maximum."""
104
+
105
+ return normalize_dnfr(nd, get_norm(ctx, "dnfr_max"))
106
+
107
+
108
+ # -------------------------
109
+ # Translation helpers
110
+ # -------------------------
111
+
112
+
113
+ def _structural_label(value: object) -> str:
114
+ """Return the canonical structural name for ``value`` when possible."""
115
+
116
+ glyph_to_name = _functional_translators()[0]
117
+ coerced = coerce_glyph(value)
118
+ if isinstance(coerced, Glyph):
119
+ name = glyph_to_name(coerced)
120
+ if name is not None:
121
+ return name
122
+ name = glyph_to_name(value if isinstance(value, Glyph) else value)
123
+ if name is not None:
124
+ return name
125
+ if value is None:
126
+ return "unknown"
127
+ return canonical_operator_name(str(value))
128
+
129
+
130
+ # -------------------------
131
+ # Validation rules
132
+ # -------------------------
133
+
134
+
135
+ def _check_oz_to_zhir(ctx: "GrammarContext", n, cand: Glyph | str) -> Glyph | str:
136
+ """Enforce OZ precedents before allowing ZHIR mutations.
137
+
138
+ When mutation is attempted without recent dissonance and low ΔNFR,
139
+ returns DISSONANCE as a fallback glyph (structural requirement).
140
+ """
141
+
142
+ from ..glyph_history import recent_glyph
143
+
144
+ nd = ctx.G.nodes[n]
145
+ cand_glyph = coerce_glyph(cand)
146
+ glyph_to_name, name_to_glyph = _functional_translators()
147
+ cand_name = glyph_to_name(cand_glyph if isinstance(cand_glyph, Glyph) else cand)
148
+ if cand_name == MUTATION:
149
+ cfg = ctx.cfg_canon
150
+ win = int(cfg.get("zhir_requires_oz_window", 3))
151
+ dn_min = float(cfg.get("zhir_dnfr_min", 0.05))
152
+ dissonance_glyph = name_to_glyph(DISSONANCE)
153
+ if dissonance_glyph is None:
154
+ return cand
155
+ norm_dn = normalized_dnfr(ctx, nd)
156
+ recent_glyph(nd, dissonance_glyph.value, win)
157
+ history = tuple(_structural_label(item) for item in nd.get("glyph_history", ()))
158
+ has_recent_dissonance = any(entry == DISSONANCE for entry in history[-win:])
159
+ if not has_recent_dissonance and norm_dn < dn_min:
160
+ # Return dissonance as fallback - structural requirement for mutation
161
+ # Maintains TNFR invariant: mutation requires prior dissonance (§3.4 operator closure)
162
+ return dissonance_glyph
163
+ return cand
164
+
165
+
166
+ def _check_thol_closure(
167
+ ctx: "GrammarContext", n, cand: Glyph | str, st: dict[str, Any]
168
+ ) -> Glyph | str:
169
+ """Close THOL blocks with canonical glyphs once stabilised."""
170
+
171
+ nd = ctx.G.nodes[n]
172
+ if st.get("thol_open", False):
173
+ glyph_to_name, name_to_glyph = _functional_translators()
174
+ cand_glyph = coerce_glyph(cand)
175
+ cand_name = glyph_to_name(cand_glyph if isinstance(cand_glyph, Glyph) else cand)
176
+
177
+ # Allow nested THOL (self_organization) blocks without incrementing length
178
+ # TNFR invariant: operational fractality (§3.7)
179
+ if cand_name == SELF_ORGANIZATION:
180
+ return cand
181
+
182
+ st["thol_len"] = int(st.get("thol_len", 0)) + 1
183
+ cfg = ctx.cfg_canon
184
+ minlen = int(cfg.get("thol_min_len", 2))
185
+ maxlen = int(cfg.get("thol_max_len", 6))
186
+ close_dn = float(cfg.get("thol_close_dnfr", 0.15))
187
+ requires_close = st["thol_len"] >= maxlen or (
188
+ st["thol_len"] >= minlen and normalized_dnfr(ctx, nd) <= close_dn
189
+ )
190
+ if requires_close:
191
+ si_high = float(cfg.get("si_high", 0.66))
192
+ si = _si(nd)
193
+ target_name = SILENCE if si >= si_high else CONTRACTION
194
+ target_glyph = name_to_glyph(target_name)
195
+
196
+ if cand_name == target_name and isinstance(cand_glyph, Glyph):
197
+ return cand_glyph
198
+
199
+ if target_glyph is not None and cand_name in {CONTRACTION, SILENCE}:
200
+ return target_glyph
201
+
202
+ history = tuple(
203
+ _structural_label(item) for item in nd.get("glyph_history", ())
204
+ )
205
+ cand_label = cand_name if cand_name is not None else _structural_label(cand)
206
+ order = (*history[-st["thol_len"] :], cand_label)
207
+ from ..operators import grammar as _grammar
208
+
209
+ raise _grammar.TholClosureError(
210
+ rule="thol-closure",
211
+ candidate=cand_label,
212
+ message=(
213
+ f"{operator_display_name(SELF_ORGANIZATION)} block requires {operator_display_name(target_name)} closure"
214
+ ),
215
+ window=st["thol_len"],
216
+ threshold=close_dn,
217
+ order=order,
218
+ context={
219
+ "thol_min_len": minlen,
220
+ "thol_max_len": maxlen,
221
+ "si": si,
222
+ "si_high": si_high,
223
+ "required_closure": target_name,
224
+ },
225
+ )
226
+ return cand
227
+
228
+
229
+ def _check_compatibility(ctx: "GrammarContext", n, cand: Glyph | str) -> Glyph | str:
230
+ """Verify canonical transition compatibility based on TNFR structural dynamics.
231
+
232
+ Note: Frequency-based validation (R5) has been removed as it was not a
233
+ fundamental physical constraint. Only C1-C3 constraints remain:
234
+ - C1: EXISTENCE & CLOSURE (valid start/end)
235
+ - C2: BOUNDEDNESS (stabilizers required)
236
+ - C3: THRESHOLD PHYSICS (bifurcations need context)
237
+
238
+ These are validated in grammar.py, not here. This function now simply
239
+ allows all transitions - validation happens at sequence level.
240
+ """
241
+ # All transitions allowed - validation at sequence level via C1-C3
242
+ return cand
243
+
244
+
245
+ @lru_cache(maxsize=1)
246
+ def _functional_translators():
247
+ from ..operators import grammar as _grammar
248
+
249
+ return _grammar.glyph_function_name, _grammar.function_name_to_glyph
250
+
251
+
252
+ # NOTE: Compatibility tables deprecated - grammar rules now emerge naturally
253
+ # from TNFR structural dynamics (frequency transitions only)
@@ -0,0 +1,44 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable, Mapping
4
+ from typing import Any, TypeVar
5
+
6
+ from typing import Any, Mapping, TypeVar
7
+
8
+ from ..types import Glyph, NodeId
9
+ from .grammar import GrammarContext
10
+
11
+ __all__ = (
12
+ "coerce_glyph",
13
+ "glyph_fallback",
14
+ "get_norm",
15
+ "normalized_dnfr",
16
+ "_norm_attr",
17
+ "_si",
18
+ "_check_oz_to_zhir",
19
+ "_check_thol_closure",
20
+ "_check_compatibility",
21
+ )
22
+
23
+ _T = TypeVar("_T")
24
+
25
+ def coerce_glyph(val: _T) -> Glyph | _T: ...
26
+ def glyph_fallback(cand_key: str, fallbacks: Mapping[str, Any]) -> Glyph | str: ...
27
+ def get_norm(ctx: GrammarContext, key: str) -> float: ...
28
+ def _norm_attr(
29
+ ctx: GrammarContext, nd: Mapping[str, Any], attr_alias: str, norm_key: str
30
+ ) -> float: ...
31
+ def _si(nd: Mapping[str, Any]) -> float: ...
32
+ def normalized_dnfr(ctx: GrammarContext, nd: Mapping[str, Any]) -> float: ...
33
+ def _check_oz_to_zhir(
34
+ ctx: GrammarContext, n: NodeId, cand: Glyph | str
35
+ ) -> Glyph | str: ...
36
+ def _check_thol_closure(
37
+ ctx: GrammarContext,
38
+ n: NodeId,
39
+ cand: Glyph | str,
40
+ st: dict[str, Any],
41
+ ) -> Glyph | str: ...
42
+ def _check_compatibility(
43
+ ctx: GrammarContext, n: NodeId, cand: Glyph | str
44
+ ) -> Glyph | str: ...
@@ -0,0 +1,279 @@
1
+ """Runtime validation helpers exposing canonical graph contracts."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ from collections.abc import Mapping, MutableMapping, MutableSequence
7
+ from typing import Any, cast
8
+
9
+ import numpy as np
10
+
11
+ from ..alias import (
12
+ get_attr,
13
+ get_theta_attr,
14
+ multi_recompute_abs_max,
15
+ set_attr,
16
+ set_attr_generic,
17
+ set_theta,
18
+ set_theta_attr,
19
+ set_vf,
20
+ )
21
+ from ..constants import DEFAULTS
22
+ from ..types import (
23
+ NodeId,
24
+ TNFRGraph,
25
+ ZERO_BEPI_STORAGE,
26
+ ensure_bepi,
27
+ serialize_bepi,
28
+ _is_scalar,
29
+ )
30
+ from ..utils import clamp, ensure_collection
31
+ from . import ValidationOutcome, Validator
32
+ from .graph import run_validators
33
+
34
+ HistoryLog = MutableSequence[MutableMapping[str, object]]
35
+ """Mutable history buffer storing clamp alerts for strict graphs."""
36
+
37
+ __all__ = (
38
+ "GraphCanonicalValidator",
39
+ "apply_canonical_clamps",
40
+ "validate_canon",
41
+ )
42
+
43
+
44
+ def _max_bepi_magnitude(value: Any) -> float:
45
+ element = ensure_bepi(value)
46
+ mags = [
47
+ (
48
+ float(np.max(np.abs(element.f_continuous)))
49
+ if element.f_continuous.size
50
+ else 0.0
51
+ ),
52
+ float(np.max(np.abs(element.a_discrete))) if element.a_discrete.size else 0.0,
53
+ ]
54
+ return float(max(mags)) if mags else 0.0
55
+
56
+
57
+ def _clamp_component(values: Any, lower: float, upper: float) -> np.ndarray:
58
+ array = np.asarray(values, dtype=np.complex128)
59
+ if array.size == 0:
60
+ return array
61
+ magnitudes = np.abs(array)
62
+ result = array.copy()
63
+ above = magnitudes > upper
64
+ if np.any(above):
65
+ result[above] = array[above] * (upper / magnitudes[above])
66
+ if lower > 0.0:
67
+ below = (magnitudes < lower) & (magnitudes > 0.0)
68
+ if np.any(below):
69
+ result[below] = array[below] * (lower / magnitudes[below])
70
+ return result
71
+
72
+
73
+ def _clamp_bepi(value: Any, lower: float, upper: float) -> Any:
74
+ element = ensure_bepi(value)
75
+ clamped_cont = _clamp_component(element.f_continuous, lower, upper)
76
+ clamped_disc = _clamp_component(element.a_discrete, lower, upper)
77
+ return ensure_bepi(
78
+ {"continuous": clamped_cont, "discrete": clamped_disc, "grid": element.x_grid}
79
+ )
80
+
81
+
82
+ def _log_clamp(
83
+ hist: HistoryLog,
84
+ node: NodeId | None,
85
+ attr: str,
86
+ value: float,
87
+ lo: float,
88
+ hi: float,
89
+ ) -> None:
90
+ if value < lo or value > hi:
91
+ hist.append({"node": node, "attr": attr, "value": float(value)})
92
+
93
+
94
+ def apply_canonical_clamps(
95
+ nd: MutableMapping[str, Any],
96
+ G: TNFRGraph | None = None,
97
+ node: NodeId | None = None,
98
+ ) -> None:
99
+ """Clamp nodal EPI, νf and θ according to canonical bounds."""
100
+
101
+ from ..dynamics.aliases import ALIAS_EPI, ALIAS_VF
102
+
103
+ if G is not None:
104
+ graph_dict = cast(MutableMapping[str, Any], G.graph)
105
+ graph_data: Mapping[str, Any] = graph_dict
106
+ else:
107
+ graph_dict = None
108
+ graph_data = DEFAULTS
109
+ eps_min = float(graph_data.get("EPI_MIN", DEFAULTS["EPI_MIN"]))
110
+ eps_max = float(graph_data.get("EPI_MAX", DEFAULTS["EPI_MAX"]))
111
+ vf_min = float(graph_data.get("VF_MIN", DEFAULTS["VF_MIN"]))
112
+ vf_max = float(graph_data.get("VF_MAX", DEFAULTS["VF_MAX"]))
113
+ theta_wrap = bool(graph_data.get("THETA_WRAP", DEFAULTS["THETA_WRAP"]))
114
+
115
+ raw_epi = get_attr(nd, ALIAS_EPI, ZERO_BEPI_STORAGE, conv=lambda obj: obj)
116
+ was_scalar = _is_scalar(raw_epi)
117
+ original_scalar_value = float(raw_epi) if was_scalar else None
118
+ epi = ensure_bepi(raw_epi)
119
+ vf = get_attr(nd, ALIAS_VF, 0.0)
120
+ th_val = get_theta_attr(nd, 0.0)
121
+ th = 0.0 if th_val is None else float(th_val)
122
+
123
+ strict = bool(
124
+ graph_data.get("VALIDATORS_STRICT", DEFAULTS.get("VALIDATORS_STRICT", False))
125
+ )
126
+ if strict and graph_dict is not None:
127
+ history = cast(MutableMapping[str, Any], graph_dict.setdefault("history", {}))
128
+ alerts = history.get("clamp_alerts")
129
+ if alerts is None:
130
+ hist = cast(HistoryLog, history.setdefault("clamp_alerts", []))
131
+ elif isinstance(alerts, MutableSequence):
132
+ hist = cast(HistoryLog, alerts)
133
+ else:
134
+ materialized = ensure_collection(alerts, max_materialize=None)
135
+ hist = cast(HistoryLog, list(materialized))
136
+ history["clamp_alerts"] = hist
137
+ epi_mag = _max_bepi_magnitude(epi)
138
+ _log_clamp(hist, node, "EPI", epi_mag, eps_min, eps_max)
139
+ _log_clamp(hist, node, "VF", float(vf), vf_min, vf_max)
140
+
141
+ clamped_epi = _clamp_bepi(epi, eps_min, eps_max)
142
+ if was_scalar:
143
+ clamped_value = float(clamp(original_scalar_value, eps_min, eps_max))
144
+ set_attr_generic(nd, ALIAS_EPI, clamped_value, conv=lambda obj: obj)
145
+ else:
146
+ set_attr_generic(
147
+ nd, ALIAS_EPI, serialize_bepi(clamped_epi), conv=lambda obj: obj
148
+ )
149
+
150
+ vf_val = float(clamp(vf, vf_min, vf_max))
151
+ if G is not None and node is not None:
152
+ set_vf(G, node, vf_val, update_max=False)
153
+ else:
154
+ set_attr(nd, ALIAS_VF, vf_val)
155
+
156
+ if theta_wrap:
157
+ new_th = (th + math.pi) % (2 * math.pi) - math.pi
158
+ if G is not None and node is not None:
159
+ set_theta(G, node, new_th)
160
+ else:
161
+ set_theta_attr(nd, new_th)
162
+
163
+
164
+ class GraphCanonicalValidator(Validator[TNFRGraph]):
165
+ """Validator enforcing canonical runtime contracts on TNFR graphs."""
166
+
167
+ recompute_frequency_maxima: bool
168
+ enforce_graph_validators: bool
169
+
170
+ def __init__(
171
+ self,
172
+ *,
173
+ recompute_frequency_maxima: bool = True,
174
+ enforce_graph_validators: bool = True,
175
+ ) -> None:
176
+ self.recompute_frequency_maxima = bool(recompute_frequency_maxima)
177
+ self.enforce_graph_validators = bool(enforce_graph_validators)
178
+
179
+ def _diff_after_clamp(
180
+ self,
181
+ before: Mapping[str, float],
182
+ after: Mapping[str, float],
183
+ ) -> Mapping[str, Mapping[str, float]]:
184
+ changes: dict[str, Mapping[str, float]] = {}
185
+ for key, before_val in before.items():
186
+ after_val = after.get(key)
187
+ if after_val is None:
188
+ continue
189
+ if math.isclose(before_val, after_val, rel_tol=0.0, abs_tol=1e-12):
190
+ continue
191
+ changes[key] = {"before": before_val, "after": after_val}
192
+ return changes
193
+
194
+ def validate(self, subject: TNFRGraph, /, **_: Any) -> ValidationOutcome[TNFRGraph]:
195
+ """Clamp nodal attributes, refresh νf maxima and run graph validators."""
196
+
197
+ from ..dynamics.aliases import ALIAS_EPI, ALIAS_VF
198
+
199
+ clamped_nodes: list[dict[str, Any]] = []
200
+ for node, data in subject.nodes(data=True):
201
+ mapping = cast(MutableMapping[str, Any], data)
202
+ before = {
203
+ "EPI": _max_bepi_magnitude(
204
+ get_attr(
205
+ mapping, ALIAS_EPI, ZERO_BEPI_STORAGE, conv=lambda obj: obj
206
+ )
207
+ ),
208
+ "VF": float(get_attr(mapping, ALIAS_VF, 0.0)),
209
+ "THETA": float(get_theta_attr(mapping, 0.0) or 0.0),
210
+ }
211
+ apply_canonical_clamps(mapping, subject, cast(NodeId, node))
212
+ after = {
213
+ "EPI": _max_bepi_magnitude(
214
+ get_attr(
215
+ mapping, ALIAS_EPI, ZERO_BEPI_STORAGE, conv=lambda obj: obj
216
+ )
217
+ ),
218
+ "VF": float(get_attr(mapping, ALIAS_VF, 0.0)),
219
+ "THETA": float(get_theta_attr(mapping, 0.0) or 0.0),
220
+ }
221
+ changes = self._diff_after_clamp(before, after)
222
+ if changes:
223
+ clamped_nodes.append({"node": node, "attributes": changes})
224
+
225
+ maxima: Mapping[str, float] = {}
226
+ if self.recompute_frequency_maxima:
227
+ maxima = multi_recompute_abs_max(subject, {"_vfmax": ALIAS_VF})
228
+ subject.graph.update(maxima)
229
+
230
+ errors: list[str] = []
231
+ if self.enforce_graph_validators:
232
+ try:
233
+ run_validators(subject)
234
+ except Exception as exc: # pragma: no cover - defensive
235
+ errors.append(str(exc))
236
+
237
+ summary: dict[str, Any] = {
238
+ "clamped": tuple(clamped_nodes),
239
+ "maxima": dict(maxima),
240
+ }
241
+ if errors:
242
+ summary["errors"] = tuple(errors)
243
+
244
+ passed = not errors
245
+ artifacts: dict[str, Any] = {}
246
+ if clamped_nodes:
247
+ artifacts["clamped_nodes"] = clamped_nodes
248
+ if maxima:
249
+ artifacts["maxima"] = dict(maxima)
250
+
251
+ return ValidationOutcome(
252
+ subject=subject,
253
+ passed=passed,
254
+ summary=summary,
255
+ artifacts=artifacts or None,
256
+ )
257
+
258
+ def report(self, outcome: ValidationOutcome[TNFRGraph]) -> str:
259
+ """Return a concise textual summary of ``outcome``."""
260
+
261
+ if outcome.passed:
262
+ clamped = outcome.summary.get("clamped")
263
+ if clamped:
264
+ return "Graph canonical validation applied clamps successfully."
265
+ return "Graph canonical validation passed without adjustments."
266
+
267
+ errors = outcome.summary.get("errors")
268
+ if not errors:
269
+ return "Graph canonical validation failed without diagnostic details."
270
+ if isinstance(errors, (list, tuple)):
271
+ return "Graph canonical validation errors: " + ", ".join(map(str, errors))
272
+ return f"Graph canonical validation error: {errors}"
273
+
274
+
275
+ def validate_canon(G: TNFRGraph) -> ValidationOutcome[TNFRGraph]:
276
+ """Validate ``G`` using :class:`GraphCanonicalValidator`."""
277
+
278
+ validator = GraphCanonicalValidator()
279
+ return validator.validate(G)
@@ -0,0 +1,28 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import MutableMapping
4
+ from typing import Any
5
+
6
+ from ..types import NodeId, TNFRGraph
7
+ from . import ValidationOutcome, Validator
8
+
9
+ class GraphCanonicalValidator(Validator[TNFRGraph]):
10
+ def __init__(
11
+ self,
12
+ *,
13
+ recompute_frequency_maxima: bool = ...,
14
+ enforce_graph_validators: bool = ...,
15
+ ) -> None: ...
16
+ def validate(
17
+ self, subject: TNFRGraph, /, **kwargs: Any
18
+ ) -> ValidationOutcome[TNFRGraph]: ...
19
+ def report(self, outcome: ValidationOutcome[TNFRGraph]) -> str: ...
20
+
21
+ def apply_canonical_clamps(
22
+ nd: MutableMapping[str, Any],
23
+ G: TNFRGraph | None = ...,
24
+ node: NodeId | None = ...,
25
+ ) -> None: ...
26
+ def validate_canon(G: TNFRGraph) -> ValidationOutcome[TNFRGraph]: ...
27
+
28
+ __all__: tuple[str, ...]