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,2138 @@
|
|
|
1
|
+
"""Operator-specific metrics collection for TNFR structural operators.
|
|
2
|
+
|
|
3
|
+
Each operator produces characteristic metrics that reflect its structural
|
|
4
|
+
effects on nodes. This module provides metric collectors for telemetry
|
|
5
|
+
and analysis.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import TYPE_CHECKING, Any
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from ..types import NodeId, TNFRGraph
|
|
14
|
+
|
|
15
|
+
from ..alias import get_attr, get_attr_str
|
|
16
|
+
from ..constants.aliases import (
|
|
17
|
+
ALIAS_EPI,
|
|
18
|
+
ALIAS_VF,
|
|
19
|
+
ALIAS_DNFR,
|
|
20
|
+
ALIAS_THETA,
|
|
21
|
+
ALIAS_D2EPI,
|
|
22
|
+
ALIAS_EMISSION_TIMESTAMP,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"emission_metrics",
|
|
27
|
+
"reception_metrics",
|
|
28
|
+
"coherence_metrics",
|
|
29
|
+
"dissonance_metrics",
|
|
30
|
+
"coupling_metrics",
|
|
31
|
+
"resonance_metrics",
|
|
32
|
+
"silence_metrics",
|
|
33
|
+
"expansion_metrics",
|
|
34
|
+
"contraction_metrics",
|
|
35
|
+
"self_organization_metrics",
|
|
36
|
+
"mutation_metrics",
|
|
37
|
+
"transition_metrics",
|
|
38
|
+
"recursivity_metrics",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _get_node_attr(
|
|
43
|
+
G: TNFRGraph, node: NodeId, aliases: tuple[str, ...], default: float = 0.0
|
|
44
|
+
) -> float:
|
|
45
|
+
"""Get node attribute using alias fallback."""
|
|
46
|
+
return float(get_attr(G.nodes[node], aliases, default))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def emission_metrics(
|
|
50
|
+
G: TNFRGraph, node: NodeId, epi_before: float, vf_before: float
|
|
51
|
+
) -> dict[str, Any]:
|
|
52
|
+
"""AL - Emission metrics with structural fidelity indicators.
|
|
53
|
+
|
|
54
|
+
Collects emission-specific metrics that reflect canonical AL effects:
|
|
55
|
+
- EPI: Increments (form activation)
|
|
56
|
+
- vf: Activates/increases (Hz_str)
|
|
57
|
+
- DELTA_NFR: Initializes positive reorganization
|
|
58
|
+
- theta: Influences phase alignment
|
|
59
|
+
|
|
60
|
+
Parameters
|
|
61
|
+
----------
|
|
62
|
+
G : TNFRGraph
|
|
63
|
+
Graph containing the node
|
|
64
|
+
node : NodeId
|
|
65
|
+
Node to collect metrics from
|
|
66
|
+
epi_before : float
|
|
67
|
+
EPI value before operator application
|
|
68
|
+
vf_before : float
|
|
69
|
+
νf value before operator application
|
|
70
|
+
|
|
71
|
+
Returns
|
|
72
|
+
-------
|
|
73
|
+
dict
|
|
74
|
+
Emission-specific metrics including:
|
|
75
|
+
- Core deltas (delta_epi, delta_vf, dnfr_initialized, theta_current)
|
|
76
|
+
- AL-specific quality indicators:
|
|
77
|
+
- emission_quality: "valid" if both EPI and νf increased, else "weak"
|
|
78
|
+
- activation_from_latency: True if node was latent (EPI < 0.3)
|
|
79
|
+
- form_emergence_magnitude: Absolute EPI increment
|
|
80
|
+
- frequency_activation: True if νf increased
|
|
81
|
+
- reorganization_positive: True if ΔNFR > 0
|
|
82
|
+
- Traceability markers:
|
|
83
|
+
- emission_timestamp: ISO UTC timestamp of activation
|
|
84
|
+
- irreversibility_marker: True if node was activated
|
|
85
|
+
"""
|
|
86
|
+
epi_after = _get_node_attr(G, node, ALIAS_EPI)
|
|
87
|
+
vf_after = _get_node_attr(G, node, ALIAS_VF)
|
|
88
|
+
dnfr = _get_node_attr(G, node, ALIAS_DNFR)
|
|
89
|
+
theta = _get_node_attr(G, node, ALIAS_THETA)
|
|
90
|
+
|
|
91
|
+
# Fetch emission timestamp using alias system
|
|
92
|
+
emission_timestamp = None
|
|
93
|
+
try:
|
|
94
|
+
emission_timestamp = get_attr_str(
|
|
95
|
+
G.nodes[node], ALIAS_EMISSION_TIMESTAMP, default=None
|
|
96
|
+
)
|
|
97
|
+
except (AttributeError, KeyError, ImportError):
|
|
98
|
+
# Fallback if alias system unavailable or node lacks timestamp
|
|
99
|
+
emission_timestamp = G.nodes[node].get("emission_timestamp")
|
|
100
|
+
|
|
101
|
+
# Compute deltas
|
|
102
|
+
delta_epi = epi_after - epi_before
|
|
103
|
+
delta_vf = vf_after - vf_before
|
|
104
|
+
|
|
105
|
+
# AL-specific quality indicators
|
|
106
|
+
emission_quality = "valid" if (delta_epi > 0 and delta_vf > 0) else "weak"
|
|
107
|
+
activation_from_latency = epi_before < 0.3 # Latency threshold
|
|
108
|
+
frequency_activation = delta_vf > 0
|
|
109
|
+
reorganization_positive = dnfr > 0
|
|
110
|
+
|
|
111
|
+
# Irreversibility marker
|
|
112
|
+
irreversibility_marker = G.nodes[node].get("_emission_activated", False)
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
"operator": "Emission",
|
|
116
|
+
"glyph": "AL",
|
|
117
|
+
# Core metrics (existing)
|
|
118
|
+
"delta_epi": delta_epi,
|
|
119
|
+
"delta_vf": delta_vf,
|
|
120
|
+
"dnfr_initialized": dnfr,
|
|
121
|
+
"theta_current": theta,
|
|
122
|
+
# Legacy compatibility
|
|
123
|
+
"epi_final": epi_after,
|
|
124
|
+
"vf_final": vf_after,
|
|
125
|
+
"dnfr_final": dnfr,
|
|
126
|
+
"activation_strength": delta_epi,
|
|
127
|
+
"is_activated": epi_after > 0.5,
|
|
128
|
+
# AL-specific (NEW)
|
|
129
|
+
"emission_quality": emission_quality,
|
|
130
|
+
"activation_from_latency": activation_from_latency,
|
|
131
|
+
"form_emergence_magnitude": delta_epi,
|
|
132
|
+
"frequency_activation": frequency_activation,
|
|
133
|
+
"reorganization_positive": reorganization_positive,
|
|
134
|
+
# Traceability (NEW)
|
|
135
|
+
"emission_timestamp": emission_timestamp,
|
|
136
|
+
"irreversibility_marker": irreversibility_marker,
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def reception_metrics(G: TNFRGraph, node: NodeId, epi_before: float) -> dict[str, Any]:
|
|
141
|
+
"""EN - Reception metrics: EPI integration, source tracking, integration efficiency.
|
|
142
|
+
|
|
143
|
+
Extended metrics for Reception (EN) operator that track emission sources,
|
|
144
|
+
phase compatibility, and integration efficiency as specified in TNFR.pdf
|
|
145
|
+
§2.2.1 (EN - Structural reception).
|
|
146
|
+
|
|
147
|
+
Parameters
|
|
148
|
+
----------
|
|
149
|
+
G : TNFRGraph
|
|
150
|
+
Graph containing the node
|
|
151
|
+
node : NodeId
|
|
152
|
+
Node to collect metrics from
|
|
153
|
+
epi_before : float
|
|
154
|
+
EPI value before operator application
|
|
155
|
+
|
|
156
|
+
Returns
|
|
157
|
+
-------
|
|
158
|
+
dict
|
|
159
|
+
Reception-specific metrics including:
|
|
160
|
+
- Core metrics: delta_epi, epi_final, dnfr_after
|
|
161
|
+
- Legacy metrics: neighbor_count, neighbor_epi_mean, integration_strength
|
|
162
|
+
- EN-specific (NEW):
|
|
163
|
+
- num_sources: Number of detected emission sources
|
|
164
|
+
- integration_efficiency: Ratio of integrated to available coherence
|
|
165
|
+
- most_compatible_source: Most phase-compatible source node
|
|
166
|
+
- phase_compatibility_avg: Average phase compatibility with sources
|
|
167
|
+
- coherence_received: Total coherence integrated (delta_epi)
|
|
168
|
+
- stabilization_effective: Whether ΔNFR reduced below threshold
|
|
169
|
+
"""
|
|
170
|
+
epi_after = _get_node_attr(G, node, ALIAS_EPI)
|
|
171
|
+
dnfr_after = _get_node_attr(G, node, ALIAS_DNFR)
|
|
172
|
+
|
|
173
|
+
# Legacy neighbor metrics (backward compatibility)
|
|
174
|
+
neighbors = list(G.neighbors(node))
|
|
175
|
+
neighbor_count = len(neighbors)
|
|
176
|
+
|
|
177
|
+
# Calculate mean neighbor EPI
|
|
178
|
+
neighbor_epi_sum = 0.0
|
|
179
|
+
for n in neighbors:
|
|
180
|
+
neighbor_epi_sum += _get_node_attr(G, n, ALIAS_EPI)
|
|
181
|
+
neighbor_epi_mean = neighbor_epi_sum / neighbor_count if neighbor_count > 0 else 0.0
|
|
182
|
+
|
|
183
|
+
# Compute delta EPI (coherence received)
|
|
184
|
+
delta_epi = epi_after - epi_before
|
|
185
|
+
|
|
186
|
+
# EN-specific: Source tracking and integration efficiency
|
|
187
|
+
sources = G.nodes[node].get("_reception_sources", [])
|
|
188
|
+
num_sources = len(sources)
|
|
189
|
+
|
|
190
|
+
# Calculate total available coherence from sources
|
|
191
|
+
total_available_coherence = sum(strength for _, _, strength in sources)
|
|
192
|
+
|
|
193
|
+
# Integration efficiency: ratio of integrated to available coherence
|
|
194
|
+
# Only meaningful if coherence was actually available
|
|
195
|
+
integration_efficiency = (
|
|
196
|
+
delta_epi / total_available_coherence if total_available_coherence > 0 else 0.0
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
# Most compatible source (first in sorted list)
|
|
200
|
+
most_compatible_source = sources[0][0] if sources else None
|
|
201
|
+
|
|
202
|
+
# Average phase compatibility across all sources
|
|
203
|
+
phase_compatibility_avg = (
|
|
204
|
+
sum(compat for _, compat, _ in sources) / num_sources
|
|
205
|
+
if num_sources > 0
|
|
206
|
+
else 0.0
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
# Stabilization effectiveness (ΔNFR reduced?)
|
|
210
|
+
stabilization_effective = dnfr_after < 0.1
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
"operator": "Reception",
|
|
214
|
+
"glyph": "EN",
|
|
215
|
+
# Core metrics
|
|
216
|
+
"delta_epi": delta_epi,
|
|
217
|
+
"epi_final": epi_after,
|
|
218
|
+
"dnfr_after": dnfr_after,
|
|
219
|
+
# Legacy metrics (backward compatibility)
|
|
220
|
+
"neighbor_count": neighbor_count,
|
|
221
|
+
"neighbor_epi_mean": neighbor_epi_mean,
|
|
222
|
+
"integration_strength": abs(delta_epi),
|
|
223
|
+
# EN-specific (NEW)
|
|
224
|
+
"num_sources": num_sources,
|
|
225
|
+
"integration_efficiency": integration_efficiency,
|
|
226
|
+
"most_compatible_source": most_compatible_source,
|
|
227
|
+
"phase_compatibility_avg": phase_compatibility_avg,
|
|
228
|
+
"coherence_received": delta_epi,
|
|
229
|
+
"stabilization_effective": stabilization_effective,
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def coherence_metrics(G: TNFRGraph, node: NodeId, dnfr_before: float) -> dict[str, Any]:
|
|
234
|
+
"""IL - Coherence metrics: ΔC(t), stability gain, ΔNFR reduction, phase alignment.
|
|
235
|
+
|
|
236
|
+
Extended to include ΔNFR reduction percentage, C(t) coherence metrics,
|
|
237
|
+
phase alignment quality, and telemetry from the explicit reduction mechanism
|
|
238
|
+
implemented in the Coherence operator.
|
|
239
|
+
|
|
240
|
+
Parameters
|
|
241
|
+
----------
|
|
242
|
+
G : TNFRGraph
|
|
243
|
+
Graph containing the node
|
|
244
|
+
node : NodeId
|
|
245
|
+
Node to collect metrics from
|
|
246
|
+
dnfr_before : float
|
|
247
|
+
ΔNFR value before operator application
|
|
248
|
+
|
|
249
|
+
Returns
|
|
250
|
+
-------
|
|
251
|
+
dict
|
|
252
|
+
Coherence-specific metrics including:
|
|
253
|
+
- dnfr_before: ΔNFR value before operator
|
|
254
|
+
- dnfr_after: ΔNFR value after operator
|
|
255
|
+
- dnfr_reduction: Absolute reduction (before - after)
|
|
256
|
+
- dnfr_reduction_pct: Percentage reduction relative to before
|
|
257
|
+
- stability_gain: Improvement in stability (reduction of |ΔNFR|)
|
|
258
|
+
- is_stabilized: Whether node reached stable state (|ΔNFR| < 0.1)
|
|
259
|
+
- C_global: Global network coherence (current)
|
|
260
|
+
- C_local: Local neighborhood coherence (current)
|
|
261
|
+
- phase_alignment: Local phase alignment quality (Kuramoto order parameter)
|
|
262
|
+
- phase_coherence_quality: Alias for phase_alignment (for clarity)
|
|
263
|
+
- stabilization_quality: Combined metric (C_local * (1.0 - dnfr_after))
|
|
264
|
+
- epi_final, vf_final: Final structural state
|
|
265
|
+
"""
|
|
266
|
+
# Import here to avoid circular import
|
|
267
|
+
from ..metrics.coherence import compute_global_coherence, compute_local_coherence
|
|
268
|
+
from ..metrics.phase_coherence import compute_phase_alignment
|
|
269
|
+
|
|
270
|
+
dnfr_after = _get_node_attr(G, node, ALIAS_DNFR)
|
|
271
|
+
epi = _get_node_attr(G, node, ALIAS_EPI)
|
|
272
|
+
vf = _get_node_attr(G, node, ALIAS_VF)
|
|
273
|
+
|
|
274
|
+
# Compute reduction metrics
|
|
275
|
+
dnfr_reduction = dnfr_before - dnfr_after
|
|
276
|
+
dnfr_reduction_pct = (
|
|
277
|
+
(dnfr_reduction / dnfr_before * 100.0) if dnfr_before > 0 else 0.0
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
# Compute coherence metrics
|
|
281
|
+
C_global = compute_global_coherence(G)
|
|
282
|
+
C_local = compute_local_coherence(G, node)
|
|
283
|
+
|
|
284
|
+
# Compute phase alignment (Kuramoto order parameter)
|
|
285
|
+
phase_alignment = compute_phase_alignment(G, node)
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
"operator": "Coherence",
|
|
289
|
+
"glyph": "IL",
|
|
290
|
+
"dnfr_before": dnfr_before,
|
|
291
|
+
"dnfr_after": dnfr_after,
|
|
292
|
+
"dnfr_reduction": dnfr_reduction,
|
|
293
|
+
"dnfr_reduction_pct": dnfr_reduction_pct,
|
|
294
|
+
"dnfr_final": dnfr_after,
|
|
295
|
+
"stability_gain": abs(dnfr_before) - abs(dnfr_after),
|
|
296
|
+
"C_global": C_global,
|
|
297
|
+
"C_local": C_local,
|
|
298
|
+
"phase_alignment": phase_alignment,
|
|
299
|
+
"phase_coherence_quality": phase_alignment, # Alias for clarity
|
|
300
|
+
"stabilization_quality": C_local * (1.0 - dnfr_after), # Combined metric
|
|
301
|
+
"epi_final": epi,
|
|
302
|
+
"vf_final": vf,
|
|
303
|
+
"is_stabilized": abs(dnfr_after) < 0.1, # Configurable threshold
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def dissonance_metrics(
|
|
308
|
+
G: TNFRGraph, node: NodeId, dnfr_before: float, theta_before: float
|
|
309
|
+
) -> dict[str, Any]:
|
|
310
|
+
"""OZ - Comprehensive dissonance and bifurcation metrics.
|
|
311
|
+
|
|
312
|
+
Collects extended metrics for the Dissonance (OZ) operator, including
|
|
313
|
+
quantitative bifurcation analysis, topological disruption measures, and
|
|
314
|
+
viable path identification. This aligns with TNFR canonical theory (§2.3.3)
|
|
315
|
+
that OZ introduces **topological dissonance**, not just numerical instability.
|
|
316
|
+
|
|
317
|
+
Parameters
|
|
318
|
+
----------
|
|
319
|
+
G : TNFRGraph
|
|
320
|
+
Graph containing the node
|
|
321
|
+
node : NodeId
|
|
322
|
+
Node to collect metrics from
|
|
323
|
+
dnfr_before : float
|
|
324
|
+
ΔNFR value before operator application
|
|
325
|
+
theta_before : float
|
|
326
|
+
Phase value before operator application
|
|
327
|
+
|
|
328
|
+
Returns
|
|
329
|
+
-------
|
|
330
|
+
dict
|
|
331
|
+
Comprehensive dissonance metrics with keys:
|
|
332
|
+
|
|
333
|
+
**Quantitative dynamics:**
|
|
334
|
+
|
|
335
|
+
- dnfr_increase: Magnitude of introduced instability
|
|
336
|
+
- dnfr_final: Post-OZ ΔNFR value
|
|
337
|
+
- theta_shift: Phase exploration degree
|
|
338
|
+
- theta_final: Post-OZ phase value
|
|
339
|
+
- d2epi: Structural acceleration (bifurcation indicator)
|
|
340
|
+
|
|
341
|
+
**Bifurcation analysis:**
|
|
342
|
+
|
|
343
|
+
- bifurcation_score: Quantitative potential [0,1]
|
|
344
|
+
- bifurcation_active: Boolean threshold indicator (score > 0.5)
|
|
345
|
+
- viable_paths: List of viable operator glyph values
|
|
346
|
+
- viable_path_count: Number of viable paths
|
|
347
|
+
- mutation_readiness: Boolean indicator for ZHIR viability
|
|
348
|
+
|
|
349
|
+
**Topological effects:**
|
|
350
|
+
|
|
351
|
+
- topological_asymmetry_delta: Change in structural asymmetry
|
|
352
|
+
- symmetry_disrupted: Boolean (|delta| > 0.1)
|
|
353
|
+
|
|
354
|
+
**Network impact:**
|
|
355
|
+
|
|
356
|
+
- neighbor_count: Total neighbors
|
|
357
|
+
- impacted_neighbors: Count with |ΔNFR| > 0.1
|
|
358
|
+
- network_impact_radius: Ratio of impacted neighbors
|
|
359
|
+
|
|
360
|
+
**Recovery guidance:**
|
|
361
|
+
|
|
362
|
+
- recovery_estimate_IL: Estimated IL applications needed
|
|
363
|
+
- dissonance_level: |ΔNFR| magnitude
|
|
364
|
+
- critical_dissonance: Boolean (|ΔNFR| > 0.8)
|
|
365
|
+
|
|
366
|
+
Notes
|
|
367
|
+
-----
|
|
368
|
+
**Enhanced metrics vs original:**
|
|
369
|
+
|
|
370
|
+
The original implementation (lines 326-342) provided:
|
|
371
|
+
- Basic ΔNFR change
|
|
372
|
+
- Boolean bifurcation_risk
|
|
373
|
+
- Simple d2epi reading
|
|
374
|
+
|
|
375
|
+
This enhanced version adds:
|
|
376
|
+
- Quantitative bifurcation_score [0,1]
|
|
377
|
+
- Viable path identification
|
|
378
|
+
- Topological asymmetry measurement
|
|
379
|
+
- Network impact analysis
|
|
380
|
+
- Recovery estimation
|
|
381
|
+
|
|
382
|
+
**Topological asymmetry:**
|
|
383
|
+
|
|
384
|
+
Measures structural disruption in the node's ego-network using degree
|
|
385
|
+
and clustering heterogeneity. This captures the canonical effect that
|
|
386
|
+
OZ introduces **topological disruption**, not just numerical change.
|
|
387
|
+
|
|
388
|
+
**Viable paths:**
|
|
389
|
+
|
|
390
|
+
Identifies which operators can structurally resolve the dissonance:
|
|
391
|
+
- IL (Coherence): Always viable (universal resolution)
|
|
392
|
+
- ZHIR (Mutation): If νf > 0.8 (controlled transformation)
|
|
393
|
+
- NUL (Contraction): If EPI < 0.5 (safe collapse window)
|
|
394
|
+
- THOL (Self-organization): If degree >= 2 (network support)
|
|
395
|
+
|
|
396
|
+
Examples
|
|
397
|
+
--------
|
|
398
|
+
>>> from tnfr.structural import create_nfr
|
|
399
|
+
>>> from tnfr.operators.definitions import Dissonance, Coherence
|
|
400
|
+
>>>
|
|
401
|
+
>>> G, node = create_nfr("test", epi=0.5, vf=1.2)
|
|
402
|
+
>>> # Add neighbors for network analysis
|
|
403
|
+
>>> for i in range(3):
|
|
404
|
+
... G.add_node(f"n{i}")
|
|
405
|
+
... G.add_edge(node, f"n{i}")
|
|
406
|
+
>>>
|
|
407
|
+
>>> # Enable metrics collection
|
|
408
|
+
>>> G.graph['COLLECT_OPERATOR_METRICS'] = True
|
|
409
|
+
>>>
|
|
410
|
+
>>> # Apply Coherence to stabilize, then Dissonance to disrupt
|
|
411
|
+
>>> Coherence()(G, node)
|
|
412
|
+
>>> Dissonance()(G, node)
|
|
413
|
+
>>>
|
|
414
|
+
>>> # Retrieve enhanced metrics
|
|
415
|
+
>>> metrics = G.graph['operator_metrics'][-1]
|
|
416
|
+
>>> print(f"Bifurcation score: {metrics['bifurcation_score']:.2f}")
|
|
417
|
+
>>> print(f"Viable paths: {metrics['viable_paths']}")
|
|
418
|
+
>>> print(f"Network impact: {metrics['network_impact_radius']:.1%}")
|
|
419
|
+
>>> print(f"Recovery estimate: {metrics['recovery_estimate_IL']} IL")
|
|
420
|
+
|
|
421
|
+
See Also
|
|
422
|
+
--------
|
|
423
|
+
tnfr.dynamics.bifurcation.compute_bifurcation_score : Bifurcation scoring
|
|
424
|
+
tnfr.topology.asymmetry.compute_topological_asymmetry : Asymmetry measurement
|
|
425
|
+
tnfr.dynamics.bifurcation.get_bifurcation_paths : Viable path identification
|
|
426
|
+
"""
|
|
427
|
+
from ..dynamics.bifurcation import compute_bifurcation_score, get_bifurcation_paths
|
|
428
|
+
from ..topology.asymmetry import compute_topological_asymmetry
|
|
429
|
+
from .nodal_equation import compute_d2epi_dt2
|
|
430
|
+
|
|
431
|
+
# Get post-OZ node state
|
|
432
|
+
dnfr_after = _get_node_attr(G, node, ALIAS_DNFR)
|
|
433
|
+
theta_after = _get_node_attr(G, node, ALIAS_THETA)
|
|
434
|
+
epi_after = _get_node_attr(G, node, ALIAS_EPI)
|
|
435
|
+
vf_after = _get_node_attr(G, node, ALIAS_VF)
|
|
436
|
+
|
|
437
|
+
# 1. Compute d2epi actively during OZ
|
|
438
|
+
d2epi = compute_d2epi_dt2(G, node)
|
|
439
|
+
|
|
440
|
+
# 2. Quantitative bifurcation score (not just boolean)
|
|
441
|
+
bifurcation_threshold = float(G.graph.get("OZ_BIFURCATION_THRESHOLD", 0.5))
|
|
442
|
+
bifurcation_score = compute_bifurcation_score(
|
|
443
|
+
d2epi=d2epi,
|
|
444
|
+
dnfr=dnfr_after,
|
|
445
|
+
vf=vf_after,
|
|
446
|
+
epi=epi_after,
|
|
447
|
+
tau=bifurcation_threshold,
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
# 3. Topological asymmetry introduced by OZ
|
|
451
|
+
# Note: We measure asymmetry after OZ. In a full implementation, we'd also
|
|
452
|
+
# capture before state, but for metrics collection we focus on post-state.
|
|
453
|
+
# The delta is captured conceptually (OZ introduces disruption).
|
|
454
|
+
asymmetry_after = compute_topological_asymmetry(G, node)
|
|
455
|
+
|
|
456
|
+
# For now, we'll estimate delta based on the assumption that OZ increases asymmetry
|
|
457
|
+
# In a future enhancement, this could be computed by storing asymmetry_before
|
|
458
|
+
asymmetry_delta = asymmetry_after # Simplified: assume OZ caused current asymmetry
|
|
459
|
+
|
|
460
|
+
# 4. Analyze viable post-OZ paths
|
|
461
|
+
# Set bifurcation_ready flag if score exceeds threshold
|
|
462
|
+
if bifurcation_score > 0.5:
|
|
463
|
+
G.nodes[node]["_bifurcation_ready"] = True
|
|
464
|
+
|
|
465
|
+
viable_paths = get_bifurcation_paths(G, node)
|
|
466
|
+
|
|
467
|
+
# 5. Network impact (neighbors affected by dissonance)
|
|
468
|
+
neighbors = list(G.neighbors(node))
|
|
469
|
+
impacted_neighbors = 0
|
|
470
|
+
|
|
471
|
+
if neighbors:
|
|
472
|
+
# Count neighbors with significant |ΔNFR|
|
|
473
|
+
impact_threshold = 0.1
|
|
474
|
+
for n in neighbors:
|
|
475
|
+
neighbor_dnfr = abs(_get_node_attr(G, n, ALIAS_DNFR))
|
|
476
|
+
if neighbor_dnfr > impact_threshold:
|
|
477
|
+
impacted_neighbors += 1
|
|
478
|
+
|
|
479
|
+
# 6. Recovery estimate (how many IL needed to resolve)
|
|
480
|
+
# Assumes ~15% ΔNFR reduction per IL application
|
|
481
|
+
il_reduction_rate = 0.15
|
|
482
|
+
recovery_estimate = (
|
|
483
|
+
int(abs(dnfr_after) / il_reduction_rate) + 1 if dnfr_after != 0 else 1
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
# 7. Propagation analysis (if propagation occurred)
|
|
487
|
+
propagation_data = {}
|
|
488
|
+
propagation_events = G.graph.get("_oz_propagation_events", [])
|
|
489
|
+
if propagation_events:
|
|
490
|
+
latest_event = propagation_events[-1]
|
|
491
|
+
if latest_event["source"] == node:
|
|
492
|
+
propagation_data = {
|
|
493
|
+
"propagation_occurred": True,
|
|
494
|
+
"affected_neighbors": latest_event["affected_count"],
|
|
495
|
+
"propagation_magnitude": latest_event["magnitude"],
|
|
496
|
+
"affected_nodes": latest_event["affected_nodes"],
|
|
497
|
+
}
|
|
498
|
+
else:
|
|
499
|
+
propagation_data = {"propagation_occurred": False}
|
|
500
|
+
else:
|
|
501
|
+
propagation_data = {"propagation_occurred": False}
|
|
502
|
+
|
|
503
|
+
# 8. Compute network dissonance field (if propagation module available)
|
|
504
|
+
field_data = {}
|
|
505
|
+
try:
|
|
506
|
+
from ..dynamics.propagation import compute_network_dissonance_field
|
|
507
|
+
|
|
508
|
+
field = compute_network_dissonance_field(G, node, radius=2)
|
|
509
|
+
field_data = {
|
|
510
|
+
"dissonance_field_radius": len(field),
|
|
511
|
+
"max_field_strength": max(field.values()) if field else 0.0,
|
|
512
|
+
"mean_field_strength": sum(field.values()) / len(field) if field else 0.0,
|
|
513
|
+
}
|
|
514
|
+
except (ImportError, Exception):
|
|
515
|
+
# Gracefully handle if propagation module not available
|
|
516
|
+
field_data = {
|
|
517
|
+
"dissonance_field_radius": 0,
|
|
518
|
+
"max_field_strength": 0.0,
|
|
519
|
+
"mean_field_strength": 0.0,
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
return {
|
|
523
|
+
"operator": "Dissonance",
|
|
524
|
+
"glyph": "OZ",
|
|
525
|
+
# Quantitative dynamics
|
|
526
|
+
"dnfr_increase": dnfr_after - dnfr_before,
|
|
527
|
+
"dnfr_final": dnfr_after,
|
|
528
|
+
"theta_shift": abs(theta_after - theta_before),
|
|
529
|
+
"theta_final": theta_after,
|
|
530
|
+
"d2epi": d2epi,
|
|
531
|
+
# Bifurcation analysis
|
|
532
|
+
"bifurcation_score": bifurcation_score,
|
|
533
|
+
"bifurcation_active": bifurcation_score > 0.5,
|
|
534
|
+
"viable_paths": [str(g.value) for g in viable_paths],
|
|
535
|
+
"viable_path_count": len(viable_paths),
|
|
536
|
+
"mutation_readiness": any(g.value == "ZHIR" for g in viable_paths),
|
|
537
|
+
# Topological effects
|
|
538
|
+
"topological_asymmetry_delta": asymmetry_delta,
|
|
539
|
+
"symmetry_disrupted": abs(asymmetry_delta) > 0.1,
|
|
540
|
+
# Network impact
|
|
541
|
+
"neighbor_count": len(neighbors),
|
|
542
|
+
"impacted_neighbors": impacted_neighbors,
|
|
543
|
+
"network_impact_radius": (
|
|
544
|
+
impacted_neighbors / len(neighbors) if neighbors else 0.0
|
|
545
|
+
),
|
|
546
|
+
# Recovery guidance
|
|
547
|
+
"recovery_estimate_IL": recovery_estimate,
|
|
548
|
+
"dissonance_level": abs(dnfr_after),
|
|
549
|
+
"critical_dissonance": abs(dnfr_after) > 0.8,
|
|
550
|
+
# Network propagation
|
|
551
|
+
**propagation_data,
|
|
552
|
+
**field_data,
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
def coupling_metrics(
|
|
557
|
+
G: TNFRGraph,
|
|
558
|
+
node: NodeId,
|
|
559
|
+
theta_before: float,
|
|
560
|
+
dnfr_before: float = None,
|
|
561
|
+
vf_before: float = None,
|
|
562
|
+
edges_before: int = None,
|
|
563
|
+
epi_before: float = None,
|
|
564
|
+
) -> dict[str, Any]:
|
|
565
|
+
"""UM - Coupling metrics: phase alignment, link formation, synchrony, ΔNFR reduction.
|
|
566
|
+
|
|
567
|
+
Extended metrics for Coupling (UM) operator that track structural changes,
|
|
568
|
+
network formation, and synchronization effectiveness.
|
|
569
|
+
|
|
570
|
+
Parameters
|
|
571
|
+
----------
|
|
572
|
+
G : TNFRGraph
|
|
573
|
+
Graph containing the node
|
|
574
|
+
node : NodeId
|
|
575
|
+
Node to collect metrics from
|
|
576
|
+
theta_before : float
|
|
577
|
+
Phase value before operator application
|
|
578
|
+
dnfr_before : float, optional
|
|
579
|
+
ΔNFR value before operator application (for reduction tracking)
|
|
580
|
+
vf_before : float, optional
|
|
581
|
+
Structural frequency (νf) before operator application
|
|
582
|
+
edges_before : int, optional
|
|
583
|
+
Number of edges before operator application
|
|
584
|
+
epi_before : float, optional
|
|
585
|
+
EPI value before operator application (for invariance verification)
|
|
586
|
+
|
|
587
|
+
Returns
|
|
588
|
+
-------
|
|
589
|
+
dict
|
|
590
|
+
Coupling-specific metrics including:
|
|
591
|
+
|
|
592
|
+
**Phase metrics:**
|
|
593
|
+
|
|
594
|
+
- theta_shift: Absolute phase change
|
|
595
|
+
- theta_final: Post-coupling phase
|
|
596
|
+
- mean_neighbor_phase: Average phase of neighbors
|
|
597
|
+
- phase_alignment: Alignment with neighbors [0,1]
|
|
598
|
+
- phase_dispersion: Standard deviation of phases in local cluster
|
|
599
|
+
- is_synchronized: Boolean indicating strong synchronization (alignment > 0.8)
|
|
600
|
+
|
|
601
|
+
**Frequency metrics:**
|
|
602
|
+
|
|
603
|
+
- delta_vf: Change in structural frequency (νf)
|
|
604
|
+
- vf_final: Post-coupling structural frequency
|
|
605
|
+
|
|
606
|
+
**Reorganization metrics:**
|
|
607
|
+
|
|
608
|
+
- delta_dnfr: Change in ΔNFR
|
|
609
|
+
- dnfr_stabilization: Reduction of reorganization pressure (positive if stabilized)
|
|
610
|
+
- dnfr_final: Post-coupling ΔNFR
|
|
611
|
+
- dnfr_reduction: Absolute reduction (before - after)
|
|
612
|
+
- dnfr_reduction_pct: Percentage reduction
|
|
613
|
+
|
|
614
|
+
**EPI Invariance metrics:**
|
|
615
|
+
|
|
616
|
+
- epi_before: EPI value before coupling
|
|
617
|
+
- epi_after: EPI value after coupling
|
|
618
|
+
- epi_drift: Absolute difference between before and after
|
|
619
|
+
- epi_preserved: Boolean indicating EPI invariance (drift < 1e-9)
|
|
620
|
+
|
|
621
|
+
**Network metrics:**
|
|
622
|
+
|
|
623
|
+
- neighbor_count: Number of neighbors after coupling
|
|
624
|
+
- new_edges_count: Number of edges added
|
|
625
|
+
- total_edges: Total edges after coupling
|
|
626
|
+
- coupling_strength_total: Sum of coupling weights on edges
|
|
627
|
+
- local_coherence: Kuramoto order parameter of local subgraph
|
|
628
|
+
|
|
629
|
+
Notes
|
|
630
|
+
-----
|
|
631
|
+
The extended metrics align with TNFR canonical theory (§2.2.2) that UM creates
|
|
632
|
+
structural links through phase synchronization (φᵢ(t) ≈ φⱼ(t)). The metrics
|
|
633
|
+
capture both the synchronization quality and the network structural changes
|
|
634
|
+
resulting from coupling.
|
|
635
|
+
|
|
636
|
+
**EPI Invariance**: UM MUST preserve EPI identity. The epi_preserved metric
|
|
637
|
+
validates this fundamental invariant. If epi_preserved is False, it indicates
|
|
638
|
+
a violation of TNFR canonical requirements.
|
|
639
|
+
|
|
640
|
+
See Also
|
|
641
|
+
--------
|
|
642
|
+
operators.definitions.Coupling : UM operator implementation
|
|
643
|
+
metrics.phase_coherence.compute_phase_alignment : Phase alignment computation
|
|
644
|
+
"""
|
|
645
|
+
import math
|
|
646
|
+
import statistics
|
|
647
|
+
|
|
648
|
+
theta_after = _get_node_attr(G, node, ALIAS_THETA)
|
|
649
|
+
dnfr_after = _get_node_attr(G, node, ALIAS_DNFR)
|
|
650
|
+
vf_after = _get_node_attr(G, node, ALIAS_VF)
|
|
651
|
+
neighbors = list(G.neighbors(node))
|
|
652
|
+
neighbor_count = len(neighbors)
|
|
653
|
+
|
|
654
|
+
# Calculate phase coherence with neighbors
|
|
655
|
+
if neighbor_count > 0:
|
|
656
|
+
phase_sum = sum(_get_node_attr(G, n, ALIAS_THETA) for n in neighbors)
|
|
657
|
+
mean_neighbor_phase = phase_sum / neighbor_count
|
|
658
|
+
phase_alignment = 1.0 - abs(theta_after - mean_neighbor_phase) / math.pi
|
|
659
|
+
else:
|
|
660
|
+
mean_neighbor_phase = theta_after
|
|
661
|
+
phase_alignment = 0.0
|
|
662
|
+
|
|
663
|
+
# Base metrics (always present)
|
|
664
|
+
metrics = {
|
|
665
|
+
"operator": "Coupling",
|
|
666
|
+
"glyph": "UM",
|
|
667
|
+
"theta_shift": abs(theta_after - theta_before),
|
|
668
|
+
"theta_final": theta_after,
|
|
669
|
+
"neighbor_count": neighbor_count,
|
|
670
|
+
"mean_neighbor_phase": mean_neighbor_phase,
|
|
671
|
+
"phase_alignment": max(0.0, phase_alignment),
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
# Structural frequency metrics (if vf_before provided)
|
|
675
|
+
if vf_before is not None:
|
|
676
|
+
delta_vf = vf_after - vf_before
|
|
677
|
+
metrics.update(
|
|
678
|
+
{
|
|
679
|
+
"delta_vf": delta_vf,
|
|
680
|
+
"vf_final": vf_after,
|
|
681
|
+
}
|
|
682
|
+
)
|
|
683
|
+
|
|
684
|
+
# ΔNFR reduction metrics (if dnfr_before provided)
|
|
685
|
+
if dnfr_before is not None:
|
|
686
|
+
dnfr_reduction = dnfr_before - dnfr_after
|
|
687
|
+
dnfr_reduction_pct = (dnfr_reduction / (abs(dnfr_before) + 1e-9)) * 100.0
|
|
688
|
+
dnfr_stabilization = dnfr_before - dnfr_after # Positive if stabilized
|
|
689
|
+
metrics.update(
|
|
690
|
+
{
|
|
691
|
+
"dnfr_before": dnfr_before,
|
|
692
|
+
"dnfr_after": dnfr_after,
|
|
693
|
+
"delta_dnfr": dnfr_after - dnfr_before,
|
|
694
|
+
"dnfr_reduction": dnfr_reduction,
|
|
695
|
+
"dnfr_reduction_pct": dnfr_reduction_pct,
|
|
696
|
+
"dnfr_stabilization": dnfr_stabilization,
|
|
697
|
+
"dnfr_final": dnfr_after,
|
|
698
|
+
}
|
|
699
|
+
)
|
|
700
|
+
|
|
701
|
+
# EPI invariance verification (if epi_before provided)
|
|
702
|
+
# CRITICAL: UM MUST preserve EPI identity per TNFR canonical theory
|
|
703
|
+
if epi_before is not None:
|
|
704
|
+
epi_after = _get_node_attr(G, node, ALIAS_EPI)
|
|
705
|
+
epi_drift = abs(epi_after - epi_before)
|
|
706
|
+
metrics.update(
|
|
707
|
+
{
|
|
708
|
+
"epi_before": epi_before,
|
|
709
|
+
"epi_after": epi_after,
|
|
710
|
+
"epi_drift": epi_drift,
|
|
711
|
+
"epi_preserved": epi_drift < 1e-9, # Should ALWAYS be True
|
|
712
|
+
}
|
|
713
|
+
)
|
|
714
|
+
|
|
715
|
+
# Edge/network formation metrics (if edges_before provided)
|
|
716
|
+
edges_after = G.degree(node)
|
|
717
|
+
if edges_before is not None:
|
|
718
|
+
new_edges_count = edges_after - edges_before
|
|
719
|
+
metrics.update(
|
|
720
|
+
{
|
|
721
|
+
"new_edges_count": new_edges_count,
|
|
722
|
+
"total_edges": edges_after,
|
|
723
|
+
}
|
|
724
|
+
)
|
|
725
|
+
else:
|
|
726
|
+
# Still provide total_edges even without edges_before
|
|
727
|
+
metrics["total_edges"] = edges_after
|
|
728
|
+
|
|
729
|
+
# Coupling strength (sum of edge weights)
|
|
730
|
+
coupling_strength_total = 0.0
|
|
731
|
+
for neighbor in neighbors:
|
|
732
|
+
edge_data = G.get_edge_data(node, neighbor)
|
|
733
|
+
if edge_data and isinstance(edge_data, dict):
|
|
734
|
+
coupling_strength_total += edge_data.get("coupling", 0.0)
|
|
735
|
+
metrics["coupling_strength_total"] = coupling_strength_total
|
|
736
|
+
|
|
737
|
+
# Phase dispersion (standard deviation of local phases)
|
|
738
|
+
if neighbor_count > 1:
|
|
739
|
+
phases = [theta_after] + [_get_node_attr(G, n, ALIAS_THETA) for n in neighbors]
|
|
740
|
+
phase_std = statistics.stdev(phases)
|
|
741
|
+
metrics["phase_dispersion"] = phase_std
|
|
742
|
+
else:
|
|
743
|
+
metrics["phase_dispersion"] = 0.0
|
|
744
|
+
|
|
745
|
+
# Local coherence (Kuramoto order parameter of subgraph)
|
|
746
|
+
if neighbor_count > 0:
|
|
747
|
+
from ..metrics.phase_coherence import compute_phase_alignment
|
|
748
|
+
|
|
749
|
+
local_coherence = compute_phase_alignment(G, node, radius=1)
|
|
750
|
+
metrics["local_coherence"] = local_coherence
|
|
751
|
+
else:
|
|
752
|
+
metrics["local_coherence"] = 0.0
|
|
753
|
+
|
|
754
|
+
# Synchronization indicator
|
|
755
|
+
metrics["is_synchronized"] = phase_alignment > 0.8
|
|
756
|
+
|
|
757
|
+
return metrics
|
|
758
|
+
|
|
759
|
+
|
|
760
|
+
def resonance_metrics(
|
|
761
|
+
G: TNFRGraph,
|
|
762
|
+
node: NodeId,
|
|
763
|
+
epi_before: float,
|
|
764
|
+
vf_before: float | None = None,
|
|
765
|
+
) -> dict[str, Any]:
|
|
766
|
+
"""RA - Resonance metrics: EPI propagation, νf amplification, phase strengthening.
|
|
767
|
+
|
|
768
|
+
Canonical TNFR resonance metrics include:
|
|
769
|
+
- EPI propagation effectiveness
|
|
770
|
+
- νf amplification (structural frequency increase)
|
|
771
|
+
- Phase alignment strengthening
|
|
772
|
+
- Identity preservation validation
|
|
773
|
+
- Network coherence contribution
|
|
774
|
+
|
|
775
|
+
Parameters
|
|
776
|
+
----------
|
|
777
|
+
G : TNFRGraph
|
|
778
|
+
Graph containing the node
|
|
779
|
+
node : NodeId
|
|
780
|
+
Node to collect metrics from
|
|
781
|
+
epi_before : float
|
|
782
|
+
EPI value before operator application
|
|
783
|
+
vf_before : float | None
|
|
784
|
+
νf value before operator application (for amplification tracking)
|
|
785
|
+
|
|
786
|
+
Returns
|
|
787
|
+
-------
|
|
788
|
+
dict
|
|
789
|
+
Resonance-specific metrics including:
|
|
790
|
+
- EPI propagation metrics
|
|
791
|
+
- νf amplification ratio (canonical effect)
|
|
792
|
+
- Phase alignment quality
|
|
793
|
+
- Identity preservation status
|
|
794
|
+
- Network coherence contribution
|
|
795
|
+
"""
|
|
796
|
+
epi_after = _get_node_attr(G, node, ALIAS_EPI)
|
|
797
|
+
vf_after = _get_node_attr(G, node, ALIAS_VF)
|
|
798
|
+
neighbors = list(G.neighbors(node))
|
|
799
|
+
neighbor_count = len(neighbors)
|
|
800
|
+
|
|
801
|
+
# Calculate resonance strength based on neighbor coupling
|
|
802
|
+
if neighbor_count > 0:
|
|
803
|
+
neighbor_epi_sum = sum(_get_node_attr(G, n, ALIAS_EPI) for n in neighbors)
|
|
804
|
+
neighbor_epi_mean = neighbor_epi_sum / neighbor_count
|
|
805
|
+
resonance_strength = abs(epi_after - epi_before) * neighbor_count
|
|
806
|
+
|
|
807
|
+
# Canonical νf amplification tracking
|
|
808
|
+
if vf_before is not None and vf_before > 0:
|
|
809
|
+
vf_amplification = vf_after / vf_before
|
|
810
|
+
else:
|
|
811
|
+
vf_amplification = 1.0
|
|
812
|
+
|
|
813
|
+
# Phase alignment quality (measure coherence with neighbors)
|
|
814
|
+
from ..metrics.phase_coherence import compute_phase_alignment
|
|
815
|
+
|
|
816
|
+
phase_alignment = compute_phase_alignment(G, node)
|
|
817
|
+
else:
|
|
818
|
+
neighbor_epi_mean = 0.0
|
|
819
|
+
resonance_strength = 0.0
|
|
820
|
+
vf_amplification = 1.0
|
|
821
|
+
phase_alignment = 0.0
|
|
822
|
+
|
|
823
|
+
# Identity preservation check (sign should be preserved)
|
|
824
|
+
identity_preserved = epi_before * epi_after >= 0
|
|
825
|
+
|
|
826
|
+
return {
|
|
827
|
+
"operator": "Resonance",
|
|
828
|
+
"glyph": "RA",
|
|
829
|
+
"delta_epi": epi_after - epi_before,
|
|
830
|
+
"epi_final": epi_after,
|
|
831
|
+
"epi_before": epi_before,
|
|
832
|
+
"neighbor_count": neighbor_count,
|
|
833
|
+
"neighbor_epi_mean": neighbor_epi_mean,
|
|
834
|
+
"resonance_strength": resonance_strength,
|
|
835
|
+
"propagation_successful": neighbor_count > 0
|
|
836
|
+
and abs(epi_after - neighbor_epi_mean) < 0.5,
|
|
837
|
+
# Canonical TNFR effects
|
|
838
|
+
"vf_amplification": vf_amplification, # Canonical: νf increases through resonance
|
|
839
|
+
"vf_before": vf_before if vf_before is not None else vf_after,
|
|
840
|
+
"vf_after": vf_after,
|
|
841
|
+
"phase_alignment": phase_alignment, # Canonical: phase strengthens
|
|
842
|
+
"identity_preserved": identity_preserved, # Canonical: EPI identity maintained
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
|
|
846
|
+
def _compute_epi_variance(G: TNFRGraph, node: NodeId) -> float:
|
|
847
|
+
"""Compute EPI variance during silence period.
|
|
848
|
+
|
|
849
|
+
Measures the standard deviation of EPI values recorded during silence,
|
|
850
|
+
validating effective preservation (variance ≈ 0).
|
|
851
|
+
|
|
852
|
+
Parameters
|
|
853
|
+
----------
|
|
854
|
+
G : TNFRGraph
|
|
855
|
+
Graph containing the node
|
|
856
|
+
node : NodeId
|
|
857
|
+
Node to compute variance for
|
|
858
|
+
|
|
859
|
+
Returns
|
|
860
|
+
-------
|
|
861
|
+
float
|
|
862
|
+
Standard deviation of EPI during silence period
|
|
863
|
+
"""
|
|
864
|
+
import numpy as np
|
|
865
|
+
|
|
866
|
+
epi_history = G.nodes[node].get("epi_history_during_silence", [])
|
|
867
|
+
if len(epi_history) < 2:
|
|
868
|
+
return 0.0
|
|
869
|
+
return float(np.std(epi_history))
|
|
870
|
+
|
|
871
|
+
|
|
872
|
+
def _compute_preservation_integrity(preserved_epi: float, epi_after: float) -> float:
|
|
873
|
+
"""Compute preservation integrity ratio.
|
|
874
|
+
|
|
875
|
+
Measures structural preservation quality as:
|
|
876
|
+
integrity = 1 - |EPI_after - EPI_preserved| / EPI_preserved
|
|
877
|
+
|
|
878
|
+
Interpretation:
|
|
879
|
+
- integrity = 1.0: Perfect preservation
|
|
880
|
+
- integrity < 0.95: Significant degradation
|
|
881
|
+
- integrity < 0.8: Preservation failure
|
|
882
|
+
|
|
883
|
+
Parameters
|
|
884
|
+
----------
|
|
885
|
+
preserved_epi : float
|
|
886
|
+
EPI value that was preserved at silence start
|
|
887
|
+
epi_after : float
|
|
888
|
+
Current EPI value
|
|
889
|
+
|
|
890
|
+
Returns
|
|
891
|
+
-------
|
|
892
|
+
float
|
|
893
|
+
Preservation integrity in [0, 1]
|
|
894
|
+
"""
|
|
895
|
+
if preserved_epi == 0:
|
|
896
|
+
return 1.0 if epi_after == 0 else 0.0
|
|
897
|
+
|
|
898
|
+
integrity = 1.0 - abs(epi_after - preserved_epi) / abs(preserved_epi)
|
|
899
|
+
return max(0.0, integrity)
|
|
900
|
+
|
|
901
|
+
|
|
902
|
+
def _compute_reactivation_readiness(G: TNFRGraph, node: NodeId) -> float:
|
|
903
|
+
"""Compute readiness score for reactivation from silence.
|
|
904
|
+
|
|
905
|
+
Evaluates if the node can reactivate effectively based on:
|
|
906
|
+
- νf residual (must be recoverable)
|
|
907
|
+
- EPI preserved (must be coherent)
|
|
908
|
+
- Silence duration (not excessive)
|
|
909
|
+
- Network connectivity (active neighbors)
|
|
910
|
+
|
|
911
|
+
Score in [0, 1]:
|
|
912
|
+
- 1.0: Fully ready to reactivate
|
|
913
|
+
- 0.5-0.8: Moderate readiness
|
|
914
|
+
- < 0.3: Risky reactivation
|
|
915
|
+
|
|
916
|
+
Parameters
|
|
917
|
+
----------
|
|
918
|
+
G : TNFRGraph
|
|
919
|
+
Graph containing the node
|
|
920
|
+
node : NodeId
|
|
921
|
+
Node to compute readiness for
|
|
922
|
+
|
|
923
|
+
Returns
|
|
924
|
+
-------
|
|
925
|
+
float
|
|
926
|
+
Reactivation readiness score in [0, 1]
|
|
927
|
+
"""
|
|
928
|
+
vf = _get_node_attr(G, node, ALIAS_VF)
|
|
929
|
+
epi = _get_node_attr(G, node, ALIAS_EPI)
|
|
930
|
+
duration = G.nodes[node].get("silence_duration", 0.0)
|
|
931
|
+
|
|
932
|
+
# Count active neighbors
|
|
933
|
+
active_neighbors = 0
|
|
934
|
+
if G.has_node(node):
|
|
935
|
+
for n in G.neighbors(node):
|
|
936
|
+
if _get_node_attr(G, n, ALIAS_VF) > 0.1:
|
|
937
|
+
active_neighbors += 1
|
|
938
|
+
|
|
939
|
+
# Scoring components
|
|
940
|
+
vf_score = min(vf / 0.5, 1.0) # νf recoverable
|
|
941
|
+
epi_score = min(epi / 0.3, 1.0) # EPI coherent
|
|
942
|
+
duration_score = 1.0 / (1.0 + duration * 0.1) # Penalize long silence
|
|
943
|
+
network_score = min(active_neighbors / 3.0, 1.0) # Network support
|
|
944
|
+
|
|
945
|
+
return (vf_score + epi_score + duration_score + network_score) / 4.0
|
|
946
|
+
|
|
947
|
+
|
|
948
|
+
def _estimate_time_to_collapse(G: TNFRGraph, node: NodeId) -> float:
|
|
949
|
+
"""Estimate time until nodal collapse during silence.
|
|
950
|
+
|
|
951
|
+
Estimates how long silence can be maintained before structural collapse
|
|
952
|
+
based on observed drift rate or default degradation model.
|
|
953
|
+
|
|
954
|
+
Model:
|
|
955
|
+
t_collapse ≈ EPI_preserved / |DRIFT_RATE|
|
|
956
|
+
|
|
957
|
+
Parameters
|
|
958
|
+
----------
|
|
959
|
+
G : TNFRGraph
|
|
960
|
+
Graph containing the node
|
|
961
|
+
node : NodeId
|
|
962
|
+
Node to estimate collapse time for
|
|
963
|
+
|
|
964
|
+
Returns
|
|
965
|
+
-------
|
|
966
|
+
float
|
|
967
|
+
Estimated time steps until collapse (inf if no degradation)
|
|
968
|
+
"""
|
|
969
|
+
preserved_epi = G.nodes[node].get("preserved_epi", 0.0)
|
|
970
|
+
drift_rate = G.nodes[node].get("epi_drift_rate", 0.0)
|
|
971
|
+
|
|
972
|
+
if abs(drift_rate) < 1e-10:
|
|
973
|
+
# No observed degradation - return large value
|
|
974
|
+
return float("inf")
|
|
975
|
+
|
|
976
|
+
if preserved_epi <= 0:
|
|
977
|
+
# Already at or below collapse threshold
|
|
978
|
+
return 0.0
|
|
979
|
+
|
|
980
|
+
# Estimate time until EPI reaches zero
|
|
981
|
+
return abs(preserved_epi / drift_rate)
|
|
982
|
+
|
|
983
|
+
|
|
984
|
+
def silence_metrics(
|
|
985
|
+
G: TNFRGraph, node: NodeId, vf_before: float, epi_before: float
|
|
986
|
+
) -> dict[str, Any]:
|
|
987
|
+
"""SHA - Silence metrics: νf reduction, EPI preservation, duration tracking.
|
|
988
|
+
|
|
989
|
+
Extended metrics for deep analysis of structural preservation effectiveness.
|
|
990
|
+
Collects silence-specific metrics that reflect canonical SHA effects including
|
|
991
|
+
latency state management as specified in TNFR.pdf §2.3.10.
|
|
992
|
+
|
|
993
|
+
Parameters
|
|
994
|
+
----------
|
|
995
|
+
G : TNFRGraph
|
|
996
|
+
Graph containing the node
|
|
997
|
+
node : NodeId
|
|
998
|
+
Node to collect metrics from
|
|
999
|
+
vf_before : float
|
|
1000
|
+
νf value before operator application
|
|
1001
|
+
epi_before : float
|
|
1002
|
+
EPI value before operator application
|
|
1003
|
+
|
|
1004
|
+
Returns
|
|
1005
|
+
-------
|
|
1006
|
+
dict
|
|
1007
|
+
Silence-specific metrics including:
|
|
1008
|
+
|
|
1009
|
+
**Core metrics (existing):**
|
|
1010
|
+
|
|
1011
|
+
- operator: "Silence"
|
|
1012
|
+
- glyph: "SHA"
|
|
1013
|
+
- vf_reduction: Absolute reduction in νf
|
|
1014
|
+
- vf_final: Post-silence νf value
|
|
1015
|
+
- epi_preservation: Absolute EPI change (should be ≈ 0)
|
|
1016
|
+
- epi_final: Post-silence EPI value
|
|
1017
|
+
- is_silent: Boolean indicating silent state (νf < 0.1)
|
|
1018
|
+
|
|
1019
|
+
**Latency state tracking:**
|
|
1020
|
+
|
|
1021
|
+
- latent: Boolean latency flag
|
|
1022
|
+
- silence_duration: Time in silence state (steps or structural time)
|
|
1023
|
+
|
|
1024
|
+
**Extended metrics (NEW):**
|
|
1025
|
+
|
|
1026
|
+
- epi_variance: Standard deviation of EPI during silence
|
|
1027
|
+
- preservation_integrity: Quality metric [0, 1] for preservation
|
|
1028
|
+
- reactivation_readiness: Readiness score [0, 1] for reactivation
|
|
1029
|
+
- time_to_collapse: Estimated time until nodal collapse
|
|
1030
|
+
|
|
1031
|
+
Notes
|
|
1032
|
+
-----
|
|
1033
|
+
Extended metrics enable:
|
|
1034
|
+
- Detection of excessive silence (collapse risk)
|
|
1035
|
+
- Validation of preservation quality
|
|
1036
|
+
- Analysis of consolidation patterns (memory, learning)
|
|
1037
|
+
- Strategic pause effectiveness (biomedical, cognitive, social domains)
|
|
1038
|
+
|
|
1039
|
+
See Also
|
|
1040
|
+
--------
|
|
1041
|
+
_compute_epi_variance : EPI variance computation
|
|
1042
|
+
_compute_preservation_integrity : Preservation quality metric
|
|
1043
|
+
_compute_reactivation_readiness : Reactivation readiness score
|
|
1044
|
+
_estimate_time_to_collapse : Collapse time estimation
|
|
1045
|
+
"""
|
|
1046
|
+
vf_after = _get_node_attr(G, node, ALIAS_VF)
|
|
1047
|
+
epi_after = _get_node_attr(G, node, ALIAS_EPI)
|
|
1048
|
+
preserved_epi = G.nodes[node].get("preserved_epi")
|
|
1049
|
+
|
|
1050
|
+
# Core metrics (existing)
|
|
1051
|
+
core = {
|
|
1052
|
+
"operator": "Silence",
|
|
1053
|
+
"glyph": "SHA",
|
|
1054
|
+
"vf_reduction": vf_before - vf_after,
|
|
1055
|
+
"vf_final": vf_after,
|
|
1056
|
+
"epi_preservation": abs(epi_after - epi_before),
|
|
1057
|
+
"epi_final": epi_after,
|
|
1058
|
+
"is_silent": vf_after < 0.1,
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
# Latency state tracking metrics
|
|
1062
|
+
core["latent"] = G.nodes[node].get("latent", False)
|
|
1063
|
+
core["silence_duration"] = G.nodes[node].get("silence_duration", 0.0)
|
|
1064
|
+
|
|
1065
|
+
# Extended metrics (new)
|
|
1066
|
+
extended = {
|
|
1067
|
+
"epi_variance": _compute_epi_variance(G, node),
|
|
1068
|
+
"preservation_integrity": (
|
|
1069
|
+
_compute_preservation_integrity(preserved_epi, epi_after)
|
|
1070
|
+
if preserved_epi is not None
|
|
1071
|
+
else 1.0 - abs(epi_after - epi_before)
|
|
1072
|
+
),
|
|
1073
|
+
"reactivation_readiness": _compute_reactivation_readiness(G, node),
|
|
1074
|
+
"time_to_collapse": _estimate_time_to_collapse(G, node),
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
return {**core, **extended}
|
|
1078
|
+
|
|
1079
|
+
|
|
1080
|
+
def expansion_metrics(
|
|
1081
|
+
G: TNFRGraph, node: NodeId, vf_before: float, epi_before: float
|
|
1082
|
+
) -> dict[str, Any]:
|
|
1083
|
+
"""VAL - Enhanced expansion metrics with structural indicators (Issue #2724).
|
|
1084
|
+
|
|
1085
|
+
Captures comprehensive metrics reflecting canonical VAL effects:
|
|
1086
|
+
- Basic growth metrics (Δνf, ΔEPI)
|
|
1087
|
+
- Bifurcation risk (∂²EPI/∂t²)
|
|
1088
|
+
- Coherence preservation (local C(t))
|
|
1089
|
+
- Fractality indicators (growth ratios)
|
|
1090
|
+
- Network impact (phase coherence with neighbors)
|
|
1091
|
+
- Structural stability (ΔNFR bounds)
|
|
1092
|
+
|
|
1093
|
+
Parameters
|
|
1094
|
+
----------
|
|
1095
|
+
G : TNFRGraph
|
|
1096
|
+
Graph containing the node
|
|
1097
|
+
node : NodeId
|
|
1098
|
+
Node to collect metrics from
|
|
1099
|
+
vf_before : float
|
|
1100
|
+
νf value before operator application
|
|
1101
|
+
epi_before : float
|
|
1102
|
+
EPI value before operator application
|
|
1103
|
+
|
|
1104
|
+
Returns
|
|
1105
|
+
-------
|
|
1106
|
+
dict
|
|
1107
|
+
Comprehensive expansion metrics including:
|
|
1108
|
+
|
|
1109
|
+
**Core Metrics (existing)**:
|
|
1110
|
+
- operator, glyph: Identification
|
|
1111
|
+
- vf_increase, vf_final: Frequency changes
|
|
1112
|
+
- delta_epi, epi_final: EPI changes
|
|
1113
|
+
- expansion_factor: Relative νf increase
|
|
1114
|
+
|
|
1115
|
+
**Structural Stability (NEW)**:
|
|
1116
|
+
- dnfr_final: Final reorganization gradient
|
|
1117
|
+
- dnfr_positive: True if ΔNFR > 0 (required for expansion)
|
|
1118
|
+
- dnfr_stable: True if 0 < ΔNFR < 1.0 (bounded growth)
|
|
1119
|
+
|
|
1120
|
+
**Bifurcation Risk (ENHANCED)**:
|
|
1121
|
+
- d2epi: EPI acceleration (∂²EPI/∂t²)
|
|
1122
|
+
- bifurcation_risk: True when |∂²EPI/∂t²| > threshold
|
|
1123
|
+
- bifurcation_magnitude: Ratio of d2epi to threshold
|
|
1124
|
+
- bifurcation_threshold: Configurable threshold value
|
|
1125
|
+
|
|
1126
|
+
**Coherence Preservation (ENHANCED)**:
|
|
1127
|
+
- coherence_local: Local coherence measurement [0,1]
|
|
1128
|
+
- coherence_preserved: True when C_local > threshold
|
|
1129
|
+
|
|
1130
|
+
**Fractality Indicators (ENHANCED)**:
|
|
1131
|
+
- epi_growth_rate: Relative EPI growth
|
|
1132
|
+
- vf_growth_rate: Relative νf growth
|
|
1133
|
+
- growth_ratio: vf_growth_rate / epi_growth_rate
|
|
1134
|
+
- fractal_preserved: True when ratio in valid range [0.5, 2.0]
|
|
1135
|
+
|
|
1136
|
+
**Network Impact (NEW)**:
|
|
1137
|
+
- neighbor_count: Number of neighbors
|
|
1138
|
+
- phase_coherence_neighbors: Phase alignment with neighbors [0,1]
|
|
1139
|
+
- network_coupled: True if neighbors exist and phase_coherence > 0.5
|
|
1140
|
+
- theta_final: Final phase value
|
|
1141
|
+
|
|
1142
|
+
**Overall Health (NEW)**:
|
|
1143
|
+
- expansion_healthy: Combined indicator of all health metrics
|
|
1144
|
+
|
|
1145
|
+
Notes
|
|
1146
|
+
-----
|
|
1147
|
+
Key indicators:
|
|
1148
|
+
- bifurcation_risk: True when |∂²EPI/∂t²| > threshold
|
|
1149
|
+
- fractal_preserved: True when growth rates maintain scaling relationship
|
|
1150
|
+
- coherence_preserved: True when local C(t) remains above threshold
|
|
1151
|
+
- dnfr_positive: True when ΔNFR > 0 (required for expansion)
|
|
1152
|
+
|
|
1153
|
+
Thresholds are configurable via graph metadata:
|
|
1154
|
+
- VAL_BIFURCATION_THRESHOLD (default: 0.3)
|
|
1155
|
+
- VAL_MIN_COHERENCE (default: 0.5)
|
|
1156
|
+
- VAL_FRACTAL_RATIO_MIN (default: 0.5)
|
|
1157
|
+
- VAL_FRACTAL_RATIO_MAX (default: 2.0)
|
|
1158
|
+
|
|
1159
|
+
Examples
|
|
1160
|
+
--------
|
|
1161
|
+
>>> from tnfr.structural import create_nfr, run_sequence
|
|
1162
|
+
>>> from tnfr.operators.definitions import Expansion
|
|
1163
|
+
>>>
|
|
1164
|
+
>>> G, node = create_nfr("test", epi=0.4, vf=1.0)
|
|
1165
|
+
>>> G.graph["COLLECT_OPERATOR_METRICS"] = True
|
|
1166
|
+
>>> run_sequence(G, node, [Expansion()])
|
|
1167
|
+
>>>
|
|
1168
|
+
>>> metrics = G.graph["operator_metrics"][-1]
|
|
1169
|
+
>>> if metrics["bifurcation_risk"]:
|
|
1170
|
+
... print(f"WARNING: Bifurcation risk! d2epi={metrics['d2epi']:.3f}")
|
|
1171
|
+
>>> if not metrics["coherence_preserved"]:
|
|
1172
|
+
... print(f"WARNING: Coherence degraded! C={metrics['coherence_local']:.3f}")
|
|
1173
|
+
|
|
1174
|
+
See Also
|
|
1175
|
+
--------
|
|
1176
|
+
Expansion : VAL operator that produces these metrics
|
|
1177
|
+
validate_expansion : Preconditions ensuring valid expansion
|
|
1178
|
+
"""
|
|
1179
|
+
import math
|
|
1180
|
+
|
|
1181
|
+
# Basic state
|
|
1182
|
+
vf_after = _get_node_attr(G, node, ALIAS_VF)
|
|
1183
|
+
epi_after = _get_node_attr(G, node, ALIAS_EPI)
|
|
1184
|
+
dnfr = _get_node_attr(G, node, ALIAS_DNFR)
|
|
1185
|
+
d2epi = _get_node_attr(G, node, ALIAS_D2EPI)
|
|
1186
|
+
theta = _get_node_attr(G, node, ALIAS_THETA)
|
|
1187
|
+
|
|
1188
|
+
# Network context
|
|
1189
|
+
neighbors = list(G.neighbors(node))
|
|
1190
|
+
neighbor_count = len(neighbors)
|
|
1191
|
+
|
|
1192
|
+
# Thresholds (configurable)
|
|
1193
|
+
bifurcation_threshold = float(G.graph.get("VAL_BIFURCATION_THRESHOLD", 0.3))
|
|
1194
|
+
coherence_threshold = float(G.graph.get("VAL_MIN_COHERENCE", 0.5))
|
|
1195
|
+
fractal_ratio_min = float(G.graph.get("VAL_FRACTAL_RATIO_MIN", 0.5))
|
|
1196
|
+
fractal_ratio_max = float(G.graph.get("VAL_FRACTAL_RATIO_MAX", 2.0))
|
|
1197
|
+
|
|
1198
|
+
# Growth deltas
|
|
1199
|
+
delta_epi = epi_after - epi_before
|
|
1200
|
+
delta_vf = vf_after - vf_before
|
|
1201
|
+
|
|
1202
|
+
# Growth rates (relative to initial values)
|
|
1203
|
+
epi_growth_rate = (delta_epi / epi_before) if epi_before > 1e-9 else 0.0
|
|
1204
|
+
vf_growth_rate = (delta_vf / vf_before) if vf_before > 1e-9 else 0.0
|
|
1205
|
+
growth_ratio = (
|
|
1206
|
+
vf_growth_rate / epi_growth_rate if abs(epi_growth_rate) > 1e-9 else 0.0
|
|
1207
|
+
)
|
|
1208
|
+
|
|
1209
|
+
# Coherence preservation
|
|
1210
|
+
try:
|
|
1211
|
+
from ..metrics.coherence import compute_local_coherence
|
|
1212
|
+
|
|
1213
|
+
c_local = compute_local_coherence(G, node)
|
|
1214
|
+
except (ImportError, AttributeError):
|
|
1215
|
+
# Fallback if coherence module not available
|
|
1216
|
+
c_local = 0.0
|
|
1217
|
+
|
|
1218
|
+
# Phase coherence with neighbors
|
|
1219
|
+
if neighbor_count > 0:
|
|
1220
|
+
neighbor_theta_sum = sum(_get_node_attr(G, n, ALIAS_THETA) for n in neighbors)
|
|
1221
|
+
mean_neighbor_theta = neighbor_theta_sum / neighbor_count
|
|
1222
|
+
phase_diff = abs(theta - mean_neighbor_theta)
|
|
1223
|
+
# Normalize to [0, 1], 1 = perfect alignment
|
|
1224
|
+
phase_coherence_neighbors = 1.0 - min(phase_diff, math.pi) / math.pi
|
|
1225
|
+
else:
|
|
1226
|
+
phase_coherence_neighbors = 0.0
|
|
1227
|
+
|
|
1228
|
+
# Bifurcation magnitude (ratio to threshold)
|
|
1229
|
+
bifurcation_magnitude = (
|
|
1230
|
+
abs(d2epi) / bifurcation_threshold if bifurcation_threshold > 0 else 0.0
|
|
1231
|
+
)
|
|
1232
|
+
|
|
1233
|
+
# Boolean indicators
|
|
1234
|
+
bifurcation_risk = abs(d2epi) > bifurcation_threshold
|
|
1235
|
+
coherence_preserved = c_local > coherence_threshold
|
|
1236
|
+
dnfr_positive = dnfr > 0
|
|
1237
|
+
dnfr_stable = 0 < dnfr < 1.0
|
|
1238
|
+
fractal_preserved = (
|
|
1239
|
+
fractal_ratio_min < growth_ratio < fractal_ratio_max
|
|
1240
|
+
if abs(epi_growth_rate) > 1e-9
|
|
1241
|
+
else True
|
|
1242
|
+
)
|
|
1243
|
+
network_coupled = neighbor_count > 0 and phase_coherence_neighbors > 0.5
|
|
1244
|
+
|
|
1245
|
+
# Overall health indicator
|
|
1246
|
+
expansion_healthy = (
|
|
1247
|
+
dnfr_positive
|
|
1248
|
+
and not bifurcation_risk
|
|
1249
|
+
and coherence_preserved
|
|
1250
|
+
and fractal_preserved
|
|
1251
|
+
)
|
|
1252
|
+
|
|
1253
|
+
return {
|
|
1254
|
+
# Core identification
|
|
1255
|
+
"operator": "Expansion",
|
|
1256
|
+
"glyph": "VAL",
|
|
1257
|
+
# Existing basic metrics
|
|
1258
|
+
"vf_increase": delta_vf,
|
|
1259
|
+
"vf_final": vf_after,
|
|
1260
|
+
"delta_epi": delta_epi,
|
|
1261
|
+
"epi_final": epi_after,
|
|
1262
|
+
"expansion_factor": vf_after / vf_before if vf_before > 1e-9 else 1.0,
|
|
1263
|
+
# NEW: Structural stability
|
|
1264
|
+
"dnfr_final": dnfr,
|
|
1265
|
+
"dnfr_positive": dnfr_positive,
|
|
1266
|
+
"dnfr_stable": dnfr_stable,
|
|
1267
|
+
# NEW: Bifurcation risk (enhanced)
|
|
1268
|
+
"d2epi": d2epi,
|
|
1269
|
+
"bifurcation_risk": bifurcation_risk,
|
|
1270
|
+
"bifurcation_magnitude": bifurcation_magnitude,
|
|
1271
|
+
"bifurcation_threshold": bifurcation_threshold,
|
|
1272
|
+
# NEW: Coherence preservation
|
|
1273
|
+
"coherence_local": c_local,
|
|
1274
|
+
"coherence_preserved": coherence_preserved,
|
|
1275
|
+
# NEW: Fractality indicators
|
|
1276
|
+
"epi_growth_rate": epi_growth_rate,
|
|
1277
|
+
"vf_growth_rate": vf_growth_rate,
|
|
1278
|
+
"growth_ratio": growth_ratio,
|
|
1279
|
+
"fractal_preserved": fractal_preserved,
|
|
1280
|
+
# NEW: Network impact
|
|
1281
|
+
"neighbor_count": neighbor_count,
|
|
1282
|
+
"phase_coherence_neighbors": max(0.0, phase_coherence_neighbors),
|
|
1283
|
+
"network_coupled": network_coupled,
|
|
1284
|
+
"theta_final": theta,
|
|
1285
|
+
# NEW: Overall health
|
|
1286
|
+
"expansion_healthy": expansion_healthy,
|
|
1287
|
+
# Metadata
|
|
1288
|
+
"metrics_version": "3.0_canonical",
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
|
|
1292
|
+
def contraction_metrics(
|
|
1293
|
+
G: TNFRGraph, node: NodeId, vf_before: float, epi_before: float
|
|
1294
|
+
) -> dict[str, Any]:
|
|
1295
|
+
"""NUL - Contraction metrics: νf decrease, core concentration, ΔNFR densification.
|
|
1296
|
+
|
|
1297
|
+
Collects comprehensive contraction metrics including structural density dynamics
|
|
1298
|
+
that validate canonical NUL behavior and enable early warning for over-compression.
|
|
1299
|
+
|
|
1300
|
+
Parameters
|
|
1301
|
+
----------
|
|
1302
|
+
G : TNFRGraph
|
|
1303
|
+
Graph containing the node
|
|
1304
|
+
node : NodeId
|
|
1305
|
+
Node to collect metrics from
|
|
1306
|
+
vf_before : float
|
|
1307
|
+
νf value before operator application
|
|
1308
|
+
epi_before : float
|
|
1309
|
+
EPI value before operator application
|
|
1310
|
+
|
|
1311
|
+
Returns
|
|
1312
|
+
-------
|
|
1313
|
+
dict
|
|
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
|
|
1356
|
+
"""
|
|
1357
|
+
# Small epsilon for numerical stability
|
|
1358
|
+
EPSILON = 1e-9
|
|
1359
|
+
|
|
1360
|
+
vf_after = _get_node_attr(G, node, ALIAS_VF)
|
|
1361
|
+
epi_after = _get_node_attr(G, node, ALIAS_EPI)
|
|
1362
|
+
dnfr_after = _get_node_attr(G, node, ALIAS_DNFR)
|
|
1363
|
+
|
|
1364
|
+
# Extract densification telemetry if available
|
|
1365
|
+
densification_log = G.graph.get("nul_densification_log", [])
|
|
1366
|
+
densification_factor = None
|
|
1367
|
+
dnfr_before = None
|
|
1368
|
+
if densification_log:
|
|
1369
|
+
# Get the most recent densification entry for this node
|
|
1370
|
+
last_entry = densification_log[-1]
|
|
1371
|
+
densification_factor = last_entry.get("densification_factor")
|
|
1372
|
+
dnfr_before = last_entry.get("dnfr_before")
|
|
1373
|
+
|
|
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
|
+
|
|
1386
|
+
metrics = {
|
|
1387
|
+
"operator": "Contraction",
|
|
1388
|
+
"glyph": "NUL",
|
|
1389
|
+
"vf_decrease": vf_before - vf_after,
|
|
1390
|
+
"vf_final": vf_after,
|
|
1391
|
+
"delta_epi": epi_after - epi_before,
|
|
1392
|
+
"epi_final": epi_after,
|
|
1393
|
+
"dnfr_final": dnfr_after,
|
|
1394
|
+
"contraction_factor": vf_after / vf_before if vf_before > 0 else 1.0,
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
# Add densification metrics if available
|
|
1398
|
+
if densification_factor is not None:
|
|
1399
|
+
metrics["densification_factor"] = densification_factor
|
|
1400
|
+
metrics["dnfr_densified"] = True
|
|
1401
|
+
if dnfr_before is not None:
|
|
1402
|
+
metrics["dnfr_before"] = dnfr_before
|
|
1403
|
+
metrics["dnfr_increase"] = dnfr_after - dnfr_before if dnfr_before else 0.0
|
|
1404
|
+
|
|
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
|
+
|
|
1411
|
+
return metrics
|
|
1412
|
+
|
|
1413
|
+
|
|
1414
|
+
def self_organization_metrics(
|
|
1415
|
+
G: TNFRGraph, node: NodeId, epi_before: float, vf_before: float
|
|
1416
|
+
) -> dict[str, Any]:
|
|
1417
|
+
"""THOL - Enhanced metrics with cascade dynamics and collective coherence.
|
|
1418
|
+
|
|
1419
|
+
Collects comprehensive THOL metrics including bifurcation, cascade propagation,
|
|
1420
|
+
collective coherence of sub-EPIs, and metabolic activity indicators.
|
|
1421
|
+
|
|
1422
|
+
Parameters
|
|
1423
|
+
----------
|
|
1424
|
+
G : TNFRGraph
|
|
1425
|
+
Graph containing the node
|
|
1426
|
+
node : NodeId
|
|
1427
|
+
Node to collect metrics from
|
|
1428
|
+
epi_before : float
|
|
1429
|
+
EPI value before operator application
|
|
1430
|
+
vf_before : float
|
|
1431
|
+
νf value before operator application
|
|
1432
|
+
|
|
1433
|
+
Returns
|
|
1434
|
+
-------
|
|
1435
|
+
dict
|
|
1436
|
+
Self-organization-specific metrics including:
|
|
1437
|
+
|
|
1438
|
+
**Base operator metrics:**
|
|
1439
|
+
|
|
1440
|
+
- operator: "Self-organization"
|
|
1441
|
+
- glyph: "THOL"
|
|
1442
|
+
- delta_epi: Change in EPI
|
|
1443
|
+
- delta_vf: Change in νf
|
|
1444
|
+
- epi_final: Final EPI value
|
|
1445
|
+
- vf_final: Final νf value
|
|
1446
|
+
- d2epi: Structural acceleration
|
|
1447
|
+
- dnfr_final: Final ΔNFR
|
|
1448
|
+
|
|
1449
|
+
**Bifurcation metrics:**
|
|
1450
|
+
|
|
1451
|
+
- bifurcation_occurred: Boolean indicator
|
|
1452
|
+
- nested_epi_count: Number of sub-EPIs created
|
|
1453
|
+
- d2epi_magnitude: Absolute acceleration
|
|
1454
|
+
|
|
1455
|
+
**Cascade dynamics (NEW):**
|
|
1456
|
+
|
|
1457
|
+
- cascade_depth: Maximum hierarchical bifurcation depth
|
|
1458
|
+
- propagation_radius: Total unique nodes affected
|
|
1459
|
+
- cascade_detected: Boolean cascade indicator
|
|
1460
|
+
- affected_node_count: Nodes reached by cascade
|
|
1461
|
+
- total_propagations: Total propagation events
|
|
1462
|
+
|
|
1463
|
+
**Collective coherence (NEW):**
|
|
1464
|
+
|
|
1465
|
+
- subepi_coherence: Coherence of sub-EPI ensemble [0,1]
|
|
1466
|
+
- metabolic_activity_index: Network context usage [0,1]
|
|
1467
|
+
|
|
1468
|
+
**Network emergence indicator (NEW):**
|
|
1469
|
+
|
|
1470
|
+
- network_emergence: Combined indicator (cascade + high coherence)
|
|
1471
|
+
|
|
1472
|
+
Notes
|
|
1473
|
+
-----
|
|
1474
|
+
TNFR Principle: Complete traceability of self-organization dynamics.
|
|
1475
|
+
These metrics enable reconstruction of entire cascade evolution,
|
|
1476
|
+
validation of controlled emergence, and identification of collective
|
|
1477
|
+
network phenomena.
|
|
1478
|
+
|
|
1479
|
+
See Also
|
|
1480
|
+
--------
|
|
1481
|
+
operators.metabolism.compute_cascade_depth : Cascade depth computation
|
|
1482
|
+
operators.metabolism.compute_subepi_collective_coherence : Coherence metric
|
|
1483
|
+
operators.metabolism.compute_metabolic_activity_index : Metabolic tracking
|
|
1484
|
+
operators.cascade.detect_cascade : Cascade detection
|
|
1485
|
+
"""
|
|
1486
|
+
from .cascade import detect_cascade
|
|
1487
|
+
from .metabolism import (
|
|
1488
|
+
compute_cascade_depth,
|
|
1489
|
+
compute_propagation_radius,
|
|
1490
|
+
compute_subepi_collective_coherence,
|
|
1491
|
+
compute_metabolic_activity_index,
|
|
1492
|
+
)
|
|
1493
|
+
|
|
1494
|
+
epi_after = _get_node_attr(G, node, ALIAS_EPI)
|
|
1495
|
+
vf_after = _get_node_attr(G, node, ALIAS_VF)
|
|
1496
|
+
d2epi = _get_node_attr(G, node, ALIAS_D2EPI)
|
|
1497
|
+
dnfr = _get_node_attr(G, node, ALIAS_DNFR)
|
|
1498
|
+
|
|
1499
|
+
# Track nested EPI count from node attribute or graph (backward compatibility)
|
|
1500
|
+
nested_epi_count = len(G.nodes[node].get("sub_epis", []))
|
|
1501
|
+
if nested_epi_count == 0:
|
|
1502
|
+
# Fallback to old location for backward compatibility
|
|
1503
|
+
nested_epi_count = len(G.graph.get("sub_epi", []))
|
|
1504
|
+
|
|
1505
|
+
# Cascade and propagation analysis
|
|
1506
|
+
cascade_analysis = detect_cascade(G)
|
|
1507
|
+
|
|
1508
|
+
# NEW: Enhanced cascade and emergence metrics
|
|
1509
|
+
cascade_depth = compute_cascade_depth(G, node)
|
|
1510
|
+
propagation_radius = compute_propagation_radius(G)
|
|
1511
|
+
subepi_coherence = compute_subepi_collective_coherence(G, node)
|
|
1512
|
+
metabolic_activity = compute_metabolic_activity_index(G, node)
|
|
1513
|
+
|
|
1514
|
+
return {
|
|
1515
|
+
# Base operator metrics
|
|
1516
|
+
"operator": "Self-organization",
|
|
1517
|
+
"glyph": "THOL",
|
|
1518
|
+
"delta_epi": epi_after - epi_before,
|
|
1519
|
+
"delta_vf": vf_after - vf_before,
|
|
1520
|
+
"epi_final": epi_after,
|
|
1521
|
+
"vf_final": vf_after,
|
|
1522
|
+
"d2epi": d2epi,
|
|
1523
|
+
"dnfr_final": dnfr,
|
|
1524
|
+
# Bifurcation metrics
|
|
1525
|
+
"bifurcation_occurred": nested_epi_count > 0,
|
|
1526
|
+
"nested_epi_count": nested_epi_count,
|
|
1527
|
+
"d2epi_magnitude": abs(d2epi),
|
|
1528
|
+
# NEW: Cascade dynamics
|
|
1529
|
+
"cascade_depth": cascade_depth,
|
|
1530
|
+
"propagation_radius": propagation_radius,
|
|
1531
|
+
"cascade_detected": cascade_analysis["is_cascade"],
|
|
1532
|
+
"affected_node_count": len(cascade_analysis["affected_nodes"]),
|
|
1533
|
+
"total_propagations": cascade_analysis["total_propagations"],
|
|
1534
|
+
# NEW: Collective coherence
|
|
1535
|
+
"subepi_coherence": subepi_coherence,
|
|
1536
|
+
"metabolic_activity_index": metabolic_activity,
|
|
1537
|
+
# NEW: Network emergence indicator
|
|
1538
|
+
"network_emergence": (
|
|
1539
|
+
cascade_analysis["is_cascade"] and subepi_coherence > 0.5
|
|
1540
|
+
),
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
|
|
1544
|
+
def mutation_metrics(
|
|
1545
|
+
G: TNFRGraph,
|
|
1546
|
+
node: NodeId,
|
|
1547
|
+
theta_before: float,
|
|
1548
|
+
epi_before: float,
|
|
1549
|
+
vf_before: float | None = None,
|
|
1550
|
+
dnfr_before: float | None = None,
|
|
1551
|
+
) -> dict[str, Any]:
|
|
1552
|
+
"""ZHIR - Comprehensive mutation metrics with canonical structural indicators.
|
|
1553
|
+
|
|
1554
|
+
Collects extended metrics reflecting canonical ZHIR effects:
|
|
1555
|
+
- Threshold verification (∂EPI/∂t > ξ)
|
|
1556
|
+
- Phase transformation quality (θ → θ')
|
|
1557
|
+
- Bifurcation potential (∂²EPI/∂t² > τ)
|
|
1558
|
+
- Structural identity preservation
|
|
1559
|
+
- Network impact and propagation
|
|
1560
|
+
- Destabilizer context (R4 Extended)
|
|
1561
|
+
- Grammar validation status
|
|
1562
|
+
|
|
1563
|
+
Parameters
|
|
1564
|
+
----------
|
|
1565
|
+
G : TNFRGraph
|
|
1566
|
+
Graph containing the node
|
|
1567
|
+
node : NodeId
|
|
1568
|
+
Node to collect metrics from
|
|
1569
|
+
theta_before : float
|
|
1570
|
+
Phase value before operator application
|
|
1571
|
+
epi_before : float
|
|
1572
|
+
EPI value before operator application
|
|
1573
|
+
vf_before : float, optional
|
|
1574
|
+
νf before mutation (for frequency shift tracking)
|
|
1575
|
+
dnfr_before : float, optional
|
|
1576
|
+
ΔNFR before mutation (for pressure tracking)
|
|
1577
|
+
|
|
1578
|
+
Returns
|
|
1579
|
+
-------
|
|
1580
|
+
dict
|
|
1581
|
+
Comprehensive mutation metrics organized by category:
|
|
1582
|
+
|
|
1583
|
+
**Core metrics (existing):**
|
|
1584
|
+
|
|
1585
|
+
- operator, glyph: Identification
|
|
1586
|
+
- theta_shift, theta_final: Phase changes
|
|
1587
|
+
- delta_epi, epi_final: EPI changes
|
|
1588
|
+
- phase_change: Boolean indicator
|
|
1589
|
+
|
|
1590
|
+
**Threshold verification (ENHANCED):**
|
|
1591
|
+
|
|
1592
|
+
- depi_dt: Structural velocity (∂EPI/∂t)
|
|
1593
|
+
- threshold_xi: Configured threshold
|
|
1594
|
+
- threshold_met: Boolean (∂EPI/∂t > ξ)
|
|
1595
|
+
- threshold_ratio: depi_dt / ξ
|
|
1596
|
+
- threshold_exceeded_by: max(0, depi_dt - ξ)
|
|
1597
|
+
|
|
1598
|
+
**Phase transformation (ENHANCED):**
|
|
1599
|
+
|
|
1600
|
+
- theta_regime_before: Initial phase regime [0-3]
|
|
1601
|
+
- theta_regime_after: Final phase regime [0-3]
|
|
1602
|
+
- regime_changed: Boolean regime transition
|
|
1603
|
+
- theta_shift_direction: +1 (forward) or -1 (backward)
|
|
1604
|
+
- phase_transformation_magnitude: Normalized shift [0, 1]
|
|
1605
|
+
|
|
1606
|
+
**Bifurcation analysis (NEW):**
|
|
1607
|
+
|
|
1608
|
+
- d2epi: Structural acceleration
|
|
1609
|
+
- bifurcation_threshold_tau: Configured τ
|
|
1610
|
+
- bifurcation_potential: Boolean (∂²EPI/∂t² > τ)
|
|
1611
|
+
- bifurcation_score: Quantitative potential [0, 1]
|
|
1612
|
+
- bifurcation_triggered: Boolean (event recorded)
|
|
1613
|
+
- bifurcation_event_count: Number of bifurcation events
|
|
1614
|
+
|
|
1615
|
+
**Structural preservation (NEW):**
|
|
1616
|
+
|
|
1617
|
+
- epi_kind_before: Identity before mutation
|
|
1618
|
+
- epi_kind_after: Identity after mutation
|
|
1619
|
+
- identity_preserved: Boolean (must be True)
|
|
1620
|
+
- delta_vf: Change in structural frequency
|
|
1621
|
+
- vf_final: Final νf
|
|
1622
|
+
- delta_dnfr: Change in reorganization pressure
|
|
1623
|
+
- dnfr_final: Final ΔNFR
|
|
1624
|
+
|
|
1625
|
+
**Network impact (NEW):**
|
|
1626
|
+
|
|
1627
|
+
- neighbor_count: Number of neighbors
|
|
1628
|
+
- impacted_neighbors: Count with phase shift detected
|
|
1629
|
+
- network_impact_radius: Ratio of impacted neighbors
|
|
1630
|
+
- phase_coherence_neighbors: Phase alignment after mutation
|
|
1631
|
+
|
|
1632
|
+
**Destabilizer context (NEW - R4 Extended):**
|
|
1633
|
+
|
|
1634
|
+
- destabilizer_type: "strong"/"moderate"/"weak"/None
|
|
1635
|
+
- destabilizer_operator: Glyph that enabled mutation
|
|
1636
|
+
- destabilizer_distance: Operators since destabilizer
|
|
1637
|
+
- recent_history: Last 4 operators
|
|
1638
|
+
|
|
1639
|
+
**Grammar validation (NEW):**
|
|
1640
|
+
|
|
1641
|
+
- grammar_u4b_satisfied: Boolean (IL precedence + destabilizer)
|
|
1642
|
+
- il_precedence_found: Boolean (IL in history)
|
|
1643
|
+
- destabilizer_recent: Boolean (within window)
|
|
1644
|
+
|
|
1645
|
+
Examples
|
|
1646
|
+
--------
|
|
1647
|
+
>>> from tnfr.structural import create_nfr, run_sequence
|
|
1648
|
+
>>> from tnfr.operators.definitions import Coherence, Dissonance, Mutation
|
|
1649
|
+
>>>
|
|
1650
|
+
>>> G, node = create_nfr("test", epi=0.5, vf=1.2)
|
|
1651
|
+
>>> G.graph["COLLECT_OPERATOR_METRICS"] = True
|
|
1652
|
+
>>>
|
|
1653
|
+
>>> # Apply canonical sequence (IL → OZ → ZHIR)
|
|
1654
|
+
>>> run_sequence(G, node, [Coherence(), Dissonance(), Mutation()])
|
|
1655
|
+
>>>
|
|
1656
|
+
>>> # Retrieve comprehensive metrics
|
|
1657
|
+
>>> metrics = G.graph["operator_metrics"][-1]
|
|
1658
|
+
>>> print(f"Threshold met: {metrics['threshold_met']}")
|
|
1659
|
+
>>> print(f"Bifurcation score: {metrics['bifurcation_score']:.2f}")
|
|
1660
|
+
>>> print(f"Identity preserved: {metrics['identity_preserved']}")
|
|
1661
|
+
>>> print(f"Grammar satisfied: {metrics['grammar_u4b_satisfied']}")
|
|
1662
|
+
|
|
1663
|
+
See Also
|
|
1664
|
+
--------
|
|
1665
|
+
operators.definitions.Mutation : ZHIR operator implementation
|
|
1666
|
+
dynamics.bifurcation.compute_bifurcation_score : Bifurcation scoring
|
|
1667
|
+
operators.preconditions.validate_mutation : Precondition validation with context tracking
|
|
1668
|
+
"""
|
|
1669
|
+
import math
|
|
1670
|
+
|
|
1671
|
+
# === GET POST-MUTATION STATE ===
|
|
1672
|
+
theta_after = _get_node_attr(G, node, ALIAS_THETA)
|
|
1673
|
+
epi_after = _get_node_attr(G, node, ALIAS_EPI)
|
|
1674
|
+
vf_after = _get_node_attr(G, node, ALIAS_VF)
|
|
1675
|
+
dnfr_after = _get_node_attr(G, node, ALIAS_DNFR)
|
|
1676
|
+
d2epi = _get_node_attr(G, node, ALIAS_D2EPI, 0.0)
|
|
1677
|
+
|
|
1678
|
+
# === THRESHOLD VERIFICATION ===
|
|
1679
|
+
# Compute ∂EPI/∂t from history
|
|
1680
|
+
epi_history = G.nodes[node].get("epi_history") or G.nodes[node].get(
|
|
1681
|
+
"_epi_history", []
|
|
1682
|
+
)
|
|
1683
|
+
if len(epi_history) >= 2:
|
|
1684
|
+
depi_dt = abs(epi_history[-1] - epi_history[-2])
|
|
1685
|
+
else:
|
|
1686
|
+
depi_dt = 0.0
|
|
1687
|
+
|
|
1688
|
+
xi = float(G.graph.get("ZHIR_THRESHOLD_XI", 0.1))
|
|
1689
|
+
threshold_met = depi_dt >= xi
|
|
1690
|
+
threshold_ratio = depi_dt / xi if xi > 0 else 0.0
|
|
1691
|
+
|
|
1692
|
+
# === PHASE TRANSFORMATION ===
|
|
1693
|
+
# Extract transformation telemetry from glyph storage
|
|
1694
|
+
theta_shift_stored = G.nodes[node].get("_zhir_theta_shift", None)
|
|
1695
|
+
regime_changed = G.nodes[node].get("_zhir_regime_changed", False)
|
|
1696
|
+
regime_before_stored = G.nodes[node].get("_zhir_regime_before", None)
|
|
1697
|
+
regime_after_stored = G.nodes[node].get("_zhir_regime_after", None)
|
|
1698
|
+
fixed_mode = G.nodes[node].get("_zhir_fixed_mode", False)
|
|
1699
|
+
|
|
1700
|
+
# Compute theta shift
|
|
1701
|
+
theta_shift = theta_after - theta_before
|
|
1702
|
+
theta_shift_magnitude = abs(theta_shift)
|
|
1703
|
+
|
|
1704
|
+
# Compute regimes if not stored
|
|
1705
|
+
regime_before = (
|
|
1706
|
+
regime_before_stored
|
|
1707
|
+
if regime_before_stored is not None
|
|
1708
|
+
else int(theta_before // (math.pi / 2))
|
|
1709
|
+
)
|
|
1710
|
+
regime_after = (
|
|
1711
|
+
regime_after_stored
|
|
1712
|
+
if regime_after_stored is not None
|
|
1713
|
+
else int(theta_after // (math.pi / 2))
|
|
1714
|
+
)
|
|
1715
|
+
|
|
1716
|
+
# Normalized phase transformation magnitude [0, 1]
|
|
1717
|
+
phase_transformation_magnitude = min(theta_shift_magnitude / math.pi, 1.0)
|
|
1718
|
+
|
|
1719
|
+
# === BIFURCATION ANALYSIS ===
|
|
1720
|
+
tau = float(
|
|
1721
|
+
G.graph.get(
|
|
1722
|
+
"BIFURCATION_THRESHOLD_TAU", G.graph.get("ZHIR_BIFURCATION_THRESHOLD", 0.5)
|
|
1723
|
+
)
|
|
1724
|
+
)
|
|
1725
|
+
bifurcation_potential = d2epi > tau
|
|
1726
|
+
|
|
1727
|
+
# Compute bifurcation score using canonical formula
|
|
1728
|
+
from ..dynamics.bifurcation import compute_bifurcation_score
|
|
1729
|
+
|
|
1730
|
+
bifurcation_score = compute_bifurcation_score(
|
|
1731
|
+
d2epi=d2epi, dnfr=dnfr_after, vf=vf_after, epi=epi_after, tau=tau
|
|
1732
|
+
)
|
|
1733
|
+
|
|
1734
|
+
# Check if bifurcation was triggered (event recorded)
|
|
1735
|
+
bifurcation_events = G.graph.get("zhir_bifurcation_events", [])
|
|
1736
|
+
bifurcation_triggered = len(bifurcation_events) > 0
|
|
1737
|
+
bifurcation_event_count = len(bifurcation_events)
|
|
1738
|
+
|
|
1739
|
+
# === STRUCTURAL PRESERVATION ===
|
|
1740
|
+
epi_kind_before = G.nodes[node].get("_epi_kind_before")
|
|
1741
|
+
epi_kind_after = G.nodes[node].get("epi_kind")
|
|
1742
|
+
identity_preserved = (
|
|
1743
|
+
epi_kind_before == epi_kind_after if epi_kind_before is not None else True
|
|
1744
|
+
)
|
|
1745
|
+
|
|
1746
|
+
# Track frequency and pressure changes
|
|
1747
|
+
delta_vf = vf_after - vf_before if vf_before is not None else 0.0
|
|
1748
|
+
delta_dnfr = dnfr_after - dnfr_before if dnfr_before is not None else 0.0
|
|
1749
|
+
|
|
1750
|
+
# === NETWORK IMPACT ===
|
|
1751
|
+
neighbors = list(G.neighbors(node))
|
|
1752
|
+
neighbor_count = len(neighbors)
|
|
1753
|
+
|
|
1754
|
+
# Count neighbors that experienced phase shifts
|
|
1755
|
+
# This is a simplified heuristic - we check if neighbors have recent phase changes
|
|
1756
|
+
impacted_neighbors = 0
|
|
1757
|
+
phase_impact_threshold = 0.1
|
|
1758
|
+
|
|
1759
|
+
if neighbor_count > 0:
|
|
1760
|
+
# Check neighbors for phase alignment/disruption
|
|
1761
|
+
for n in neighbors:
|
|
1762
|
+
neighbor_theta = _get_node_attr(G, n, ALIAS_THETA)
|
|
1763
|
+
# Simplified: check if neighbor is in similar phase regime after mutation
|
|
1764
|
+
phase_diff = abs(neighbor_theta - theta_after)
|
|
1765
|
+
# If phase diff is large, neighbor might be impacted
|
|
1766
|
+
if phase_diff > phase_impact_threshold:
|
|
1767
|
+
# Check if neighbor has changed recently (has history)
|
|
1768
|
+
neighbor_theta_history = G.nodes[n].get("theta_history", [])
|
|
1769
|
+
if len(neighbor_theta_history) >= 2:
|
|
1770
|
+
neighbor_change = abs(
|
|
1771
|
+
neighbor_theta_history[-1] - neighbor_theta_history[-2]
|
|
1772
|
+
)
|
|
1773
|
+
if neighbor_change > 0.05: # Neighbor experienced change
|
|
1774
|
+
impacted_neighbors += 1
|
|
1775
|
+
|
|
1776
|
+
# Phase coherence with neighbors after mutation
|
|
1777
|
+
from ..metrics.phase_coherence import compute_phase_alignment
|
|
1778
|
+
|
|
1779
|
+
phase_coherence = compute_phase_alignment(G, node, radius=1)
|
|
1780
|
+
else:
|
|
1781
|
+
phase_coherence = 0.0
|
|
1782
|
+
|
|
1783
|
+
# === DESTABILIZER CONTEXT (R4 Extended) ===
|
|
1784
|
+
mutation_context = G.nodes[node].get("_mutation_context", {})
|
|
1785
|
+
destabilizer_type = mutation_context.get("destabilizer_type")
|
|
1786
|
+
destabilizer_operator = mutation_context.get("destabilizer_operator")
|
|
1787
|
+
destabilizer_distance = mutation_context.get("destabilizer_distance")
|
|
1788
|
+
recent_history = mutation_context.get("recent_history", [])
|
|
1789
|
+
|
|
1790
|
+
# === GRAMMAR VALIDATION (U4b) ===
|
|
1791
|
+
# Check if U4b satisfied (IL precedence + recent destabilizer)
|
|
1792
|
+
glyph_history = G.nodes[node].get("glyph_history", [])
|
|
1793
|
+
|
|
1794
|
+
# Look for IL in history
|
|
1795
|
+
il_precedence_found = any("IL" in str(g) for g in glyph_history)
|
|
1796
|
+
|
|
1797
|
+
# Check if destabilizer is recent (within ~3 operators)
|
|
1798
|
+
destabilizer_recent = (
|
|
1799
|
+
destabilizer_distance is not None and destabilizer_distance <= 3
|
|
1800
|
+
)
|
|
1801
|
+
|
|
1802
|
+
grammar_u4b_satisfied = il_precedence_found and destabilizer_recent
|
|
1803
|
+
|
|
1804
|
+
# === RETURN COMPREHENSIVE METRICS ===
|
|
1805
|
+
return {
|
|
1806
|
+
# === CORE (existing) ===
|
|
1807
|
+
"operator": "Mutation",
|
|
1808
|
+
"glyph": "ZHIR",
|
|
1809
|
+
"theta_shift": theta_shift_magnitude,
|
|
1810
|
+
"theta_shift_signed": (
|
|
1811
|
+
theta_shift_stored if theta_shift_stored is not None else theta_shift
|
|
1812
|
+
),
|
|
1813
|
+
"theta_before": theta_before,
|
|
1814
|
+
"theta_after": theta_after,
|
|
1815
|
+
"theta_final": theta_after,
|
|
1816
|
+
"phase_change": theta_shift_magnitude > 0.5, # Configurable threshold
|
|
1817
|
+
"transformation_mode": "fixed" if fixed_mode else "canonical",
|
|
1818
|
+
# === THRESHOLD VERIFICATION (ENHANCED) ===
|
|
1819
|
+
"depi_dt": depi_dt,
|
|
1820
|
+
"threshold_xi": xi,
|
|
1821
|
+
"threshold_met": threshold_met,
|
|
1822
|
+
"threshold_ratio": threshold_ratio,
|
|
1823
|
+
"threshold_exceeded_by": max(0.0, depi_dt - xi),
|
|
1824
|
+
"threshold_warning": G.nodes[node].get("_zhir_threshold_warning", False),
|
|
1825
|
+
"threshold_validated": G.nodes[node].get("_zhir_threshold_met", False),
|
|
1826
|
+
"threshold_unknown": G.nodes[node].get("_zhir_threshold_unknown", False),
|
|
1827
|
+
# === PHASE TRANSFORMATION (ENHANCED) ===
|
|
1828
|
+
"theta_regime_before": regime_before,
|
|
1829
|
+
"theta_regime_after": regime_after,
|
|
1830
|
+
"regime_changed": regime_changed or (regime_before != regime_after),
|
|
1831
|
+
"theta_regime_change": regime_changed or (regime_before != regime_after), # Backwards compat
|
|
1832
|
+
"regime_before": regime_before, # Backwards compat
|
|
1833
|
+
"regime_after": regime_after, # Backwards compat
|
|
1834
|
+
"theta_shift_direction": math.copysign(1.0, theta_shift),
|
|
1835
|
+
"phase_transformation_magnitude": phase_transformation_magnitude,
|
|
1836
|
+
# === BIFURCATION ANALYSIS (NEW) ===
|
|
1837
|
+
"d2epi": d2epi,
|
|
1838
|
+
"bifurcation_threshold_tau": tau,
|
|
1839
|
+
"bifurcation_potential": bifurcation_potential,
|
|
1840
|
+
"bifurcation_score": bifurcation_score,
|
|
1841
|
+
"bifurcation_triggered": bifurcation_triggered,
|
|
1842
|
+
"bifurcation_event_count": bifurcation_event_count,
|
|
1843
|
+
# === EPI METRICS ===
|
|
1844
|
+
"delta_epi": epi_after - epi_before,
|
|
1845
|
+
"epi_before": epi_before,
|
|
1846
|
+
"epi_after": epi_after,
|
|
1847
|
+
"epi_final": epi_after,
|
|
1848
|
+
# === STRUCTURAL PRESERVATION (NEW) ===
|
|
1849
|
+
"epi_kind_before": epi_kind_before,
|
|
1850
|
+
"epi_kind_after": epi_kind_after,
|
|
1851
|
+
"identity_preserved": identity_preserved,
|
|
1852
|
+
"delta_vf": delta_vf,
|
|
1853
|
+
"vf_before": vf_before if vf_before is not None else vf_after,
|
|
1854
|
+
"vf_final": vf_after,
|
|
1855
|
+
"delta_dnfr": delta_dnfr,
|
|
1856
|
+
"dnfr_before": dnfr_before if dnfr_before is not None else dnfr_after,
|
|
1857
|
+
"dnfr_final": dnfr_after,
|
|
1858
|
+
# === NETWORK IMPACT (NEW) ===
|
|
1859
|
+
"neighbor_count": neighbor_count,
|
|
1860
|
+
"impacted_neighbors": impacted_neighbors,
|
|
1861
|
+
"network_impact_radius": (
|
|
1862
|
+
impacted_neighbors / neighbor_count if neighbor_count > 0 else 0.0
|
|
1863
|
+
),
|
|
1864
|
+
"phase_coherence_neighbors": phase_coherence,
|
|
1865
|
+
# === DESTABILIZER CONTEXT (NEW - R4 Extended) ===
|
|
1866
|
+
"destabilizer_type": destabilizer_type,
|
|
1867
|
+
"destabilizer_operator": destabilizer_operator,
|
|
1868
|
+
"destabilizer_distance": destabilizer_distance,
|
|
1869
|
+
"recent_history": recent_history,
|
|
1870
|
+
# === GRAMMAR VALIDATION (NEW) ===
|
|
1871
|
+
"grammar_u4b_satisfied": grammar_u4b_satisfied,
|
|
1872
|
+
"il_precedence_found": il_precedence_found,
|
|
1873
|
+
"destabilizer_recent": destabilizer_recent,
|
|
1874
|
+
# === METADATA ===
|
|
1875
|
+
"metrics_version": "2.0_canonical",
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
|
|
1879
|
+
def transition_metrics(
|
|
1880
|
+
G: TNFRGraph,
|
|
1881
|
+
node: NodeId,
|
|
1882
|
+
dnfr_before: float,
|
|
1883
|
+
vf_before: float,
|
|
1884
|
+
theta_before: float,
|
|
1885
|
+
epi_before: float | None = None,
|
|
1886
|
+
) -> dict[str, Any]:
|
|
1887
|
+
"""NAV - Transition metrics: regime classification, phase shift, frequency scaling.
|
|
1888
|
+
|
|
1889
|
+
Collects comprehensive transition metrics including regime origin/destination,
|
|
1890
|
+
phase shift magnitude (properly wrapped), transition type classification, and
|
|
1891
|
+
structural preservation ratios as specified in TNFR.pdf Table 2.3.
|
|
1892
|
+
|
|
1893
|
+
Parameters
|
|
1894
|
+
----------
|
|
1895
|
+
G : TNFRGraph
|
|
1896
|
+
Graph containing the node
|
|
1897
|
+
node : NodeId
|
|
1898
|
+
Node to collect metrics from
|
|
1899
|
+
dnfr_before : float
|
|
1900
|
+
ΔNFR value before operator application
|
|
1901
|
+
vf_before : float
|
|
1902
|
+
νf value before operator application
|
|
1903
|
+
theta_before : float
|
|
1904
|
+
Phase value before operator application
|
|
1905
|
+
epi_before : float, optional
|
|
1906
|
+
EPI value before operator application (for preservation tracking)
|
|
1907
|
+
|
|
1908
|
+
Returns
|
|
1909
|
+
-------
|
|
1910
|
+
dict
|
|
1911
|
+
Transition-specific metrics including:
|
|
1912
|
+
|
|
1913
|
+
**Core metrics (existing)**:
|
|
1914
|
+
|
|
1915
|
+
- operator: "Transition"
|
|
1916
|
+
- glyph: "NAV"
|
|
1917
|
+
- delta_theta: Signed phase change
|
|
1918
|
+
- delta_vf: Change in νf
|
|
1919
|
+
- delta_dnfr: Change in ΔNFR
|
|
1920
|
+
- dnfr_final: Final ΔNFR value
|
|
1921
|
+
- vf_final: Final νf value
|
|
1922
|
+
- theta_final: Final phase value
|
|
1923
|
+
- transition_complete: Boolean (|ΔNFR| < |νf|)
|
|
1924
|
+
|
|
1925
|
+
**Regime classification (NEW)**:
|
|
1926
|
+
|
|
1927
|
+
- regime_origin: "latent" | "active" | "resonant"
|
|
1928
|
+
- regime_destination: "latent" | "active" | "resonant"
|
|
1929
|
+
- transition_type: "reactivation" | "phase_shift" | "regime_change"
|
|
1930
|
+
|
|
1931
|
+
**Phase metrics (NEW)**:
|
|
1932
|
+
|
|
1933
|
+
- phase_shift_magnitude: Absolute phase change (radians, 0-π)
|
|
1934
|
+
- phase_shift_signed: Signed phase change (radians, wrapped to [-π, π])
|
|
1935
|
+
|
|
1936
|
+
**Structural scaling (NEW)**:
|
|
1937
|
+
|
|
1938
|
+
- vf_scaling_factor: vf_after / vf_before
|
|
1939
|
+
- dnfr_damping_ratio: dnfr_after / dnfr_before
|
|
1940
|
+
- epi_preservation: epi_after / epi_before (if epi_before provided)
|
|
1941
|
+
|
|
1942
|
+
**Latency tracking (NEW)**:
|
|
1943
|
+
|
|
1944
|
+
- latency_duration: Time in silence (seconds) if transitioning from SHA
|
|
1945
|
+
|
|
1946
|
+
Notes
|
|
1947
|
+
-----
|
|
1948
|
+
**Regime Classification**:
|
|
1949
|
+
|
|
1950
|
+
- **Latent**: latent flag set OR νf < 0.05
|
|
1951
|
+
- **Active**: Default operational state
|
|
1952
|
+
- **Resonant**: EPI > 0.5 AND νf > 0.8
|
|
1953
|
+
|
|
1954
|
+
**Transition Type**:
|
|
1955
|
+
|
|
1956
|
+
- **reactivation**: From latent state (SHA → NAV flow)
|
|
1957
|
+
- **phase_shift**: Significant phase change (|Δθ| > 0.3 rad)
|
|
1958
|
+
- **regime_change**: Regime switch without significant phase shift
|
|
1959
|
+
|
|
1960
|
+
**Phase Shift Wrapping**:
|
|
1961
|
+
|
|
1962
|
+
Phase shifts are properly wrapped to [-π, π] range to handle 0-2π boundary
|
|
1963
|
+
crossings correctly, ensuring accurate phase change measurement.
|
|
1964
|
+
|
|
1965
|
+
Examples
|
|
1966
|
+
--------
|
|
1967
|
+
>>> from tnfr.structural import create_nfr, run_sequence
|
|
1968
|
+
>>> from tnfr.operators.definitions import Silence, Transition
|
|
1969
|
+
>>>
|
|
1970
|
+
>>> # Example: SHA → NAV reactivation
|
|
1971
|
+
>>> G, node = create_nfr("test", epi=0.5, vf=0.8)
|
|
1972
|
+
>>> G.graph["COLLECT_OPERATOR_METRICS"] = True
|
|
1973
|
+
>>> run_sequence(G, node, [Silence(), Transition()])
|
|
1974
|
+
>>>
|
|
1975
|
+
>>> metrics = G.graph["operator_metrics"][-1]
|
|
1976
|
+
>>> assert metrics["operator"] == "Transition"
|
|
1977
|
+
>>> assert metrics["transition_type"] == "reactivation"
|
|
1978
|
+
>>> assert metrics["regime_origin"] == "latent"
|
|
1979
|
+
>>> assert metrics["latency_duration"] is not None
|
|
1980
|
+
|
|
1981
|
+
See Also
|
|
1982
|
+
--------
|
|
1983
|
+
operators.definitions.Transition : NAV operator implementation
|
|
1984
|
+
operators.definitions.Transition._detect_regime : Regime detection logic
|
|
1985
|
+
"""
|
|
1986
|
+
import math
|
|
1987
|
+
|
|
1988
|
+
# Get current state (after transformation)
|
|
1989
|
+
epi_after = _get_node_attr(G, node, ALIAS_EPI)
|
|
1990
|
+
dnfr_after = _get_node_attr(G, node, ALIAS_DNFR)
|
|
1991
|
+
vf_after = _get_node_attr(G, node, ALIAS_VF)
|
|
1992
|
+
theta_after = _get_node_attr(G, node, ALIAS_THETA)
|
|
1993
|
+
|
|
1994
|
+
# === REGIME CLASSIFICATION ===
|
|
1995
|
+
# Get regime origin from node attribute (stored by Transition operator before super().__call__)
|
|
1996
|
+
regime_origin = G.nodes[node].get("_regime_before", None)
|
|
1997
|
+
if regime_origin is None:
|
|
1998
|
+
# Fallback: detect regime from before state
|
|
1999
|
+
regime_origin = _detect_regime_from_state(
|
|
2000
|
+
epi_before or epi_after, vf_before, False # Cannot access latent flag from before
|
|
2001
|
+
)
|
|
2002
|
+
|
|
2003
|
+
# Detect destination regime
|
|
2004
|
+
regime_destination = _detect_regime_from_state(
|
|
2005
|
+
epi_after, vf_after, G.nodes[node].get("latent", False)
|
|
2006
|
+
)
|
|
2007
|
+
|
|
2008
|
+
# === TRANSITION TYPE CLASSIFICATION ===
|
|
2009
|
+
# Calculate phase shift (properly wrapped)
|
|
2010
|
+
phase_shift_raw = theta_after - theta_before
|
|
2011
|
+
if phase_shift_raw > math.pi:
|
|
2012
|
+
phase_shift_raw -= 2 * math.pi
|
|
2013
|
+
elif phase_shift_raw < -math.pi:
|
|
2014
|
+
phase_shift_raw += 2 * math.pi
|
|
2015
|
+
|
|
2016
|
+
# Classify transition type
|
|
2017
|
+
if regime_origin == "latent":
|
|
2018
|
+
transition_type = "reactivation"
|
|
2019
|
+
elif abs(phase_shift_raw) > 0.3:
|
|
2020
|
+
transition_type = "phase_shift"
|
|
2021
|
+
else:
|
|
2022
|
+
transition_type = "regime_change"
|
|
2023
|
+
|
|
2024
|
+
# === STRUCTURAL SCALING FACTORS ===
|
|
2025
|
+
vf_scaling = vf_after / vf_before if vf_before > 0 else 1.0
|
|
2026
|
+
dnfr_damping = dnfr_after / dnfr_before if abs(dnfr_before) > 1e-9 else 1.0
|
|
2027
|
+
|
|
2028
|
+
# === EPI PRESERVATION ===
|
|
2029
|
+
epi_preservation = None
|
|
2030
|
+
if epi_before is not None and epi_before > 0:
|
|
2031
|
+
epi_preservation = epi_after / epi_before
|
|
2032
|
+
|
|
2033
|
+
# === LATENCY DURATION ===
|
|
2034
|
+
# Get from node if transitioning from silence
|
|
2035
|
+
latency_duration = G.nodes[node].get("silence_duration", None)
|
|
2036
|
+
|
|
2037
|
+
return {
|
|
2038
|
+
# === CORE (existing, preserved) ===
|
|
2039
|
+
"operator": "Transition",
|
|
2040
|
+
"glyph": "NAV",
|
|
2041
|
+
"delta_theta": phase_shift_raw,
|
|
2042
|
+
"delta_vf": vf_after - vf_before,
|
|
2043
|
+
"delta_dnfr": dnfr_after - dnfr_before,
|
|
2044
|
+
"dnfr_final": dnfr_after,
|
|
2045
|
+
"vf_final": vf_after,
|
|
2046
|
+
"theta_final": theta_after,
|
|
2047
|
+
"transition_complete": abs(dnfr_after) < abs(vf_after),
|
|
2048
|
+
# Legacy compatibility
|
|
2049
|
+
"dnfr_change": abs(dnfr_after - dnfr_before),
|
|
2050
|
+
"vf_change": abs(vf_after - vf_before),
|
|
2051
|
+
"theta_shift": abs(phase_shift_raw),
|
|
2052
|
+
# === REGIME CLASSIFICATION (NEW) ===
|
|
2053
|
+
"regime_origin": regime_origin,
|
|
2054
|
+
"regime_destination": regime_destination,
|
|
2055
|
+
"transition_type": transition_type,
|
|
2056
|
+
# === PHASE METRICS (NEW) ===
|
|
2057
|
+
"phase_shift_magnitude": abs(phase_shift_raw),
|
|
2058
|
+
"phase_shift_signed": phase_shift_raw,
|
|
2059
|
+
# === STRUCTURAL SCALING (NEW) ===
|
|
2060
|
+
"vf_scaling_factor": vf_scaling,
|
|
2061
|
+
"dnfr_damping_ratio": dnfr_damping,
|
|
2062
|
+
"epi_preservation": epi_preservation,
|
|
2063
|
+
# === LATENCY TRACKING (NEW) ===
|
|
2064
|
+
"latency_duration": latency_duration,
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
|
|
2068
|
+
def _detect_regime_from_state(epi: float, vf: float, latent: bool) -> str:
|
|
2069
|
+
"""Detect structural regime from node state.
|
|
2070
|
+
|
|
2071
|
+
Helper function for transition_metrics to classify regime without
|
|
2072
|
+
accessing the Transition operator directly.
|
|
2073
|
+
|
|
2074
|
+
Parameters
|
|
2075
|
+
----------
|
|
2076
|
+
epi : float
|
|
2077
|
+
EPI value
|
|
2078
|
+
vf : float
|
|
2079
|
+
νf value
|
|
2080
|
+
latent : bool
|
|
2081
|
+
Latent flag
|
|
2082
|
+
|
|
2083
|
+
Returns
|
|
2084
|
+
-------
|
|
2085
|
+
str
|
|
2086
|
+
Regime classification: "latent", "active", or "resonant"
|
|
2087
|
+
|
|
2088
|
+
Notes
|
|
2089
|
+
-----
|
|
2090
|
+
Matches logic in Transition._detect_regime (definitions.py).
|
|
2091
|
+
"""
|
|
2092
|
+
if latent or vf < 0.05:
|
|
2093
|
+
return "latent"
|
|
2094
|
+
elif epi > 0.5 and vf > 0.8:
|
|
2095
|
+
return "resonant"
|
|
2096
|
+
else:
|
|
2097
|
+
return "active"
|
|
2098
|
+
|
|
2099
|
+
|
|
2100
|
+
def recursivity_metrics(
|
|
2101
|
+
G: TNFRGraph, node: NodeId, epi_before: float, vf_before: float
|
|
2102
|
+
) -> dict[str, Any]:
|
|
2103
|
+
"""REMESH - Recursivity metrics: fractal propagation, multi-scale coherence.
|
|
2104
|
+
|
|
2105
|
+
Parameters
|
|
2106
|
+
----------
|
|
2107
|
+
G : TNFRGraph
|
|
2108
|
+
Graph containing the node
|
|
2109
|
+
node : NodeId
|
|
2110
|
+
Node to collect metrics from
|
|
2111
|
+
epi_before : float
|
|
2112
|
+
EPI value before operator application
|
|
2113
|
+
vf_before : float
|
|
2114
|
+
νf value before operator application
|
|
2115
|
+
|
|
2116
|
+
Returns
|
|
2117
|
+
-------
|
|
2118
|
+
dict
|
|
2119
|
+
Recursivity-specific metrics including fractal pattern indicators
|
|
2120
|
+
"""
|
|
2121
|
+
epi_after = _get_node_attr(G, node, ALIAS_EPI)
|
|
2122
|
+
vf_after = _get_node_attr(G, node, ALIAS_VF)
|
|
2123
|
+
|
|
2124
|
+
# Track echo traces if graph maintains them
|
|
2125
|
+
echo_traces = G.graph.get("echo_trace", [])
|
|
2126
|
+
echo_count = len(echo_traces)
|
|
2127
|
+
|
|
2128
|
+
return {
|
|
2129
|
+
"operator": "Recursivity",
|
|
2130
|
+
"glyph": "REMESH",
|
|
2131
|
+
"delta_epi": epi_after - epi_before,
|
|
2132
|
+
"delta_vf": vf_after - vf_before,
|
|
2133
|
+
"epi_final": epi_after,
|
|
2134
|
+
"vf_final": vf_after,
|
|
2135
|
+
"echo_count": echo_count,
|
|
2136
|
+
"fractal_depth": echo_count,
|
|
2137
|
+
"multi_scale_active": echo_count > 0,
|
|
2138
|
+
}
|