Skip to content

Commit 5a463cc

Browse files
authored
Merge pull request #2867 from fermga/copilot/add-over-compression-validation
Add over-compression validation to NUL preconditions
2 parents fc61b33 + d756b6d commit 5a463cc

File tree

2 files changed

+556
-2
lines changed

2 files changed

+556
-2
lines changed

src/tnfr/operators/preconditions/__init__.py

Lines changed: 82 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -602,7 +602,25 @@ def validate_expansion(G: "TNFRGraph", node: "NodeId") -> None:
602602

603603

604604
def validate_contraction(G: "TNFRGraph", node: "NodeId") -> None:
605-
"""NUL - Contraction requires vf > minimum to reduce.
605+
"""NUL - Enhanced precondition validation with over-compression check.
606+
607+
Canonical Requirements (TNFR Physics):
608+
1. **νf > min_vf**: Structural frequency above minimum for reorganization
609+
2. **EPI >= min_epi**: Sufficient structural form to contract safely
610+
3. **density <= max_density**: Not already at critical compression
611+
612+
Physical Basis:
613+
----------------
614+
From nodal equation: ∂EPI/∂t = νf · ΔNFR(t)
615+
616+
For safe contraction:
617+
- EPI must have sufficient magnitude (can't compress vacuum)
618+
- Density ρ = |ΔNFR| / EPI must not exceed critical threshold
619+
- Over-compression (ρ → ∞) causes structural collapse
620+
621+
Density is the structural pressure per unit form. When EPI contracts
622+
while ΔNFR increases (canonical densification), density rises. If already
623+
at critical density, further contraction risks fragmentation.
606624
607625
Parameters
608626
----------
@@ -614,15 +632,77 @@ def validate_contraction(G: "TNFRGraph", node: "NodeId") -> None:
614632
Raises
615633
------
616634
OperatorPreconditionError
617-
If structural frequency already at minimum
635+
If any precondition fails:
636+
- Structural frequency at minimum
637+
- EPI too low for safe contraction
638+
- Node already at critical density
639+
640+
Configuration Parameters
641+
------------------------
642+
NUL_MIN_VF : float, default 0.1
643+
Minimum structural frequency threshold
644+
NUL_MIN_EPI : float, default 0.1
645+
Minimum EPI for safe contraction
646+
NUL_MAX_DENSITY : float, default 10.0
647+
Maximum density threshold (ρ = |ΔNFR| / max(EPI, ε))
648+
649+
Examples
650+
--------
651+
>>> from tnfr.structural import create_nfr
652+
>>> from tnfr.operators.preconditions import validate_contraction
653+
>>>
654+
>>> # Valid node for contraction
655+
>>> G, node = create_nfr("contracting", epi=0.5, vf=1.0)
656+
>>> G.nodes[node]['delta_nfr'] = 0.2
657+
>>> validate_contraction(G, node) # Passes
658+
>>>
659+
>>> # Invalid: EPI too low
660+
>>> G, node = create_nfr("too_small", epi=0.05, vf=1.0)
661+
>>> validate_contraction(G, node) # Raises OperatorPreconditionError
662+
>>>
663+
>>> # Invalid: density too high
664+
>>> G, node = create_nfr("over_compressed", epi=0.1, vf=1.0)
665+
>>> G.nodes[node]['delta_nfr'] = 2.0 # High ΔNFR
666+
>>> validate_contraction(G, node) # Raises OperatorPreconditionError
667+
668+
See Also
669+
--------
670+
Contraction : NUL operator implementation
671+
validate_expansion : VAL preconditions (inverse operation)
618672
"""
619673
vf = _get_node_attr(G, node, ALIAS_VF)
674+
epi = _get_node_attr(G, node, ALIAS_EPI)
675+
dnfr = _get_node_attr(G, node, ALIAS_DNFR)
676+
677+
# Check 1: νf must be above minimum
620678
min_vf = float(G.graph.get("NUL_MIN_VF", 0.1))
621679
if vf <= min_vf:
622680
raise OperatorPreconditionError(
623681
"Contraction",
624682
f"Structural frequency at minimum (νf={vf:.3f} <= {min_vf:.3f})",
625683
)
684+
685+
# Check 2: EPI must be above minimum for contraction
686+
min_epi = float(G.graph.get("NUL_MIN_EPI", 0.1))
687+
if epi < min_epi:
688+
raise OperatorPreconditionError(
689+
"Contraction",
690+
f"EPI too low for safe contraction (EPI={epi:.3f} < {min_epi:.3f}). "
691+
f"Cannot compress structure below minimum coherent form.",
692+
)
693+
694+
# Check 3: Density must not exceed critical threshold
695+
# Density ρ = |ΔNFR| / max(EPI, ε) - structural pressure per unit form
696+
epsilon = 1e-9
697+
density = abs(dnfr) / max(epi, epsilon)
698+
max_density = float(G.graph.get("NUL_MAX_DENSITY", 10.0))
699+
if density > max_density:
700+
raise OperatorPreconditionError(
701+
"Contraction",
702+
f"Node already at critical density (ρ={density:.3f} > {max_density:.3f}). "
703+
f"Further contraction risks structural collapse. "
704+
f"Consider IL (Coherence) to stabilize or reduce ΔNFR first.",
705+
)
626706

627707

628708
def validate_self_organization(G: "TNFRGraph", node: "NodeId") -> None:

0 commit comments

Comments
 (0)