Skip to content

Commit c154e56

Browse files
authored
Merge pull request #2815 from fermga/copilot/strengthen-preconditions-validation
feat(thol): strengthen preconditions - require connectivity, νf, and EPI history
2 parents 2d86579 + bdb8dee commit c154e56

File tree

5 files changed

+496
-16
lines changed

5 files changed

+496
-16
lines changed

conventional/md/CHANGELOG.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,40 @@
44
55
<!-- version list -->
66

7+
## Unreleased
8+
9+
### Changed (BREAKING)
10+
11+
#### THOL Preconditions Strengthened
12+
13+
- **THOL now requires network connectivity by default** (degree ≥ 1)
14+
- Rationale: Canonical THOL metabolizes network context
15+
- **Migration:** Set `THOL_ALLOW_ISOLATED=True` for isolated nodes
16+
17+
- **THOL now validates structural frequency** (νf ≥ 0.1)
18+
- Rationale: Reorganization requires structural capacity
19+
- **Migration:** Ensure nodes have νf > 0 before THOL
20+
21+
- **THOL now requires EPI history** (≥ 3 points)
22+
- Rationale: d²EPI/dt² computation needs trajectory
23+
- **Migration:** Apply 2+ operators before THOL to build history
24+
25+
**Why these changes?**
26+
27+
Previous implementation allowed THOL in structurally incoherent contexts
28+
(isolated nodes, zero frequency, no history). This violated canonical TNFR
29+
principles where self-organization is a **network phenomenon** requiring
30+
metabolic context.
31+
32+
New validation ensures THOL operates only in structurally viable conditions,
33+
while preserving flexibility through configuration flags.
34+
35+
**Configuration parameters added:**
36+
- `THOL_MIN_VF`: Minimum structural frequency (default: 0.1 Hz_str)
37+
- `THOL_MIN_DEGREE`: Minimum network connectivity (default: 1)
38+
- `THOL_MIN_HISTORY_LENGTH`: Minimum EPI history points (default: 3)
39+
- `THOL_ALLOW_ISOLATED`: Allow isolated nodes (default: False)
40+
741
## v1.0.0 (2025-10-27)
842

943
- Initial Release

src/tnfr/config/defaults_core.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,13 @@ class CoreDefaults:
147147
THOL_PROPAGATION_ATTENUATION: float = 0.7
148148
THOL_CASCADE_MIN_NODES: int = 3
149149

150+
# THOL precondition thresholds
151+
THOL_MIN_EPI: float = 0.2 # Minimum EPI for bifurcation
152+
THOL_MIN_VF: float = 0.1 # Minimum structural frequency for reorganization
153+
THOL_MIN_DEGREE: int = 1 # Minimum network connectivity
154+
THOL_MIN_HISTORY_LENGTH: int = 3 # Minimum EPI history for acceleration computation
155+
THOL_ALLOW_ISOLATED: bool = False # Require network context by default
156+
150157

151158
@dataclass(frozen=True, slots=True)
152159
class RemeshDefaults:

src/tnfr/operators/preconditions/__init__.py

Lines changed: 69 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -530,10 +530,14 @@ def validate_contraction(G: "TNFRGraph", node: "NodeId") -> None:
530530

531531

