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,1226 @@
|
|
|
1
|
+
"""Precondition validators for TNFR structural operators.
|
|
2
|
+
|
|
3
|
+
Each operator has specific requirements that must be met before execution
|
|
4
|
+
to maintain TNFR structural invariants. This package provides validators
|
|
5
|
+
for each of the 13 canonical operators.
|
|
6
|
+
|
|
7
|
+
The preconditions package has been restructured to support both legacy
|
|
8
|
+
imports (from ..preconditions import validate_*) and new modular imports
|
|
9
|
+
(from ..preconditions.emission import validate_emission_strict).
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from typing import TYPE_CHECKING, Any
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from ...types import NodeId, TNFRGraph
|
|
18
|
+
import logging
|
|
19
|
+
|
|
20
|
+
from ...alias import get_attr
|
|
21
|
+
from ...config.operator_names import (
|
|
22
|
+
BIFURCATION_WINDOWS,
|
|
23
|
+
DESTABILIZERS_MODERATE,
|
|
24
|
+
DESTABILIZERS_STRONG,
|
|
25
|
+
DESTABILIZERS_WEAK,
|
|
26
|
+
)
|
|
27
|
+
from ...constants.aliases import ALIAS_DNFR, ALIAS_EPI, ALIAS_THETA, ALIAS_VF
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"OperatorPreconditionError",
|
|
31
|
+
"validate_emission",
|
|
32
|
+
"validate_reception",
|
|
33
|
+
"validate_coherence",
|
|
34
|
+
"validate_dissonance",
|
|
35
|
+
"validate_coupling",
|
|
36
|
+
"validate_resonance",
|
|
37
|
+
"validate_silence",
|
|
38
|
+
"validate_expansion",
|
|
39
|
+
"validate_contraction",
|
|
40
|
+
"validate_self_organization",
|
|
41
|
+
"validate_mutation",
|
|
42
|
+
"validate_transition",
|
|
43
|
+
"validate_recursivity",
|
|
44
|
+
"diagnose_coherence_readiness",
|
|
45
|
+
"diagnose_resonance_readiness",
|
|
46
|
+
"diagnose_mutation_readiness",
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class OperatorPreconditionError(Exception):
|
|
51
|
+
"""Raised when an operator's preconditions are not met."""
|
|
52
|
+
|
|
53
|
+
def __init__(self, operator: str, reason: str) -> None:
|
|
54
|
+
"""Initialize precondition error.
|
|
55
|
+
|
|
56
|
+
Parameters
|
|
57
|
+
----------
|
|
58
|
+
operator : str
|
|
59
|
+
Name of the operator that failed validation
|
|
60
|
+
reason : str
|
|
61
|
+
Description of why the precondition failed
|
|
62
|
+
"""
|
|
63
|
+
self.operator = operator
|
|
64
|
+
self.reason = reason
|
|
65
|
+
super().__init__(f"{operator}: {reason}")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _get_node_attr(
|
|
69
|
+
G: "TNFRGraph", node: "NodeId", aliases: tuple[str, ...], default: float = 0.0
|
|
70
|
+
) -> float:
|
|
71
|
+
"""Get node attribute using alias fallback."""
|
|
72
|
+
return float(get_attr(G.nodes[node], aliases, default))
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def validate_emission(G: "TNFRGraph", node: "NodeId") -> None:
|
|
76
|
+
"""AL - Emission requires node in latent or low activation state.
|
|
77
|
+
|
|
78
|
+
Parameters
|
|
79
|
+
----------
|
|
80
|
+
G : TNFRGraph
|
|
81
|
+
Graph containing the node
|
|
82
|
+
node : NodeId
|
|
83
|
+
Node to validate
|
|
84
|
+
|
|
85
|
+
Raises
|
|
86
|
+
------
|
|
87
|
+
OperatorPreconditionError
|
|
88
|
+
If EPI is already too high for emission to be meaningful
|
|
89
|
+
"""
|
|
90
|
+
epi = _get_node_attr(G, node, ALIAS_EPI)
|
|
91
|
+
# Emission is meant to activate latent nodes, not boost already active ones
|
|
92
|
+
# This is a soft threshold - configurable via graph metadata
|
|
93
|
+
max_epi = float(G.graph.get("AL_MAX_EPI_FOR_EMISSION", 0.8))
|
|
94
|
+
if epi >= max_epi:
|
|
95
|
+
raise OperatorPreconditionError(
|
|
96
|
+
"Emission", f"Node already active (EPI={epi:.3f} >= {max_epi:.3f})"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def validate_reception(G: "TNFRGraph", node: "NodeId") -> None:
|
|
101
|
+
"""EN - Reception requires node to have neighbors to receive from.
|
|
102
|
+
|
|
103
|
+
Parameters
|
|
104
|
+
----------
|
|
105
|
+
G : TNFRGraph
|
|
106
|
+
Graph containing the node
|
|
107
|
+
node : NodeId
|
|
108
|
+
Node to validate
|
|
109
|
+
|
|
110
|
+
Raises
|
|
111
|
+
------
|
|
112
|
+
OperatorPreconditionError
|
|
113
|
+
If node has no neighbors to receive energy from
|
|
114
|
+
"""
|
|
115
|
+
neighbors = list(G.neighbors(node))
|
|
116
|
+
if not neighbors:
|
|
117
|
+
raise OperatorPreconditionError(
|
|
118
|
+
"Reception", "Node has no neighbors to receive energy from"
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def validate_coherence(G: "TNFRGraph", node: "NodeId") -> None:
|
|
123
|
+
"""IL - Coherence requires active EPI, νf, and manageable ΔNFR.
|
|
124
|
+
|
|
125
|
+
This function delegates to the strict validation implementation
|
|
126
|
+
in coherence.py module, which provides comprehensive canonical
|
|
127
|
+
precondition checks according to TNFR.pdf §2.2.1.
|
|
128
|
+
|
|
129
|
+
Parameters
|
|
130
|
+
----------
|
|
131
|
+
G : TNFRGraph
|
|
132
|
+
Graph containing the node
|
|
133
|
+
node : NodeId
|
|
134
|
+
Node to validate
|
|
135
|
+
|
|
136
|
+
Raises
|
|
137
|
+
------
|
|
138
|
+
ValueError
|
|
139
|
+
If critical preconditions are not met (active EPI, νf, non-saturated state)
|
|
140
|
+
|
|
141
|
+
Warnings
|
|
142
|
+
--------
|
|
143
|
+
UserWarning
|
|
144
|
+
For suboptimal conditions (zero ΔNFR, critical ΔNFR, isolated node)
|
|
145
|
+
|
|
146
|
+
Notes
|
|
147
|
+
-----
|
|
148
|
+
For backward compatibility, this function maintains the same signature
|
|
149
|
+
as the legacy validate_coherence but now provides enhanced validation.
|
|
150
|
+
|
|
151
|
+
See Also
|
|
152
|
+
--------
|
|
153
|
+
tnfr.operators.preconditions.coherence.validate_coherence_strict : Full implementation
|
|
154
|
+
"""
|
|
155
|
+
from .coherence import validate_coherence_strict
|
|
156
|
+
|
|
157
|
+
validate_coherence_strict(G, node)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def diagnose_coherence_readiness(G: "TNFRGraph", node: "NodeId") -> dict:
|
|
161
|
+
"""Diagnose node readiness for IL (Coherence) operator.
|
|
162
|
+
|
|
163
|
+
Provides comprehensive diagnostic report with readiness status and
|
|
164
|
+
actionable recommendations for IL operator application.
|
|
165
|
+
|
|
166
|
+
Parameters
|
|
167
|
+
----------
|
|
168
|
+
G : TNFRGraph
|
|
169
|
+
Graph containing the node
|
|
170
|
+
node : NodeId
|
|
171
|
+
Node to diagnose
|
|
172
|
+
|
|
173
|
+
Returns
|
|
174
|
+
-------
|
|
175
|
+
dict
|
|
176
|
+
Diagnostic report with readiness status, check results, values, and recommendations
|
|
177
|
+
|
|
178
|
+
See Also
|
|
179
|
+
--------
|
|
180
|
+
tnfr.operators.preconditions.coherence.diagnose_coherence_readiness : Full implementation
|
|
181
|
+
"""
|
|
182
|
+
from .coherence import diagnose_coherence_readiness as _diagnose
|
|
183
|
+
|
|
184
|
+
return _diagnose(G, node)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def validate_dissonance(G: "TNFRGraph", node: "NodeId") -> None:
|
|
188
|
+
"""OZ - Dissonance requires comprehensive structural preconditions.
|
|
189
|
+
|
|
190
|
+
This function delegates to the strict validation implementation in
|
|
191
|
+
dissonance.py module, which provides canonical precondition checks:
|
|
192
|
+
|
|
193
|
+
1. Minimum coherence base (EPI >= threshold)
|
|
194
|
+
2. ΔNFR not critically high (avoid overload)
|
|
195
|
+
3. Sufficient νf for reorganization response
|
|
196
|
+
4. No overload pattern (sobrecarga disonante)
|
|
197
|
+
5. Network connectivity (warning)
|
|
198
|
+
|
|
199
|
+
Also detects bifurcation readiness when ∂²EPI/∂t² > τ, enabling
|
|
200
|
+
alternative structural paths (ZHIR, NUL, IL, THOL).
|
|
201
|
+
|
|
202
|
+
Parameters
|
|
203
|
+
----------
|
|
204
|
+
G : TNFRGraph
|
|
205
|
+
Graph containing the node
|
|
206
|
+
node : NodeId
|
|
207
|
+
Node to validate
|
|
208
|
+
|
|
209
|
+
Raises
|
|
210
|
+
------
|
|
211
|
+
OperatorPreconditionError
|
|
212
|
+
If critical preconditions are not met (EPI, ΔNFR, νf, overload)
|
|
213
|
+
|
|
214
|
+
Notes
|
|
215
|
+
-----
|
|
216
|
+
For backward compatibility, this function maintains the same signature
|
|
217
|
+
as the legacy validate_dissonance but now provides enhanced validation.
|
|
218
|
+
|
|
219
|
+
When bifurcation threshold is exceeded, sets node['_bifurcation_ready'] = True
|
|
220
|
+
and logs the event for telemetry.
|
|
221
|
+
|
|
222
|
+
See Also
|
|
223
|
+
--------
|
|
224
|
+
tnfr.operators.preconditions.dissonance.validate_dissonance_strict : Full implementation
|
|
225
|
+
"""
|
|
226
|
+
import logging
|
|
227
|
+
|
|
228
|
+
logger = logging.getLogger(__name__)
|
|
229
|
+
|
|
230
|
+
# First, apply strict canonical preconditions
|
|
231
|
+
# This validates EPI, ΔNFR, νf, overload, and connectivity
|
|
232
|
+
from .dissonance import validate_dissonance_strict
|
|
233
|
+
|
|
234
|
+
try:
|
|
235
|
+
validate_dissonance_strict(G, node)
|
|
236
|
+
except ValueError as e:
|
|
237
|
+
# Convert ValueError to OperatorPreconditionError for backward compatibility
|
|
238
|
+
raise OperatorPreconditionError(
|
|
239
|
+
"Dissonance", str(e).replace("OZ precondition failed: ", "")
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
# Check bifurcation readiness using existing THOL infrastructure
|
|
243
|
+
# Reuse _compute_epi_acceleration from SelfOrganization
|
|
244
|
+
from ..definitions import SelfOrganization
|
|
245
|
+
|
|
246
|
+
thol_instance = SelfOrganization()
|
|
247
|
+
d2_epi = thol_instance._compute_epi_acceleration(G, node)
|
|
248
|
+
|
|
249
|
+
# Get bifurcation threshold
|
|
250
|
+
tau = float(G.graph.get("BIFURCATION_THRESHOLD_TAU", 0.5))
|
|
251
|
+
|
|
252
|
+
# Store d²EPI for telemetry (using existing ALIAS_D2EPI)
|
|
253
|
+
from ...alias import set_attr
|
|
254
|
+
from ...constants.aliases import ALIAS_D2EPI
|
|
255
|
+
|
|
256
|
+
set_attr(G.nodes[node], ALIAS_D2EPI, d2_epi)
|
|
257
|
+
|
|
258
|
+
# Check if bifurcation threshold exceeded
|
|
259
|
+
if d2_epi > tau:
|
|
260
|
+
# Mark node as bifurcation-ready
|
|
261
|
+
G.nodes[node]["_bifurcation_ready"] = True
|
|
262
|
+
logger.info(
|
|
263
|
+
f"Node {node}: bifurcation threshold exceeded "
|
|
264
|
+
f"(∂²EPI/∂t²={d2_epi:.3f} > τ={tau}). "
|
|
265
|
+
f"Alternative structural paths enabled."
|
|
266
|
+
)
|
|
267
|
+
else:
|
|
268
|
+
# Clear flag if previously set
|
|
269
|
+
G.nodes[node]["_bifurcation_ready"] = False
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def validate_coupling(G: "TNFRGraph", node: "NodeId") -> None:
|
|
273
|
+
"""UM - Coupling requires active nodes with compatible phases.
|
|
274
|
+
|
|
275
|
+
Validates comprehensive canonical preconditions for the UM (Coupling) operator
|
|
276
|
+
according to TNFR theory:
|
|
277
|
+
|
|
278
|
+
1. **Graph connectivity**: At least one other node exists for coupling
|
|
279
|
+
2. **Active EPI**: Node has sufficient structural form (EPI > threshold)
|
|
280
|
+
3. **Structural frequency**: Node has capacity for synchronization (νf > threshold)
|
|
281
|
+
4. **Phase compatibility** (MANDATORY per Invariant #5): At least one neighbor within phase range
|
|
282
|
+
|
|
283
|
+
Configuration Parameters
|
|
284
|
+
------------------------
|
|
285
|
+
UM_MIN_EPI : float, default 0.05
|
|
286
|
+
Minimum EPI magnitude required for coupling
|
|
287
|
+
UM_MIN_VF : float, default 0.01
|
|
288
|
+
Minimum structural frequency required for coupling
|
|
289
|
+
UM_STRICT_PHASE_CHECK : bool, default True (changed from False per RC3)
|
|
290
|
+
Enable strict phase compatibility checking with existing neighbors.
|
|
291
|
+
**MANDATORY per AGENTS.md Invariant #5**: "no coupling is valid without
|
|
292
|
+
explicit phase verification (synchrony)"
|
|
293
|
+
UM_MAX_PHASE_DIFF : float, default π/2
|
|
294
|
+
Maximum phase difference for compatible coupling (radians)
|
|
295
|
+
|
|
296
|
+
Parameters
|
|
297
|
+
----------
|
|
298
|
+
G : TNFRGraph
|
|
299
|
+
Graph containing the node
|
|
300
|
+
node : NodeId
|
|
301
|
+
Node to validate
|
|
302
|
+
|
|
303
|
+
Raises
|
|
304
|
+
------
|
|
305
|
+
OperatorPreconditionError
|
|
306
|
+
If node state is unsuitable for coupling:
|
|
307
|
+
- Graph has no other nodes
|
|
308
|
+
- EPI below threshold
|
|
309
|
+
- Structural frequency below threshold
|
|
310
|
+
- No phase-compatible neighbors (when strict checking enabled)
|
|
311
|
+
|
|
312
|
+
Notes
|
|
313
|
+
-----
|
|
314
|
+
**IMPORTANT**: Phase compatibility check is now MANDATORY by default
|
|
315
|
+
(UM_STRICT_PHASE_CHECK=True) to align with AGENTS.md Invariant #5 and RC3.
|
|
316
|
+
|
|
317
|
+
Set UM_STRICT_PHASE_CHECK=False to disable (NOT RECOMMENDED - violates
|
|
318
|
+
canonical physics requirements).
|
|
319
|
+
|
|
320
|
+
Examples
|
|
321
|
+
--------
|
|
322
|
+
>>> from tnfr.structural import create_nfr
|
|
323
|
+
>>> from tnfr.operators.preconditions import validate_coupling
|
|
324
|
+
>>>
|
|
325
|
+
>>> # Valid node for coupling
|
|
326
|
+
>>> G, node = create_nfr("active", epi=0.15, vf=0.50)
|
|
327
|
+
>>> validate_coupling(G, node) # Passes
|
|
328
|
+
>>>
|
|
329
|
+
>>> # Invalid: EPI too low
|
|
330
|
+
>>> G, node = create_nfr("inactive", epi=0.02, vf=0.50)
|
|
331
|
+
>>> validate_coupling(G, node) # Raises OperatorPreconditionError
|
|
332
|
+
|
|
333
|
+
See Also
|
|
334
|
+
--------
|
|
335
|
+
Coupling : UM operator that uses this validation
|
|
336
|
+
AGENTS.md : Invariant #5 (phase check mandatory)
|
|
337
|
+
EMERGENT_GRAMMAR_ANALYSIS.md : RC3 derivation
|
|
338
|
+
"""
|
|
339
|
+
import math
|
|
340
|
+
|
|
341
|
+
# Basic graph check - at least one other node required
|
|
342
|
+
if G.number_of_nodes() <= 1:
|
|
343
|
+
raise OperatorPreconditionError(
|
|
344
|
+
"Coupling", "Graph has no other nodes to couple with"
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
# Node must be active (non-zero EPI)
|
|
348
|
+
epi = _get_node_attr(G, node, ALIAS_EPI)
|
|
349
|
+
min_epi = float(G.graph.get("UM_MIN_EPI", 0.05))
|
|
350
|
+
if abs(epi) < min_epi:
|
|
351
|
+
raise OperatorPreconditionError(
|
|
352
|
+
"Coupling",
|
|
353
|
+
f"Node EPI too low for coupling (|EPI|={abs(epi):.3f} < {min_epi:.3f})",
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
# Node must have structural frequency capacity
|
|
357
|
+
vf = _get_node_attr(G, node, ALIAS_VF)
|
|
358
|
+
min_vf = float(G.graph.get("UM_MIN_VF", 0.01))
|
|
359
|
+
if vf < min_vf:
|
|
360
|
+
raise OperatorPreconditionError(
|
|
361
|
+
"Coupling", f"Structural frequency too low (νf={vf:.3f} < {min_vf:.3f})"
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
# RC3: Phase compatibility check
|
|
365
|
+
# Per AGENTS.md Invariant #5: "no coupling is valid without explicit phase verification"
|
|
366
|
+
# Changed from False to True to align with canonical physics requirements
|
|
367
|
+
strict_phase = bool(G.graph.get("UM_STRICT_PHASE_CHECK", True))
|
|
368
|
+
if strict_phase:
|
|
369
|
+
neighbors = list(G.neighbors(node))
|
|
370
|
+
if neighbors:
|
|
371
|
+
from ...utils.numeric import angle_diff
|
|
372
|
+
|
|
373
|
+
theta_i = _get_node_attr(G, node, ALIAS_THETA)
|
|
374
|
+
max_phase_diff = float(G.graph.get("UM_MAX_PHASE_DIFF", math.pi / 2))
|
|
375
|
+
|
|
376
|
+
# Check if at least one neighbor is phase-compatible
|
|
377
|
+
has_compatible = False
|
|
378
|
+
for neighbor in neighbors:
|
|
379
|
+
theta_j = _get_node_attr(G, neighbor, ALIAS_THETA)
|
|
380
|
+
phase_diff = abs(angle_diff(theta_i, theta_j))
|
|
381
|
+
if phase_diff <= max_phase_diff:
|
|
382
|
+
has_compatible = True
|
|
383
|
+
break
|
|
384
|
+
|
|
385
|
+
if not has_compatible:
|
|
386
|
+
raise OperatorPreconditionError(
|
|
387
|
+
"Coupling",
|
|
388
|
+
f"No phase-compatible neighbors (all |Δθ| > {max_phase_diff:.3f})",
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def validate_resonance(G: "TNFRGraph", node: "NodeId") -> None:
|
|
393
|
+
"""RA - Resonance requires comprehensive canonical preconditions.
|
|
394
|
+
|
|
395
|
+
This function delegates to the strict validation implementation in
|
|
396
|
+
resonance.py module, which provides canonical precondition checks:
|
|
397
|
+
|
|
398
|
+
1. Coherent source EPI (minimum structural form)
|
|
399
|
+
2. Network connectivity (edges for propagation)
|
|
400
|
+
3. Phase compatibility with neighbors (synchronization)
|
|
401
|
+
4. Controlled dissonance (stable resonance state)
|
|
402
|
+
5. Sufficient νf (propagation capacity)
|
|
403
|
+
|
|
404
|
+
Parameters
|
|
405
|
+
----------
|
|
406
|
+
G : TNFRGraph
|
|
407
|
+
Graph containing the node
|
|
408
|
+
node : NodeId
|
|
409
|
+
Node to validate
|
|
410
|
+
|
|
411
|
+
Raises
|
|
412
|
+
------
|
|
413
|
+
ValueError
|
|
414
|
+
If critical preconditions are not met (EPI, connectivity, νf, ΔNFR)
|
|
415
|
+
|
|
416
|
+
Warnings
|
|
417
|
+
--------
|
|
418
|
+
UserWarning
|
|
419
|
+
For suboptimal conditions (phase misalignment, isolated node)
|
|
420
|
+
|
|
421
|
+
Notes
|
|
422
|
+
-----
|
|
423
|
+
For backward compatibility, this function maintains the same signature
|
|
424
|
+
as the legacy validate_resonance but now provides enhanced validation.
|
|
425
|
+
|
|
426
|
+
Typical canonical sequences that satisfy RA preconditions:
|
|
427
|
+
- UM → RA: Coupling followed by propagation
|
|
428
|
+
- AL → RA: Emission followed by propagation
|
|
429
|
+
- IL → RA: Coherence stabilized then propagated
|
|
430
|
+
|
|
431
|
+
See Also
|
|
432
|
+
--------
|
|
433
|
+
tnfr.operators.preconditions.resonance.validate_resonance_strict : Full implementation
|
|
434
|
+
"""
|
|
435
|
+
from .resonance import validate_resonance_strict
|
|
436
|
+
|
|
437
|
+
validate_resonance_strict(G, node)
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def diagnose_resonance_readiness(G: "TNFRGraph", node: "NodeId") -> dict:
|
|
441
|
+
"""Diagnose node readiness for RA (Resonance) operator.
|
|
442
|
+
|
|
443
|
+
Provides comprehensive diagnostic report with readiness status and
|
|
444
|
+
actionable recommendations for RA operator application.
|
|
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 readiness status, check results, values, and recommendations
|
|
457
|
+
|
|
458
|
+
See Also
|
|
459
|
+
--------
|
|
460
|
+
tnfr.operators.preconditions.resonance.diagnose_resonance_readiness : Full implementation
|
|
461
|
+
"""
|
|
462
|
+
from .resonance import diagnose_resonance_readiness as _diagnose
|
|
463
|
+
|
|
464
|
+
return _diagnose(G, node)
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
def validate_silence(G: "TNFRGraph", node: "NodeId") -> None:
|
|
468
|
+
"""SHA - Silence requires vf > 0 to reduce.
|
|
469
|
+
|
|
470
|
+
Parameters
|
|
471
|
+
----------
|
|
472
|
+
G : TNFRGraph
|
|
473
|
+
Graph containing the node
|
|
474
|
+
node : NodeId
|
|
475
|
+
Node to validate
|
|
476
|
+
|
|
477
|
+
Raises
|
|
478
|
+
------
|
|
479
|
+
OperatorPreconditionError
|
|
480
|
+
If structural frequency already near zero
|
|
481
|
+
"""
|
|
482
|
+
vf = _get_node_attr(G, node, ALIAS_VF)
|
|
483
|
+
min_vf = float(G.graph.get("SHA_MIN_VF", 0.01))
|
|
484
|
+
if vf < min_vf:
|
|
485
|
+
raise OperatorPreconditionError(
|
|
486
|
+
"Silence",
|
|
487
|
+
f"Structural frequency already minimal (νf={vf:.3f} < {min_vf:.3f})",
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
def validate_expansion(G: "TNFRGraph", node: "NodeId") -> None:
|
|
492
|
+
"""VAL - Expansion requires comprehensive canonical preconditions.
|
|
493
|
+
|
|
494
|
+
Canonical Requirements (TNFR Physics):
|
|
495
|
+
1. **νf < max_vf**: Structural frequency below saturation
|
|
496
|
+
2. **ΔNFR > 0**: Positive reorganization gradient (growth pressure)
|
|
497
|
+
3. **EPI >= min_epi**: Sufficient base coherence for expansion
|
|
498
|
+
4. **(Optional) Network capacity**: Check if network can support expansion
|
|
499
|
+
|
|
500
|
+
Physical Basis:
|
|
501
|
+
----------------
|
|
502
|
+
From nodal equation: ∂EPI/∂t = νf · ΔNFR(t)
|
|
503
|
+
|
|
504
|
+
For coherent expansion:
|
|
505
|
+
- ΔNFR > 0 required: expansion needs outward pressure
|
|
506
|
+
- EPI > threshold: must have coherent base to expand from
|
|
507
|
+
- νf < max: must have capacity for increased reorganization
|
|
508
|
+
|
|
509
|
+
Parameters
|
|
510
|
+
----------
|
|
511
|
+
G : TNFRGraph
|
|
512
|
+
Graph containing the node
|
|
513
|
+
node : NodeId
|
|
514
|
+
Node to validate
|
|
515
|
+
|
|
516
|
+
Raises
|
|
517
|
+
------
|
|
518
|
+
OperatorPreconditionError
|
|
519
|
+
If any precondition fails:
|
|
520
|
+
- Structural frequency at maximum
|
|
521
|
+
- ΔNFR non-positive (no growth pressure)
|
|
522
|
+
- EPI below minimum (insufficient coherence base)
|
|
523
|
+
- (Optional) Network at capacity
|
|
524
|
+
|
|
525
|
+
Configuration Parameters
|
|
526
|
+
------------------------
|
|
527
|
+
VAL_MAX_VF : float, default 10.0
|
|
528
|
+
Maximum structural frequency threshold
|
|
529
|
+
VAL_MIN_DNFR : float, default 1e-6
|
|
530
|
+
Minimum ΔNFR for expansion (must be positive, very low to minimize breaking changes)
|
|
531
|
+
VAL_MIN_EPI : float, default 0.2
|
|
532
|
+
Minimum EPI for coherent expansion
|
|
533
|
+
VAL_CHECK_NETWORK_CAPACITY : bool, default False
|
|
534
|
+
Enable network capacity validation
|
|
535
|
+
VAL_MAX_NETWORK_SIZE : int, default 1000
|
|
536
|
+
Maximum network size if capacity checking enabled
|
|
537
|
+
|
|
538
|
+
Examples
|
|
539
|
+
--------
|
|
540
|
+
>>> from tnfr.structural import create_nfr
|
|
541
|
+
>>> from tnfr.operators.preconditions import validate_expansion
|
|
542
|
+
>>>
|
|
543
|
+
>>> # Valid node for expansion
|
|
544
|
+
>>> G, node = create_nfr("expanding", epi=0.5, vf=2.0)
|
|
545
|
+
>>> G.nodes[node]['delta_nfr'] = 0.1 # Positive ΔNFR
|
|
546
|
+
>>> validate_expansion(G, node) # Passes
|
|
547
|
+
>>>
|
|
548
|
+
>>> # Invalid: negative ΔNFR
|
|
549
|
+
>>> G.nodes[node]['delta_nfr'] = -0.1
|
|
550
|
+
>>> validate_expansion(G, node) # Raises OperatorPreconditionError
|
|
551
|
+
|
|
552
|
+
Notes
|
|
553
|
+
-----
|
|
554
|
+
VAL increases both EPI magnitude and νf, enabling exploration of new
|
|
555
|
+
structural configurations while maintaining core identity (fractality).
|
|
556
|
+
|
|
557
|
+
See Also
|
|
558
|
+
--------
|
|
559
|
+
Expansion : VAL operator implementation
|
|
560
|
+
validate_contraction : NUL preconditions (inverse operation)
|
|
561
|
+
"""
|
|
562
|
+
# 1. νf below maximum (existing check)
|
|
563
|
+
vf = _get_node_attr(G, node, ALIAS_VF)
|
|
564
|
+
max_vf = float(G.graph.get("VAL_MAX_VF", 10.0))
|
|
565
|
+
if vf >= max_vf:
|
|
566
|
+
raise OperatorPreconditionError(
|
|
567
|
+
"Expansion",
|
|
568
|
+
f"Structural frequency at maximum (νf={vf:.3f} >= {max_vf:.3f}). "
|
|
569
|
+
f"Node at reorganization capacity limit.",
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
# 2. ΔNFR positivity check (NEW - CRITICAL)
|
|
573
|
+
dnfr = _get_node_attr(G, node, ALIAS_DNFR)
|
|
574
|
+
min_dnfr = float(G.graph.get("VAL_MIN_DNFR", 1e-6))
|
|
575
|
+
if dnfr < min_dnfr:
|
|
576
|
+
raise OperatorPreconditionError(
|
|
577
|
+
"Expansion",
|
|
578
|
+
f"ΔNFR must be positive for expansion (ΔNFR={dnfr:.3f} < {min_dnfr:.3f}). "
|
|
579
|
+
f"No outward growth pressure detected. Consider OZ (Dissonance) to generate ΔNFR.",
|
|
580
|
+
)
|
|
581
|
+
|
|
582
|
+
# 3. EPI minimum check (NEW - IMPORTANT)
|
|
583
|
+
epi = _get_node_attr(G, node, ALIAS_EPI)
|
|
584
|
+
min_epi = float(G.graph.get("VAL_MIN_EPI", 0.2))
|
|
585
|
+
if epi < min_epi:
|
|
586
|
+
raise OperatorPreconditionError(
|
|
587
|
+
"Expansion",
|
|
588
|
+
f"EPI too low for coherent expansion (EPI={epi:.3f} < {min_epi:.3f}). "
|
|
589
|
+
f"Insufficient structural base. Consider AL (Emission) to activate node first.",
|
|
590
|
+
)
|
|
591
|
+
|
|
592
|
+
# 4. Network capacity check (OPTIONAL - for large-scale systems)
|
|
593
|
+
check_capacity = bool(G.graph.get("VAL_CHECK_NETWORK_CAPACITY", False))
|
|
594
|
+
if check_capacity:
|
|
595
|
+
max_network_size = int(G.graph.get("VAL_MAX_NETWORK_SIZE", 1000))
|
|
596
|
+
current_size = G.number_of_nodes()
|
|
597
|
+
if current_size >= max_network_size:
|
|
598
|
+
raise OperatorPreconditionError(
|
|
599
|
+
"Expansion",
|
|
600
|
+
f"Network at capacity (n={current_size} >= {max_network_size}). "
|
|
601
|
+
f"Cannot support further expansion. Set VAL_CHECK_NETWORK_CAPACITY=False to disable.",
|
|
602
|
+
)
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
def validate_contraction(G: "TNFRGraph", node: "NodeId") -> None:
|
|
606
|
+
"""NUL - Enhanced precondition validation with over-compression check.
|
|
607
|
+
|
|
608
|
+
Canonical Requirements (TNFR Physics):
|
|
609
|
+
1. **νf > min_vf**: Structural frequency above minimum for reorganization
|
|
610
|
+
2. **EPI >= min_epi**: Sufficient structural form to contract safely
|
|
611
|
+
3. **density <= max_density**: Not already at critical compression
|
|
612
|
+
|
|
613
|
+
Physical Basis:
|
|
614
|
+
----------------
|
|
615
|
+
From nodal equation: ∂EPI/∂t = νf · ΔNFR(t)
|
|
616
|
+
|
|
617
|
+
For safe contraction:
|
|
618
|
+
- EPI must have sufficient magnitude (can't compress vacuum)
|
|
619
|
+
- Density ρ = |ΔNFR| / EPI must not exceed critical threshold
|
|
620
|
+
- Over-compression (ρ → ∞) causes structural collapse
|
|
621
|
+
|
|
622
|
+
Density is the structural pressure per unit form. When EPI contracts
|
|
623
|
+
while ΔNFR increases (canonical densification), density rises. If already
|
|
624
|
+
at critical density, further contraction risks fragmentation.
|
|
625
|
+
|
|
626
|
+
Parameters
|
|
627
|
+
----------
|
|
628
|
+
G : TNFRGraph
|
|
629
|
+
Graph containing the node
|
|
630
|
+
node : NodeId
|
|
631
|
+
Node to validate
|
|
632
|
+
|
|
633
|
+
Raises
|
|
634
|
+
------
|
|
635
|
+
OperatorPreconditionError
|
|
636
|
+
If any precondition fails:
|
|
637
|
+
- Structural frequency at minimum
|
|
638
|
+
- EPI too low for safe contraction
|
|
639
|
+
- Node already at critical density
|
|
640
|
+
|
|
641
|
+
Configuration Parameters
|
|
642
|
+
------------------------
|
|
643
|
+
NUL_MIN_VF : float, default 0.1
|
|
644
|
+
Minimum structural frequency threshold
|
|
645
|
+
NUL_MIN_EPI : float, default 0.1
|
|
646
|
+
Minimum EPI for safe contraction
|
|
647
|
+
NUL_MAX_DENSITY : float, default 10.0
|
|
648
|
+
Maximum density threshold (ρ = |ΔNFR| / max(EPI, ε))
|
|
649
|
+
|
|
650
|
+
Examples
|
|
651
|
+
--------
|
|
652
|
+
>>> from tnfr.structural import create_nfr
|
|
653
|
+
>>> from tnfr.operators.preconditions import validate_contraction
|
|
654
|
+
>>>
|
|
655
|
+
>>> # Valid node for contraction
|
|
656
|
+
>>> G, node = create_nfr("contracting", epi=0.5, vf=1.0)
|
|
657
|
+
>>> G.nodes[node]['delta_nfr'] = 0.2
|
|
658
|
+
>>> validate_contraction(G, node) # Passes
|
|
659
|
+
>>>
|
|
660
|
+
>>> # Invalid: EPI too low
|
|
661
|
+
>>> G, node = create_nfr("too_small", epi=0.05, vf=1.0)
|
|
662
|
+
>>> validate_contraction(G, node) # Raises OperatorPreconditionError
|
|
663
|
+
>>>
|
|
664
|
+
>>> # Invalid: density too high
|
|
665
|
+
>>> G, node = create_nfr("over_compressed", epi=0.1, vf=1.0)
|
|
666
|
+
>>> G.nodes[node]['delta_nfr'] = 2.0 # High ΔNFR
|
|
667
|
+
>>> validate_contraction(G, node) # Raises OperatorPreconditionError
|
|
668
|
+
|
|
669
|
+
See Also
|
|
670
|
+
--------
|
|
671
|
+
Contraction : NUL operator implementation
|
|
672
|
+
validate_expansion : VAL preconditions (inverse operation)
|
|
673
|
+
"""
|
|
674
|
+
vf = _get_node_attr(G, node, ALIAS_VF)
|
|
675
|
+
epi = _get_node_attr(G, node, ALIAS_EPI)
|
|
676
|
+
dnfr = _get_node_attr(G, node, ALIAS_DNFR)
|
|
677
|
+
|
|
678
|
+
# Check 1: νf must be above minimum
|
|
679
|
+
min_vf = float(G.graph.get("NUL_MIN_VF", 0.1))
|
|
680
|
+
if vf <= min_vf:
|
|
681
|
+
raise OperatorPreconditionError(
|
|
682
|
+
"Contraction",
|
|
683
|
+
f"Structural frequency at minimum (νf={vf:.3f} <= {min_vf:.3f})",
|
|
684
|
+
)
|
|
685
|
+
|
|
686
|
+
# Check 2: EPI must be above minimum for contraction
|
|
687
|
+
min_epi = float(G.graph.get("NUL_MIN_EPI", 0.1))
|
|
688
|
+
if epi < min_epi:
|
|
689
|
+
raise OperatorPreconditionError(
|
|
690
|
+
"Contraction",
|
|
691
|
+
f"EPI too low for safe contraction (EPI={epi:.3f} < {min_epi:.3f}). "
|
|
692
|
+
f"Cannot compress structure below minimum coherent form.",
|
|
693
|
+
)
|
|
694
|
+
|
|
695
|
+
# Check 3: Density must not exceed critical threshold
|
|
696
|
+
# Density ρ = |ΔNFR| / max(EPI, ε) - structural pressure per unit form
|
|
697
|
+
epsilon = 1e-9
|
|
698
|
+
density = abs(dnfr) / max(epi, epsilon)
|
|
699
|
+
max_density = float(G.graph.get("NUL_MAX_DENSITY", 10.0))
|
|
700
|
+
if density > max_density:
|
|
701
|
+
raise OperatorPreconditionError(
|
|
702
|
+
"Contraction",
|
|
703
|
+
f"Node already at critical density (ρ={density:.3f} > {max_density:.3f}). "
|
|
704
|
+
f"Further contraction risks structural collapse. "
|
|
705
|
+
f"Consider IL (Coherence) to stabilize or reduce ΔNFR first.",
|
|
706
|
+
)
|
|
707
|
+
|
|
708
|
+
|
|
709
|
+
def validate_self_organization(G: "TNFRGraph", node: "NodeId") -> None:
|
|
710
|
+
"""THOL - Enhanced validation: connectivity, metabolic context, acceleration.
|
|
711
|
+
|
|
712
|
+
Self-organization requires:
|
|
713
|
+
1. Sufficient EPI for bifurcation
|
|
714
|
+
2. Positive reorganization pressure (ΔNFR > 0)
|
|
715
|
+
3. Structural reorganization capacity (νf > 0)
|
|
716
|
+
4. Network connectivity for metabolism (degree ≥ 1)
|
|
717
|
+
5. EPI history for acceleration computation (≥3 points)
|
|
718
|
+
6. **NEW**: Bifurcation threshold check (∂²EPI/∂t² vs τ) with telemetry
|
|
719
|
+
|
|
720
|
+
Also detects and records the destabilizer type that enabled this self-organization
|
|
721
|
+
for telemetry and structural tracing purposes.
|
|
722
|
+
|
|
723
|
+
**Bifurcation Threshold Validation (∂²EPI/∂t² > τ):**
|
|
724
|
+
|
|
725
|
+
According to TNFR.pdf §2.2.10, THOL bifurcation occurs only when structural
|
|
726
|
+
acceleration exceeds threshold τ. This function now explicitly validates this
|
|
727
|
+
condition and sets telemetry flags:
|
|
728
|
+
|
|
729
|
+
- If ∂²EPI/∂t² > τ: Bifurcation will occur (normal THOL behavior)
|
|
730
|
+
- If ∂²EPI/∂t² ≤ τ: THOL executes but no sub-EPIs generated (warning logged)
|
|
731
|
+
|
|
732
|
+
The validation is NON-BLOCKING (warning only) because THOL can meaningfully
|
|
733
|
+
execute without bifurcation - it still applies coherence and metabolic effects.
|
|
734
|
+
|
|
735
|
+
Parameters
|
|
736
|
+
----------
|
|
737
|
+
G : TNFRGraph
|
|
738
|
+
Graph containing the node
|
|
739
|
+
node : NodeId
|
|
740
|
+
Node to validate
|
|
741
|
+
|
|
742
|
+
Raises
|
|
743
|
+
------
|
|
744
|
+
OperatorPreconditionError
|
|
745
|
+
If any structural requirement is not met
|
|
746
|
+
|
|
747
|
+
Notes
|
|
748
|
+
-----
|
|
749
|
+
This function implements R4 Extended telemetry by analyzing the glyph_history
|
|
750
|
+
to determine which destabilizer (strong/moderate/weak) enabled the self-organization.
|
|
751
|
+
|
|
752
|
+
Configuration Parameters
|
|
753
|
+
------------------------
|
|
754
|
+
THOL_MIN_EPI : float, default 0.2
|
|
755
|
+
Minimum EPI for bifurcation
|
|
756
|
+
THOL_MIN_VF : float, default 0.1
|
|
757
|
+
Minimum structural frequency for reorganization
|
|
758
|
+
THOL_MIN_DEGREE : int, default 1
|
|
759
|
+
Minimum network connectivity
|
|
760
|
+
THOL_MIN_HISTORY_LENGTH : int, default 3
|
|
761
|
+
Minimum EPI history for acceleration computation
|
|
762
|
+
THOL_ALLOW_ISOLATED : bool, default False
|
|
763
|
+
Allow isolated nodes for internal-only bifurcation
|
|
764
|
+
THOL_METABOLIC_ENABLED : bool, default True
|
|
765
|
+
Require metabolic network context
|
|
766
|
+
BIFURCATION_THRESHOLD_TAU : float, default 0.1
|
|
767
|
+
Bifurcation threshold for ∂²EPI/∂t² (see THOL_BIFURCATION_THRESHOLD)
|
|
768
|
+
THOL_BIFURCATION_THRESHOLD : float, default 0.1
|
|
769
|
+
Alias for BIFURCATION_THRESHOLD_TAU (operator-specific config)
|
|
770
|
+
"""
|
|
771
|
+
import logging
|
|
772
|
+
|
|
773
|
+
logger = logging.getLogger(__name__)
|
|
774
|
+
|
|
775
|
+
epi = _get_node_attr(G, node, ALIAS_EPI)
|
|
776
|
+
dnfr = _get_node_attr(G, node, ALIAS_DNFR)
|
|
777
|
+
vf = _get_node_attr(G, node, ALIAS_VF)
|
|
778
|
+
|
|
779
|
+
# 1. EPI sufficiency
|
|
780
|
+
min_epi = float(G.graph.get("THOL_MIN_EPI", 0.2))
|
|
781
|
+
if epi < min_epi:
|
|
782
|
+
raise OperatorPreconditionError(
|
|
783
|
+
"Self-organization",
|
|
784
|
+
f"EPI too low for bifurcation (EPI={epi:.3f} < {min_epi:.3f})",
|
|
785
|
+
)
|
|
786
|
+
|
|
787
|
+
# 2. Reorganization pressure
|
|
788
|
+
if dnfr <= 0:
|
|
789
|
+
raise OperatorPreconditionError(
|
|
790
|
+
"Self-organization",
|
|
791
|
+
f"ΔNFR non-positive, no reorganization pressure (ΔNFR={dnfr:.3f})",
|
|
792
|
+
)
|
|
793
|
+
|
|
794
|
+
# 3. Structural frequency validation
|
|
795
|
+
min_vf = float(G.graph.get("THOL_MIN_VF", 0.1))
|
|
796
|
+
if vf < min_vf:
|
|
797
|
+
raise OperatorPreconditionError(
|
|
798
|
+
"Self-organization",
|
|
799
|
+
f"Structural frequency too low for reorganization (νf={vf:.3f} < {min_vf:.3f})",
|
|
800
|
+
)
|
|
801
|
+
|
|
802
|
+
# 4. Connectivity requirement (ELEVATED FROM WARNING)
|
|
803
|
+
min_degree = int(G.graph.get("THOL_MIN_DEGREE", 1))
|
|
804
|
+
node_degree = G.degree(node)
|
|
805
|
+
|
|
806
|
+
# Allow isolated THOL if explicitly enabled
|
|
807
|
+
allow_isolated = bool(G.graph.get("THOL_ALLOW_ISOLATED", False))
|
|
808
|
+
|
|
809
|
+
if node_degree < min_degree and not allow_isolated:
|
|
810
|
+
raise OperatorPreconditionError(
|
|
811
|
+
"Self-organization",
|
|
812
|
+
f"Node insufficiently connected for network metabolism "
|
|
813
|
+
f"(degree={node_degree} < {min_degree}). "
|
|
814
|
+
f"Set THOL_ALLOW_ISOLATED=True to enable internal-only bifurcation.",
|
|
815
|
+
)
|
|
816
|
+
|
|
817
|
+
# 5. EPI history validation (for d²EPI/dt² computation)
|
|
818
|
+
epi_history = G.nodes[node].get("epi_history", [])
|
|
819
|
+
min_history_length = int(G.graph.get("THOL_MIN_HISTORY_LENGTH", 3))
|
|
820
|
+
|
|
821
|
+
if len(epi_history) < min_history_length:
|
|
822
|
+
raise OperatorPreconditionError(
|
|
823
|
+
"Self-organization",
|
|
824
|
+
f"Insufficient EPI history for acceleration computation "
|
|
825
|
+
f"(have {len(epi_history)}, need ≥{min_history_length}). "
|
|
826
|
+
f"Apply operators to build history before THOL.",
|
|
827
|
+
)
|
|
828
|
+
|
|
829
|
+
# 6. Metabolic context validation (if metabolism enabled)
|
|
830
|
+
if G.graph.get("THOL_METABOLIC_ENABLED", True):
|
|
831
|
+
# If network metabolism is expected, verify neighbors exist
|
|
832
|
+
if node_degree == 0:
|
|
833
|
+
raise OperatorPreconditionError(
|
|
834
|
+
"Self-organization",
|
|
835
|
+
"Metabolic mode enabled but node is isolated. "
|
|
836
|
+
"Disable THOL_METABOLIC_ENABLED or add network connections.",
|
|
837
|
+
)
|
|
838
|
+
|
|
839
|
+
# R4 Extended: Detect and record destabilizer type for telemetry
|
|
840
|
+
_record_destabilizer_context(G, node, logger)
|
|
841
|
+
|
|
842
|
+
# NEW: Bifurcation threshold validation (∂²EPI/∂t² > τ)
|
|
843
|
+
# This is NON-BLOCKING - THOL can execute without bifurcation
|
|
844
|
+
# Note: SelfOrganization uses its own _compute_epi_acceleration which looks at 'epi_history'
|
|
845
|
+
# while compute_d2epi_dt2 looks at '_epi_history'. We check both for compatibility.
|
|
846
|
+
|
|
847
|
+
# Get EPI history from node (try both keys for compatibility)
|
|
848
|
+
history = G.nodes[node].get("_epi_history") or G.nodes[node].get("epi_history", [])
|
|
849
|
+
|
|
850
|
+
# Compute d²EPI/dt² directly from history (same logic as both functions)
|
|
851
|
+
if len(history) >= 3:
|
|
852
|
+
epi_t = float(history[-1])
|
|
853
|
+
epi_t1 = float(history[-2])
|
|
854
|
+
epi_t2 = float(history[-3])
|
|
855
|
+
d2_epi_signed = epi_t - 2.0 * epi_t1 + epi_t2
|
|
856
|
+
d2_epi = abs(d2_epi_signed)
|
|
857
|
+
else:
|
|
858
|
+
# Insufficient history - should have been caught earlier, but handle gracefully
|
|
859
|
+
d2_epi = 0.0
|
|
860
|
+
|
|
861
|
+
# Get bifurcation threshold from graph configuration
|
|
862
|
+
# Try BIFURCATION_THRESHOLD_TAU first (canonical), then THOL_BIFURCATION_THRESHOLD
|
|
863
|
+
tau = G.graph.get("BIFURCATION_THRESHOLD_TAU")
|
|
864
|
+
if tau is None:
|
|
865
|
+
tau = float(G.graph.get("THOL_BIFURCATION_THRESHOLD", 0.1))
|
|
866
|
+
else:
|
|
867
|
+
tau = float(tau)
|
|
868
|
+
|
|
869
|
+
# Check if bifurcation threshold will be exceeded
|
|
870
|
+
if d2_epi <= tau:
|
|
871
|
+
# Log warning but allow execution - THOL can be meaningful without bifurcation
|
|
872
|
+
logger.warning(
|
|
873
|
+
f"Node {node}: THOL applied with ∂²EPI/∂t²={d2_epi:.3f} ≤ τ={tau:.3f}. "
|
|
874
|
+
f"No bifurcation will occur (empty THOL window expected). "
|
|
875
|
+
f"Sub-EPIs will not be generated. "
|
|
876
|
+
f"Consider stronger destabilizer (OZ, VAL) to increase acceleration."
|
|
877
|
+
)
|
|
878
|
+
# Set telemetry flag for post-hoc analysis
|
|
879
|
+
G.nodes[node]["_thol_no_bifurcation_expected"] = True
|
|
880
|
+
else:
|
|
881
|
+
# Clear flag if previously set
|
|
882
|
+
G.nodes[node]["_thol_no_bifurcation_expected"] = False
|
|
883
|
+
logger.debug(
|
|
884
|
+
f"Node {node}: THOL bifurcation threshold exceeded "
|
|
885
|
+
f"(∂²EPI/∂t²={d2_epi:.3f} > τ={tau:.3f}). "
|
|
886
|
+
f"Sub-EPI generation expected."
|
|
887
|
+
)
|
|
888
|
+
|
|
889
|
+
|
|
890
|
+
# Moved to mutation.py module for modularity
|
|
891
|
+
# Import here for backward compatibility
|
|
892
|
+
try:
|
|
893
|
+
from .mutation import record_destabilizer_context as _record_destabilizer_context
|
|
894
|
+
except ImportError:
|
|
895
|
+
# Fallback if mutation.py not available (shouldn't happen)
|
|
896
|
+
def _record_destabilizer_context(
|
|
897
|
+
G: "TNFRGraph", node: "NodeId", logger: "logging.Logger"
|
|
898
|
+
) -> None:
|
|
899
|
+
"""Fallback implementation - see mutation.py for canonical version."""
|
|
900
|
+
G.nodes[node]["_mutation_context"] = {
|
|
901
|
+
"destabilizer_type": None,
|
|
902
|
+
"destabilizer_operator": None,
|
|
903
|
+
"destabilizer_distance": None,
|
|
904
|
+
"recent_history": [],
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
|
|
908
|
+
def validate_mutation(G: "TNFRGraph", node: "NodeId") -> None:
|
|
909
|
+
"""ZHIR - Mutation requires node to be in valid structural state.
|
|
910
|
+
|
|
911
|
+
Implements canonical TNFR requirements for mutation (AGENTS.md §11, TNFR.pdf §2.2.11):
|
|
912
|
+
|
|
913
|
+
1. Minimum νf for phase transformation capacity
|
|
914
|
+
2. **∂EPI/∂t > ξ: Structural change velocity exceeds threshold**
|
|
915
|
+
3. **U4b Part 1: Prior IL (Coherence) for stable transformation base**
|
|
916
|
+
4. **U4b Part 2: Recent destabilizer (~3 ops) for threshold energy**
|
|
917
|
+
|
|
918
|
+
Also detects and records the destabilizer type that enabled this mutation
|
|
919
|
+
for telemetry and structural tracing purposes.
|
|
920
|
+
|
|
921
|
+
Parameters
|
|
922
|
+
----------
|
|
923
|
+
G : TNFRGraph
|
|
924
|
+
Graph containing the node
|
|
925
|
+
node : NodeId
|
|
926
|
+
Node to validate
|
|
927
|
+
|
|
928
|
+
Raises
|
|
929
|
+
------
|
|
930
|
+
OperatorPreconditionError
|
|
931
|
+
If node state is unsuitable for mutation or U4b requirements not met
|
|
932
|
+
|
|
933
|
+
Configuration Parameters
|
|
934
|
+
------------------------
|
|
935
|
+
ZHIR_MIN_VF : float, default 0.05
|
|
936
|
+
Minimum structural frequency for phase transformation
|
|
937
|
+
ZHIR_THRESHOLD_XI : float, default 0.1
|
|
938
|
+
Threshold for ∂EPI/∂t velocity check
|
|
939
|
+
VALIDATE_OPERATOR_PRECONDITIONS : bool, default False
|
|
940
|
+
Enable strict U4b validation (IL precedence + destabilizer requirement)
|
|
941
|
+
ZHIR_REQUIRE_IL_PRECEDENCE : bool, default False
|
|
942
|
+
Require prior IL even if VALIDATE_OPERATOR_PRECONDITIONS=False
|
|
943
|
+
ZHIR_REQUIRE_DESTABILIZER : bool, default False
|
|
944
|
+
Require recent destabilizer even if VALIDATE_OPERATOR_PRECONDITIONS=False
|
|
945
|
+
|
|
946
|
+
Notes
|
|
947
|
+
-----
|
|
948
|
+
**Canonical threshold verification (∂EPI/∂t > ξ)**:
|
|
949
|
+
|
|
950
|
+
ZHIR is a phase transformation that requires sufficient structural reorganization
|
|
951
|
+
velocity to justify the transition. The threshold ξ represents the minimum rate
|
|
952
|
+
of structural change needed for a phase shift to be physically meaningful.
|
|
953
|
+
|
|
954
|
+
- If ∂EPI/∂t < ξ: Logs warning (soft check for backward compatibility)
|
|
955
|
+
- If ∂EPI/∂t ≥ ξ: Logs success, sets validation flag
|
|
956
|
+
- If insufficient history: Logs warning, cannot verify
|
|
957
|
+
|
|
958
|
+
**U4b Validation (Grammar Rule)**:
|
|
959
|
+
|
|
960
|
+
When strict validation enabled (VALIDATE_OPERATOR_PRECONDITIONS=True):
|
|
961
|
+
- **Part 1**: Prior IL (Coherence) required for stable base
|
|
962
|
+
- **Part 2**: Recent destabilizer (OZ/VAL/etc) required within ~3 ops
|
|
963
|
+
|
|
964
|
+
Without strict validation: Only telemetry/warnings logged.
|
|
965
|
+
|
|
966
|
+
This function implements R4 Extended telemetry by analyzing the glyph_history
|
|
967
|
+
to determine which destabilizer (strong/moderate/weak) enabled the mutation.
|
|
968
|
+
The destabilizer context is stored in node metadata for structural tracing.
|
|
969
|
+
"""
|
|
970
|
+
import logging
|
|
971
|
+
|
|
972
|
+
logger = logging.getLogger(__name__)
|
|
973
|
+
|
|
974
|
+
# Mutation is a phase change, require minimum vf for meaningful transition
|
|
975
|
+
vf = _get_node_attr(G, node, ALIAS_VF)
|
|
976
|
+
min_vf = float(G.graph.get("ZHIR_MIN_VF", 0.05))
|
|
977
|
+
if vf < min_vf:
|
|
978
|
+
raise OperatorPreconditionError(
|
|
979
|
+
"Mutation",
|
|
980
|
+
f"Structural frequency too low for mutation (νf={vf:.3f} < {min_vf:.3f})",
|
|
981
|
+
)
|
|
982
|
+
|
|
983
|
+
# NEW: Threshold crossing validation (∂EPI/∂t > ξ)
|
|
984
|
+
# Get EPI history - check both keys for compatibility
|
|
985
|
+
epi_history = G.nodes[node].get("epi_history") or G.nodes[node].get("_epi_history", [])
|
|
986
|
+
|
|
987
|
+
if len(epi_history) >= 2:
|
|
988
|
+
# Compute ∂EPI/∂t (discrete approximation using last two points)
|
|
989
|
+
# For discrete operator applications with Δt=1: ∂EPI/∂t ≈ EPI_t - EPI_{t-1}
|
|
990
|
+
depi_dt = abs(epi_history[-1] - epi_history[-2])
|
|
991
|
+
|
|
992
|
+
# Get threshold from configuration
|
|
993
|
+
xi_threshold = float(G.graph.get("ZHIR_THRESHOLD_XI", 0.1))
|
|
994
|
+
|
|
995
|
+
# Verify threshold crossed
|
|
996
|
+
if depi_dt < xi_threshold:
|
|
997
|
+
# Allow mutation but log warning (soft check for backward compatibility)
|
|
998
|
+
logger.warning(
|
|
999
|
+
f"Node {node}: ZHIR applied with ∂EPI/∂t={depi_dt:.3f} < ξ={xi_threshold}. "
|
|
1000
|
+
f"Mutation may lack structural justification. "
|
|
1001
|
+
f"Consider increasing dissonance (OZ) first."
|
|
1002
|
+
)
|
|
1003
|
+
G.nodes[node]["_zhir_threshold_warning"] = True
|
|
1004
|
+
else:
|
|
1005
|
+
# Threshold met - log success
|
|
1006
|
+
logger.info(
|
|
1007
|
+
f"Node {node}: ZHIR threshold crossed (∂EPI/∂t={depi_dt:.3f} > ξ={xi_threshold})"
|
|
1008
|
+
)
|
|
1009
|
+
G.nodes[node]["_zhir_threshold_met"] = True
|
|
1010
|
+
else:
|
|
1011
|
+
# Insufficient history - cannot verify threshold
|
|
1012
|
+
logger.warning(
|
|
1013
|
+
f"Node {node}: ZHIR applied without sufficient EPI history "
|
|
1014
|
+
f"(need ≥2 points, have {len(epi_history)}). Cannot verify threshold."
|
|
1015
|
+
)
|
|
1016
|
+
G.nodes[node]["_zhir_threshold_unknown"] = True
|
|
1017
|
+
|
|
1018
|
+
# U4b Part 1: IL Precedence Check (stable base for transformation)
|
|
1019
|
+
# Check if strict validation enabled
|
|
1020
|
+
strict_validation = bool(G.graph.get("VALIDATE_OPERATOR_PRECONDITIONS", False))
|
|
1021
|
+
require_il = strict_validation or bool(G.graph.get("ZHIR_REQUIRE_IL_PRECEDENCE", False))
|
|
1022
|
+
|
|
1023
|
+
if require_il:
|
|
1024
|
+
# Get glyph history
|
|
1025
|
+
glyph_history = G.nodes[node].get("glyph_history", [])
|
|
1026
|
+
|
|
1027
|
+
# Import glyph_function_name to convert glyphs to operator names
|
|
1028
|
+
from ..grammar import glyph_function_name
|
|
1029
|
+
|
|
1030
|
+
# Convert history to operator names
|
|
1031
|
+
history_names = [glyph_function_name(g) for g in glyph_history]
|
|
1032
|
+
|
|
1033
|
+
# Check for prior IL (coherence)
|
|
1034
|
+
il_found = "coherence" in history_names
|
|
1035
|
+
|
|
1036
|
+
if not il_found:
|
|
1037
|
+
raise OperatorPreconditionError(
|
|
1038
|
+
"Mutation",
|
|
1039
|
+
"U4b violation: ZHIR requires prior IL (Coherence) for stable transformation base. "
|
|
1040
|
+
"Apply Coherence before mutation sequence. "
|
|
1041
|
+
f"Recent history: {history_names[-5:] if len(history_names) > 5 else history_names}"
|
|
1042
|
+
)
|
|
1043
|
+
|
|
1044
|
+
logger.debug(f"Node {node}: ZHIR IL precedence satisfied (prior Coherence found)")
|
|
1045
|
+
|
|
1046
|
+
# U4b Part 2: Recent Destabilizer Check (threshold energy for bifurcation)
|
|
1047
|
+
# R4 Extended: Detect and record destabilizer type for telemetry
|
|
1048
|
+
_record_destabilizer_context(G, node, logger)
|
|
1049
|
+
|
|
1050
|
+
# If strict validation enabled, enforce destabilizer requirement
|
|
1051
|
+
require_destabilizer = strict_validation or bool(G.graph.get("ZHIR_REQUIRE_DESTABILIZER", False))
|
|
1052
|
+
|
|
1053
|
+
if require_destabilizer:
|
|
1054
|
+
context = G.nodes[node].get("_mutation_context", {})
|
|
1055
|
+
destabilizer_found = context.get("destabilizer_operator")
|
|
1056
|
+
|
|
1057
|
+
if destabilizer_found is None:
|
|
1058
|
+
recent_history = context.get("recent_history", [])
|
|
1059
|
+
raise OperatorPreconditionError(
|
|
1060
|
+
"Mutation",
|
|
1061
|
+
"U4b violation: ZHIR requires recent destabilizer (OZ/VAL/etc) within ~3 ops. "
|
|
1062
|
+
f"Recent history: {recent_history}. "
|
|
1063
|
+
"Apply Dissonance or Expansion to elevate ΔNFR first."
|
|
1064
|
+
)
|
|
1065
|
+
|
|
1066
|
+
|
|
1067
|
+
def validate_transition(G: "TNFRGraph", node: "NodeId") -> None:
|
|
1068
|
+
"""NAV - Comprehensive canonical preconditions for transition.
|
|
1069
|
+
|
|
1070
|
+
Validates comprehensive preconditions for NAV (Transition) operator according
|
|
1071
|
+
to TNFR.pdf §2.3.11:
|
|
1072
|
+
|
|
1073
|
+
1. **Minimum νf**: Structural frequency must exceed threshold
|
|
1074
|
+
2. **Controlled ΔNFR**: |ΔNFR| must be below maximum for stable transition
|
|
1075
|
+
3. **Regime validation**: Warns if transitioning from deep latency (EPI < 0.05)
|
|
1076
|
+
4. **Sequence compatibility** (optional): Warns if NAV applied after incompatible operators
|
|
1077
|
+
|
|
1078
|
+
Configuration Parameters
|
|
1079
|
+
------------------------
|
|
1080
|
+
NAV_MIN_VF : float, default 0.01
|
|
1081
|
+
Minimum structural frequency for transition
|
|
1082
|
+
NAV_MAX_DNFR : float, default 1.0
|
|
1083
|
+
Maximum |ΔNFR| for stable transition
|
|
1084
|
+
NAV_STRICT_SEQUENCE_CHECK : bool, default False
|
|
1085
|
+
Enable strict sequence compatibility checking
|
|
1086
|
+
NAV_MIN_EPI_FROM_LATENCY : float, default 0.05
|
|
1087
|
+
Minimum EPI for smooth transition from latency
|
|
1088
|
+
|
|
1089
|
+
Parameters
|
|
1090
|
+
----------
|
|
1091
|
+
G : TNFRGraph
|
|
1092
|
+
Graph containing the node
|
|
1093
|
+
node : NodeId
|
|
1094
|
+
Node to validate
|
|
1095
|
+
|
|
1096
|
+
Raises
|
|
1097
|
+
------
|
|
1098
|
+
OperatorPreconditionError
|
|
1099
|
+
If node lacks necessary dynamics for transition:
|
|
1100
|
+
- νf below minimum threshold
|
|
1101
|
+
- |ΔNFR| exceeds maximum threshold (unstable state)
|
|
1102
|
+
|
|
1103
|
+
Warnings
|
|
1104
|
+
--------
|
|
1105
|
+
UserWarning
|
|
1106
|
+
For suboptimal but valid conditions:
|
|
1107
|
+
- Transitioning from deep latency (EPI < 0.05)
|
|
1108
|
+
- NAV applied after incompatible operator (when strict checking enabled)
|
|
1109
|
+
|
|
1110
|
+
Notes
|
|
1111
|
+
-----
|
|
1112
|
+
NAV requires controlled ΔNFR to prevent instability during regime transitions.
|
|
1113
|
+
High |ΔNFR| indicates significant reorganization pressure that could cause
|
|
1114
|
+
structural disruption during transition. Apply IL (Coherence) first to reduce
|
|
1115
|
+
ΔNFR before attempting regime transition.
|
|
1116
|
+
|
|
1117
|
+
Deep latency transitions (EPI < 0.05 after SHA) benefit from prior AL (Emission)
|
|
1118
|
+
to provide smoother reactivation path.
|
|
1119
|
+
|
|
1120
|
+
Examples
|
|
1121
|
+
--------
|
|
1122
|
+
>>> from tnfr.structural import create_nfr
|
|
1123
|
+
>>> from tnfr.operators.preconditions import validate_transition
|
|
1124
|
+
>>>
|
|
1125
|
+
>>> # Valid transition - controlled state
|
|
1126
|
+
>>> G, node = create_nfr("test", epi=0.5, vf=0.6)
|
|
1127
|
+
>>> G.nodes[node]['delta_nfr'] = 0.3 # Controlled ΔNFR
|
|
1128
|
+
>>> validate_transition(G, node) # Passes
|
|
1129
|
+
>>>
|
|
1130
|
+
>>> # Invalid - ΔNFR too high
|
|
1131
|
+
>>> G.nodes[node]['delta_nfr'] = 1.5
|
|
1132
|
+
>>> validate_transition(G, node) # Raises OperatorPreconditionError
|
|
1133
|
+
|
|
1134
|
+
See Also
|
|
1135
|
+
--------
|
|
1136
|
+
Transition : NAV operator implementation
|
|
1137
|
+
validate_coherence : IL preconditions for ΔNFR reduction
|
|
1138
|
+
TNFR.pdf : §2.3.11 for NAV canonical requirements
|
|
1139
|
+
"""
|
|
1140
|
+
import warnings
|
|
1141
|
+
|
|
1142
|
+
# 1. νf minimum (existing check - preserved for backward compatibility)
|
|
1143
|
+
vf = _get_node_attr(G, node, ALIAS_VF)
|
|
1144
|
+
min_vf = float(G.graph.get("NAV_MIN_VF", 0.01))
|
|
1145
|
+
if vf < min_vf:
|
|
1146
|
+
raise OperatorPreconditionError(
|
|
1147
|
+
"Transition",
|
|
1148
|
+
f"Structural frequency too low (νf={vf:.3f} < {min_vf:.3f})",
|
|
1149
|
+
)
|
|
1150
|
+
|
|
1151
|
+
# 2. ΔNFR positivity and bounds check (NEW - CRITICAL)
|
|
1152
|
+
dnfr = _get_node_attr(G, node, ALIAS_DNFR)
|
|
1153
|
+
max_dnfr = float(G.graph.get("NAV_MAX_DNFR", 1.0))
|
|
1154
|
+
if abs(dnfr) > max_dnfr:
|
|
1155
|
+
raise OperatorPreconditionError(
|
|
1156
|
+
"Transition",
|
|
1157
|
+
f"ΔNFR too high for stable transition (|ΔNFR|={abs(dnfr):.3f} > {max_dnfr}). "
|
|
1158
|
+
f"Apply IL (Coherence) first to reduce reorganization pressure.",
|
|
1159
|
+
)
|
|
1160
|
+
|
|
1161
|
+
# 3. Regime origin validation (NEW - WARNING)
|
|
1162
|
+
latent = G.nodes[node].get("latent", False)
|
|
1163
|
+
epi = _get_node_attr(G, node, ALIAS_EPI)
|
|
1164
|
+
min_epi_from_latency = float(G.graph.get("NAV_MIN_EPI_FROM_LATENCY", 0.05))
|
|
1165
|
+
|
|
1166
|
+
if latent and epi < min_epi_from_latency:
|
|
1167
|
+
# Warning: transitioning from deep latency
|
|
1168
|
+
warnings.warn(
|
|
1169
|
+
f"Node {node} in deep latency (EPI={epi:.3f} < {min_epi_from_latency:.3f}). "
|
|
1170
|
+
f"Consider AL (Emission) before NAV for smoother activation.",
|
|
1171
|
+
UserWarning,
|
|
1172
|
+
stacklevel=2,
|
|
1173
|
+
)
|
|
1174
|
+
|
|
1175
|
+
# 4. Sequence compatibility check (NEW - OPTIONAL)
|
|
1176
|
+
if G.graph.get("NAV_STRICT_SEQUENCE_CHECK", False):
|
|
1177
|
+
history = G.nodes[node].get("glyph_history", [])
|
|
1178
|
+
if history:
|
|
1179
|
+
from ..grammar import glyph_function_name
|
|
1180
|
+
|
|
1181
|
+
last_op = glyph_function_name(history[-1]) if history else None
|
|
1182
|
+
|
|
1183
|
+
# NAV works best after stabilizers or generators
|
|
1184
|
+
# Valid predecessors per TNFR.pdf §2.3.11 and AGENTS.md
|
|
1185
|
+
valid_predecessors = {
|
|
1186
|
+
"emission", # AL → NAV (activation-transition)
|
|
1187
|
+
"coherence", # IL → NAV (stable-transition)
|
|
1188
|
+
"silence", # SHA → NAV (latency-transition)
|
|
1189
|
+
"self_organization", # THOL → NAV (bifurcation-transition)
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
if last_op and last_op not in valid_predecessors:
|
|
1193
|
+
warnings.warn(
|
|
1194
|
+
f"NAV applied after {last_op}. "
|
|
1195
|
+
f"More coherent after: {', '.join(sorted(valid_predecessors))}",
|
|
1196
|
+
UserWarning,
|
|
1197
|
+
stacklevel=2,
|
|
1198
|
+
)
|
|
1199
|
+
|
|
1200
|
+
|
|
1201
|
+
def validate_recursivity(G: "TNFRGraph", node: "NodeId") -> None:
|
|
1202
|
+
"""REMESH - Recursivity requires global network coherence threshold.
|
|
1203
|
+
|
|
1204
|
+
Parameters
|
|
1205
|
+
----------
|
|
1206
|
+
G : TNFRGraph
|
|
1207
|
+
Graph containing the node
|
|
1208
|
+
node : NodeId
|
|
1209
|
+
Node to validate
|
|
1210
|
+
|
|
1211
|
+
Raises
|
|
1212
|
+
------
|
|
1213
|
+
OperatorPreconditionError
|
|
1214
|
+
If network is not ready for remesh operation
|
|
1215
|
+
"""
|
|
1216
|
+
# REMESH is a network-scale operation, check graph state
|
|
1217
|
+
min_nodes = int(G.graph.get("REMESH_MIN_NODES", 2))
|
|
1218
|
+
if G.number_of_nodes() < min_nodes:
|
|
1219
|
+
raise OperatorPreconditionError(
|
|
1220
|
+
"Recursivity",
|
|
1221
|
+
f"Network too small for remesh (n={G.number_of_nodes()} < {min_nodes})",
|
|
1222
|
+
)
|
|
1223
|
+
|
|
1224
|
+
|
|
1225
|
+
# Import diagnostic functions from modular implementations
|
|
1226
|
+
from .mutation import diagnose_mutation_readiness # noqa: E402
|