Skip to content

Commit f90409b

Browse files
Copilotfermga
andcommitted
Add structural density metrics to NUL contraction operator
- Enhanced contraction_metrics with density_before, density_after, densification_ratio, is_critical_density - Added comprehensive test coverage (12 new tests) - All existing tests pass - Density calculated as |ΔNFR| / max(EPI, ε) with ε = 1e-9 - Configurable CRITICAL_DENSITY_THRESHOLD (default: 5.0) Co-authored-by: fermga <203334638+fermga@users.noreply.github.com>
1 parent 4794976 commit f90409b

File tree

2 files changed

+431
-2
lines changed

2 files changed

+431
-2
lines changed

src/tnfr/operators/metrics.py

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1294,6 +1294,9 @@ def contraction_metrics(
12941294
) -> dict[str, Any]:
12951295
"""NUL - Contraction metrics: νf decrease, core concentration, ΔNFR densification.
12961296
1297+
Collects comprehensive contraction metrics including structural density dynamics
1298+
that validate canonical NUL behavior and enable early warning for over-compression.
1299+
12971300
Parameters
12981301
----------
12991302
G : TNFRGraph
@@ -1308,9 +1311,52 @@ def contraction_metrics(
13081311
Returns
13091312
-------
13101313
dict
1311-
Contraction-specific metrics including structural compression and
1312-
canonical ΔNFR densification tracking.
1314+
Contraction-specific metrics including:
1315+
1316+
**Basic metrics:**
1317+
1318+
- operator: "Contraction"
1319+
- glyph: "NUL"
1320+
- vf_decrease: Absolute reduction in νf
1321+
- vf_final: Post-contraction νf
1322+
- delta_epi: EPI change
1323+
- epi_final: Post-contraction EPI
1324+
- dnfr_final: Post-contraction ΔNFR
1325+
- contraction_factor: Ratio of vf_after / vf_before
1326+
1327+
**Densification metrics (if available):**
1328+
1329+
- densification_factor: ΔNFR amplification factor (typically 1.35)
1330+
- dnfr_densified: Boolean indicating densification occurred
1331+
- dnfr_before: ΔNFR value before contraction
1332+
- dnfr_increase: Absolute ΔNFR change (dnfr_after - dnfr_before)
1333+
1334+
**Structural density metrics (NEW):**
1335+
1336+
- density_before: |ΔNFR| / max(EPI, ε) before contraction
1337+
- density_after: |ΔNFR| / max(EPI, ε) after contraction
1338+
- densification_ratio: density_after / density_before
1339+
- is_critical_density: Warning flag (density > threshold)
1340+
1341+
Notes
1342+
-----
1343+
**Structural Density**: Defined as ρ = |ΔNFR| / max(EPI, ε) where ε = 1e-9.
1344+
This captures the concentration of reorganization pressure per unit structure.
1345+
1346+
**Critical Density**: When density exceeds CRITICAL_DENSITY_THRESHOLD (default: 5.0),
1347+
it indicates over-compression risk where the node may become unstable.
1348+
1349+
**Densification Ratio**: Quantifies how much density increased during contraction.
1350+
Canonical NUL should produce densification_ratio ≈ densification_factor / contraction_factor.
1351+
1352+
See Also
1353+
--------
1354+
Contraction : NUL operator implementation
1355+
validate_contraction : Preconditions for safe contraction
13131356
"""
1357+
# Small epsilon for numerical stability
1358+
EPSILON = 1e-9
1359+
13141360
vf_after = _get_node_attr(G, node, ALIAS_VF)
13151361
epi_after = _get_node_attr(G, node, ALIAS_EPI)
13161362
dnfr_after = _get_node_attr(G, node, ALIAS_DNFR)
@@ -1325,6 +1371,18 @@ def contraction_metrics(
13251371
densification_factor = last_entry.get("densification_factor")
13261372
dnfr_before = last_entry.get("dnfr_before")
13271373

1374+
# Calculate structural density before and after
1375+
# Density = |ΔNFR| / max(EPI, ε)
1376+
density_before = abs(dnfr_before) / max(abs(epi_before), EPSILON) if dnfr_before is not None else 0.0
1377+
density_after = abs(dnfr_after) / max(abs(epi_after), EPSILON)
1378+
1379+
# Calculate densification ratio (how much density increased)
1380+
densification_ratio = density_after / density_before if density_before > EPSILON else float('inf')
1381+
1382+
# Get critical density threshold from graph config or use default
1383+
critical_density_threshold = float(G.graph.get("CRITICAL_DENSITY_THRESHOLD", 5.0))
1384+
is_critical_density = density_after > critical_density_threshold
1385+
13281386
metrics = {
13291387
"operator": "Contraction",
13301388
"glyph": "NUL",
@@ -1344,6 +1402,12 @@ def contraction_metrics(
13441402
metrics["dnfr_before"] = dnfr_before
13451403
metrics["dnfr_increase"] = dnfr_after - dnfr_before if dnfr_before else 0.0
13461404

1405+
# Add NEW structural density metrics
1406+
metrics["density_before"] = density_before
1407+
metrics["density_after"] = density_after
1408+
metrics["densification_ratio"] = densification_ratio
1409+
metrics["is_critical_density"] = is_critical_density
1410+
13471411
return metrics
13481412

13491413

0 commit comments

Comments
 (0)