@@ -602,7 +602,25 @@ def validate_expansion(G: "TNFRGraph", node: "NodeId") -> None:
602602
603603
604604def 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
628708def validate_self_organization (G : "TNFRGraph" , node : "NodeId" ) -> None :
0 commit comments