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.
- tnfr/__init__.py +334 -50
- tnfr/__init__.pyi +33 -0
- tnfr/_compat.py +10 -0
- tnfr/_generated_version.py +34 -0
- tnfr/_version.py +49 -0
- tnfr/_version.pyi +7 -0
- tnfr/alias.py +214 -37
- tnfr/alias.pyi +108 -0
- tnfr/backends/__init__.py +354 -0
- tnfr/backends/jax_backend.py +173 -0
- tnfr/backends/numpy_backend.py +238 -0
- tnfr/backends/optimized_numpy.py +420 -0
- tnfr/backends/torch_backend.py +408 -0
- tnfr/cache.py +149 -556
- tnfr/cache.pyi +13 -0
- tnfr/cli/__init__.py +51 -16
- tnfr/cli/__init__.pyi +26 -0
- tnfr/cli/arguments.py +344 -32
- tnfr/cli/arguments.pyi +29 -0
- tnfr/cli/execution.py +676 -50
- tnfr/cli/execution.pyi +70 -0
- tnfr/cli/interactive_validator.py +614 -0
- tnfr/cli/utils.py +18 -3
- tnfr/cli/utils.pyi +7 -0
- tnfr/cli/validate.py +236 -0
- tnfr/compat/__init__.py +85 -0
- tnfr/compat/dataclass.py +136 -0
- tnfr/compat/jsonschema_stub.py +61 -0
- tnfr/compat/matplotlib_stub.py +73 -0
- tnfr/compat/numpy_stub.py +155 -0
- tnfr/config/__init__.py +224 -0
- tnfr/config/__init__.pyi +10 -0
- tnfr/{constants_glyphs.py → config/constants.py} +26 -20
- tnfr/config/constants.pyi +12 -0
- tnfr/config/defaults.py +54 -0
- tnfr/{constants/core.py → config/defaults_core.py} +59 -6
- tnfr/config/defaults_init.py +33 -0
- tnfr/config/defaults_metric.py +104 -0
- tnfr/config/feature_flags.py +81 -0
- tnfr/config/feature_flags.pyi +16 -0
- tnfr/config/glyph_constants.py +31 -0
- tnfr/config/init.py +77 -0
- tnfr/config/init.pyi +8 -0
- tnfr/config/operator_names.py +254 -0
- tnfr/config/operator_names.pyi +36 -0
- tnfr/config/physics_derivation.py +354 -0
- tnfr/config/presets.py +83 -0
- tnfr/config/presets.pyi +7 -0
- tnfr/config/security.py +927 -0
- tnfr/config/thresholds.py +114 -0
- tnfr/config/tnfr_config.py +498 -0
- tnfr/constants/__init__.py +51 -133
- tnfr/constants/__init__.pyi +92 -0
- tnfr/constants/aliases.py +33 -0
- tnfr/constants/aliases.pyi +27 -0
- tnfr/constants/init.py +3 -1
- tnfr/constants/init.pyi +12 -0
- tnfr/constants/metric.py +9 -15
- tnfr/constants/metric.pyi +19 -0
- tnfr/core/__init__.py +33 -0
- tnfr/core/container.py +226 -0
- tnfr/core/default_implementations.py +329 -0
- tnfr/core/interfaces.py +279 -0
- tnfr/dynamics/__init__.py +213 -633
- tnfr/dynamics/__init__.pyi +83 -0
- tnfr/dynamics/adaptation.py +267 -0
- tnfr/dynamics/adaptation.pyi +7 -0
- tnfr/dynamics/adaptive_sequences.py +189 -0
- tnfr/dynamics/adaptive_sequences.pyi +14 -0
- tnfr/dynamics/aliases.py +23 -0
- tnfr/dynamics/aliases.pyi +19 -0
- tnfr/dynamics/bifurcation.py +232 -0
- tnfr/dynamics/canonical.py +229 -0
- tnfr/dynamics/canonical.pyi +48 -0
- tnfr/dynamics/coordination.py +385 -0
- tnfr/dynamics/coordination.pyi +25 -0
- tnfr/dynamics/dnfr.py +2699 -398
- tnfr/dynamics/dnfr.pyi +26 -0
- tnfr/dynamics/dynamic_limits.py +225 -0
- tnfr/dynamics/feedback.py +252 -0
- tnfr/dynamics/feedback.pyi +24 -0
- tnfr/dynamics/fused_dnfr.py +454 -0
- tnfr/dynamics/homeostasis.py +157 -0
- tnfr/dynamics/homeostasis.pyi +14 -0
- tnfr/dynamics/integrators.py +496 -102
- tnfr/dynamics/integrators.pyi +36 -0
- tnfr/dynamics/learning.py +310 -0
- tnfr/dynamics/learning.pyi +33 -0
- tnfr/dynamics/metabolism.py +254 -0
- tnfr/dynamics/nbody.py +796 -0
- tnfr/dynamics/nbody_tnfr.py +783 -0
- tnfr/dynamics/propagation.py +326 -0
- tnfr/dynamics/runtime.py +908 -0
- tnfr/dynamics/runtime.pyi +77 -0
- tnfr/dynamics/sampling.py +10 -5
- tnfr/dynamics/sampling.pyi +7 -0
- tnfr/dynamics/selectors.py +711 -0
- tnfr/dynamics/selectors.pyi +85 -0
- tnfr/dynamics/structural_clip.py +207 -0
- tnfr/errors/__init__.py +37 -0
- tnfr/errors/contextual.py +492 -0
- tnfr/execution.py +77 -55
- tnfr/execution.pyi +45 -0
- tnfr/extensions/__init__.py +205 -0
- tnfr/extensions/__init__.pyi +18 -0
- tnfr/extensions/base.py +173 -0
- tnfr/extensions/base.pyi +35 -0
- tnfr/extensions/business/__init__.py +71 -0
- tnfr/extensions/business/__init__.pyi +11 -0
- tnfr/extensions/business/cookbook.py +88 -0
- tnfr/extensions/business/cookbook.pyi +8 -0
- tnfr/extensions/business/health_analyzers.py +202 -0
- tnfr/extensions/business/health_analyzers.pyi +9 -0
- tnfr/extensions/business/patterns.py +183 -0
- tnfr/extensions/business/patterns.pyi +8 -0
- tnfr/extensions/medical/__init__.py +73 -0
- tnfr/extensions/medical/__init__.pyi +11 -0
- tnfr/extensions/medical/cookbook.py +88 -0
- tnfr/extensions/medical/cookbook.pyi +8 -0
- tnfr/extensions/medical/health_analyzers.py +181 -0
- tnfr/extensions/medical/health_analyzers.pyi +9 -0
- tnfr/extensions/medical/patterns.py +163 -0
- tnfr/extensions/medical/patterns.pyi +8 -0
- tnfr/flatten.py +29 -50
- tnfr/flatten.pyi +21 -0
- tnfr/gamma.py +66 -53
- tnfr/gamma.pyi +36 -0
- tnfr/glyph_history.py +144 -57
- tnfr/glyph_history.pyi +35 -0
- tnfr/glyph_runtime.py +19 -0
- tnfr/glyph_runtime.pyi +8 -0
- tnfr/immutable.py +70 -30
- tnfr/immutable.pyi +36 -0
- tnfr/initialization.py +22 -16
- tnfr/initialization.pyi +65 -0
- tnfr/io.py +5 -241
- tnfr/io.pyi +13 -0
- tnfr/locking.pyi +7 -0
- tnfr/mathematics/__init__.py +79 -0
- tnfr/mathematics/backend.py +453 -0
- tnfr/mathematics/backend.pyi +99 -0
- tnfr/mathematics/dynamics.py +408 -0
- tnfr/mathematics/dynamics.pyi +90 -0
- tnfr/mathematics/epi.py +391 -0
- tnfr/mathematics/epi.pyi +65 -0
- tnfr/mathematics/generators.py +242 -0
- tnfr/mathematics/generators.pyi +29 -0
- tnfr/mathematics/metrics.py +119 -0
- tnfr/mathematics/metrics.pyi +16 -0
- tnfr/mathematics/operators.py +239 -0
- tnfr/mathematics/operators.pyi +59 -0
- tnfr/mathematics/operators_factory.py +124 -0
- tnfr/mathematics/operators_factory.pyi +11 -0
- tnfr/mathematics/projection.py +87 -0
- tnfr/mathematics/projection.pyi +33 -0
- tnfr/mathematics/runtime.py +182 -0
- tnfr/mathematics/runtime.pyi +64 -0
- tnfr/mathematics/spaces.py +256 -0
- tnfr/mathematics/spaces.pyi +83 -0
- tnfr/mathematics/transforms.py +305 -0
- tnfr/mathematics/transforms.pyi +62 -0
- tnfr/metrics/__init__.py +47 -9
- tnfr/metrics/__init__.pyi +20 -0
- tnfr/metrics/buffer_cache.py +163 -0
- tnfr/metrics/buffer_cache.pyi +24 -0
- tnfr/metrics/cache_utils.py +214 -0
- tnfr/metrics/coherence.py +1510 -330
- tnfr/metrics/coherence.pyi +129 -0
- tnfr/metrics/common.py +23 -16
- tnfr/metrics/common.pyi +35 -0
- tnfr/metrics/core.py +251 -36
- tnfr/metrics/core.pyi +13 -0
- tnfr/metrics/diagnosis.py +709 -110
- tnfr/metrics/diagnosis.pyi +86 -0
- tnfr/metrics/emergence.py +245 -0
- tnfr/metrics/export.py +60 -18
- tnfr/metrics/export.pyi +7 -0
- tnfr/metrics/glyph_timing.py +233 -43
- tnfr/metrics/glyph_timing.pyi +81 -0
- tnfr/metrics/learning_metrics.py +280 -0
- tnfr/metrics/learning_metrics.pyi +21 -0
- tnfr/metrics/phase_coherence.py +351 -0
- tnfr/metrics/phase_compatibility.py +349 -0
- tnfr/metrics/reporting.py +63 -28
- tnfr/metrics/reporting.pyi +25 -0
- tnfr/metrics/sense_index.py +1126 -43
- tnfr/metrics/sense_index.pyi +9 -0
- tnfr/metrics/trig.py +215 -23
- tnfr/metrics/trig.pyi +13 -0
- tnfr/metrics/trig_cache.py +148 -24
- tnfr/metrics/trig_cache.pyi +10 -0
- tnfr/multiscale/__init__.py +32 -0
- tnfr/multiscale/hierarchical.py +517 -0
- tnfr/node.py +646 -140
- tnfr/node.pyi +139 -0
- tnfr/observers.py +160 -45
- tnfr/observers.pyi +31 -0
- tnfr/ontosim.py +23 -19
- tnfr/ontosim.pyi +28 -0
- tnfr/operators/__init__.py +1358 -106
- tnfr/operators/__init__.pyi +31 -0
- tnfr/operators/algebra.py +277 -0
- tnfr/operators/canonical_patterns.py +420 -0
- tnfr/operators/cascade.py +267 -0
- tnfr/operators/cycle_detection.py +358 -0
- tnfr/operators/definitions.py +4108 -0
- tnfr/operators/definitions.pyi +78 -0
- tnfr/operators/grammar.py +1164 -0
- tnfr/operators/grammar.pyi +140 -0
- tnfr/operators/hamiltonian.py +710 -0
- tnfr/operators/health_analyzer.py +809 -0
- tnfr/operators/jitter.py +107 -38
- tnfr/operators/jitter.pyi +11 -0
- tnfr/operators/lifecycle.py +314 -0
- tnfr/operators/metabolism.py +618 -0
- tnfr/operators/metrics.py +2138 -0
- tnfr/operators/network_analysis/__init__.py +27 -0
- tnfr/operators/network_analysis/source_detection.py +186 -0
- tnfr/operators/nodal_equation.py +395 -0
- tnfr/operators/pattern_detection.py +660 -0
- tnfr/operators/patterns.py +669 -0
- tnfr/operators/postconditions/__init__.py +38 -0
- tnfr/operators/postconditions/mutation.py +236 -0
- tnfr/operators/preconditions/__init__.py +1226 -0
- tnfr/operators/preconditions/coherence.py +305 -0
- tnfr/operators/preconditions/dissonance.py +236 -0
- tnfr/operators/preconditions/emission.py +128 -0
- tnfr/operators/preconditions/mutation.py +580 -0
- tnfr/operators/preconditions/reception.py +125 -0
- tnfr/operators/preconditions/resonance.py +364 -0
- tnfr/operators/registry.py +74 -0
- tnfr/operators/registry.pyi +9 -0
- tnfr/operators/remesh.py +1415 -91
- tnfr/operators/remesh.pyi +26 -0
- tnfr/operators/structural_units.py +268 -0
- tnfr/operators/unified_grammar.py +105 -0
- tnfr/parallel/__init__.py +54 -0
- tnfr/parallel/auto_scaler.py +234 -0
- tnfr/parallel/distributed.py +384 -0
- tnfr/parallel/engine.py +238 -0
- tnfr/parallel/gpu_engine.py +420 -0
- tnfr/parallel/monitoring.py +248 -0
- tnfr/parallel/partitioner.py +459 -0
- tnfr/py.typed +0 -0
- tnfr/recipes/__init__.py +22 -0
- tnfr/recipes/cookbook.py +743 -0
- tnfr/rng.py +75 -151
- tnfr/rng.pyi +26 -0
- tnfr/schemas/__init__.py +8 -0
- tnfr/schemas/grammar.json +94 -0
- tnfr/sdk/__init__.py +107 -0
- tnfr/sdk/__init__.pyi +19 -0
- tnfr/sdk/adaptive_system.py +173 -0
- tnfr/sdk/adaptive_system.pyi +21 -0
- tnfr/sdk/builders.py +370 -0
- tnfr/sdk/builders.pyi +51 -0
- tnfr/sdk/fluent.py +1121 -0
- tnfr/sdk/fluent.pyi +74 -0
- tnfr/sdk/templates.py +342 -0
- tnfr/sdk/templates.pyi +41 -0
- tnfr/sdk/utils.py +341 -0
- tnfr/secure_config.py +46 -0
- tnfr/security/__init__.py +70 -0
- tnfr/security/database.py +514 -0
- tnfr/security/subprocess.py +503 -0
- tnfr/security/validation.py +290 -0
- tnfr/selector.py +59 -22
- tnfr/selector.pyi +19 -0
- tnfr/sense.py +92 -67
- tnfr/sense.pyi +23 -0
- tnfr/services/__init__.py +17 -0
- tnfr/services/orchestrator.py +325 -0
- tnfr/sparse/__init__.py +39 -0
- tnfr/sparse/representations.py +492 -0
- tnfr/structural.py +639 -263
- tnfr/structural.pyi +83 -0
- tnfr/telemetry/__init__.py +35 -0
- tnfr/telemetry/cache_metrics.py +226 -0
- tnfr/telemetry/cache_metrics.pyi +64 -0
- tnfr/telemetry/nu_f.py +422 -0
- tnfr/telemetry/nu_f.pyi +108 -0
- tnfr/telemetry/verbosity.py +36 -0
- tnfr/telemetry/verbosity.pyi +15 -0
- tnfr/tokens.py +2 -4
- tnfr/tokens.pyi +36 -0
- tnfr/tools/__init__.py +20 -0
- tnfr/tools/domain_templates.py +478 -0
- tnfr/tools/sequence_generator.py +846 -0
- tnfr/topology/__init__.py +13 -0
- tnfr/topology/asymmetry.py +151 -0
- tnfr/trace.py +300 -126
- tnfr/trace.pyi +42 -0
- tnfr/tutorials/__init__.py +38 -0
- tnfr/tutorials/autonomous_evolution.py +285 -0
- tnfr/tutorials/interactive.py +1576 -0
- tnfr/tutorials/structural_metabolism.py +238 -0
- tnfr/types.py +743 -12
- tnfr/types.pyi +357 -0
- tnfr/units.py +68 -0
- tnfr/units.pyi +13 -0
- tnfr/utils/__init__.py +282 -0
- tnfr/utils/__init__.pyi +215 -0
- tnfr/utils/cache.py +4223 -0
- tnfr/utils/cache.pyi +470 -0
- tnfr/{callback_utils.py → utils/callbacks.py} +26 -39
- tnfr/utils/callbacks.pyi +49 -0
- tnfr/utils/chunks.py +108 -0
- tnfr/utils/chunks.pyi +22 -0
- tnfr/utils/data.py +428 -0
- tnfr/utils/data.pyi +74 -0
- tnfr/utils/graph.py +85 -0
- tnfr/utils/graph.pyi +10 -0
- tnfr/utils/init.py +821 -0
- tnfr/utils/init.pyi +80 -0
- tnfr/utils/io.py +559 -0
- tnfr/utils/io.pyi +66 -0
- tnfr/{helpers → utils}/numeric.py +51 -24
- tnfr/utils/numeric.pyi +21 -0
- tnfr/validation/__init__.py +257 -0
- tnfr/validation/__init__.pyi +85 -0
- tnfr/validation/compatibility.py +460 -0
- tnfr/validation/compatibility.pyi +6 -0
- tnfr/validation/config.py +73 -0
- tnfr/validation/graph.py +139 -0
- tnfr/validation/graph.pyi +18 -0
- tnfr/validation/input_validation.py +755 -0
- tnfr/validation/invariants.py +712 -0
- tnfr/validation/rules.py +253 -0
- tnfr/validation/rules.pyi +44 -0
- tnfr/validation/runtime.py +279 -0
- tnfr/validation/runtime.pyi +28 -0
- tnfr/validation/sequence_validator.py +162 -0
- tnfr/validation/soft_filters.py +170 -0
- tnfr/validation/soft_filters.pyi +32 -0
- tnfr/validation/spectral.py +164 -0
- tnfr/validation/spectral.pyi +42 -0
- tnfr/validation/validator.py +1266 -0
- tnfr/validation/window.py +39 -0
- tnfr/validation/window.pyi +1 -0
- tnfr/visualization/__init__.py +98 -0
- tnfr/visualization/cascade_viz.py +256 -0
- tnfr/visualization/hierarchy.py +284 -0
- tnfr/visualization/sequence_plotter.py +784 -0
- tnfr/viz/__init__.py +60 -0
- tnfr/viz/matplotlib.py +278 -0
- tnfr/viz/matplotlib.pyi +35 -0
- tnfr-8.5.0.dist-info/METADATA +573 -0
- tnfr-8.5.0.dist-info/RECORD +353 -0
- {tnfr-4.5.2.dist-info → tnfr-8.5.0.dist-info}/entry_points.txt +1 -0
- {tnfr-4.5.2.dist-info → tnfr-8.5.0.dist-info}/licenses/LICENSE.md +1 -1
- tnfr/collections_utils.py +0 -300
- tnfr/config.py +0 -32
- tnfr/grammar.py +0 -344
- tnfr/graph_utils.py +0 -84
- tnfr/helpers/__init__.py +0 -71
- tnfr/import_utils.py +0 -228
- tnfr/json_utils.py +0 -162
- tnfr/logging_utils.py +0 -116
- tnfr/presets.py +0 -60
- tnfr/validators.py +0 -84
- tnfr/value_utils.py +0 -59
- tnfr-4.5.2.dist-info/METADATA +0 -379
- tnfr-4.5.2.dist-info/RECORD +0 -67
- {tnfr-4.5.2.dist-info → tnfr-8.5.0.dist-info}/WHEEL +0 -0
- {tnfr-4.5.2.dist-info → tnfr-8.5.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,580 @@
|
|
|
1
|
+
"""Canonical precondition validators for ZHIR (Mutation) operator.
|
|
2
|
+
|
|
3
|
+
Implements comprehensive validation of mutation prerequisites including
|
|
4
|
+
threshold verification, grammar U4b compliance, and structural readiness.
|
|
5
|
+
|
|
6
|
+
This module provides strict, modular validation for the Mutation (ZHIR) operator,
|
|
7
|
+
aligning with the architectural pattern used by Coherence (IL) and Dissonance (OZ).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import TYPE_CHECKING
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from ...types import NodeId, TNFRGraph
|
|
16
|
+
import logging
|
|
17
|
+
|
|
18
|
+
from ...alias import get_attr
|
|
19
|
+
from ...config.operator_names import (
|
|
20
|
+
BIFURCATION_WINDOWS,
|
|
21
|
+
DESTABILIZERS_MODERATE,
|
|
22
|
+
DESTABILIZERS_STRONG,
|
|
23
|
+
DESTABILIZERS_WEAK,
|
|
24
|
+
)
|
|
25
|
+
from ...constants.aliases import ALIAS_DNFR, ALIAS_EPI, ALIAS_VF
|
|
26
|
+
from . import OperatorPreconditionError
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"validate_mutation_strict",
|
|
30
|
+
"validate_threshold_crossing",
|
|
31
|
+
"validate_grammar_u4b",
|
|
32
|
+
"record_destabilizer_context",
|
|
33
|
+
"diagnose_mutation_readiness",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def validate_mutation_strict(G: TNFRGraph, node: NodeId) -> None:
|
|
38
|
+
"""Comprehensive canonical validation for ZHIR.
|
|
39
|
+
|
|
40
|
+
Validates all TNFR requirements for mutation (AGENTS.md §11, TNFR.pdf §2.2.11):
|
|
41
|
+
|
|
42
|
+
1. **Minimum νf**: Reorganization capacity for phase transformation
|
|
43
|
+
2. **Threshold crossing**: ∂EPI/∂t > ξ (structural velocity sufficient)
|
|
44
|
+
3. **Grammar U4b Part 1**: Prior IL (Coherence) for stable base
|
|
45
|
+
4. **Grammar U4b Part 2**: Recent destabilizer (~3 ops) for threshold energy
|
|
46
|
+
5. **Sufficient history**: EPI history for velocity calculation
|
|
47
|
+
|
|
48
|
+
Parameters
|
|
49
|
+
----------
|
|
50
|
+
G : TNFRGraph
|
|
51
|
+
Graph containing the node
|
|
52
|
+
node : NodeId
|
|
53
|
+
Node to validate
|
|
54
|
+
|
|
55
|
+
Raises
|
|
56
|
+
------
|
|
57
|
+
OperatorPreconditionError
|
|
58
|
+
If any canonical requirement not met
|
|
59
|
+
|
|
60
|
+
Notes
|
|
61
|
+
-----
|
|
62
|
+
This function implements strict validation when:
|
|
63
|
+
- ``VALIDATE_OPERATOR_PRECONDITIONS=True`` (global strict mode)
|
|
64
|
+
- Individual flags enabled (ZHIR_REQUIRE_IL_PRECEDENCE, etc.)
|
|
65
|
+
|
|
66
|
+
For backward compatibility, threshold and U4b checks may be soft
|
|
67
|
+
(warnings only) when strict validation disabled.
|
|
68
|
+
|
|
69
|
+
Examples
|
|
70
|
+
--------
|
|
71
|
+
>>> from tnfr.structural import create_nfr
|
|
72
|
+
>>> from tnfr.operators.preconditions.mutation import validate_mutation_strict
|
|
73
|
+
>>> G, node = create_nfr("test", epi=0.5, vf=1.0)
|
|
74
|
+
>>> G.nodes[node]["epi_history"] = [0.4, 0.5]
|
|
75
|
+
>>> G.graph["VALIDATE_OPERATOR_PRECONDITIONS"] = True
|
|
76
|
+
>>> # This would raise if U4b not satisfied
|
|
77
|
+
>>> # validate_mutation_strict(G, node) # doctest: +SKIP
|
|
78
|
+
"""
|
|
79
|
+
import logging
|
|
80
|
+
|
|
81
|
+
logger = logging.getLogger(__name__)
|
|
82
|
+
|
|
83
|
+
# 1. Minimum νf validation
|
|
84
|
+
_validate_minimum_vf(G, node)
|
|
85
|
+
|
|
86
|
+
# 2. Threshold crossing validation (∂EPI/∂t > ξ)
|
|
87
|
+
validate_threshold_crossing(G, node, logger)
|
|
88
|
+
|
|
89
|
+
# 3. Grammar U4b validation
|
|
90
|
+
strict_validation = bool(G.graph.get("VALIDATE_OPERATOR_PRECONDITIONS", False))
|
|
91
|
+
if strict_validation:
|
|
92
|
+
validate_grammar_u4b(G, node, logger)
|
|
93
|
+
|
|
94
|
+
# 4. History length validation
|
|
95
|
+
_validate_history_length(G, node)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _validate_minimum_vf(G: TNFRGraph, node: NodeId) -> None:
|
|
99
|
+
"""Validate minimum structural frequency for phase transformation."""
|
|
100
|
+
vf = float(get_attr(G.nodes[node], ALIAS_VF, 0.0))
|
|
101
|
+
min_vf = float(G.graph.get("ZHIR_MIN_VF", 0.05))
|
|
102
|
+
|
|
103
|
+
if vf < min_vf:
|
|
104
|
+
raise OperatorPreconditionError(
|
|
105
|
+
"Mutation",
|
|
106
|
+
f"Structural frequency too low for mutation (νf={vf:.3f} < {min_vf:.3f})",
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _validate_history_length(G: TNFRGraph, node: NodeId) -> None:
|
|
111
|
+
"""Validate sufficient EPI history for velocity calculation."""
|
|
112
|
+
epi_history = G.nodes[node].get("epi_history") or G.nodes[node].get("_epi_history", [])
|
|
113
|
+
min_length = int(G.graph.get("ZHIR_MIN_HISTORY_LENGTH", 2))
|
|
114
|
+
|
|
115
|
+
if len(epi_history) < min_length:
|
|
116
|
+
import logging
|
|
117
|
+
logger = logging.getLogger(__name__)
|
|
118
|
+
logger.warning(
|
|
119
|
+
f"Node {node}: ZHIR applied without sufficient EPI history "
|
|
120
|
+
f"(need ≥{min_length} points, have {len(epi_history)}). "
|
|
121
|
+
f"Threshold verification may be inaccurate."
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def validate_threshold_crossing(
|
|
126
|
+
G: TNFRGraph, node: NodeId, logger: logging.Logger | None = None
|
|
127
|
+
) -> None:
|
|
128
|
+
"""Validate ∂EPI/∂t > ξ requirement for phase transformation.
|
|
129
|
+
|
|
130
|
+
ZHIR is a phase transformation that requires sufficient structural reorganization
|
|
131
|
+
velocity to justify the transition. The threshold ξ represents the minimum rate
|
|
132
|
+
of structural change needed for a phase shift to be physically meaningful.
|
|
133
|
+
|
|
134
|
+
Parameters
|
|
135
|
+
----------
|
|
136
|
+
G : TNFRGraph
|
|
137
|
+
Graph containing the node
|
|
138
|
+
node : NodeId
|
|
139
|
+
Node to validate
|
|
140
|
+
logger : logging.Logger, optional
|
|
141
|
+
Logger for telemetry output
|
|
142
|
+
|
|
143
|
+
Notes
|
|
144
|
+
-----
|
|
145
|
+
- If ∂EPI/∂t < ξ: Logs warning (soft check for backward compatibility)
|
|
146
|
+
- If ∂EPI/∂t ≥ ξ: Logs success, sets validation flag
|
|
147
|
+
- If insufficient history: Logs warning, cannot verify
|
|
148
|
+
|
|
149
|
+
The check is soft (warning only) unless ZHIR_STRICT_THRESHOLD_CHECK=True,
|
|
150
|
+
maintaining backward compatibility with existing code.
|
|
151
|
+
|
|
152
|
+
Examples
|
|
153
|
+
--------
|
|
154
|
+
>>> from tnfr.structural import create_nfr
|
|
155
|
+
>>> G, node = create_nfr("test", epi=0.5, vf=1.0)
|
|
156
|
+
>>> G.nodes[node]["epi_history"] = [0.3, 0.5] # velocity = 0.2
|
|
157
|
+
>>> G.graph["ZHIR_THRESHOLD_XI"] = 0.1
|
|
158
|
+
>>> validate_threshold_crossing(G, node) # Should pass (0.2 > 0.1)
|
|
159
|
+
"""
|
|
160
|
+
if logger is None:
|
|
161
|
+
import logging
|
|
162
|
+
logger = logging.getLogger(__name__)
|
|
163
|
+
|
|
164
|
+
# Get EPI history - check both keys for compatibility
|
|
165
|
+
epi_history = G.nodes[node].get("epi_history") or G.nodes[node].get("_epi_history", [])
|
|
166
|
+
|
|
167
|
+
if len(epi_history) < 2:
|
|
168
|
+
# Insufficient history - cannot verify threshold
|
|
169
|
+
logger.warning(
|
|
170
|
+
f"Node {node}: ZHIR applied without sufficient EPI history "
|
|
171
|
+
f"(need ≥2 points, have {len(epi_history)}). Cannot verify threshold."
|
|
172
|
+
)
|
|
173
|
+
G.nodes[node]["_zhir_threshold_unknown"] = True
|
|
174
|
+
return
|
|
175
|
+
|
|
176
|
+
# Compute ∂EPI/∂t (discrete approximation using last two points)
|
|
177
|
+
# For discrete operator applications with Δt=1: ∂EPI/∂t ≈ EPI_t - EPI_{t-1}
|
|
178
|
+
depi_dt = abs(epi_history[-1] - epi_history[-2])
|
|
179
|
+
|
|
180
|
+
# Get threshold from configuration
|
|
181
|
+
xi_threshold = float(G.graph.get("ZHIR_THRESHOLD_XI", 0.1))
|
|
182
|
+
|
|
183
|
+
# Verify threshold crossed
|
|
184
|
+
if depi_dt < xi_threshold:
|
|
185
|
+
# Allow mutation but log warning (soft check for backward compatibility)
|
|
186
|
+
logger.warning(
|
|
187
|
+
f"Node {node}: ZHIR applied with ∂EPI/∂t={depi_dt:.3f} < ξ={xi_threshold}. "
|
|
188
|
+
f"Mutation may lack structural justification. "
|
|
189
|
+
f"Consider increasing dissonance (OZ) first."
|
|
190
|
+
)
|
|
191
|
+
G.nodes[node]["_zhir_threshold_warning"] = True
|
|
192
|
+
|
|
193
|
+
# Strict check if configured
|
|
194
|
+
if bool(G.graph.get("ZHIR_STRICT_THRESHOLD_CHECK", False)):
|
|
195
|
+
raise OperatorPreconditionError(
|
|
196
|
+
"Mutation",
|
|
197
|
+
f"Threshold not crossed: ∂EPI/∂t={depi_dt:.3f} < ξ={xi_threshold}. "
|
|
198
|
+
f"Apply Dissonance (OZ) or Expansion (VAL) to increase structural velocity first."
|
|
199
|
+
)
|
|
200
|
+
else:
|
|
201
|
+
# Threshold met - log success
|
|
202
|
+
logger.info(
|
|
203
|
+
f"Node {node}: ZHIR threshold crossed (∂EPI/∂t={depi_dt:.3f} > ξ={xi_threshold})"
|
|
204
|
+
)
|
|
205
|
+
G.nodes[node]["_zhir_threshold_met"] = True
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def validate_grammar_u4b(
|
|
209
|
+
G: TNFRGraph, node: NodeId, logger: logging.Logger | None = None
|
|
210
|
+
) -> None:
|
|
211
|
+
"""Validate U4b: IL precedence + recent destabilizer.
|
|
212
|
+
|
|
213
|
+
Grammar rule U4b (BIFURCATION DYNAMICS - Transformers Need Context) requires:
|
|
214
|
+
|
|
215
|
+
1. **Prior IL (Coherence)**: Stable base for transformation
|
|
216
|
+
2. **Recent destabilizer**: OZ/VAL/etc within ~3 operations for threshold energy
|
|
217
|
+
|
|
218
|
+
This is a STRONG canonicity rule derived from bifurcation theory - phase
|
|
219
|
+
transformations need both stability (IL) and elevated energy (destabilizer).
|
|
220
|
+
|
|
221
|
+
Parameters
|
|
222
|
+
----------
|
|
223
|
+
G : TNFRGraph
|
|
224
|
+
Graph containing the node
|
|
225
|
+
node : NodeId
|
|
226
|
+
Node to validate
|
|
227
|
+
logger : logging.Logger, optional
|
|
228
|
+
Logger for telemetry output
|
|
229
|
+
|
|
230
|
+
Raises
|
|
231
|
+
------
|
|
232
|
+
OperatorPreconditionError
|
|
233
|
+
If U4b requirements not met when strict validation enabled
|
|
234
|
+
|
|
235
|
+
Notes
|
|
236
|
+
-----
|
|
237
|
+
Validation is strict when:
|
|
238
|
+
- ``VALIDATE_OPERATOR_PRECONDITIONS=True`` (global)
|
|
239
|
+
- ``ZHIR_REQUIRE_IL_PRECEDENCE=True`` (Part 1)
|
|
240
|
+
- ``ZHIR_REQUIRE_DESTABILIZER=True`` (Part 2)
|
|
241
|
+
|
|
242
|
+
Examples
|
|
243
|
+
--------
|
|
244
|
+
>>> from tnfr.structural import create_nfr
|
|
245
|
+
>>> from tnfr.operators import Coherence, Dissonance
|
|
246
|
+
>>> G, node = create_nfr("test", epi=0.5, vf=1.0)
|
|
247
|
+
>>> G.graph["VALIDATE_OPERATOR_PRECONDITIONS"] = True
|
|
248
|
+
>>> # Apply required sequence
|
|
249
|
+
>>> Coherence()(G, node) # IL for stable base
|
|
250
|
+
>>> Dissonance()(G, node) # OZ for destabilization
|
|
251
|
+
>>> # Now validate_grammar_u4b would pass
|
|
252
|
+
"""
|
|
253
|
+
if logger is None:
|
|
254
|
+
import logging
|
|
255
|
+
logger = logging.getLogger(__name__)
|
|
256
|
+
|
|
257
|
+
# Get glyph history
|
|
258
|
+
glyph_history = G.nodes[node].get("glyph_history", [])
|
|
259
|
+
if not glyph_history:
|
|
260
|
+
# No history - cannot validate U4b
|
|
261
|
+
logger.warning(
|
|
262
|
+
f"Node {node}: No glyph history available. Cannot verify U4b compliance."
|
|
263
|
+
)
|
|
264
|
+
return
|
|
265
|
+
|
|
266
|
+
# Import glyph_function_name to convert glyphs to operator names
|
|
267
|
+
from ..grammar import glyph_function_name
|
|
268
|
+
|
|
269
|
+
# Convert history to operator names
|
|
270
|
+
history_names = [glyph_function_name(g) for g in glyph_history]
|
|
271
|
+
|
|
272
|
+
# Part 1: Check for prior IL (Coherence)
|
|
273
|
+
require_il = bool(G.graph.get("ZHIR_REQUIRE_IL_PRECEDENCE", False))
|
|
274
|
+
il_found = "coherence" in history_names
|
|
275
|
+
|
|
276
|
+
if require_il and not il_found:
|
|
277
|
+
raise OperatorPreconditionError(
|
|
278
|
+
"Mutation",
|
|
279
|
+
"U4b violation: ZHIR requires prior IL (Coherence) for stable transformation base. "
|
|
280
|
+
"Apply Coherence before mutation sequence. "
|
|
281
|
+
f"Recent history: {history_names[-5:] if len(history_names) > 5 else history_names}"
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
if il_found:
|
|
285
|
+
logger.debug(f"Node {node}: ZHIR IL precedence satisfied (prior Coherence found)")
|
|
286
|
+
|
|
287
|
+
# Part 2: Check for recent destabilizer
|
|
288
|
+
# This also records destabilizer context for telemetry
|
|
289
|
+
context = record_destabilizer_context(G, node, logger)
|
|
290
|
+
|
|
291
|
+
require_destabilizer = bool(G.graph.get("ZHIR_REQUIRE_DESTABILIZER", False))
|
|
292
|
+
destabilizer_found = context.get("destabilizer_operator")
|
|
293
|
+
|
|
294
|
+
if require_destabilizer and destabilizer_found is None:
|
|
295
|
+
recent_history = context.get("recent_history", [])
|
|
296
|
+
raise OperatorPreconditionError(
|
|
297
|
+
"Mutation",
|
|
298
|
+
"U4b violation: ZHIR requires recent destabilizer (OZ/VAL/etc) within ~3 ops. "
|
|
299
|
+
f"Recent history: {recent_history}. "
|
|
300
|
+
"Apply Dissonance or Expansion to elevate ΔNFR first."
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def record_destabilizer_context(
|
|
305
|
+
G: TNFRGraph, node: NodeId, logger: logging.Logger | None = None
|
|
306
|
+
) -> dict:
|
|
307
|
+
"""Detect and record which destabilizer enabled the current mutation.
|
|
308
|
+
|
|
309
|
+
This implements R4 Extended telemetry by analyzing the glyph_history
|
|
310
|
+
to determine which destabilizer type (strong/moderate/weak) is within
|
|
311
|
+
its appropriate bifurcation window.
|
|
312
|
+
|
|
313
|
+
Parameters
|
|
314
|
+
----------
|
|
315
|
+
G : TNFRGraph
|
|
316
|
+
Graph containing the node
|
|
317
|
+
node : NodeId
|
|
318
|
+
Node being mutated
|
|
319
|
+
logger : logging.Logger, optional
|
|
320
|
+
Logger for telemetry output
|
|
321
|
+
|
|
322
|
+
Returns
|
|
323
|
+
-------
|
|
324
|
+
dict
|
|
325
|
+
Destabilizer context with keys:
|
|
326
|
+
- destabilizer_type: "strong"/"moderate"/"weak"/None
|
|
327
|
+
- destabilizer_operator: Name of destabilizer glyph
|
|
328
|
+
- destabilizer_distance: Operations since destabilizer
|
|
329
|
+
- recent_history: Last N operator names
|
|
330
|
+
|
|
331
|
+
Notes
|
|
332
|
+
-----
|
|
333
|
+
The destabilizer context is stored in node['_mutation_context'] for
|
|
334
|
+
structural tracing and post-hoc analysis. This enables understanding
|
|
335
|
+
of bifurcation pathways without breaking TNFR structural invariants.
|
|
336
|
+
|
|
337
|
+
**Bifurcation Windows (from BIFURCATION_WINDOWS)**:
|
|
338
|
+
- Strong destabilizers (OZ, VAL): window = 4 operations
|
|
339
|
+
- Moderate destabilizers: window = 2 operations
|
|
340
|
+
- Weak destabilizers: window = 1 operation (immediate only)
|
|
341
|
+
|
|
342
|
+
Examples
|
|
343
|
+
--------
|
|
344
|
+
>>> from tnfr.structural import create_nfr
|
|
345
|
+
>>> from tnfr.operators import Dissonance
|
|
346
|
+
>>> G, node = create_nfr("test", epi=0.5, vf=1.0)
|
|
347
|
+
>>> Dissonance()(G, node) # Apply OZ (strong destabilizer)
|
|
348
|
+
>>> context = record_destabilizer_context(G, node)
|
|
349
|
+
>>> context["destabilizer_type"] # doctest: +SKIP
|
|
350
|
+
'strong'
|
|
351
|
+
>>> context["destabilizer_operator"] # doctest: +SKIP
|
|
352
|
+
'dissonance'
|
|
353
|
+
"""
|
|
354
|
+
if logger is None:
|
|
355
|
+
import logging
|
|
356
|
+
logger = logging.getLogger(__name__)
|
|
357
|
+
|
|
358
|
+
# Get glyph history from node
|
|
359
|
+
history = G.nodes[node].get("glyph_history", [])
|
|
360
|
+
if not history:
|
|
361
|
+
# No history available, mutation enabled by external factors
|
|
362
|
+
context = {
|
|
363
|
+
"destabilizer_type": None,
|
|
364
|
+
"destabilizer_operator": None,
|
|
365
|
+
"destabilizer_distance": None,
|
|
366
|
+
"recent_history": [],
|
|
367
|
+
}
|
|
368
|
+
G.nodes[node]["_mutation_context"] = context
|
|
369
|
+
return context
|
|
370
|
+
|
|
371
|
+
# Import glyph_function_name to convert glyphs to operator names
|
|
372
|
+
from ..grammar import glyph_function_name
|
|
373
|
+
|
|
374
|
+
# Get recent history (up to max window size)
|
|
375
|
+
max_window = BIFURCATION_WINDOWS["strong"]
|
|
376
|
+
recent = list(history)[-max_window:] if len(history) > max_window else list(history)
|
|
377
|
+
recent_names = [glyph_function_name(g) for g in recent]
|
|
378
|
+
|
|
379
|
+
# Search backwards for destabilizers, checking window constraints
|
|
380
|
+
destabilizer_found = None
|
|
381
|
+
destabilizer_type = None
|
|
382
|
+
destabilizer_distance = None
|
|
383
|
+
|
|
384
|
+
for i, op_name in enumerate(reversed(recent_names)):
|
|
385
|
+
distance = i + 1 # Distance from mutation (1 = immediate predecessor)
|
|
386
|
+
|
|
387
|
+
# Check strong destabilizers (window = 4)
|
|
388
|
+
if (
|
|
389
|
+
op_name in DESTABILIZERS_STRONG
|
|
390
|
+
and distance <= BIFURCATION_WINDOWS["strong"]
|
|
391
|
+
):
|
|
392
|
+
destabilizer_found = op_name
|
|
393
|
+
destabilizer_type = "strong"
|
|
394
|
+
destabilizer_distance = distance
|
|
395
|
+
break
|
|
396
|
+
|
|
397
|
+
# Check moderate destabilizers (window = 2)
|
|
398
|
+
if (
|
|
399
|
+
op_name in DESTABILIZERS_MODERATE
|
|
400
|
+
and distance <= BIFURCATION_WINDOWS["moderate"]
|
|
401
|
+
):
|
|
402
|
+
destabilizer_found = op_name
|
|
403
|
+
destabilizer_type = "moderate"
|
|
404
|
+
destabilizer_distance = distance
|
|
405
|
+
break
|
|
406
|
+
|
|
407
|
+
# Check weak destabilizers (window = 1, immediate only)
|
|
408
|
+
if op_name in DESTABILIZERS_WEAK and distance == 1:
|
|
409
|
+
destabilizer_found = op_name
|
|
410
|
+
destabilizer_type = "weak"
|
|
411
|
+
destabilizer_distance = distance
|
|
412
|
+
break
|
|
413
|
+
|
|
414
|
+
# Store context in node metadata for telemetry
|
|
415
|
+
context = {
|
|
416
|
+
"destabilizer_type": destabilizer_type,
|
|
417
|
+
"destabilizer_operator": destabilizer_found,
|
|
418
|
+
"destabilizer_distance": destabilizer_distance,
|
|
419
|
+
"recent_history": recent_names,
|
|
420
|
+
}
|
|
421
|
+
G.nodes[node]["_mutation_context"] = context
|
|
422
|
+
|
|
423
|
+
# Log telemetry for structural tracing
|
|
424
|
+
if destabilizer_found:
|
|
425
|
+
logger.info(
|
|
426
|
+
f"Node {node}: ZHIR enabled by {destabilizer_type} destabilizer "
|
|
427
|
+
f"({destabilizer_found}) at distance {destabilizer_distance}"
|
|
428
|
+
)
|
|
429
|
+
else:
|
|
430
|
+
logger.warning(
|
|
431
|
+
f"Node {node}: ZHIR without detectable destabilizer in history. "
|
|
432
|
+
f"Recent operators: {recent_names}"
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
return context
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def diagnose_mutation_readiness(G: TNFRGraph, node: NodeId) -> dict:
|
|
439
|
+
"""Comprehensive diagnostic for ZHIR readiness.
|
|
440
|
+
|
|
441
|
+
Analyzes node state and returns detailed readiness report with:
|
|
442
|
+
- Overall readiness boolean
|
|
443
|
+
- Individual check results
|
|
444
|
+
- Recommendations for corrections
|
|
445
|
+
|
|
446
|
+
Parameters
|
|
447
|
+
----------
|
|
448
|
+
G : TNFRGraph
|
|
449
|
+
Graph containing the node
|
|
450
|
+
node : NodeId
|
|
451
|
+
Node to diagnose
|
|
452
|
+
|
|
453
|
+
Returns
|
|
454
|
+
-------
|
|
455
|
+
dict
|
|
456
|
+
Diagnostic report with structure:
|
|
457
|
+
{
|
|
458
|
+
"ready": bool,
|
|
459
|
+
"checks": {
|
|
460
|
+
"minimum_vf": {"passed": bool, "value": float, "threshold": float},
|
|
461
|
+
"threshold_crossing": {"passed": bool, "depi_dt": float, "xi": float},
|
|
462
|
+
"il_precedence": {"passed": bool, "found": bool},
|
|
463
|
+
"recent_destabilizer": {"passed": bool, "type": str|None, "distance": int|None},
|
|
464
|
+
"history_length": {"passed": bool, "length": int, "required": int},
|
|
465
|
+
},
|
|
466
|
+
"recommendations": [str, ...]
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
Examples
|
|
470
|
+
--------
|
|
471
|
+
>>> from tnfr.structural import create_nfr
|
|
472
|
+
>>> G, node = create_nfr("test", epi=0.5, vf=1.0)
|
|
473
|
+
>>> report = diagnose_mutation_readiness(G, node)
|
|
474
|
+
>>> report["ready"] # doctest: +SKIP
|
|
475
|
+
False
|
|
476
|
+
>>> report["recommendations"] # doctest: +SKIP
|
|
477
|
+
['Apply IL (Coherence) for stable base', 'Apply OZ (Dissonance) to elevate ΔNFR', ...]
|
|
478
|
+
"""
|
|
479
|
+
import logging
|
|
480
|
+
|
|
481
|
+
checks = {}
|
|
482
|
+
recommendations = []
|
|
483
|
+
|
|
484
|
+
# Check 1: Minimum νf
|
|
485
|
+
vf = float(get_attr(G.nodes[node], ALIAS_VF, 0.0))
|
|
486
|
+
min_vf = float(G.graph.get("ZHIR_MIN_VF", 0.05))
|
|
487
|
+
vf_passed = vf >= min_vf
|
|
488
|
+
checks["minimum_vf"] = {
|
|
489
|
+
"passed": vf_passed,
|
|
490
|
+
"value": vf,
|
|
491
|
+
"threshold": min_vf,
|
|
492
|
+
}
|
|
493
|
+
if not vf_passed:
|
|
494
|
+
recommendations.append(
|
|
495
|
+
f"Increase νf: current={vf:.3f}, required={min_vf:.3f}. "
|
|
496
|
+
f"Apply AL (Emission) or NAV (Transition) to boost structural frequency."
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
# Check 2: Threshold crossing
|
|
500
|
+
epi_history = G.nodes[node].get("epi_history") or G.nodes[node].get("_epi_history", [])
|
|
501
|
+
xi_threshold = float(G.graph.get("ZHIR_THRESHOLD_XI", 0.1))
|
|
502
|
+
|
|
503
|
+
if len(epi_history) >= 2:
|
|
504
|
+
depi_dt = abs(epi_history[-1] - epi_history[-2])
|
|
505
|
+
threshold_passed = depi_dt >= xi_threshold
|
|
506
|
+
checks["threshold_crossing"] = {
|
|
507
|
+
"passed": threshold_passed,
|
|
508
|
+
"depi_dt": depi_dt,
|
|
509
|
+
"xi": xi_threshold,
|
|
510
|
+
}
|
|
511
|
+
if not threshold_passed:
|
|
512
|
+
recommendations.append(
|
|
513
|
+
f"Increase structural velocity: ∂EPI/∂t={depi_dt:.3f} < ξ={xi_threshold}. "
|
|
514
|
+
f"Apply OZ (Dissonance) or VAL (Expansion) to elevate reorganization."
|
|
515
|
+
)
|
|
516
|
+
else:
|
|
517
|
+
checks["threshold_crossing"] = {
|
|
518
|
+
"passed": False,
|
|
519
|
+
"depi_dt": None,
|
|
520
|
+
"xi": xi_threshold,
|
|
521
|
+
"reason": "Insufficient history"
|
|
522
|
+
}
|
|
523
|
+
recommendations.append(
|
|
524
|
+
f"Build EPI history: only {len(epi_history)} points available (need ≥2). "
|
|
525
|
+
f"Apply several operators to establish history."
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
# Check 3: IL precedence
|
|
529
|
+
glyph_history = G.nodes[node].get("glyph_history", [])
|
|
530
|
+
if glyph_history:
|
|
531
|
+
from ..grammar import glyph_function_name
|
|
532
|
+
history_names = [glyph_function_name(g) for g in glyph_history]
|
|
533
|
+
il_found = "coherence" in history_names
|
|
534
|
+
else:
|
|
535
|
+
il_found = False
|
|
536
|
+
history_names = []
|
|
537
|
+
|
|
538
|
+
checks["il_precedence"] = {
|
|
539
|
+
"passed": il_found,
|
|
540
|
+
"found": il_found,
|
|
541
|
+
}
|
|
542
|
+
if not il_found:
|
|
543
|
+
recommendations.append(
|
|
544
|
+
"Apply IL (Coherence) for stable transformation base (U4b Part 1)."
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
# Check 4: Recent destabilizer
|
|
548
|
+
logger = logging.getLogger(__name__)
|
|
549
|
+
context = record_destabilizer_context(G, node, logger)
|
|
550
|
+
destabilizer_found = context.get("destabilizer_operator") is not None
|
|
551
|
+
|
|
552
|
+
checks["recent_destabilizer"] = {
|
|
553
|
+
"passed": destabilizer_found,
|
|
554
|
+
"type": context.get("destabilizer_type"),
|
|
555
|
+
"distance": context.get("destabilizer_distance"),
|
|
556
|
+
"operator": context.get("destabilizer_operator"),
|
|
557
|
+
}
|
|
558
|
+
if not destabilizer_found:
|
|
559
|
+
recommendations.append(
|
|
560
|
+
"Apply destabilizer (OZ/VAL) within last ~3 operations to elevate ΔNFR (U4b Part 2)."
|
|
561
|
+
)
|
|
562
|
+
|
|
563
|
+
# Check 5: History length
|
|
564
|
+
min_history = int(G.graph.get("ZHIR_MIN_HISTORY_LENGTH", 2))
|
|
565
|
+
history_passed = len(epi_history) >= min_history
|
|
566
|
+
checks["history_length"] = {
|
|
567
|
+
"passed": history_passed,
|
|
568
|
+
"length": len(epi_history),
|
|
569
|
+
"required": min_history,
|
|
570
|
+
}
|
|
571
|
+
# Already covered by threshold check recommendations
|
|
572
|
+
|
|
573
|
+
# Overall readiness
|
|
574
|
+
all_passed = all(check.get("passed", False) for check in checks.values())
|
|
575
|
+
|
|
576
|
+
return {
|
|
577
|
+
"ready": all_passed,
|
|
578
|
+
"checks": checks,
|
|
579
|
+
"recommendations": recommendations,
|
|
580
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""Strict precondition validation for EN (Reception) operator.
|
|
2
|
+
|
|
3
|
+
This module implements canonical precondition validation for the Reception (EN)
|
|
4
|
+
structural operator according to TNFR.pdf §2.2.1. EN requires specific structural
|
|
5
|
+
conditions to maintain TNFR operational fidelity:
|
|
6
|
+
|
|
7
|
+
1. **Receptive capacity**: EPI must be below saturation threshold (node not saturated)
|
|
8
|
+
2. **Minimal dissonance**: DNFR must be below threshold (low reorganization pressure)
|
|
9
|
+
3. **Emission sources**: Network should have active emission sources (warning for isolated nodes)
|
|
10
|
+
|
|
11
|
+
These validations protect structural integrity by ensuring EN is only applied to
|
|
12
|
+
nodes in the appropriate state for coherence integration.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from typing import TYPE_CHECKING, Any
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from ...types import TNFRGraph
|
|
21
|
+
|
|
22
|
+
__all__ = ["validate_reception_strict"]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def validate_reception_strict(G: TNFRGraph, node: Any) -> None:
|
|
26
|
+
"""Validate strict canonical preconditions for EN (Reception) operator.
|
|
27
|
+
|
|
28
|
+
According to TNFR.pdf §2.2.1, Reception (EN - Recepción estructural) requires:
|
|
29
|
+
|
|
30
|
+
1. **Receptive capacity**: EPI < saturation threshold (node has capacity to receive)
|
|
31
|
+
2. **Minimal dissonance**: DNFR < threshold (low reorganization pressure for stable integration)
|
|
32
|
+
3. **Emission sources**: Network connectivity with active sources (warning if isolated)
|
|
33
|
+
|
|
34
|
+
Parameters
|
|
35
|
+
----------
|
|
36
|
+
G : TNFRGraph
|
|
37
|
+
Graph containing the node to validate
|
|
38
|
+
node : Any
|
|
39
|
+
Node identifier for validation
|
|
40
|
+
|
|
41
|
+
Raises
|
|
42
|
+
------
|
|
43
|
+
ValueError
|
|
44
|
+
If EPI >= saturation threshold (node saturated - cannot receive more coherence)
|
|
45
|
+
If DNFR >= threshold (excessive dissonance - consider IL/Coherence first)
|
|
46
|
+
|
|
47
|
+
Warnings
|
|
48
|
+
--------
|
|
49
|
+
UserWarning
|
|
50
|
+
If node is isolated in a multi-node network (no emission sources available)
|
|
51
|
+
|
|
52
|
+
Notes
|
|
53
|
+
-----
|
|
54
|
+
Thresholds are configurable via:
|
|
55
|
+
- Graph metadata: ``G.graph["EPI_SATURATION_MAX"]``, ``G.graph["DNFR_RECEPTION_MAX"]``
|
|
56
|
+
- Module defaults: :data:`tnfr.config.thresholds.EPI_SATURATION_MAX`, etc.
|
|
57
|
+
|
|
58
|
+
Examples
|
|
59
|
+
--------
|
|
60
|
+
>>> from tnfr.structural import create_nfr
|
|
61
|
+
>>> from tnfr.operators.preconditions.reception import validate_reception_strict
|
|
62
|
+
>>> G, node = create_nfr("test", epi=0.5, vf=0.9)
|
|
63
|
+
>>> G.nodes[node]["dnfr"] = 0.08
|
|
64
|
+
>>> validate_reception_strict(G, node) # OK - receptive capacity available
|
|
65
|
+
|
|
66
|
+
>>> G2, node2 = create_nfr("saturated", epi=0.95, vf=1.0)
|
|
67
|
+
>>> validate_reception_strict(G2, node2) # doctest: +SKIP
|
|
68
|
+
Traceback (most recent call last):
|
|
69
|
+
...
|
|
70
|
+
ValueError: EN precondition failed: EPI=0.950 >= 0.9. Node saturated, cannot receive more coherence.
|
|
71
|
+
|
|
72
|
+
See Also
|
|
73
|
+
--------
|
|
74
|
+
tnfr.config.thresholds : Configurable threshold constants
|
|
75
|
+
tnfr.operators.preconditions : Base precondition validators
|
|
76
|
+
tnfr.operators.definitions.Reception : Reception operator implementation
|
|
77
|
+
"""
|
|
78
|
+
import warnings
|
|
79
|
+
|
|
80
|
+
from ...alias import get_attr
|
|
81
|
+
from ...constants.aliases import ALIAS_DNFR, ALIAS_EPI
|
|
82
|
+
from ...config.thresholds import DNFR_RECEPTION_MAX, EPI_SATURATION_MAX
|
|
83
|
+
|
|
84
|
+
# Get current node state
|
|
85
|
+
epi = float(get_attr(G.nodes[node], ALIAS_EPI, 0.0))
|
|
86
|
+
dnfr = float(get_attr(G.nodes[node], ALIAS_DNFR, 0.0))
|
|
87
|
+
|
|
88
|
+
# Get configurable thresholds (allow override via graph metadata)
|
|
89
|
+
epi_threshold = float(G.graph.get("EPI_SATURATION_MAX", EPI_SATURATION_MAX))
|
|
90
|
+
dnfr_threshold = float(G.graph.get("DNFR_RECEPTION_MAX", DNFR_RECEPTION_MAX))
|
|
91
|
+
|
|
92
|
+
# Precondition 1: EPI must be below saturation threshold (receptive capacity available)
|
|
93
|
+
# Reception integrates external coherence into local structure.
|
|
94
|
+
# If EPI is saturated, node cannot accommodate more coherence.
|
|
95
|
+
if epi >= epi_threshold:
|
|
96
|
+
raise ValueError(
|
|
97
|
+
f"EN precondition failed: EPI={epi:.3f} >= {epi_threshold:.3f}. "
|
|
98
|
+
f"Node saturated, cannot receive more coherence. "
|
|
99
|
+
f"Apply IL (Coherence) first to stabilize and compress structure, "
|
|
100
|
+
f"or apply NUL (Contraction) to reduce complexity if appropriate."
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# Precondition 2: DNFR must be below threshold (minimal dissonance for stable integration)
|
|
104
|
+
# Excessive reorganization pressure prevents effective integration of external coherence.
|
|
105
|
+
# Node must first stabilize before receiving more information.
|
|
106
|
+
if dnfr >= dnfr_threshold:
|
|
107
|
+
raise ValueError(
|
|
108
|
+
f"EN precondition failed: DNFR={dnfr:.3f} >= {dnfr_threshold:.3f}. "
|
|
109
|
+
f"Excessive dissonance prevents reception. "
|
|
110
|
+
f"Consider IL (Coherence) first to reduce reorganization pressure."
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# Precondition 3: Emission sources check (warning only - not a hard failure)
|
|
114
|
+
# Isolated nodes can still apply EN, but there are no external sources to receive from
|
|
115
|
+
node_degree = G.degree(node)
|
|
116
|
+
network_size = len(G)
|
|
117
|
+
|
|
118
|
+
if node_degree == 0 and network_size > 1:
|
|
119
|
+
warnings.warn(
|
|
120
|
+
f"EN warning: Node {node!r} isolated. No emission sources available. "
|
|
121
|
+
f"Reception possible but no external coherence to integrate. "
|
|
122
|
+
f"Consider UM (Coupling) to establish network connections first.",
|
|
123
|
+
UserWarning,
|
|
124
|
+
stacklevel=3,
|
|
125
|
+
)
|