tnfr 6.0.0__py3-none-any.whl → 7.0.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 (176) hide show
  1. tnfr/__init__.py +50 -5
  2. tnfr/__init__.pyi +0 -7
  3. tnfr/_compat.py +0 -1
  4. tnfr/_generated_version.py +34 -0
  5. tnfr/_version.py +44 -2
  6. tnfr/alias.py +14 -13
  7. tnfr/alias.pyi +5 -37
  8. tnfr/cache.py +9 -729
  9. tnfr/cache.pyi +8 -224
  10. tnfr/callback_utils.py +16 -31
  11. tnfr/callback_utils.pyi +3 -29
  12. tnfr/cli/__init__.py +17 -11
  13. tnfr/cli/__init__.pyi +0 -21
  14. tnfr/cli/arguments.py +175 -14
  15. tnfr/cli/arguments.pyi +5 -11
  16. tnfr/cli/execution.py +434 -48
  17. tnfr/cli/execution.pyi +14 -24
  18. tnfr/cli/utils.py +20 -3
  19. tnfr/cli/utils.pyi +5 -5
  20. tnfr/config/__init__.py +2 -1
  21. tnfr/config/__init__.pyi +2 -0
  22. tnfr/config/feature_flags.py +83 -0
  23. tnfr/config/init.py +1 -1
  24. tnfr/config/operator_names.py +1 -14
  25. tnfr/config/presets.py +6 -26
  26. tnfr/constants/__init__.py +10 -13
  27. tnfr/constants/__init__.pyi +10 -22
  28. tnfr/constants/aliases.py +31 -0
  29. tnfr/constants/core.py +4 -3
  30. tnfr/constants/init.py +1 -1
  31. tnfr/constants/metric.py +3 -3
  32. tnfr/dynamics/__init__.py +64 -10
  33. tnfr/dynamics/__init__.pyi +3 -4
  34. tnfr/dynamics/adaptation.py +79 -13
  35. tnfr/dynamics/aliases.py +10 -9
  36. tnfr/dynamics/coordination.py +77 -35
  37. tnfr/dynamics/dnfr.py +575 -274
  38. tnfr/dynamics/dnfr.pyi +1 -10
  39. tnfr/dynamics/integrators.py +47 -33
  40. tnfr/dynamics/integrators.pyi +0 -1
  41. tnfr/dynamics/runtime.py +489 -129
  42. tnfr/dynamics/sampling.py +2 -0
  43. tnfr/dynamics/selectors.py +101 -62
  44. tnfr/execution.py +15 -8
  45. tnfr/execution.pyi +5 -25
  46. tnfr/flatten.py +7 -3
  47. tnfr/flatten.pyi +1 -8
  48. tnfr/gamma.py +22 -26
  49. tnfr/gamma.pyi +0 -6
  50. tnfr/glyph_history.py +37 -26
  51. tnfr/glyph_history.pyi +1 -19
  52. tnfr/glyph_runtime.py +16 -0
  53. tnfr/glyph_runtime.pyi +9 -0
  54. tnfr/immutable.py +20 -15
  55. tnfr/immutable.pyi +4 -7
  56. tnfr/initialization.py +5 -7
  57. tnfr/initialization.pyi +1 -9
  58. tnfr/io.py +6 -305
  59. tnfr/io.pyi +13 -8
  60. tnfr/mathematics/__init__.py +81 -0
  61. tnfr/mathematics/backend.py +426 -0
  62. tnfr/mathematics/dynamics.py +398 -0
  63. tnfr/mathematics/epi.py +254 -0
  64. tnfr/mathematics/generators.py +222 -0
  65. tnfr/mathematics/metrics.py +119 -0
  66. tnfr/mathematics/operators.py +233 -0
  67. tnfr/mathematics/operators_factory.py +71 -0
  68. tnfr/mathematics/projection.py +78 -0
  69. tnfr/mathematics/runtime.py +173 -0
  70. tnfr/mathematics/spaces.py +247 -0
  71. tnfr/mathematics/transforms.py +292 -0
  72. tnfr/metrics/__init__.py +10 -10
  73. tnfr/metrics/coherence.py +123 -94
  74. tnfr/metrics/common.py +22 -13
  75. tnfr/metrics/common.pyi +42 -11
  76. tnfr/metrics/core.py +72 -14
  77. tnfr/metrics/diagnosis.py +48 -57
  78. tnfr/metrics/diagnosis.pyi +3 -7
  79. tnfr/metrics/export.py +3 -5
  80. tnfr/metrics/glyph_timing.py +41 -31
  81. tnfr/metrics/reporting.py +13 -6
  82. tnfr/metrics/sense_index.py +884 -114
  83. tnfr/metrics/trig.py +167 -11
  84. tnfr/metrics/trig.pyi +1 -0
  85. tnfr/metrics/trig_cache.py +112 -15
  86. tnfr/node.py +400 -17
  87. tnfr/node.pyi +55 -38
  88. tnfr/observers.py +111 -8
  89. tnfr/observers.pyi +0 -15
  90. tnfr/ontosim.py +9 -6
  91. tnfr/ontosim.pyi +0 -5
  92. tnfr/operators/__init__.py +529 -42
  93. tnfr/operators/__init__.pyi +14 -0
  94. tnfr/operators/definitions.py +350 -18
  95. tnfr/operators/definitions.pyi +0 -14
  96. tnfr/operators/grammar.py +760 -0
  97. tnfr/operators/jitter.py +28 -22
  98. tnfr/operators/registry.py +7 -12
  99. tnfr/operators/registry.pyi +0 -2
  100. tnfr/operators/remesh.py +38 -61
  101. tnfr/rng.py +17 -300
  102. tnfr/schemas/__init__.py +8 -0
  103. tnfr/schemas/grammar.json +94 -0
  104. tnfr/selector.py +3 -4
  105. tnfr/selector.pyi +1 -1
  106. tnfr/sense.py +22 -24
  107. tnfr/sense.pyi +0 -7
  108. tnfr/structural.py +504 -21
  109. tnfr/structural.pyi +41 -18
  110. tnfr/telemetry/__init__.py +23 -1
  111. tnfr/telemetry/cache_metrics.py +226 -0
  112. tnfr/telemetry/nu_f.py +423 -0
  113. tnfr/telemetry/nu_f.pyi +123 -0
  114. tnfr/tokens.py +1 -4
  115. tnfr/tokens.pyi +1 -6
  116. tnfr/trace.py +20 -53
  117. tnfr/trace.pyi +9 -37
  118. tnfr/types.py +244 -15
  119. tnfr/types.pyi +200 -14
  120. tnfr/units.py +69 -0
  121. tnfr/units.pyi +16 -0
  122. tnfr/utils/__init__.py +107 -48
  123. tnfr/utils/__init__.pyi +80 -11
  124. tnfr/utils/cache.py +1705 -65
  125. tnfr/utils/cache.pyi +370 -58
  126. tnfr/utils/chunks.py +104 -0
  127. tnfr/utils/chunks.pyi +21 -0
  128. tnfr/utils/data.py +95 -5
  129. tnfr/utils/data.pyi +8 -17
  130. tnfr/utils/graph.py +2 -4
  131. tnfr/utils/init.py +31 -7
  132. tnfr/utils/init.pyi +4 -11
  133. tnfr/utils/io.py +313 -14
  134. tnfr/{helpers → utils}/numeric.py +50 -24
  135. tnfr/utils/numeric.pyi +21 -0
  136. tnfr/validation/__init__.py +92 -4
  137. tnfr/validation/__init__.pyi +77 -17
  138. tnfr/validation/compatibility.py +79 -43
  139. tnfr/validation/compatibility.pyi +4 -6
  140. tnfr/validation/grammar.py +55 -133
  141. tnfr/validation/grammar.pyi +37 -8
  142. tnfr/validation/graph.py +138 -0
  143. tnfr/validation/graph.pyi +17 -0
  144. tnfr/validation/rules.py +161 -74
  145. tnfr/validation/rules.pyi +55 -18
  146. tnfr/validation/runtime.py +263 -0
  147. tnfr/validation/runtime.pyi +31 -0
  148. tnfr/validation/soft_filters.py +170 -0
  149. tnfr/validation/soft_filters.pyi +37 -0
  150. tnfr/validation/spectral.py +159 -0
  151. tnfr/validation/spectral.pyi +46 -0
  152. tnfr/validation/syntax.py +28 -139
  153. tnfr/validation/syntax.pyi +7 -4
  154. tnfr/validation/window.py +39 -0
  155. tnfr/validation/window.pyi +1 -0
  156. tnfr/viz/__init__.py +9 -0
  157. tnfr/viz/matplotlib.py +246 -0
  158. {tnfr-6.0.0.dist-info → tnfr-7.0.0.dist-info}/METADATA +63 -19
  159. tnfr-7.0.0.dist-info/RECORD +185 -0
  160. {tnfr-6.0.0.dist-info → tnfr-7.0.0.dist-info}/licenses/LICENSE.md +1 -1
  161. tnfr/constants_glyphs.py +0 -16
  162. tnfr/constants_glyphs.pyi +0 -12
  163. tnfr/grammar.py +0 -25
  164. tnfr/grammar.pyi +0 -13
  165. tnfr/helpers/__init__.py +0 -151
  166. tnfr/helpers/__init__.pyi +0 -66
  167. tnfr/helpers/numeric.pyi +0 -12
  168. tnfr/presets.py +0 -15
  169. tnfr/presets.pyi +0 -7
  170. tnfr/utils/io.pyi +0 -10
  171. tnfr/utils/validators.py +0 -130
  172. tnfr/utils/validators.pyi +0 -19
  173. tnfr-6.0.0.dist-info/RECORD +0 -157
  174. {tnfr-6.0.0.dist-info → tnfr-7.0.0.dist-info}/WHEEL +0 -0
  175. {tnfr-6.0.0.dist-info → tnfr-7.0.0.dist-info}/entry_points.txt +0 -0
  176. {tnfr-6.0.0.dist-info → tnfr-7.0.0.dist-info}/top_level.txt +0 -0
