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.
- tnfr/__init__.py +50 -5
- tnfr/__init__.pyi +0 -7
- tnfr/_compat.py +0 -1
- tnfr/_generated_version.py +34 -0
- tnfr/_version.py +44 -2
- tnfr/alias.py +14 -13
- tnfr/alias.pyi +5 -37
- tnfr/cache.py +9 -729
- tnfr/cache.pyi +8 -224
- tnfr/callback_utils.py +16 -31
- tnfr/callback_utils.pyi +3 -29
- tnfr/cli/__init__.py +17 -11
- tnfr/cli/__init__.pyi +0 -21
- tnfr/cli/arguments.py +175 -14
- tnfr/cli/arguments.pyi +5 -11
- tnfr/cli/execution.py +434 -48
- tnfr/cli/execution.pyi +14 -24
- tnfr/cli/utils.py +20 -3
- tnfr/cli/utils.pyi +5 -5
- tnfr/config/__init__.py +2 -1
- tnfr/config/__init__.pyi +2 -0
- tnfr/config/feature_flags.py +83 -0
- tnfr/config/init.py +1 -1
- tnfr/config/operator_names.py +1 -14
- tnfr/config/presets.py +6 -26
- tnfr/constants/__init__.py +10 -13
- tnfr/constants/__init__.pyi +10 -22
- tnfr/constants/aliases.py +31 -0
- tnfr/constants/core.py +4 -3
- tnfr/constants/init.py +1 -1
- tnfr/constants/metric.py +3 -3
- tnfr/dynamics/__init__.py +64 -10
- tnfr/dynamics/__init__.pyi +3 -4
- tnfr/dynamics/adaptation.py +79 -13
- tnfr/dynamics/aliases.py +10 -9
- tnfr/dynamics/coordination.py +77 -35
- tnfr/dynamics/dnfr.py +575 -274
- tnfr/dynamics/dnfr.pyi +1 -10
- tnfr/dynamics/integrators.py +47 -33
- tnfr/dynamics/integrators.pyi +0 -1
- tnfr/dynamics/runtime.py +489 -129
- tnfr/dynamics/sampling.py +2 -0
- tnfr/dynamics/selectors.py +101 -62
- tnfr/execution.py +15 -8
- tnfr/execution.pyi +5 -25
- tnfr/flatten.py +7 -3
- tnfr/flatten.pyi +1 -8
- tnfr/gamma.py +22 -26
- tnfr/gamma.pyi +0 -6
- tnfr/glyph_history.py +37 -26
- tnfr/glyph_history.pyi +1 -19
- tnfr/glyph_runtime.py +16 -0
- tnfr/glyph_runtime.pyi +9 -0
- tnfr/immutable.py +20 -15
- tnfr/immutable.pyi +4 -7
- tnfr/initialization.py +5 -7
- tnfr/initialization.pyi +1 -9
- tnfr/io.py +6 -305
- tnfr/io.pyi +13 -8
- tnfr/mathematics/__init__.py +81 -0
- tnfr/mathematics/backend.py +426 -0
- tnfr/mathematics/dynamics.py +398 -0
- tnfr/mathematics/epi.py +254 -0
- tnfr/mathematics/generators.py +222 -0
- tnfr/mathematics/metrics.py +119 -0
- tnfr/mathematics/operators.py +233 -0
- tnfr/mathematics/operators_factory.py +71 -0
- tnfr/mathematics/projection.py +78 -0
- tnfr/mathematics/runtime.py +173 -0
- tnfr/mathematics/spaces.py +247 -0
- tnfr/mathematics/transforms.py +292 -0
- tnfr/metrics/__init__.py +10 -10
- tnfr/metrics/coherence.py +123 -94
- tnfr/metrics/common.py +22 -13
- tnfr/metrics/common.pyi +42 -11
- tnfr/metrics/core.py +72 -14
- tnfr/metrics/diagnosis.py +48 -57
- tnfr/metrics/diagnosis.pyi +3 -7
- tnfr/metrics/export.py +3 -5
- tnfr/metrics/glyph_timing.py +41 -31
- tnfr/metrics/reporting.py +13 -6
- tnfr/metrics/sense_index.py +884 -114
- tnfr/metrics/trig.py +167 -11
- tnfr/metrics/trig.pyi +1 -0
- tnfr/metrics/trig_cache.py +112 -15
- tnfr/node.py +400 -17
- tnfr/node.pyi +55 -38
- tnfr/observers.py +111 -8
- tnfr/observers.pyi +0 -15
- tnfr/ontosim.py +9 -6
- tnfr/ontosim.pyi +0 -5
- tnfr/operators/__init__.py +529 -42
- tnfr/operators/__init__.pyi +14 -0
- tnfr/operators/definitions.py +350 -18
- tnfr/operators/definitions.pyi +0 -14
- tnfr/operators/grammar.py +760 -0
- tnfr/operators/jitter.py +28 -22
- tnfr/operators/registry.py +7 -12
- tnfr/operators/registry.pyi +0 -2
- tnfr/operators/remesh.py +38 -61
- tnfr/rng.py +17 -300
- tnfr/schemas/__init__.py +8 -0
- tnfr/schemas/grammar.json +94 -0
- tnfr/selector.py +3 -4
- tnfr/selector.pyi +1 -1
- tnfr/sense.py +22 -24
- tnfr/sense.pyi +0 -7
- tnfr/structural.py +504 -21
- tnfr/structural.pyi +41 -18
- tnfr/telemetry/__init__.py +23 -1
- tnfr/telemetry/cache_metrics.py +226 -0
- tnfr/telemetry/nu_f.py +423 -0
- tnfr/telemetry/nu_f.pyi +123 -0
- tnfr/tokens.py +1 -4
- tnfr/tokens.pyi +1 -6
- tnfr/trace.py +20 -53
- tnfr/trace.pyi +9 -37
- tnfr/types.py +244 -15
- tnfr/types.pyi +200 -14
- tnfr/units.py +69 -0
- tnfr/units.pyi +16 -0
- tnfr/utils/__init__.py +107 -48
- tnfr/utils/__init__.pyi +80 -11
- tnfr/utils/cache.py +1705 -65
- tnfr/utils/cache.pyi +370 -58
- tnfr/utils/chunks.py +104 -0
- tnfr/utils/chunks.pyi +21 -0
- tnfr/utils/data.py +95 -5
- tnfr/utils/data.pyi +8 -17
- tnfr/utils/graph.py +2 -4
- tnfr/utils/init.py +31 -7
- tnfr/utils/init.pyi +4 -11
- tnfr/utils/io.py +313 -14
- tnfr/{helpers → utils}/numeric.py +50 -24
- tnfr/utils/numeric.pyi +21 -0
- tnfr/validation/__init__.py +92 -4
- tnfr/validation/__init__.pyi +77 -17
- tnfr/validation/compatibility.py +79 -43
- tnfr/validation/compatibility.pyi +4 -6
- tnfr/validation/grammar.py +55 -133
- tnfr/validation/grammar.pyi +37 -8
- tnfr/validation/graph.py +138 -0
- tnfr/validation/graph.pyi +17 -0
- tnfr/validation/rules.py +161 -74
- tnfr/validation/rules.pyi +55 -18
- tnfr/validation/runtime.py +263 -0
- tnfr/validation/runtime.pyi +31 -0
- tnfr/validation/soft_filters.py +170 -0
- tnfr/validation/soft_filters.pyi +37 -0
- tnfr/validation/spectral.py +159 -0
- tnfr/validation/spectral.pyi +46 -0
- tnfr/validation/syntax.py +28 -139
- tnfr/validation/syntax.pyi +7 -4
- tnfr/validation/window.py +39 -0
- tnfr/validation/window.pyi +1 -0
- tnfr/viz/__init__.py +9 -0
- tnfr/viz/matplotlib.py +246 -0
- {tnfr-6.0.0.dist-info → tnfr-7.0.0.dist-info}/METADATA +63 -19
- tnfr-7.0.0.dist-info/RECORD +185 -0
- {tnfr-6.0.0.dist-info → tnfr-7.0.0.dist-info}/licenses/LICENSE.md +1 -1
- tnfr/constants_glyphs.py +0 -16
- tnfr/constants_glyphs.pyi +0 -12
- tnfr/grammar.py +0 -25
- tnfr/grammar.pyi +0 -13
- tnfr/helpers/__init__.py +0 -151
- tnfr/helpers/__init__.pyi +0 -66
- tnfr/helpers/numeric.pyi +0 -12
- tnfr/presets.py +0 -15
- tnfr/presets.pyi +0 -7
- tnfr/utils/io.pyi +0 -10
- tnfr/utils/validators.py +0 -130
- tnfr/utils/validators.pyi +0 -19
- tnfr-6.0.0.dist-info/RECORD +0 -157
- {tnfr-6.0.0.dist-info → tnfr-7.0.0.dist-info}/WHEEL +0 -0
- {tnfr-6.0.0.dist-info → tnfr-7.0.0.dist-info}/entry_points.txt +0 -0
- {tnfr-6.0.0.dist-info → tnfr-7.0.0.dist-info}/top_level.txt +0 -0
tnfr/validation/compatibility.py
CHANGED
|
@@ -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
|
|
16
|
-
|
|
24
|
+
# Canonical compatibilities (allowed next operators) expressed via structural names
|
|
25
|
+
_STRUCTURAL_COMPAT: dict[str, set[str]] = {
|
|
17
26
|
# Opening / initiation
|
|
18
|
-
|
|
19
|
-
|
|
27
|
+
EMISSION: {RECEPTION, RESONANCE, TRANSITION, EXPANSION, COUPLING},
|
|
28
|
+
RECEPTION: {COHERENCE, COUPLING, RESONANCE, TRANSITION},
|
|
20
29
|
# Stabilisation / diffusion / coupling
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
35
|
+
DISSONANCE: {MUTATION, TRANSITION},
|
|
36
|
+
MUTATION: {COHERENCE, TRANSITION},
|
|
37
|
+
TRANSITION: {DISSONANCE, MUTATION, RESONANCE, COHERENCE, COUPLING},
|
|
29
38
|
# Closures / latent states
|
|
30
|
-
|
|
31
|
-
|
|
39
|
+
SILENCE: {EMISSION, RECEPTION},
|
|
40
|
+
CONTRACTION: {EMISSION, COHERENCE},
|
|
32
41
|
# Self-organising blocks
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
|
1
|
+
from ..types import Glyph
|
|
2
2
|
|
|
3
|
-
__all__
|
|
3
|
+
__all__ = ("CANON_COMPAT", "CANON_FALLBACK")
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
CANON_COMPAT: Any
|
|
8
|
-
CANON_FALLBACK: Any
|
|
5
|
+
CANON_COMPAT: dict[Glyph, set[Glyph]]
|
|
6
|
+
CANON_FALLBACK: dict[Glyph, Glyph]
|
tnfr/validation/grammar.py
CHANGED
|
@@ -1,149 +1,71 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Deprecated compatibility wrapper for :mod:`tnfr.operators.grammar`.
|
|
2
2
|
|
|
3
|
-
|
|
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
|
|
6
|
-
from dataclasses import dataclass
|
|
7
|
-
from typing import Any, Optional, TYPE_CHECKING, cast
|
|
8
|
+
from __future__ import annotations
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
from
|
|
11
|
-
|
|
12
|
-
from .
|
|
13
|
-
|
|
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 ..
|
|
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
|
-
"
|
|
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
|
-
|
|
77
|
-
|
|
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
|
-
"""
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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)
|
tnfr/validation/grammar.pyi
CHANGED
|
@@ -1,11 +1,40 @@
|
|
|
1
|
-
from
|
|
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__
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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: ...
|
tnfr/validation/graph.py
ADDED
|
@@ -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: ...
|