532532
def validate_self_organization(G: "TNFRGraph", node: "NodeId") -> None:
533-
"""THOL - Self-organization requires minimum EPI, positive ΔNFR, and connectivity.
533+
"""THOL - Enhanced validation: connectivity, metabolic context, acceleration.
534534
535-
T'HOL implements structural metabolism and bifurcation. Preconditions ensure
536-
sufficient structure and reorganization pressure for self-organization.
535+
Self-organization requires:
536+
1. Sufficient EPI for bifurcation
537+
2. Positive reorganization pressure (ΔNFR > 0)
538+
3. Structural reorganization capacity (νf > 0)
539+
4. Network connectivity for metabolism (degree ≥ 1)
540+
5. EPI history for acceleration computation (≥3 points)
537541
538542
Also detects and records the destabilizer type that enabled this self-organization
539543
for telemetry and structural tracing purposes.
@@ -548,47 +552,96 @@ def validate_self_organization(G: "TNFRGraph", node: "NodeId") -> None:
548552
Raises
549553
------
550554
OperatorPreconditionError
551-
If EPI is too low for bifurcation, or if ΔNFR is non-positive
552-
553-
Warnings
554-
--------
555-
Warns if node is isolated - bifurcation may not propagate through network
555+
If any structural requirement is not met
556556
557557
Notes
558558
-----
559559
This function implements R4 Extended telemetry by analyzing the glyph_history
560560
to determine which destabilizer (strong/moderate/weak) enabled the self-organization.
561+
562+
Configuration Parameters
563+
------------------------
564+
THOL_MIN_EPI : float, default 0.2
565+
Minimum EPI for bifurcation
566+
THOL_MIN_VF : float, default 0.1
567+
Minimum structural frequency for reorganization
568+
THOL_MIN_DEGREE : int, default 1
569+
Minimum network connectivity
570+
THOL_MIN_HISTORY_LENGTH : int, default 3
571+
Minimum EPI history for acceleration computation
572+
THOL_ALLOW_ISOLATED : bool, default False
573+
Allow isolated nodes for internal-only bifurcation
574+
THOL_METABOLIC_ENABLED : bool, default True
575+
Require metabolic network context
561576
"""
562577
import logging
563-
import warnings
564578

565579
logger = logging.getLogger(__name__)
566580

567581
epi = _get_node_attr(G, node, ALIAS_EPI)
568582
dnfr = _get_node_attr(G, node, ALIAS_DNFR)
583+
vf = _get_node_attr(G, node, ALIAS_VF)
569584

570-
# EPI must be sufficient for bifurcation
585+
# 1. EPI sufficiency
571586
min_epi = float(G.graph.get("THOL_MIN_EPI", 0.2))
572587
if epi < min_epi:
573588
raise OperatorPreconditionError(
574589
"Self-organization",
575590
f"EPI too low for bifurcation (EPI={epi:.3f} < {min_epi:.3f})",
576591
)
577592

578-
# ΔNFR must be positive (reorganization pressure required)
593+
# 2. Reorganization pressure
579594
if dnfr <= 0:
580595
raise OperatorPreconditionError(
581596
"Self-organization",
582597
f"ΔNFR non-positive, no reorganization pressure (ΔNFR={dnfr:.3f})",
583598
)
584599

585-
# Warn if node is isolated (bifurcation won't propagate)
586-
if G.degree(node) == 0:
587-
warnings.warn(
588-
f"Node {node} is isolated - bifurcation may not propagate through network",
589-
stacklevel=3,
600+
# 3. Structural frequency validation
601+
min_vf = float(G.graph.get("THOL_MIN_VF", 0.1))
602+
if vf < min_vf:
603+
raise OperatorPreconditionError(
604+
"Self-organization",
605+
f"Structural frequency too low for reorganization (νf={vf:.3f} < {min_vf:.3f})",
606+
)
607+
608+
# 4. Connectivity requirement (ELEVATED FROM WARNING)
609+
min_degree = int(G.graph.get("THOL_MIN_DEGREE", 1))
610+
node_degree = G.degree(node)
611+
612+
# Allow isolated THOL if explicitly enabled
613+
allow_isolated = bool(G.graph.get("THOL_ALLOW_ISOLATED", False))
614+
615+
if node_degree < min_degree and not allow_isolated:
616+
raise OperatorPreconditionError(
617+
"Self-organization",
618+
f"Node insufficiently connected for network metabolism "
619+
f"(degree={node_degree} < {min_degree}). "
620+
f"Set THOL_ALLOW_ISOLATED=True to enable internal-only bifurcation.",
590621
)
591622

623+
# 5. EPI history validation (for d²EPI/dt² computation)
624+
epi_history = G.nodes[node].get("epi_history", [])
625+
min_history_length = int(G.graph.get("THOL_MIN_HISTORY_LENGTH", 3))
626+
627+
if len(epi_history) < min_history_length:
628+
raise OperatorPreconditionError(
629+
"Self-organization",
630+
f"Insufficient EPI history for acceleration computation "
631+
f"(have {len(epi_history)}, need ≥{min_history_length}). "
632+
f"Apply operators to build history before THOL.",
633+
)
634+
635+
# 6. Metabolic context validation (if metabolism enabled)
636+
if G.graph.get("THOL_METABOLIC_ENABLED", True):
637+
# If network metabolism is expected, verify neighbors exist
638+
if node_degree == 0:
639+
raise OperatorPreconditionError(
640+
"Self-organization",
641+
"Metabolic mode enabled but node is isolated. "
642+
"Disable THOL_METABOLIC_ENABLED or add network connections.",
643+
)
644+
592645
# R4 Extended: Detect and record destabilizer type for telemetry
593646
_record_destabilizer_context(G, node, logger)
594647

tests/unit/operators/test_destabilizer_telemetry.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ def nfr_with_validation():
1515
"""Create an NFR node with precondition validation enabled."""
1616
G, node = create_nfr("test", epi=0.3, vf=1.0)
1717
G.graph['VALIDATE_OPERATOR_PRECONDITIONS'] = True
18+
# Enable isolated THOL for telemetry tests
19+
G.graph['THOL_ALLOW_ISOLATED'] = True
20+
G.graph['THOL_METABOLIC_ENABLED'] = False
21+
# Reduce history requirement for telemetry tests
22+
G.graph['THOL_MIN_HISTORY_LENGTH'] = 0
1823
return G, node
1924

2025

0 commit comments

Comments
 (0)