tnfr 3.0.3__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 +375 -56
- 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 +723 -0
- 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 +171 -0
- tnfr/cache.pyi +13 -0
- tnfr/cli/__init__.py +110 -0
- tnfr/cli/__init__.pyi +26 -0
- tnfr/cli/arguments.py +489 -0
- tnfr/cli/arguments.pyi +29 -0
- tnfr/cli/execution.py +914 -0
- tnfr/cli/execution.pyi +70 -0
- tnfr/cli/interactive_validator.py +614 -0
- tnfr/cli/utils.py +51 -0
- 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/config/constants.py +104 -0
- tnfr/config/constants.pyi +12 -0
- tnfr/config/defaults.py +54 -0
- tnfr/config/defaults_core.py +212 -0
- 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 +92 -0
- tnfr/constants/__init__.pyi +92 -0
- tnfr/constants/aliases.py +33 -0
- tnfr/constants/aliases.pyi +27 -0
- tnfr/constants/init.py +33 -0
- tnfr/constants/init.pyi +12 -0
- tnfr/constants/metric.py +104 -0
- 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 +238 -0
- 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 +3034 -0
- 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 +661 -0
- 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 +36 -0
- 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 +223 -0
- 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 +262 -0
- tnfr/flatten.pyi +21 -0
- tnfr/gamma.py +354 -0
- tnfr/gamma.pyi +36 -0
- tnfr/glyph_history.py +377 -0
- tnfr/glyph_history.pyi +35 -0
- tnfr/glyph_runtime.py +19 -0
- tnfr/glyph_runtime.pyi +8 -0
- tnfr/immutable.py +218 -0
- tnfr/immutable.pyi +36 -0
- tnfr/initialization.py +203 -0
- tnfr/initialization.pyi +65 -0
- tnfr/io.py +10 -0
- tnfr/io.pyi +13 -0
- tnfr/locking.py +37 -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 +79 -0
- 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 +2009 -0
- tnfr/metrics/coherence.pyi +129 -0
- tnfr/metrics/common.py +158 -0
- tnfr/metrics/common.pyi +35 -0
- tnfr/metrics/core.py +316 -0
- tnfr/metrics/core.pyi +13 -0
- tnfr/metrics/diagnosis.py +833 -0
- tnfr/metrics/diagnosis.pyi +86 -0
- tnfr/metrics/emergence.py +245 -0
- tnfr/metrics/export.py +179 -0
- tnfr/metrics/export.pyi +7 -0
- tnfr/metrics/glyph_timing.py +379 -0
- 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 +183 -0
- tnfr/metrics/reporting.pyi +25 -0
- tnfr/metrics/sense_index.py +1203 -0
- tnfr/metrics/sense_index.pyi +9 -0
- tnfr/metrics/trig.py +373 -0
- tnfr/metrics/trig.pyi +13 -0
- tnfr/metrics/trig_cache.py +233 -0
- tnfr/metrics/trig_cache.pyi +10 -0
- tnfr/multiscale/__init__.py +32 -0
- tnfr/multiscale/hierarchical.py +517 -0
- tnfr/node.py +763 -0
- tnfr/node.pyi +139 -0
- tnfr/observers.py +255 -130
- tnfr/observers.pyi +31 -0
- tnfr/ontosim.py +144 -137
- tnfr/ontosim.pyi +28 -0
- tnfr/operators/__init__.py +1672 -0
- 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 +272 -0
- 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 +1809 -0
- 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 +178 -0
- 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 +247 -0
- tnfr/selector.pyi +19 -0
- tnfr/sense.py +378 -0
- 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 +705 -0
- 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 +58 -0
- 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 +543 -0
- 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 +775 -0
- 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/utils/callbacks.py +375 -0
- 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/utils/numeric.py +114 -0
- 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-8.5.0.dist-info/entry_points.txt +3 -0
- tnfr-3.0.3.dist-info/licenses/LICENSE.txt → tnfr-8.5.0.dist-info/licenses/LICENSE.md +1 -1
- tnfr/constants.py +0 -183
- tnfr/dynamics.py +0 -543
- tnfr/helpers.py +0 -198
- tnfr/main.py +0 -37
- tnfr/operators.py +0 -296
- tnfr-3.0.3.dist-info/METADATA +0 -35
- tnfr-3.0.3.dist-info/RECORD +0 -13
- {tnfr-3.0.3.dist-info → tnfr-8.5.0.dist-info}/WHEEL +0 -0
- {tnfr-3.0.3.dist-info → tnfr-8.5.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Network analysis utilities for TNFR structural operators.
|
|
2
|
+
|
|
3
|
+
This module provides network-level analysis tools for structural operators,
|
|
4
|
+
particularly for detecting and analyzing emission sources, phase compatibility,
|
|
5
|
+
and coherence flow patterns.
|
|
6
|
+
|
|
7
|
+
TNFR Context
|
|
8
|
+
------------
|
|
9
|
+
According to TNFR.pdf §2.2.1 (EN - Recepción estructural), Reception (EN) is
|
|
10
|
+
not passive absorption but active reorganization that requires:
|
|
11
|
+
|
|
12
|
+
- Detection of compatible emission sources in the network
|
|
13
|
+
- Phase compatibility validation (θᵢ ≈ θⱼ for coupling)
|
|
14
|
+
- Integration efficiency measurement (coherence received vs. integrated)
|
|
15
|
+
- Source traceability (which nodes contribute to EPI)
|
|
16
|
+
|
|
17
|
+
This module implements these capabilities for the Reception operator and other
|
|
18
|
+
operators that require network-level coherence analysis.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"detect_emission_sources",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
from .source_detection import detect_emission_sources
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"""Source detection for Reception (EN) operator.
|
|
2
|
+
|
|
3
|
+
This module implements emission source detection for the Reception operator,
|
|
4
|
+
enabling active reorganization through identification of compatible coherence
|
|
5
|
+
sources in the network.
|
|
6
|
+
|
|
7
|
+
TNFR Context
|
|
8
|
+
------------
|
|
9
|
+
According to TNFR.pdf §2.2.1, EN (Reception) requires:
|
|
10
|
+
|
|
11
|
+
1. **Source Detection**: Identify nodes emitting coherence (active EPI)
|
|
12
|
+
2. **Phase Compatibility**: Validate θᵢ ≈ θⱼ for effective coupling
|
|
13
|
+
3. **Coherence Strength**: Measure available coherence (EPI × νf)
|
|
14
|
+
4. **Network Distance**: Respect structural proximity in network
|
|
15
|
+
|
|
16
|
+
These functions enable Reception to operate as "active reorganization from
|
|
17
|
+
the exterior" rather than passive data absorption.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
from typing import TYPE_CHECKING, Any
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from ...types import TNFRGraph
|
|
26
|
+
|
|
27
|
+
import math
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
import networkx as nx
|
|
31
|
+
except ImportError:
|
|
32
|
+
nx = None # Fallback to neighbor-only detection if networkx unavailable
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
"detect_emission_sources",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
# Active emission threshold: minimum EPI for node to be considered emission source
|
|
39
|
+
# Below this threshold, structural form is too weak to contribute coherence
|
|
40
|
+
ACTIVE_EMISSION_THRESHOLD = 0.2
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def detect_emission_sources(
|
|
44
|
+
G: TNFRGraph,
|
|
45
|
+
receiver_node: Any,
|
|
46
|
+
max_distance: int = 2,
|
|
47
|
+
) -> list[tuple[Any, float, float]]:
|
|
48
|
+
"""Detect potential emission sources for EN receiver node.
|
|
49
|
+
|
|
50
|
+
Identifies nodes in the network that can serve as coherence sources for
|
|
51
|
+
the receiving node, ranked by phase compatibility. This implements the
|
|
52
|
+
"active reception" principle from TNFR.pdf §2.2.1 where EN must detect
|
|
53
|
+
and validate compatible emission sources before integrating external
|
|
54
|
+
coherence.
|
|
55
|
+
|
|
56
|
+
Parameters
|
|
57
|
+
----------
|
|
58
|
+
G : TNFRGraph
|
|
59
|
+
Network graph containing TNFR nodes
|
|
60
|
+
receiver_node : Any
|
|
61
|
+
Node applying EN (Reception) that needs to detect sources
|
|
62
|
+
max_distance : int, optional
|
|
63
|
+
Maximum network distance to search for sources (default: 2)
|
|
64
|
+
Respects structural locality principle - distant nodes have
|
|
65
|
+
negligible coupling
|
|
66
|
+
|
|
67
|
+
Returns
|
|
68
|
+
-------
|
|
69
|
+
list[tuple[Any, float, float]]
|
|
70
|
+
List of (source_node, phase_compatibility, coherence_strength) tuples,
|
|
71
|
+
sorted by phase compatibility (most compatible first).
|
|
72
|
+
|
|
73
|
+
- source_node: Node identifier
|
|
74
|
+
- phase_compatibility: 0.0 (incompatible) to 1.0 (perfect sync)
|
|
75
|
+
- coherence_strength: Available coherence (EPI × νf)
|
|
76
|
+
|
|
77
|
+
TNFR Structural Logic
|
|
78
|
+
---------------------
|
|
79
|
+
**Phase Compatibility Calculation:**
|
|
80
|
+
|
|
81
|
+
Given receiver phase θ_r and source phase θ_s:
|
|
82
|
+
|
|
83
|
+
.. code-block:: text
|
|
84
|
+
|
|
85
|
+
phase_diff = |θ_r - θ_s|
|
|
86
|
+
normalized_diff = min(phase_diff / π, 1.0) # Normalize to [0, 1]
|
|
87
|
+
compatibility = 1.0 - normalized_diff
|
|
88
|
+
|
|
89
|
+
Phase values are normalized to [0, π] range before comparison to respect
|
|
90
|
+
phase periodicity in TNFR.
|
|
91
|
+
|
|
92
|
+
**Coherence Strength:**
|
|
93
|
+
|
|
94
|
+
Coherence strength represents the emission capacity of the source:
|
|
95
|
+
|
|
96
|
+
.. code-block:: text
|
|
97
|
+
|
|
98
|
+
coherence_strength = EPI × νf
|
|
99
|
+
|
|
100
|
+
Higher values indicate stronger emission that can be more effectively
|
|
101
|
+
integrated by the receiver.
|
|
102
|
+
|
|
103
|
+
**Active Emission Threshold:**
|
|
104
|
+
|
|
105
|
+
Only nodes with EPI ≥ 0.2 are considered active emission sources.
|
|
106
|
+
Below this threshold, the node's structural form is too weak to
|
|
107
|
+
effectively contribute coherence.
|
|
108
|
+
|
|
109
|
+
Examples
|
|
110
|
+
--------
|
|
111
|
+
>>> from tnfr.structural import create_nfr
|
|
112
|
+
>>> import networkx as nx
|
|
113
|
+
>>> # Create network with emitter and receiver
|
|
114
|
+
>>> G = nx.Graph()
|
|
115
|
+
>>> G, emitter = create_nfr("teacher", epi=0.5, vf=1.0, theta=0.3, G=G)
|
|
116
|
+
>>> _, receiver = create_nfr("student", epi=0.25, vf=0.9, theta=0.35, G=G)
|
|
117
|
+
>>> G.add_edge(emitter, receiver)
|
|
118
|
+
>>> # Detect sources
|
|
119
|
+
>>> sources = detect_emission_sources(G, receiver)
|
|
120
|
+
>>> len(sources)
|
|
121
|
+
1
|
|
122
|
+
>>> source_node, compatibility, strength = sources[0]
|
|
123
|
+
>>> source_node == emitter
|
|
124
|
+
True
|
|
125
|
+
>>> 0.9 <= compatibility <= 1.0 # High phase compatibility
|
|
126
|
+
True
|
|
127
|
+
>>> strength > 0.4 # Strong coherence (0.5 * 1.0)
|
|
128
|
+
True
|
|
129
|
+
|
|
130
|
+
See Also
|
|
131
|
+
--------
|
|
132
|
+
Reception : Operator that uses source detection
|
|
133
|
+
"""
|
|
134
|
+
from ...alias import get_attr
|
|
135
|
+
from ...constants.aliases import ALIAS_THETA, ALIAS_EPI, ALIAS_VF
|
|
136
|
+
|
|
137
|
+
# Get receiver phase
|
|
138
|
+
receiver_theta = float(get_attr(G.nodes[receiver_node], ALIAS_THETA, 0.0))
|
|
139
|
+
# Normalize to [0, π] range for phase comparison
|
|
140
|
+
receiver_theta = abs(receiver_theta) % math.pi
|
|
141
|
+
|
|
142
|
+
sources = []
|
|
143
|
+
|
|
144
|
+
# Scan network for potential sources
|
|
145
|
+
for source in G.nodes():
|
|
146
|
+
if source == receiver_node:
|
|
147
|
+
continue
|
|
148
|
+
|
|
149
|
+
# Check network distance
|
|
150
|
+
if nx is not None:
|
|
151
|
+
try:
|
|
152
|
+
distance = nx.shortest_path_length(G, source, receiver_node)
|
|
153
|
+
if distance > max_distance:
|
|
154
|
+
continue
|
|
155
|
+
except nx.NetworkXNoPath:
|
|
156
|
+
continue
|
|
157
|
+
else:
|
|
158
|
+
# Fallback: only check immediate neighbors
|
|
159
|
+
if source not in G.neighbors(receiver_node):
|
|
160
|
+
continue
|
|
161
|
+
|
|
162
|
+
# Check if source is active (has coherent EPI)
|
|
163
|
+
source_epi = float(get_attr(G.nodes[source], ALIAS_EPI, 0.0))
|
|
164
|
+
if source_epi < ACTIVE_EMISSION_THRESHOLD:
|
|
165
|
+
continue
|
|
166
|
+
|
|
167
|
+
# Calculate phase compatibility
|
|
168
|
+
source_theta = float(get_attr(G.nodes[source], ALIAS_THETA, 0.0))
|
|
169
|
+
# Normalize to [0, π] range
|
|
170
|
+
source_theta = abs(source_theta) % math.pi
|
|
171
|
+
|
|
172
|
+
# Phase difference normalized to [0, 1] scale
|
|
173
|
+
phase_diff = abs(receiver_theta - source_theta)
|
|
174
|
+
normalized_diff = min(phase_diff / math.pi, 1.0)
|
|
175
|
+
phase_compatibility = 1.0 - normalized_diff
|
|
176
|
+
|
|
177
|
+
# Coherence strength (EPI × νf)
|
|
178
|
+
source_vf = float(get_attr(G.nodes[source], ALIAS_VF, 0.0))
|
|
179
|
+
coherence_strength = source_epi * source_vf
|
|
180
|
+
|
|
181
|
+
sources.append((source, phase_compatibility, coherence_strength))
|
|
182
|
+
|
|
183
|
+
# Sort by phase compatibility (most compatible first)
|
|
184
|
+
sources.sort(key=lambda x: x[1], reverse=True)
|
|
185
|
+
|
|
186
|
+
return sources
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
"""Nodal equation validation for TNFR structural operators.
|
|
2
|
+
|
|
3
|
+
This module provides validation for the fundamental TNFR nodal equation:
|
|
4
|
+
|
|
5
|
+
∂EPI/∂t = νf · ΔNFR(t)
|
|
6
|
+
|
|
7
|
+
This equation governs how the Primary Information Structure (EPI) evolves
|
|
8
|
+
over time based on the structural frequency (νf) and internal reorganization
|
|
9
|
+
operator (ΔNFR). All structural operator applications must respect this
|
|
10
|
+
canonical relationship to maintain TNFR theoretical fidelity.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from typing import TYPE_CHECKING, Any
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from ..types import NodeId, TNFRGraph
|
|
19
|
+
|
|
20
|
+
from ..alias import get_attr, set_attr
|
|
21
|
+
from ..constants.aliases import ALIAS_DNFR, ALIAS_EPI, ALIAS_VF, ALIAS_D2EPI
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"NodalEquationViolation",
|
|
25
|
+
"validate_nodal_equation",
|
|
26
|
+
"compute_expected_depi_dt",
|
|
27
|
+
"compute_d2epi_dt2",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
# Default tolerance for nodal equation validation
|
|
31
|
+
DEFAULT_NODAL_EQUATION_TOLERANCE = 1e-3
|
|
32
|
+
DEFAULT_NODAL_EQUATION_CLIP_AWARE = True
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class NodalEquationViolation(Exception):
|
|
36
|
+
"""Raised when operator application violates the nodal equation.
|
|
37
|
+
|
|
38
|
+
The nodal equation ∂EPI/∂t = νf · ΔNFR(t) is the fundamental equation
|
|
39
|
+
governing node evolution in TNFR. Violations indicate non-canonical
|
|
40
|
+
structural transformations.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
operator: str,
|
|
46
|
+
measured_depi_dt: float,
|
|
47
|
+
expected_depi_dt: float,
|
|
48
|
+
tolerance: float,
|
|
49
|
+
details: dict[str, Any] | None = None,
|
|
50
|
+
) -> None:
|
|
51
|
+
"""Initialize nodal equation violation.
|
|
52
|
+
|
|
53
|
+
Parameters
|
|
54
|
+
----------
|
|
55
|
+
operator : str
|
|
56
|
+
Name of the operator that caused the violation
|
|
57
|
+
measured_depi_dt : float
|
|
58
|
+
Measured ∂EPI/∂t from before/after states
|
|
59
|
+
expected_depi_dt : float
|
|
60
|
+
Expected ∂EPI/∂t from νf · ΔNFR(t)
|
|
61
|
+
tolerance : float
|
|
62
|
+
Tolerance threshold that was exceeded
|
|
63
|
+
details : dict, optional
|
|
64
|
+
Additional diagnostic information
|
|
65
|
+
"""
|
|
66
|
+
self.operator = operator
|
|
67
|
+
self.measured_depi_dt = measured_depi_dt
|
|
68
|
+
self.expected_depi_dt = expected_depi_dt
|
|
69
|
+
self.tolerance = tolerance
|
|
70
|
+
self.details = details or {}
|
|
71
|
+
|
|
72
|
+
error = abs(measured_depi_dt - expected_depi_dt)
|
|
73
|
+
super().__init__(
|
|
74
|
+
f"Nodal equation violation in {operator}: "
|
|
75
|
+
f"|∂EPI/∂t_measured - νf·ΔNFR| = {error:.3e} > {tolerance:.3e}\n"
|
|
76
|
+
f" Measured: {measured_depi_dt:.6f}\n"
|
|
77
|
+
f" Expected: {expected_depi_dt:.6f}"
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _get_node_attr(
|
|
82
|
+
G: TNFRGraph, node: NodeId, aliases: tuple[str, ...], default: float = 0.0
|
|
83
|
+
) -> float:
|
|
84
|
+
"""Get node attribute using alias fallback."""
|
|
85
|
+
return float(get_attr(G.nodes[node], aliases, default))
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def compute_expected_depi_dt(G: TNFRGraph, node: NodeId) -> float:
|
|
89
|
+
"""Compute expected ∂EPI/∂t from current νf and ΔNFR values.
|
|
90
|
+
|
|
91
|
+
Implements the canonical TNFR nodal equation:
|
|
92
|
+
∂EPI/∂t = νf · ΔNFR(t)
|
|
93
|
+
|
|
94
|
+
Parameters
|
|
95
|
+
----------
|
|
96
|
+
G : TNFRGraph
|
|
97
|
+
Graph containing the node
|
|
98
|
+
node : NodeId
|
|
99
|
+
Node to compute expected rate for
|
|
100
|
+
|
|
101
|
+
Returns
|
|
102
|
+
-------
|
|
103
|
+
float
|
|
104
|
+
Expected rate of EPI change (∂EPI/∂t)
|
|
105
|
+
|
|
106
|
+
Notes
|
|
107
|
+
-----
|
|
108
|
+
The structural frequency (νf) is in Hz_str (structural hertz) units,
|
|
109
|
+
and ΔNFR is the dimensionless internal reorganization operator.
|
|
110
|
+
Their product gives the rate of structural reorganization.
|
|
111
|
+
"""
|
|
112
|
+
vf = _get_node_attr(G, node, ALIAS_VF)
|
|
113
|
+
dnfr = _get_node_attr(G, node, ALIAS_DNFR)
|
|
114
|
+
return vf * dnfr
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def validate_nodal_equation(
|
|
118
|
+
G: TNFRGraph,
|
|
119
|
+
node: NodeId,
|
|
120
|
+
epi_before: float,
|
|
121
|
+
epi_after: float,
|
|
122
|
+
dt: float,
|
|
123
|
+
*,
|
|
124
|
+
operator_name: str = "unknown",
|
|
125
|
+
tolerance: float | None = None,
|
|
126
|
+
strict: bool = False,
|
|
127
|
+
clip_aware: bool | None = None,
|
|
128
|
+
) -> bool:
|
|
129
|
+
"""Validate that EPI change respects the nodal equation.
|
|
130
|
+
|
|
131
|
+
Verifies that the change in EPI between before and after states
|
|
132
|
+
matches the prediction from the nodal equation:
|
|
133
|
+
|
|
134
|
+
∂EPI/∂t = νf · ΔNFR(t)
|
|
135
|
+
|
|
136
|
+
Parameters
|
|
137
|
+
----------
|
|
138
|
+
G : TNFRGraph
|
|
139
|
+
Graph containing the node
|
|
140
|
+
node : NodeId
|
|
141
|
+
Node that underwent transformation
|
|
142
|
+
epi_before : float
|
|
143
|
+
EPI value before operator application
|
|
144
|
+
epi_after : float
|
|
145
|
+
EPI value after operator application
|
|
146
|
+
dt : float
|
|
147
|
+
Time step (typically 1.0 for discrete operator applications)
|
|
148
|
+
operator_name : str, optional
|
|
149
|
+
Name of the operator for error reporting
|
|
150
|
+
tolerance : float, optional
|
|
151
|
+
Absolute tolerance for equation validation.
|
|
152
|
+
If None, uses graph configuration or default (1e-3).
|
|
153
|
+
strict : bool, default False
|
|
154
|
+
If True, raises NodalEquationViolation on failure.
|
|
155
|
+
If False, returns validation result without raising.
|
|
156
|
+
clip_aware : bool, optional
|
|
157
|
+
If True, validates using structural_clip to account for boundary
|
|
158
|
+
preservation: EPI_expected = structural_clip(EPI_theoretical).
|
|
159
|
+
If False, uses classic mode without clip adjustment.
|
|
160
|
+
If None, uses graph configuration or default (True).
|
|
161
|
+
|
|
162
|
+
Returns
|
|
163
|
+
-------
|
|
164
|
+
bool
|
|
165
|
+
True if equation is satisfied within tolerance, False otherwise
|
|
166
|
+
|
|
167
|
+
Raises
|
|
168
|
+
------
|
|
169
|
+
NodalEquationViolation
|
|
170
|
+
If strict=True and validation fails
|
|
171
|
+
|
|
172
|
+
Notes
|
|
173
|
+
-----
|
|
174
|
+
The nodal equation is validated using the post-transformation νf and ΔNFR
|
|
175
|
+
values, as these represent the structural state after the operator effect.
|
|
176
|
+
|
|
177
|
+
For discrete operator applications, dt is typically 1.0, making the
|
|
178
|
+
validation equivalent to: (epi_after - epi_before) ≈ νf_after · ΔNFR_after
|
|
179
|
+
|
|
180
|
+
**Clip-aware mode** (default): When structural_clip intervenes to preserve
|
|
181
|
+
boundaries, the actual EPI differs from the theoretical prediction. This
|
|
182
|
+
mode accounts for boundary preservation by applying structural_clip to the
|
|
183
|
+
theoretical value before comparison:
|
|
184
|
+
|
|
185
|
+
EPI_expected = structural_clip(EPI_before + νf · ΔNFR · dt)
|
|
186
|
+
|
|
187
|
+
This ensures validation passes when clip interventions are legitimate parts
|
|
188
|
+
of the operator's structural boundary preservation.
|
|
189
|
+
|
|
190
|
+
**Classic mode** (clip_aware=False): Validates without clip adjustment,
|
|
191
|
+
useful for detecting when unexpected clipping occurs.
|
|
192
|
+
|
|
193
|
+
Examples
|
|
194
|
+
--------
|
|
195
|
+
>>> from tnfr.structural import create_nfr
|
|
196
|
+
>>> G, node = create_nfr("test", epi=0.5, vf=1.0, dnfr=0.1)
|
|
197
|
+
>>> epi_before = G.nodes[node]["EPI"]
|
|
198
|
+
>>> # Apply some transformation...
|
|
199
|
+
>>> epi_after = G.nodes[node]["EPI"]
|
|
200
|
+
>>> is_valid = validate_nodal_equation(G, node, epi_before, epi_after, dt=1.0)
|
|
201
|
+
"""
|
|
202
|
+
if tolerance is None:
|
|
203
|
+
# Try graph configuration first, then use default constant
|
|
204
|
+
tolerance = float(
|
|
205
|
+
G.graph.get("NODAL_EQUATION_TOLERANCE", DEFAULT_NODAL_EQUATION_TOLERANCE)
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
if clip_aware is None:
|
|
209
|
+
# Try graph configuration first, then use default
|
|
210
|
+
clip_aware = G.graph.get(
|
|
211
|
+
"NODAL_EQUATION_CLIP_AWARE", DEFAULT_NODAL_EQUATION_CLIP_AWARE
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
# Measured rate of EPI change
|
|
215
|
+
measured_depi_dt = (epi_after - epi_before) / dt if dt > 0 else 0.0
|
|
216
|
+
|
|
217
|
+
# Expected rate from nodal equation: νf · ΔNFR
|
|
218
|
+
# Use post-transformation values as they represent the new structural state
|
|
219
|
+
expected_depi_dt = compute_expected_depi_dt(G, node)
|
|
220
|
+
|
|
221
|
+
if clip_aware:
|
|
222
|
+
# Clip-aware mode: apply structural_clip to theoretical EPI before comparison
|
|
223
|
+
from ..dynamics.structural_clip import structural_clip
|
|
224
|
+
|
|
225
|
+
# Get structural boundaries from graph configuration
|
|
226
|
+
epi_min = float(G.graph.get("EPI_MIN", -1.0))
|
|
227
|
+
epi_max = float(G.graph.get("EPI_MAX", 1.0))
|
|
228
|
+
clip_mode = G.graph.get("CLIP_MODE", "hard")
|
|
229
|
+
|
|
230
|
+
# Compute theoretical EPI based on nodal equation
|
|
231
|
+
epi_theoretical = epi_before + (expected_depi_dt * dt)
|
|
232
|
+
|
|
233
|
+
# Validate and normalize clip_mode
|
|
234
|
+
clip_mode_str = str(clip_mode).lower()
|
|
235
|
+
if clip_mode_str not in ("hard", "soft"):
|
|
236
|
+
clip_mode_str = "hard" # Default to safe fallback
|
|
237
|
+
|
|
238
|
+
# Apply structural_clip to get expected EPI (what the operator should produce)
|
|
239
|
+
epi_expected = structural_clip(
|
|
240
|
+
epi_theoretical, lo=epi_min, hi=epi_max, mode=clip_mode_str # type: ignore[arg-type]
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
# Validate against clipped expected value
|
|
244
|
+
error = abs(epi_after - epi_expected)
|
|
245
|
+
is_valid = error <= tolerance
|
|
246
|
+
|
|
247
|
+
if not is_valid and strict:
|
|
248
|
+
vf = _get_node_attr(G, node, ALIAS_VF)
|
|
249
|
+
dnfr = _get_node_attr(G, node, ALIAS_DNFR)
|
|
250
|
+
|
|
251
|
+
raise NodalEquationViolation(
|
|
252
|
+
operator=operator_name,
|
|
253
|
+
measured_depi_dt=measured_depi_dt,
|
|
254
|
+
expected_depi_dt=expected_depi_dt,
|
|
255
|
+
tolerance=tolerance,
|
|
256
|
+
details={
|
|
257
|
+
"epi_before": epi_before,
|
|
258
|
+
"epi_after": epi_after,
|
|
259
|
+
"epi_theoretical": epi_theoretical,
|
|
260
|
+
"epi_expected": epi_expected,
|
|
261
|
+
"dt": dt,
|
|
262
|
+
"vf": vf,
|
|
263
|
+
"dnfr": dnfr,
|
|
264
|
+
"error": error,
|
|
265
|
+
"clip_aware": True,
|
|
266
|
+
"clip_intervened": abs(epi_theoretical - epi_expected) > 1e-10,
|
|
267
|
+
},
|
|
268
|
+
)
|
|
269
|
+
else:
|
|
270
|
+
# Classic mode: validate rate of change directly
|
|
271
|
+
error = abs(measured_depi_dt - expected_depi_dt)
|
|
272
|
+
is_valid = error <= tolerance
|
|
273
|
+
|
|
274
|
+
if not is_valid and strict:
|
|
275
|
+
vf = _get_node_attr(G, node, ALIAS_VF)
|
|
276
|
+
dnfr = _get_node_attr(G, node, ALIAS_DNFR)
|
|
277
|
+
|
|
278
|
+
raise NodalEquationViolation(
|
|
279
|
+
operator=operator_name,
|
|
280
|
+
measured_depi_dt=measured_depi_dt,
|
|
281
|
+
expected_depi_dt=expected_depi_dt,
|
|
282
|
+
tolerance=tolerance,
|
|
283
|
+
details={
|
|
284
|
+
"epi_before": epi_before,
|
|
285
|
+
"epi_after": epi_after,
|
|
286
|
+
"dt": dt,
|
|
287
|
+
"vf": vf,
|
|
288
|
+
"dnfr": dnfr,
|
|
289
|
+
"error": error,
|
|
290
|
+
"clip_aware": False,
|
|
291
|
+
},
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
return is_valid
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def compute_d2epi_dt2(G: "TNFRGraph", node: "NodeId") -> float:
|
|
298
|
+
"""Compute ∂²EPI/∂t² (structural acceleration).
|
|
299
|
+
|
|
300
|
+
According to TNFR canonical theory (§2.3.3, R4), bifurcation occurs when
|
|
301
|
+
structural acceleration exceeds threshold τ:
|
|
302
|
+
|∂²EPI/∂t²| > τ → multiple reorganization paths viable
|
|
303
|
+
|
|
304
|
+
This function computes the second-order time derivative of EPI using
|
|
305
|
+
finite differences from the node's EPI history. The acceleration indicates
|
|
306
|
+
how rapidly the rate of structural change is itself changing, which is
|
|
307
|
+
the key indicator of bifurcation readiness.
|
|
308
|
+
|
|
309
|
+
Parameters
|
|
310
|
+
----------
|
|
311
|
+
G : TNFRGraph
|
|
312
|
+
Graph containing the node
|
|
313
|
+
node : NodeId
|
|
314
|
+
Node identifier to compute acceleration for
|
|
315
|
+
|
|
316
|
+
Returns
|
|
317
|
+
-------
|
|
318
|
+
float
|
|
319
|
+
Structural acceleration ∂²EPI/∂t². Positive values indicate accelerating
|
|
320
|
+
growth, negative values indicate accelerating contraction. Magnitude
|
|
321
|
+
indicates bifurcation potential.
|
|
322
|
+
|
|
323
|
+
Notes
|
|
324
|
+
-----
|
|
325
|
+
**Computation method:**
|
|
326
|
+
|
|
327
|
+
Uses second-order finite difference approximation:
|
|
328
|
+
∂²EPI/∂t² ≈ (EPI_t - 2·EPI_{t-1} + EPI_{t-2}) / Δt²
|
|
329
|
+
|
|
330
|
+
For discrete operator applications with Δt=1:
|
|
331
|
+
∂²EPI/∂t² ≈ EPI_t - 2·EPI_{t-1} + EPI_{t-2}
|
|
332
|
+
|
|
333
|
+
**History requirements:**
|
|
334
|
+
|
|
335
|
+
Requires at least 3 historical EPI values stored in node's `_epi_history`
|
|
336
|
+
attribute. If insufficient history exists, returns 0.0 (no acceleration).
|
|
337
|
+
|
|
338
|
+
The computed value is automatically stored in the node's `D2_EPI` attribute
|
|
339
|
+
(using ALIAS_D2EPI aliases) for telemetry and metrics collection.
|
|
340
|
+
|
|
341
|
+
**Physical interpretation:**
|
|
342
|
+
|
|
343
|
+
- **d2epi ≈ 0**: Steady structural evolution (constant rate)
|
|
344
|
+
- **d2epi > τ**: Positive acceleration, expanding reorganization
|
|
345
|
+
- **d2epi < -τ**: Negative acceleration, collapsing reorganization
|
|
346
|
+
- **|d2epi| > τ**: Bifurcation active, multiple paths viable
|
|
347
|
+
|
|
348
|
+
Examples
|
|
349
|
+
--------
|
|
350
|
+
>>> from tnfr.structural import create_nfr
|
|
351
|
+
>>> from tnfr.operators.definitions import Emission, Dissonance
|
|
352
|
+
>>> from tnfr.operators.nodal_equation import compute_d2epi_dt2
|
|
353
|
+
>>>
|
|
354
|
+
>>> G, node = create_nfr("test", epi=0.2, vf=1.0)
|
|
355
|
+
>>>
|
|
356
|
+
>>> # Build EPI history through operator applications
|
|
357
|
+
>>> Emission()(G, node) # EPI increases
|
|
358
|
+
>>> Emission()(G, node) # EPI increases more
|
|
359
|
+
>>> Dissonance()(G, node) # Introduce instability
|
|
360
|
+
>>>
|
|
361
|
+
>>> # Compute acceleration
|
|
362
|
+
>>> d2epi = compute_d2epi_dt2(G, node)
|
|
363
|
+
>>>
|
|
364
|
+
>>> # Check if bifurcation threshold exceeded
|
|
365
|
+
>>> tau = G.graph.get("OZ_BIFURCATION_THRESHOLD", 0.5)
|
|
366
|
+
>>> bifurcation_active = abs(d2epi) > tau
|
|
367
|
+
|
|
368
|
+
See Also
|
|
369
|
+
--------
|
|
370
|
+
tnfr.dynamics.bifurcation.compute_bifurcation_score : Uses d2epi for scoring
|
|
371
|
+
tnfr.operators.metrics.dissonance_metrics : Reports d2epi in OZ metrics
|
|
372
|
+
tnfr.operators.preconditions.validate_dissonance : Checks d2epi for bifurcation
|
|
373
|
+
"""
|
|
374
|
+
# Get EPI history from node
|
|
375
|
+
history = G.nodes[node].get("_epi_history", [])
|
|
376
|
+
|
|
377
|
+
if len(history) < 3:
|
|
378
|
+
# Insufficient history for second derivative
|
|
379
|
+
# Need at least 3 points: t-2, t-1, t
|
|
380
|
+
return 0.0
|
|
381
|
+
|
|
382
|
+
# Extract last 3 EPI values
|
|
383
|
+
epi_t = history[-1] # Current (most recent)
|
|
384
|
+
epi_t1 = history[-2] # One step ago
|
|
385
|
+
epi_t2 = history[-3] # Two steps ago
|
|
386
|
+
|
|
387
|
+
# Second-order finite difference (assuming dt=1 for discrete operators)
|
|
388
|
+
# ∂²EPI/∂t² ≈ (EPI_t - 2·EPI_{t-1} + EPI_{t-2}) / dt²
|
|
389
|
+
# For dt=1: ∂²EPI/∂t² ≈ EPI_t - 2·EPI_{t-1} + EPI_{t-2}
|
|
390
|
+
d2epi = epi_t - 2.0 * epi_t1 + epi_t2
|
|
391
|
+
|
|
392
|
+
# Store in node for telemetry (using set_attr to handle aliases)
|
|
393
|
+
set_attr(G.nodes[node], ALIAS_D2EPI, d2epi)
|
|
394
|
+
|
|
395
|
+
return float(d2epi)
|