@@ -1,59 +1,95 @@
1
- """Canonical TNFR compatibility tables.
2
-
3
- This module centralises the canonical transitions that keep TNFR
4
- coherence consistent across refactors. It is intentionally lightweight
5
- so :mod:`tnfr.validation.grammar` can depend on it without introducing
6
- cycles.
7
- """
1
+ """Canonical TNFR compatibility tables expressed via structural operators."""
8
2
 
9
3
  from __future__ import annotations
10
4
 
5
+ from ..config.operator_names import (
6
+ COHERENCE,
7
+ CONTRACTION,
8
+ COUPLING,
9
+ DISSONANCE,
10
+ EMISSION,
11
+ EXPANSION,
12
+ MUTATION,
13
+ RECEPTION,
14
+ RESONANCE,
15
+ SELF_ORGANIZATION,
16
+ SILENCE,
17
+ TRANSITION,
18
+ )
19
+ from ..operators import grammar as _grammar
11
20
  from ..types import Glyph
12
21
 
13
22
  __all__ = ["CANON_COMPAT", "CANON_FALLBACK"]
14
23
 
15
- # Canonical compatibilities (allowed next glyphs)
16
- CANON_COMPAT: dict[Glyph, set[Glyph]] = {
24
+ # Canonical compatibilities (allowed next operators) expressed via structural names
25
+ _STRUCTURAL_COMPAT: dict[str, set[str]] = {
17
26
  # Opening / initiation
18
- Glyph.AL: {Glyph.EN, Glyph.RA, Glyph.NAV, Glyph.VAL, Glyph.UM},
19
- Glyph.EN: {Glyph.IL, Glyph.UM, Glyph.RA, Glyph.NAV},
27
+ EMISSION: {RECEPTION, RESONANCE, TRANSITION, EXPANSION, COUPLING},
28
+ RECEPTION: {COHERENCE, COUPLING, RESONANCE, TRANSITION},
20
29
  # Stabilisation / diffusion / coupling
21
- Glyph.IL: {Glyph.RA, Glyph.VAL, Glyph.UM, Glyph.SHA},
22
- Glyph.UM: {Glyph.RA, Glyph.IL, Glyph.VAL, Glyph.NAV},
23
- Glyph.RA: {Glyph.IL, Glyph.VAL, Glyph.UM, Glyph.NAV},
24
- Glyph.VAL: {Glyph.UM, Glyph.RA, Glyph.IL, Glyph.NAV},
30
+ COHERENCE: {RESONANCE, EXPANSION, COUPLING, SILENCE},
31
+ COUPLING: {RESONANCE, COHERENCE, EXPANSION, TRANSITION},
32
+ RESONANCE: {COHERENCE, EXPANSION, COUPLING, TRANSITION},
33
+ EXPANSION: {COUPLING, RESONANCE, COHERENCE, TRANSITION},
25
34
  # Dissonance → transition → mutation
26
- Glyph.OZ: {Glyph.ZHIR, Glyph.NAV},
27
- Glyph.ZHIR: {Glyph.IL, Glyph.NAV},
28
- Glyph.NAV: {Glyph.OZ, Glyph.ZHIR, Glyph.RA, Glyph.IL, Glyph.UM},
35
+ DISSONANCE: {MUTATION, TRANSITION},
36
+ MUTATION: {COHERENCE, TRANSITION},
37
+ TRANSITION: {DISSONANCE, MUTATION, RESONANCE, COHERENCE, COUPLING},
29
38
  # Closures / latent states
30
- Glyph.SHA: {Glyph.AL, Glyph.EN},
31
- Glyph.NUL: {Glyph.AL, Glyph.IL},
39
+ SILENCE: {EMISSION, RECEPTION},
40
+ CONTRACTION: {EMISSION, COHERENCE},
32
41
  # Self-organising blocks
33
- Glyph.THOL: {
34
- Glyph.OZ,
35
- Glyph.ZHIR,
36
- Glyph.NAV,
37
- Glyph.RA,
38
- Glyph.IL,
39
- Glyph.UM,
40
- Glyph.SHA,
41
- Glyph.NUL,
42
+ SELF_ORGANIZATION: {
43
+ DISSONANCE,
44
+ MUTATION,
45
+ TRANSITION,
46
+ RESONANCE,
47
+ COHERENCE,
48
+ COUPLING,
49
+ SILENCE,
50
+ CONTRACTION,
42
51
  },
43
52
  }
44
53
 
45
- # Canonical fallbacks when a transition is not allowed
46
- CANON_FALLBACK: dict[Glyph, Glyph] = {
47
- Glyph.AL: Glyph.EN,
48
- Glyph.EN: Glyph.IL,
49
- Glyph.IL: Glyph.RA,
50
- Glyph.NAV: Glyph.RA,
51
- Glyph.NUL: Glyph.AL,
52
- Glyph.OZ: Glyph.ZHIR,
53
- Glyph.RA: Glyph.IL,
54
- Glyph.SHA: Glyph.AL,
55
- Glyph.THOL: Glyph.NAV,
56
- Glyph.UM: Glyph.RA,
57
- Glyph.VAL: Glyph.RA,
58
- Glyph.ZHIR: Glyph.IL,
54
+
55
+ def _name_to_glyph(name: str) -> Glyph:
56
+ glyph = _grammar.function_name_to_glyph(name)
57
+ if glyph is None:
58
+ raise KeyError(f"No glyph mapped to structural operator '{name}'")
59
+ return glyph
60
+
61
+
62
+ def _translate_structural() -> tuple[dict[Glyph, set[Glyph]], dict[Glyph, Glyph]]:
63
+ compat: dict[Glyph, set[Glyph]] = {}
64
+ for src, targets in _STRUCTURAL_COMPAT.items():
65
+ src_glyph = _name_to_glyph(src)
66
+ compat[src_glyph] = {_name_to_glyph(target) for target in targets}
67
+ fallback: dict[Glyph, Glyph] = {}
68
+ for src, target in _STRUCTURAL_FALLBACK.items():
69
+ fallback[_name_to_glyph(src)] = _name_to_glyph(target)
70
+ return compat, fallback
71
+
72
+
73
+ # Canonical fallbacks when a transition is not allowed (structural names)
74
+ _STRUCTURAL_FALLBACK: dict[str, str] = {
75
+ EMISSION: RECEPTION,
76
+ RECEPTION: COHERENCE,
77
+ COHERENCE: RESONANCE,
78
+ TRANSITION: RESONANCE,
79
+ CONTRACTION: EMISSION,
80
+ DISSONANCE: MUTATION,
81
+ RESONANCE: COHERENCE,
82
+ SILENCE: EMISSION,
83
+ SELF_ORGANIZATION: TRANSITION,
84
+ COUPLING: RESONANCE,
85
+ EXPANSION: RESONANCE,
86
+ MUTATION: COHERENCE,
59
87
  }
88
+
89
+
90
+ CANON_COMPAT, CANON_FALLBACK = _translate_structural()
91
+
92
+ # Re-export structural tables for internal consumers that operate on functional
93
+ # identifiers without exposing them as part of the public API.
94
+ _STRUCTURAL_COMPAT_TABLE = _STRUCTURAL_COMPAT
95
+ _STRUCTURAL_FALLBACK_TABLE = _STRUCTURAL_FALLBACK
@@ -1,8 +1,6 @@
1
- from typing import Any
1
+ from ..types import Glyph
2
2
 
3
- __all__: Any
3
+ __all__ = ("CANON_COMPAT", "CANON_FALLBACK")
4
4
 
5
- def __getattr__(name: str) -> Any: ...
6
-
7
- CANON_COMPAT: Any
8
- CANON_FALLBACK: Any
5
+ CANON_COMPAT: dict[Glyph, set[Glyph]]
6
+ CANON_FALLBACK: dict[Glyph, Glyph]
@@ -1,149 +1,71 @@
1
- """Canonical grammar enforcement utilities."""
1
+ """Deprecated compatibility wrapper for :mod:`tnfr.operators.grammar`.
2
2
 
3
- from __future__ import annotations
3
+ Import :mod:`tnfr.operators.grammar` directly to access the canonical grammar
4
+ interfaces. This module remains as a thin shim for historical callers and will
5
+ be removed once downstream packages adopt the canonical entry point.
6
+ """
4
7
 
5
- from collections.abc import Iterable
6
- from dataclasses import dataclass
7
- from typing import Any, Optional, TYPE_CHECKING, cast
8
+ from __future__ import annotations
8
9
 
9
- from ..constants import DEFAULTS, get_param
10
- from ..operators import apply_glyph
11
- from ..types import Glyph, NodeId, TNFRGraph
12
- from .compatibility import CANON_COMPAT
13
- from . import rules as _rules
10
+ import warnings
11
+ from typing import TYPE_CHECKING
12
+
13
+ from ..operators.grammar import (
14
+ GrammarContext,
15
+ MutationPreconditionError,
16
+ RepeatWindowError,
17
+ record_grammar_violation as _canonical_record_violation,
18
+ SequenceSyntaxError,
19
+ SequenceValidationResult,
20
+ StructuralGrammarError,
21
+ TholClosureError,
22
+ TransitionCompatibilityError,
23
+ _gram_state,
24
+ apply_glyph_with_grammar,
25
+ enforce_canonical_grammar,
26
+ on_applied_glyph,
27
+ parse_sequence,
28
+ validate_sequence,
29
+ )
14
30
 
15
31
  if TYPE_CHECKING: # pragma: no cover - typing only
16
- from ..node import NodeProtocol
32
+ from ..types import NodeId, TNFRGraph
17
33
 
18
34
  __all__ = [
19
35
  "GrammarContext",
36
+ "StructuralGrammarError",
37
+ "RepeatWindowError",
38
+ "MutationPreconditionError",
39
+ "TholClosureError",
40
+ "TransitionCompatibilityError",
41
+ "SequenceSyntaxError",
42
+ "SequenceValidationResult",
43
+ "record_grammar_violation",
20
44
  "_gram_state",
45
+ "apply_glyph_with_grammar",
21
46
  "enforce_canonical_grammar",
22
47
  "on_applied_glyph",
23
- "apply_glyph_with_grammar",
48
+ "parse_sequence",
49
+ "validate_sequence",
24
50
  ]
25
51
 
52
+ warnings.warn(
53
+ "'tnfr.validation.grammar' is deprecated; import from 'tnfr.operators.grammar' "
54
+ "for the canonical grammar interface.",
55
+ DeprecationWarning,
56
+ stacklevel=2,
57
+ )
26
58
 
27
- @dataclass
28
- class GrammarContext:
29
- """Shared context for grammar helpers.
30
-
31
- Collects graph-level settings to reduce positional parameters across
32
- helper functions.
33
- """
34
-
35
- G: TNFRGraph
36
- cfg_soft: dict[str, Any]
37
- cfg_canon: dict[str, Any]
38
- norms: dict[str, Any]
39
-
40
- @classmethod
41
- def from_graph(cls, G: TNFRGraph) -> "GrammarContext":
42
- """Create a :class:`GrammarContext` for ``G``."""
43
-
44
- return cls(
45
- G=G,
46
- cfg_soft=G.graph.get("GRAMMAR", DEFAULTS.get("GRAMMAR", {})),
47
- cfg_canon=G.graph.get(
48
- "GRAMMAR_CANON", DEFAULTS.get("GRAMMAR_CANON", {})
49
- ),
50
- norms=G.graph.get("_sel_norms") or {},
51
- )
52
-
53
-
54
- # -------------------------
55
- # Per-node grammar state
56
- # -------------------------
57
-
58
- def _gram_state(nd: dict[str, Any]) -> dict[str, Any]:
59
- """Create or return the node grammar state."""
60
-
61
- return nd.setdefault("_GRAM", {"thol_open": False, "thol_len": 0})
62
-
63
-
64
- # -------------------------
65
- # Core: enforce grammar on a candidate
66
- # -------------------------
67
-
68
- def enforce_canonical_grammar(
69
- G: TNFRGraph,
70
- n: NodeId,
71
- cand: Glyph | str,
72
- ctx: Optional[GrammarContext] = None,
73
- ) -> Glyph | str:
74
- """Validate and adjust a candidate glyph according to canonical grammar."""
75
59
 
76
- if ctx is None:
77
- ctx = GrammarContext.from_graph(G)
78
-
79
- nd = ctx.G.nodes[n]
80
- st = _gram_state(nd)
81
-
82
- raw_cand = cand
83
- cand = _rules.coerce_glyph(cand)
84
- input_was_str = isinstance(raw_cand, str)
85
-
86
- # 0) If glyphs outside the alphabet arrive, leave untouched
87
- if not isinstance(cand, Glyph) or cand not in CANON_COMPAT:
88
- return raw_cand if input_was_str else cand
89
-
90
- original = cand
91
- cand = _rules._check_repeats(ctx, n, cand)
92
-
93
- cand = _rules._maybe_force(ctx, n, cand, original, _rules.normalized_dnfr, "force_dnfr")
94
- cand = _rules._maybe_force(ctx, n, cand, original, _rules._accel_norm, "force_accel")
95
- cand = _rules._check_oz_to_zhir(ctx, n, cand)
96
- cand = _rules._check_thol_closure(ctx, n, cand, st)
97
- cand = _rules._check_compatibility(ctx, n, cand)
98
-
99
- coerced_final = _rules.coerce_glyph(cand)
100
- if input_was_str:
101
- if isinstance(coerced_final, Glyph):
102
- return coerced_final.value
103
- return str(cand)
104
- return coerced_final if isinstance(coerced_final, Glyph) else cand
105
-
106
-
107
- # -------------------------
108
- # Post-selection: update grammar state
109
- # -------------------------
110
-
111
- def on_applied_glyph(G: TNFRGraph, n: NodeId, applied: Glyph | str) -> None:
112
- nd = G.nodes[n]
113
- st = _gram_state(nd)
114
- try:
115
- glyph = applied if isinstance(applied, Glyph) else Glyph(str(applied))
116
- except ValueError:
117
- glyph = None
118
-
119
- if glyph is Glyph.THOL:
120
- st["thol_open"] = True
121
- st["thol_len"] = 0
122
- elif glyph in (Glyph.SHA, Glyph.NUL):
123
- st["thol_open"] = False
124
- st["thol_len"] = 0
125
-
126
-
127
- # -------------------------
128
- # Direct application with canonical grammar
129
- # -------------------------
130
-
131
- def apply_glyph_with_grammar(
132
- G: TNFRGraph,
133
- nodes: Optional[Iterable[NodeId | "NodeProtocol"]],
134
- glyph: Glyph | str,
135
- window: Optional[int] = None,
60
+ def record_grammar_violation(
61
+ G: "TNFRGraph", node: "NodeId", error: StructuralGrammarError, *, stage: str
136
62
  ) -> None:
137
- """Apply ``glyph`` to ``nodes`` enforcing the canonical grammar."""
138
-
139
- if window is None:
140
- window = get_param(G, "GLYPH_HYSTERESIS_WINDOW")
141
-
142
- g_str = glyph.value if isinstance(glyph, Glyph) else str(glyph)
143
- iter_nodes = G.nodes() if nodes is None else nodes
144
- ctx = GrammarContext.from_graph(G)
145
- for node_ref in iter_nodes:
146
- node_id = cast(NodeId, getattr(node_ref, "n", node_ref))
147
- g_eff = enforce_canonical_grammar(G, node_id, g_str, ctx)
148
- apply_glyph(G, node_id, g_eff, window=window)
149
- on_applied_glyph(G, node_id, g_eff)
63
+ """Bridge to :func:`tnfr.operators.grammar.record_grammar_violation`."""
64
+
65
+ warnings.warn(
66
+ "'tnfr.validation.grammar.record_grammar_violation' is deprecated; "
67
+ "use 'tnfr.operators.grammar.record_grammar_violation' instead.",
68
+ DeprecationWarning,
69
+ stacklevel=2,
70
+ )
71
+ _canonical_record_violation(G, node, error, stage=stage)
@@ -1,11 +1,40 @@
1
- from typing import Any
1
+ from ..operators.grammar import (
2
+ GrammarContext,
3
+ StructuralGrammarError,
4
+ RepeatWindowError,
5
+ MutationPreconditionError,
6
+ TholClosureError,
7
+ TransitionCompatibilityError,
8
+ SequenceSyntaxError,
9
+ SequenceValidationResult,
10
+ _gram_state,
11
+ apply_glyph_with_grammar,
12
+ enforce_canonical_grammar,
13
+ on_applied_glyph,
14
+ parse_sequence,
15
+ validate_sequence,
16
+ )
17
+ from ..types import NodeId, TNFRGraph
2
18
 
3
- __all__: Any
19
+ __all__ = (
20
+ "GrammarContext",
21
+ "StructuralGrammarError",
22
+ "RepeatWindowError",
23
+ "MutationPreconditionError",
24
+ "TholClosureError",
25
+ "TransitionCompatibilityError",
26
+ "SequenceSyntaxError",
27
+ "SequenceValidationResult",
28
+ "record_grammar_violation",
29
+ "_gram_state",
30
+ "apply_glyph_with_grammar",
31
+ "enforce_canonical_grammar",
32
+ "on_applied_glyph",
33
+ "parse_sequence",
34
+ "validate_sequence",
35
+ )
4
36
 
5
- def __getattr__(name: str) -> Any: ...
6
37
 
7
- GrammarContext: Any
8
- _gram_state: Any
9
- apply_glyph_with_grammar: Any
10
- enforce_canonical_grammar: Any
11
- on_applied_glyph: Any
38
+ def record_grammar_violation(
39
+ G: TNFRGraph, node: NodeId, error: StructuralGrammarError, *, stage: str
40
+ ) -> None: ...
@@ -0,0 +1,138 @@
1
+ """Graph-level validation helpers enforcing TNFR invariants."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from collections.abc import Sequence
7
+
8
+ import numpy as np
9
+
10
+ from ..alias import get_attr
11
+ from ..glyph_runtime import last_glyph
12
+ from ..config.constants import GLYPHS_CANONICAL_SET
13
+ from ..constants import get_param
14
+ from ..constants.aliases import ALIAS_EPI, ALIAS_VF
15
+ from ..utils import within_range
16
+ from ..types import (
17
+ EPIValue,
18
+ NodeAttrMap,
19
+ NodeId,
20
+ StructuralFrequency,
21
+ TNFRGraph,
22
+ ValidatorFunc,
23
+ ensure_bepi,
24
+ )
25
+ NodeData = NodeAttrMap
26
+ """Read-only node attribute mapping used by validators."""
27
+
28
+ AliasSequence = Sequence[str]
29
+ """Sequence of accepted attribute aliases."""
30
+
31
+ __all__ = ("run_validators", "GRAPH_VALIDATORS")
32
+
33
+
34
+ def _materialize_node_mapping(data: NodeData) -> dict[str, object]:
35
+ if isinstance(data, dict):
36
+ return data
37
+ return dict(data)
38
+
39
+
40
+ def _require_attr(
41
+ data: NodeData, alias: AliasSequence, node: NodeId, name: str
42
+ ) -> float:
43
+ """Return scalar attribute value or raise if missing."""
44
+
45
+ mapping = _materialize_node_mapping(data)
46
+ val = get_attr(mapping, alias, None)
47
+ if val is None:
48
+ raise ValueError(f"Missing {name} attribute in node {node}")
49
+ return float(val)
50
+
51
+
52
+ def _require_epi(data: NodeData, node: NodeId) -> EPIValue:
53
+ """Return a validated BEPI element stored in ``data``."""
54
+
55
+ mapping = _materialize_node_mapping(data)
56
+ value = get_attr(mapping, ALIAS_EPI, None, conv=lambda obj: obj)
57
+ if value is None:
58
+ raise ValueError(f"Missing EPI attribute in node {node}")
59
+ try:
60
+ return ensure_bepi(value)
61
+ except (TypeError, ValueError) as exc:
62
+ raise ValueError(f"Invalid EPI payload in node {node}: {exc}") from exc
63
+
64
+
65
+ def _validate_sigma(graph: TNFRGraph) -> None:
66
+ from ..sense import sigma_vector_from_graph
67
+
68
+ sv = sigma_vector_from_graph(graph)
69
+ if sv.get("mag", 0.0) > 1.0 + sys.float_info.epsilon:
70
+ raise ValueError("σ norm exceeds 1")
71
+
72
+
73
+ GRAPH_VALIDATORS: tuple[ValidatorFunc, ...] = (_validate_sigma,)
74
+ """Ordered collection of graph-level validators."""
75
+
76
+
77
+ def _max_abs(values: np.ndarray) -> float:
78
+ if values.size == 0:
79
+ return 0.0
80
+ return float(np.max(np.abs(values)))
81
+
82
+
83
+ def _check_epi(
84
+ epi: EPIValue,
85
+ epi_min: float,
86
+ epi_max: float,
87
+ node: NodeId,
88
+ ) -> None:
89
+ continuous_max = _max_abs(epi.f_continuous)
90
+ discrete_max = _max_abs(epi.a_discrete)
91
+ _check_range(continuous_max, epi_min, epi_max, "EPI continuous", node)
92
+ _check_range(discrete_max, epi_min, epi_max, "EPI discrete", node)
93
+
94
+ spacings = np.diff(epi.x_grid)
95
+ if np.any(spacings <= 0.0):
96
+ raise ValueError(f"EPI grid must be strictly increasing for node {node}")
97
+ if not np.allclose(spacings, spacings[0], rtol=1e-9, atol=1e-12):
98
+ raise ValueError(f"EPI grid must be uniform for node {node}")
99
+
100
+
101
+ def _out_of_range_msg(name: str, node: NodeId, val: float) -> str:
102
+ return f"{name} out of range in node {node}: {val}"
103
+
104
+
105
+ def _check_range(
106
+ val: float,
107
+ lower: float,
108
+ upper: float,
109
+ name: str,
110
+ node: NodeId,
111
+ tol: float = 1e-9,
112
+ ) -> None:
113
+ if not within_range(val, lower, upper, tol):
114
+ raise ValueError(_out_of_range_msg(name, node, val))
115
+
116
+
117
+ def _check_glyph(glyph: str | None, node: NodeId) -> None:
118
+ if glyph and glyph not in GLYPHS_CANONICAL_SET:
119
+ raise KeyError(f"Invalid glyph {glyph} in node {node}")
120
+
121
+
122
+ def run_validators(graph: TNFRGraph) -> None:
123
+ """Run all invariant validators on ``graph`` with a single node pass."""
124
+
125
+ epi_min = float(get_param(graph, "EPI_MIN"))
126
+ epi_max = float(get_param(graph, "EPI_MAX"))
127
+ vf_min = float(get_param(graph, "VF_MIN"))
128
+ vf_max = float(get_param(graph, "VF_MAX"))
129
+
130
+ for node, data in graph.nodes(data=True):
131
+ epi = _require_epi(data, node)
132
+ vf = StructuralFrequency(_require_attr(data, ALIAS_VF, node, "VF"))
133
+ _check_epi(epi, epi_min, epi_max, node)
134
+ _check_range(vf, vf_min, vf_max, "VF", node)
135
+ _check_glyph(last_glyph(data), node)
136
+
137
+ for validator in GRAPH_VALIDATORS:
138
+ validator(graph)
@@ -0,0 +1,17 @@
1
+ from collections.abc import Sequence
2
+ from typing import Tuple
3
+
4
+ from ..types import (
5
+ EPIValue,
6
+ NodeAttrMap,
7
+ NodeId,
8
+ StructuralFrequency,
9
+ TNFRGraph,
10
+ ValidatorFunc,
11
+ )
12
+ NodeData = NodeAttrMap
13
+ AliasSequence = Sequence[str]
14
+
15
+ GRAPH_VALIDATORS: Tuple[ValidatorFunc, ...]
16
+
17
+ def run_validators(graph: TNFRGraph) -> None: ...