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
tnfr/operators/remesh.py
ADDED
|
@@ -0,0 +1,1809 @@
|
|
|
1
|
+
"""Canonical REMESH operator: Recursive pattern propagation preserving structural coherence.
|
|
2
|
+
|
|
3
|
+
REMESH (Recursivity) - Glyph: REMESH
|
|
4
|
+
====================================
|
|
5
|
+
|
|
6
|
+
Physical Foundation
|
|
7
|
+
-------------------
|
|
8
|
+
From the nodal equation: ∂EPI/∂t = νf · ΔNFR(t)
|
|
9
|
+
|
|
10
|
+
REMESH implements: **EPI(t) ↔ EPI(t-τ)** (operational fractality)
|
|
11
|
+
|
|
12
|
+
REMESH enables patterns to echo across temporal and spatial scales while maintaining
|
|
13
|
+
coherence. "What persists at one scale can be rewritten at another, with coherence
|
|
14
|
+
propagating structurally, not imposed." - El pulso que nos atraviesa
|
|
15
|
+
|
|
16
|
+
Canonical Physical Behavior
|
|
17
|
+
----------------------------
|
|
18
|
+
REMESH acts on the nodal equation by creating temporal/spatial coupling:
|
|
19
|
+
|
|
20
|
+
1. **Memory Activation**: References EPI(t-τ) from structural history
|
|
21
|
+
2. **Pattern Recognition**: Identifies similar EPIs across network (structural_similarity)
|
|
22
|
+
3. **Coherent Propagation**: Propagates patterns maintaining identity (StructuralIdentity)
|
|
23
|
+
4. **Scale Invariance**: Preserves structural properties across reorganizations
|
|
24
|
+
|
|
25
|
+
Effect on nodal components:
|
|
26
|
+
- EPI: Mixed with historical states (EPI_new = (1-α)·EPI_now + α·EPI_past)
|
|
27
|
+
- νf: Can increase during memory activation (reactivation of dormant patterns)
|
|
28
|
+
- ΔNFR: Implicitly calculated from reorganization (ΔNFR = ΔEPI/νf from nodal equation)
|
|
29
|
+
- Phase: Preserved through StructuralIdentity tracking
|
|
30
|
+
|
|
31
|
+
Operator Relationships (from Nodal Equation Physics)
|
|
32
|
+
-----------------------------------------------------
|
|
33
|
+
All relationships emerge naturally from how operators affect EPI, νf, ΔNFR, and phase:
|
|
34
|
+
|
|
35
|
+
### REMESH Hierarchical (Central Control → Periphery)
|
|
36
|
+
**Physics**: Controlled replication from center maintaining coherence descendente
|
|
37
|
+
|
|
38
|
+
1. **IL (Coherence)**: Reduces |ΔNFR| → stabilizes each recursion level
|
|
39
|
+
- Relationship: Estabilización multinivel (coherence extended)
|
|
40
|
+
- Dynamics: REMESH propagates pattern, IL consolidates at each level
|
|
41
|
+
- Sequence: REMESH → IL (recursive propagation → multi-level stabilization)
|
|
42
|
+
|
|
43
|
+
2. **VAL (Expansion)**: Increases dim(EPI) → structural expansion
|
|
44
|
+
- Relationship: Expansión estructural coherente (fractal growth)
|
|
45
|
+
- Dynamics: REMESH replicates, VAL expands each replica maintaining form
|
|
46
|
+
- Sequence: VAL → REMESH (expand → replicate expanded form)
|
|
47
|
+
|
|
48
|
+
3. **SHA (Silence)**: νf → 0 → latent memory
|
|
49
|
+
- Relationship: Estabilización de red latente (structural memory)
|
|
50
|
+
- Dynamics: SHA freezes pattern (∂EPI/∂t → 0), REMESH propagates frozen state
|
|
51
|
+
- Sequence: SHA → REMESH (freeze → propagate frozen memory)
|
|
52
|
+
- **Critical**: NO functional redundancy - uses existing Silence operator
|
|
53
|
+
|
|
54
|
+
4. **NUL (Contraction)**: Reduces dim(EPI) → compression
|
|
55
|
+
- Relationship: Compresión estructural coherente (fractal distillation)
|
|
56
|
+
- Dynamics: Complementary to VAL, reduces dimensionality maintaining identity
|
|
57
|
+
- Sequence: NUL → REMESH (compress → replicate compressed essence)
|
|
58
|
+
- **Note**: Hierarchical simplification preserving core structure
|
|
59
|
+
|
|
60
|
+
### REMESH Rhizomatic (Decentralized Propagation)
|
|
61
|
+
**Physics**: Propagation without fixed center, by local resonance
|
|
62
|
+
|
|
63
|
+
1. **OZ (Dissonance)**: Increases |ΔNFR| → exploration
|
|
64
|
+
- Relationship: Bifurcación distribuida (distributed bifurcation)
|
|
65
|
+
- Dynamics: REMESH + OZ creates decentralized local variations
|
|
66
|
+
- Sequence: OZ → REMESH (destabilize → replicate variations)
|
|
67
|
+
|
|
68
|
+
2. **UM (Coupling)**: φᵢ → φⱼ → structural connection
|
|
69
|
+
- Relationship: Acoplamiento multiescala (multi-scale coupling)
|
|
70
|
+
- Dynamics: REMESH propagates, UM connects replicas without hierarchy
|
|
71
|
+
- Sequence: REMESH → UM (propagate → connect peers)
|
|
72
|
+
|
|
73
|
+
3. **THOL (Self-organization)**: Creates sub-EPIs → emergence
|
|
74
|
+
- Relationship: Auto-organización recursiva (recursive self-organization)
|
|
75
|
+
- Dynamics: REMESH + THOL generates emergent structures without center
|
|
76
|
+
- Sequence: THOL → REMESH (emerge sub-EPIs → replicate emergent forms)
|
|
77
|
+
|
|
78
|
+
### REMESH Fractal Harmonic (Perfect Self-Similarity)
|
|
79
|
+
**Physics**: Scale-symmetric replication maintaining perfect auto-similitud
|
|
80
|
+
|
|
81
|
+
1. **RA (Resonance)**: Amplifies coherently → propagation
|
|
82
|
+
- Relationship: Resonancia multiescala (multi-scale resonance)
|
|
83
|
+
- Dynamics: REMESH replicates, RA amplifies with perfect symmetry
|
|
84
|
+
- Sequence: REMESH → RA (replicate → amplify symmetrically)
|
|
85
|
+
|
|
86
|
+
2. **NAV (Transition)**: Activates latent EPI → regime shift
|
|
87
|
+
- Relationship: Transición entre attractores fractales
|
|
88
|
+
- Dynamics: REMESH navigates between self-similar attractor states
|
|
89
|
+
- Sequence: NAV → REMESH (transition → replicate new regime)
|
|
90
|
+
|
|
91
|
+
3. **AL (Emission)**: Creates EPI from vacuum → generation
|
|
92
|
+
- Relationship: Emisión fractal (fractal emission)
|
|
93
|
+
- Dynamics: REMESH + AL generates self-similar patterns from origin
|
|
94
|
+
- Sequence: AL → REMESH (emit seed → replicate fractally)
|
|
95
|
+
|
|
96
|
+
4. **EN (Reception)**: Updates EPI from network → reception
|
|
97
|
+
- Relationship: Recepción multi-escala simétrica (symmetric multi-scale reception)
|
|
98
|
+
- Dynamics: EN captures patterns from multiple sources → REMESH replicates symmetrically
|
|
99
|
+
- Sequence: EN → REMESH (receive multi-scale → propagate symmetrically)
|
|
100
|
+
- **Note**: Pre-recursion operator that feeds REMESH
|
|
101
|
+
|
|
102
|
+
### Operators with Indirect Relationships
|
|
103
|
+
|
|
104
|
+
**ZHIR (Mutation)**: Present in canonical relationships but NOT in types
|
|
105
|
+
- **Physical Reason**: ZHIR is a TRANSFORMER that emerges POST-recursion
|
|
106
|
+
- **Dynamics**: REMESH propagates → local variations + destabilizers → ZHIR transforms
|
|
107
|
+
- **Grammar**: Requires IL previo + recent destabilizer (U4b)
|
|
108
|
+
- **Relationship**: Mutación replicativa (replication facilitates mutation)
|
|
109
|
+
- **Conclusion**: Operates AFTER REMESH completes, not during
|
|
110
|
+
|
|
111
|
+
Grammar Implications from Physical Analysis
|
|
112
|
+
--------------------------------------------
|
|
113
|
+
REMESH's physical behavior affects unified grammar rules (UNIFIED_GRAMMAR_RULES.md):
|
|
114
|
+
|
|
115
|
+
### U1: STRUCTURAL INITIATION & CLOSURE
|
|
116
|
+
**Physical Basis**: REMESH echoes EPI(t-τ), can activate from dormant/null states
|
|
117
|
+
|
|
118
|
+
**U1a (Initiation)**: REMESH is a GENERATOR
|
|
119
|
+
- Can start sequences when operating on latent structure
|
|
120
|
+
- Activates dormant patterns via temporal coupling
|
|
121
|
+
- **Rule**: Sequences can begin with REMESH
|
|
122
|
+
|
|
123
|
+
**U1b (Closure)**: REMESH is a CLOSURE operator
|
|
124
|
+
- Distributes structure across scales leaving system in recursive attractor
|
|
125
|
+
- Creates self-sustaining multi-scale coherence
|
|
126
|
+
- **Rule**: Sequences can end with REMESH
|
|
127
|
+
|
|
128
|
+
### U2: CONVERGENCE & BOUNDEDNESS
|
|
129
|
+
**Physical Basis**: REMESH mixing with historical states can amplify or dampen ΔNFR
|
|
130
|
+
|
|
131
|
+
**Requirement**: REMESH + destabilizers → must include stabilizers
|
|
132
|
+
- Example: REMESH + VAL (expansion) → requires IL (coherence)
|
|
133
|
+
- Prevents: Unbounded growth through recursive expansion
|
|
134
|
+
- **Rule**: If REMESH precedes/follows VAL, OZ, or ZHIR → require IL or THOL
|
|
135
|
+
|
|
136
|
+
**Integration Convergence**: ∫ νf · ΔNFR dt must converge
|
|
137
|
+
- REMESH temporal mixing: (1-α)·EPI_now + α·EPI_past
|
|
138
|
+
- Without stabilizers: Can create positive feedback loop
|
|
139
|
+
- **Rule**: Stabilizers ensure convergence of recursive reorganization
|
|
140
|
+
|
|
141
|
+
### U3: RESONANT COUPLING
|
|
142
|
+
**Physical Basis**: REMESH propagates patterns - must verify phase compatibility
|
|
143
|
+
|
|
144
|
+
**Requirement**: REMESH with UM or RA → verify |φᵢ - φⱼ| ≤ Δφ_max
|
|
145
|
+
- REMESH creates replicas that must be phase-compatible for resonance
|
|
146
|
+
- Antiphase replicas → destructive interference
|
|
147
|
+
- **Rule**: StructuralIdentity.matches() includes phase verification
|
|
148
|
+
- **Implementation**: Phase pattern captured and validated during propagation
|
|
149
|
+
|
|
150
|
+
### U4: BIFURCATION DYNAMICS
|
|
151
|
+
**Physical Basis**: REMESH can trigger bifurcation through recursive amplification
|
|
152
|
+
|
|
153
|
+
**U4a (Triggers Need Handlers)**: REMESH + destabilizers → need handlers
|
|
154
|
+
- REMESH → THOL sequence: Recursion enables self-organization
|
|
155
|
+
- Must handle emergent sub-EPIs from recursive bifurcation
|
|
156
|
+
- **Rule**: REMESH + OZ or ZHIR → require THOL or IL handlers
|
|
157
|
+
|
|
158
|
+
**U4b (Transformers Need Context)**: ZHIR requires REMESH context
|
|
159
|
+
- REMESH creates variations across scales
|
|
160
|
+
- ZHIR then transforms local variations (post-recursion)
|
|
161
|
+
- **Rule**: ZHIR after REMESH → requires prior IL + recent destabilizer
|
|
162
|
+
|
|
163
|
+
Centralized Flow - No Redundancy
|
|
164
|
+
---------------------------------
|
|
165
|
+
This implementation maintains a single, centralized flow:
|
|
166
|
+
|
|
167
|
+
1. **SHA Integration**: Uses existing Silence operator from definitions.py
|
|
168
|
+
- NO reimplementation of SHA functionality
|
|
169
|
+
- StructuralIdentity only CAPTURES frozen states, doesn't freeze
|
|
170
|
+
- Workflow: Silence() → capture_from_node(is_sha_frozen=True) → validate
|
|
171
|
+
|
|
172
|
+
2. **Coherence Calculation**: Simplified C(t) for REMESH validation
|
|
173
|
+
- Full coherence calculation in tnfr.metrics module
|
|
174
|
+
- compute_global_coherence() specific to REMESH fidelity checks
|
|
175
|
+
- No duplication with main coherence computation
|
|
176
|
+
|
|
177
|
+
3. **Pattern Recognition**: Unique to REMESH structural memory
|
|
178
|
+
- structural_similarity(): Pattern matching (no operator overlap)
|
|
179
|
+
- structural_memory_match(): Network-wide search (REMESH-specific)
|
|
180
|
+
- No duplication with network analysis tools
|
|
181
|
+
|
|
182
|
+
4. **Identity Tracking**: REMESH-specific fractal identity
|
|
183
|
+
- StructuralIdentity: Persistent identity across reorganizations
|
|
184
|
+
- Captures EPI signature, νf range, phase pattern
|
|
185
|
+
- Validates preservation post-recursion
|
|
186
|
+
|
|
187
|
+
Key Capabilities
|
|
188
|
+
----------------
|
|
189
|
+
- Structural memory: Pattern recognition across network nodes
|
|
190
|
+
- Identity preservation: Fractal lineage tracking across reorganizations
|
|
191
|
+
- Coherence conservation: Validating structural fidelity during remeshing
|
|
192
|
+
- Multi-modal recursivity: Hierarchical, rhizomatic, and fractal harmonic modes
|
|
193
|
+
- Grammar-compliant: All operations respect unified grammar rules (U1-U4)
|
|
194
|
+
"""
|
|
195
|
+
|
|
196
|
+
from __future__ import annotations
|
|
197
|
+
|
|
198
|
+
import hashlib
|
|
199
|
+
import heapq
|
|
200
|
+
import random
|
|
201
|
+
from collections import deque
|
|
202
|
+
from collections.abc import Hashable, Iterable, Mapping, MutableMapping, Sequence
|
|
203
|
+
from dataclasses import dataclass, field
|
|
204
|
+
from functools import cache
|
|
205
|
+
from io import StringIO
|
|
206
|
+
from itertools import combinations
|
|
207
|
+
from operator import ge, le
|
|
208
|
+
from statistics import StatisticsError, fmean
|
|
209
|
+
from types import ModuleType
|
|
210
|
+
from typing import Any, cast
|
|
211
|
+
|
|
212
|
+
from .._compat import TypeAlias
|
|
213
|
+
from ..alias import get_attr, set_attr
|
|
214
|
+
from ..constants import DEFAULTS, REMESH_DEFAULTS, get_param
|
|
215
|
+
from ..constants.aliases import ALIAS_EPI, ALIAS_VF, ALIAS_DNFR
|
|
216
|
+
from ..rng import make_rng
|
|
217
|
+
from ..types import RemeshMeta
|
|
218
|
+
from ..utils import cached_import, edge_version_update, kahan_sum_nd
|
|
219
|
+
|
|
220
|
+
CommunityGraph: TypeAlias = Any
|
|
221
|
+
NetworkxModule: TypeAlias = ModuleType
|
|
222
|
+
CommunityModule: TypeAlias = ModuleType
|
|
223
|
+
RemeshEdge: TypeAlias = tuple[Hashable, Hashable]
|
|
224
|
+
NetworkxModules: TypeAlias = tuple[NetworkxModule, CommunityModule]
|
|
225
|
+
RemeshConfigValue: TypeAlias = bool | float | int
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
# ==============================================================================
|
|
229
|
+
# Phase 1: Structural Memory & Pattern Recognition
|
|
230
|
+
# ==============================================================================
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
@dataclass
|
|
234
|
+
class StructuralIdentity:
|
|
235
|
+
"""Persistent fractal identity maintained across reorganizations.
|
|
236
|
+
|
|
237
|
+
Captures the canonical structural "signature" of a pattern that must be
|
|
238
|
+
preserved as it echoes across scales. This implements TNFR's requirement
|
|
239
|
+
that patterns maintain identity through reorganization.
|
|
240
|
+
|
|
241
|
+
**REMESH ↔ SHA Relationship**: According to TNFR theory, SHA (Silence)
|
|
242
|
+
stabilizes latent network memory by reducing νf → 0, which freezes EPI
|
|
243
|
+
via the nodal equation: ∂EPI/∂t = νf · ΔNFR → 0. When REMESH propagates
|
|
244
|
+
patterns across scales, SHA-frozen nodes act as "structural anchors" that
|
|
245
|
+
maintain identity during reorganization.
|
|
246
|
+
|
|
247
|
+
**Usage Pattern**:
|
|
248
|
+
1. Apply SHA to freeze node: νf → 0, preserves EPI
|
|
249
|
+
2. Capture identity from frozen state (this class)
|
|
250
|
+
3. Apply REMESH to propagate pattern across scales
|
|
251
|
+
4. Validate identity preservation post-reorganization
|
|
252
|
+
|
|
253
|
+
Attributes
|
|
254
|
+
----------
|
|
255
|
+
epi_signature : float
|
|
256
|
+
Characteristic EPI pattern value (preserved when SHA applied)
|
|
257
|
+
vf_range : tuple[float, float]
|
|
258
|
+
Range of structural frequencies (min, max) in Hz_str
|
|
259
|
+
phase_pattern : float | None
|
|
260
|
+
Characteristic phase pattern in [0, 2π], if applicable
|
|
261
|
+
frozen_by_sha : bool
|
|
262
|
+
Whether this identity was captured from SHA-frozen state (νf ≈ 0)
|
|
263
|
+
lineage : list[str]
|
|
264
|
+
History of transformations preserving this identity
|
|
265
|
+
tolerance : float
|
|
266
|
+
Maximum deviation for identity match (default: 0.1)
|
|
267
|
+
|
|
268
|
+
Notes
|
|
269
|
+
-----
|
|
270
|
+
From TNFR physics (definitions.py::Silence): SHA reduces νf causing
|
|
271
|
+
∂EPI/∂t → 0 regardless of ΔNFR. This creates "latent memory" - frozen
|
|
272
|
+
structural patterns that REMESH can propagate coherently across scales.
|
|
273
|
+
|
|
274
|
+
**Do NOT reimplement SHA** - use existing Silence operator from
|
|
275
|
+
tnfr.operators.definitions. This class only captures and validates
|
|
276
|
+
identity, it does NOT apply SHA itself.
|
|
277
|
+
|
|
278
|
+
See Also
|
|
279
|
+
--------
|
|
280
|
+
tnfr.operators.definitions.Silence : SHA operator implementation
|
|
281
|
+
SHA_ALGEBRA_PHYSICS.md : SHA as identity operator derivation
|
|
282
|
+
"""
|
|
283
|
+
|
|
284
|
+
epi_signature: float
|
|
285
|
+
vf_range: tuple[float, float]
|
|
286
|
+
phase_pattern: float | None = None
|
|
287
|
+
frozen_by_sha: bool = False
|
|
288
|
+
lineage: list[str] = field(default_factory=list)
|
|
289
|
+
tolerance: float = 0.1
|
|
290
|
+
|
|
291
|
+
def matches(self, node_data: Mapping[str, Any], *, tolerance: float | None = None) -> bool:
|
|
292
|
+
"""Check if a node maintains this structural identity.
|
|
293
|
+
|
|
294
|
+
Parameters
|
|
295
|
+
----------
|
|
296
|
+
node_data : Mapping
|
|
297
|
+
Node attributes containing EPI, vf, and optionally phase
|
|
298
|
+
tolerance : float, optional
|
|
299
|
+
Override default tolerance for this check
|
|
300
|
+
|
|
301
|
+
Returns
|
|
302
|
+
-------
|
|
303
|
+
bool
|
|
304
|
+
True if node matches this identity within tolerance
|
|
305
|
+
|
|
306
|
+
Notes
|
|
307
|
+
-----
|
|
308
|
+
If frozen_by_sha=True, vf check is relaxed since SHA-frozen patterns
|
|
309
|
+
have νf ≈ 0 (frozen state) while maintaining identity via EPI.
|
|
310
|
+
"""
|
|
311
|
+
tol = tolerance if tolerance is not None else self.tolerance
|
|
312
|
+
|
|
313
|
+
# Check EPI signature match (primary identity criterion)
|
|
314
|
+
node_epi = _as_float(get_attr(node_data, ALIAS_EPI, 0.0))
|
|
315
|
+
if abs(node_epi - self.epi_signature) > tol:
|
|
316
|
+
return False
|
|
317
|
+
|
|
318
|
+
# Check vf range (relaxed if SHA-frozen)
|
|
319
|
+
node_vf = _as_float(get_attr(node_data, ALIAS_VF, 0.0))
|
|
320
|
+
vf_min, vf_max = self.vf_range
|
|
321
|
+
|
|
322
|
+
if self.frozen_by_sha:
|
|
323
|
+
# SHA-frozen: accept low νf (frozen state) OR original range
|
|
324
|
+
# (pattern may be reactivated after SHA)
|
|
325
|
+
if node_vf < 0.05: # Frozen by SHA (νf → 0)
|
|
326
|
+
pass # Accept - this is expected for SHA-frozen patterns
|
|
327
|
+
elif not (vf_min - tol <= node_vf <= vf_max + tol):
|
|
328
|
+
return False
|
|
329
|
+
else:
|
|
330
|
+
# Normal check: must be within range
|
|
331
|
+
if not (vf_min - tol <= node_vf <= vf_max + tol):
|
|
332
|
+
return False
|
|
333
|
+
|
|
334
|
+
# Check phase pattern if specified
|
|
335
|
+
if self.phase_pattern is not None:
|
|
336
|
+
from ..constants.aliases import ALIAS_THETA
|
|
337
|
+
node_phase = _as_float(get_attr(node_data, ALIAS_THETA, None))
|
|
338
|
+
if node_phase is None:
|
|
339
|
+
return False
|
|
340
|
+
# Phase comparison with circular wrap-around
|
|
341
|
+
import math
|
|
342
|
+
phase_diff = abs(node_phase - self.phase_pattern)
|
|
343
|
+
phase_diff = min(phase_diff, 2 * math.pi - phase_diff)
|
|
344
|
+
if phase_diff > tol:
|
|
345
|
+
return False
|
|
346
|
+
|
|
347
|
+
return True
|
|
348
|
+
|
|
349
|
+
def record_transformation(self, operation: str) -> None:
|
|
350
|
+
"""Record a structural transformation in this identity's lineage.
|
|
351
|
+
|
|
352
|
+
Parameters
|
|
353
|
+
----------
|
|
354
|
+
operation : str
|
|
355
|
+
Description of the transformation applied
|
|
356
|
+
"""
|
|
357
|
+
self.lineage.append(operation)
|
|
358
|
+
|
|
359
|
+
@classmethod
|
|
360
|
+
def capture_from_node(
|
|
361
|
+
cls,
|
|
362
|
+
node_data: Mapping[str, Any],
|
|
363
|
+
*,
|
|
364
|
+
is_sha_frozen: bool = False,
|
|
365
|
+
tolerance: float = 0.1,
|
|
366
|
+
) -> "StructuralIdentity":
|
|
367
|
+
"""Capture structural identity from a node's current state.
|
|
368
|
+
|
|
369
|
+
This creates a "memory snapshot" of the node's structural signature
|
|
370
|
+
that can be propagated via REMESH across scales.
|
|
371
|
+
|
|
372
|
+
Parameters
|
|
373
|
+
----------
|
|
374
|
+
node_data : Mapping
|
|
375
|
+
Node attributes containing EPI, vf, phase
|
|
376
|
+
is_sha_frozen : bool, default=False
|
|
377
|
+
If True, mark identity as captured from SHA-frozen state (νf ≈ 0).
|
|
378
|
+
**NOTE**: This does NOT apply SHA - it only marks that SHA was
|
|
379
|
+
already applied. Use tnfr.operators.definitions.Silence to apply SHA.
|
|
380
|
+
tolerance : float, default=0.1
|
|
381
|
+
Tolerance for future identity matching
|
|
382
|
+
|
|
383
|
+
Returns
|
|
384
|
+
-------
|
|
385
|
+
StructuralIdentity
|
|
386
|
+
Captured structural identity
|
|
387
|
+
|
|
388
|
+
Notes
|
|
389
|
+
-----
|
|
390
|
+
**REMESH ↔ SHA Integration Pattern**:
|
|
391
|
+
|
|
392
|
+
1. Apply SHA operator to freeze node (see tnfr.operators.definitions.Silence)
|
|
393
|
+
2. Call this method with is_sha_frozen=True to capture frozen state
|
|
394
|
+
3. Apply REMESH to propagate pattern across scales
|
|
395
|
+
4. Use identity.matches() to validate preservation
|
|
396
|
+
|
|
397
|
+
**DO NOT use this to apply SHA** - SHA is a separate operator that must
|
|
398
|
+
be applied via the grammar system. This only captures state AFTER SHA.
|
|
399
|
+
|
|
400
|
+
Example
|
|
401
|
+
-------
|
|
402
|
+
>>> from tnfr.operators.definitions import Silence
|
|
403
|
+
>>> from tnfr.structural import run_sequence
|
|
404
|
+
>>> # Step 1: Apply SHA operator to freeze node
|
|
405
|
+
>>> run_sequence(G, node, [Silence()])
|
|
406
|
+
>>> # Step 2: Capture frozen identity
|
|
407
|
+
>>> identity = StructuralIdentity.capture_from_node(
|
|
408
|
+
... G.nodes[node],
|
|
409
|
+
... is_sha_frozen=True
|
|
410
|
+
... )
|
|
411
|
+
>>> # Step 3: Apply REMESH (propagates frozen pattern)
|
|
412
|
+
>>> # ... REMESH operations ...
|
|
413
|
+
>>> # Step 4: Validate identity preserved
|
|
414
|
+
>>> assert identity.matches(G.nodes[node])
|
|
415
|
+
"""
|
|
416
|
+
epi = _as_float(get_attr(node_data, ALIAS_EPI, 0.0))
|
|
417
|
+
vf = _as_float(get_attr(node_data, ALIAS_VF, 0.0))
|
|
418
|
+
|
|
419
|
+
# For vf_range, use small window around current value
|
|
420
|
+
# (unless SHA-frozen, in which case we expect νf ≈ 0)
|
|
421
|
+
vf_tolerance = 0.1
|
|
422
|
+
vf_min = max(0.0, vf - vf_tolerance)
|
|
423
|
+
vf_max = vf + vf_tolerance
|
|
424
|
+
|
|
425
|
+
# Capture phase if present
|
|
426
|
+
from ..constants.aliases import ALIAS_THETA
|
|
427
|
+
phase = _as_float(get_attr(node_data, ALIAS_THETA, None))
|
|
428
|
+
|
|
429
|
+
identity = cls(
|
|
430
|
+
epi_signature=epi,
|
|
431
|
+
vf_range=(vf_min, vf_max),
|
|
432
|
+
phase_pattern=phase if phase is not None else None,
|
|
433
|
+
frozen_by_sha=is_sha_frozen,
|
|
434
|
+
tolerance=tolerance,
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
if is_sha_frozen:
|
|
438
|
+
identity.record_transformation(
|
|
439
|
+
"Captured from SHA-frozen state (latent structural memory)"
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
return identity
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def structural_similarity(
|
|
446
|
+
epi1: float | Sequence[float],
|
|
447
|
+
epi2: float | Sequence[float],
|
|
448
|
+
*,
|
|
449
|
+
metric: str = "euclidean",
|
|
450
|
+
) -> float:
|
|
451
|
+
"""Compute structural similarity between two EPI patterns.
|
|
452
|
+
|
|
453
|
+
Implements pattern matching for structural memory recognition.
|
|
454
|
+
Returns a similarity score in [0, 1] where 1 = identical patterns.
|
|
455
|
+
|
|
456
|
+
Parameters
|
|
457
|
+
----------
|
|
458
|
+
epi1, epi2 : float or array-like
|
|
459
|
+
EPI patterns to compare. Can be scalars or vectors.
|
|
460
|
+
metric : {'euclidean', 'cosine', 'correlation'}
|
|
461
|
+
Distance/similarity metric to use
|
|
462
|
+
|
|
463
|
+
Returns
|
|
464
|
+
-------
|
|
465
|
+
float
|
|
466
|
+
Similarity score in [0, 1], where 1 indicates identical patterns
|
|
467
|
+
|
|
468
|
+
Notes
|
|
469
|
+
-----
|
|
470
|
+
This function is fundamental to REMESH's structural memory capability,
|
|
471
|
+
enabling pattern recognition across network scales.
|
|
472
|
+
|
|
473
|
+
Examples
|
|
474
|
+
--------
|
|
475
|
+
>>> structural_similarity(0.5, 0.52) # Nearly identical scalars
|
|
476
|
+
0.98
|
|
477
|
+
>>> structural_similarity([0.5, 0.3], [0.52, 0.31]) # Similar vectors
|
|
478
|
+
0.97
|
|
479
|
+
"""
|
|
480
|
+
# Convert to numpy arrays for consistent handling
|
|
481
|
+
np = _get_numpy()
|
|
482
|
+
if np is None:
|
|
483
|
+
# Fallback: scalar comparison only
|
|
484
|
+
if isinstance(epi1, (list, tuple)) or isinstance(epi2, (list, tuple)):
|
|
485
|
+
raise ImportError(
|
|
486
|
+
"NumPy required for vector EPI comparison. "
|
|
487
|
+
"Install numpy: pip install numpy"
|
|
488
|
+
)
|
|
489
|
+
# Simple scalar distance -> similarity
|
|
490
|
+
distance = abs(float(epi1) - float(epi2))
|
|
491
|
+
# Map distance to similarity using exponential decay
|
|
492
|
+
# similarity = exp(-k * distance) where k controls sensitivity
|
|
493
|
+
import math
|
|
494
|
+
k = 5.0 # Sensitivity parameter (higher = stricter matching)
|
|
495
|
+
return math.exp(-k * distance)
|
|
496
|
+
|
|
497
|
+
# NumPy available - use vector operations
|
|
498
|
+
arr1 = np.atleast_1d(np.asarray(epi1, dtype=float))
|
|
499
|
+
arr2 = np.atleast_1d(np.asarray(epi2, dtype=float))
|
|
500
|
+
|
|
501
|
+
if arr1.shape != arr2.shape:
|
|
502
|
+
raise ValueError(
|
|
503
|
+
f"EPI patterns must have same shape: {arr1.shape} vs {arr2.shape}"
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
if metric == "euclidean":
|
|
507
|
+
distance = np.linalg.norm(arr1 - arr2)
|
|
508
|
+
# Normalize by dimension for fair comparison across scales
|
|
509
|
+
distance /= np.sqrt(len(arr1))
|
|
510
|
+
# Convert to similarity
|
|
511
|
+
import math
|
|
512
|
+
k = 5.0
|
|
513
|
+
return float(math.exp(-k * distance))
|
|
514
|
+
|
|
515
|
+
elif metric == "cosine":
|
|
516
|
+
# Cosine similarity: (a · b) / (||a|| ||b||)
|
|
517
|
+
dot_product = np.dot(arr1, arr2)
|
|
518
|
+
norm1 = np.linalg.norm(arr1)
|
|
519
|
+
norm2 = np.linalg.norm(arr2)
|
|
520
|
+
if norm1 < 1e-10 or norm2 < 1e-10:
|
|
521
|
+
return 0.0 # Zero vectors have no meaningful similarity
|
|
522
|
+
similarity = dot_product / (norm1 * norm2)
|
|
523
|
+
# Map [-1, 1] to [0, 1]
|
|
524
|
+
return float((similarity + 1.0) / 2.0)
|
|
525
|
+
|
|
526
|
+
elif metric == "correlation":
|
|
527
|
+
# Pearson correlation coefficient
|
|
528
|
+
if len(arr1) < 2:
|
|
529
|
+
# Fall back to euclidean for scalars
|
|
530
|
+
return structural_similarity(epi1, epi2, metric="euclidean")
|
|
531
|
+
corr_matrix = np.corrcoef(arr1, arr2)
|
|
532
|
+
correlation = corr_matrix[0, 1]
|
|
533
|
+
# Handle NaN (constant arrays)
|
|
534
|
+
if not np.isfinite(correlation):
|
|
535
|
+
correlation = 1.0 if np.allclose(arr1, arr2) else 0.0
|
|
536
|
+
# Map [-1, 1] to [0, 1]
|
|
537
|
+
return float((correlation + 1.0) / 2.0)
|
|
538
|
+
|
|
539
|
+
else:
|
|
540
|
+
raise ValueError(
|
|
541
|
+
f"Unknown metric '{metric}'. Choose from: euclidean, cosine, correlation"
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
def structural_memory_match(
|
|
546
|
+
G: CommunityGraph,
|
|
547
|
+
source_node: Hashable,
|
|
548
|
+
target_nodes: Iterable[Hashable] | None = None,
|
|
549
|
+
*,
|
|
550
|
+
threshold: float = 0.75,
|
|
551
|
+
metric: str = "euclidean",
|
|
552
|
+
) -> list[tuple[Hashable, float]]:
|
|
553
|
+
"""Identify nodes with EPI patterns similar to source node.
|
|
554
|
+
|
|
555
|
+
Implements REMESH's structural memory: recognizing coherent patterns
|
|
556
|
+
across the network that resonate with the source pattern.
|
|
557
|
+
|
|
558
|
+
Parameters
|
|
559
|
+
----------
|
|
560
|
+
G : TNFRGraph
|
|
561
|
+
Network containing nodes with EPI attributes
|
|
562
|
+
source_node : Hashable
|
|
563
|
+
Node whose pattern to match against
|
|
564
|
+
target_nodes : Iterable, optional
|
|
565
|
+
Nodes to search. If None, searches all nodes except source.
|
|
566
|
+
threshold : float, default=0.75
|
|
567
|
+
Minimum similarity score for a match
|
|
568
|
+
metric : str, default='euclidean'
|
|
569
|
+
Similarity metric (see structural_similarity)
|
|
570
|
+
|
|
571
|
+
Returns
|
|
572
|
+
-------
|
|
573
|
+
list of (node, similarity) tuples
|
|
574
|
+
Nodes matching the source pattern, sorted by similarity (highest first)
|
|
575
|
+
|
|
576
|
+
Notes
|
|
577
|
+
-----
|
|
578
|
+
This function is critical for REMESH's ability to propagate patterns
|
|
579
|
+
coherently across scales, as specified in TNFR theoretical foundation.
|
|
580
|
+
"""
|
|
581
|
+
source_epi = _as_float(get_attr(G.nodes[source_node], ALIAS_EPI, 0.0))
|
|
582
|
+
|
|
583
|
+
if target_nodes is None:
|
|
584
|
+
target_nodes = [n for n in G.nodes() if n != source_node]
|
|
585
|
+
|
|
586
|
+
matches = []
|
|
587
|
+
for target in target_nodes:
|
|
588
|
+
if target == source_node:
|
|
589
|
+
continue
|
|
590
|
+
target_epi = _as_float(get_attr(G.nodes[target], ALIAS_EPI, 0.0))
|
|
591
|
+
similarity = structural_similarity(source_epi, target_epi, metric=metric)
|
|
592
|
+
if similarity >= threshold:
|
|
593
|
+
matches.append((target, similarity))
|
|
594
|
+
|
|
595
|
+
# Sort by similarity descending
|
|
596
|
+
matches.sort(key=lambda x: x[1], reverse=True)
|
|
597
|
+
return matches
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
def compute_structural_signature(
|
|
601
|
+
G: CommunityGraph,
|
|
602
|
+
node: Hashable,
|
|
603
|
+
) -> Any:
|
|
604
|
+
"""Compute multidimensional structural signature of a node.
|
|
605
|
+
|
|
606
|
+
The signature captures the node's complete structural identity including:
|
|
607
|
+
- EPI: coherence magnitude
|
|
608
|
+
- νf: structural frequency
|
|
609
|
+
- θ: phase
|
|
610
|
+
- ΔNFR: reorganization gradient
|
|
611
|
+
- Topological properties: degree, local clustering
|
|
612
|
+
|
|
613
|
+
This signature enables REMESH's structural memory capability - recognizing
|
|
614
|
+
the same pattern across different nodes and scales.
|
|
615
|
+
|
|
616
|
+
Parameters
|
|
617
|
+
----------
|
|
618
|
+
G : TNFRGraph
|
|
619
|
+
Network containing the node
|
|
620
|
+
node : Hashable
|
|
621
|
+
Node identifier whose signature to compute
|
|
622
|
+
|
|
623
|
+
Returns
|
|
624
|
+
-------
|
|
625
|
+
signature : ndarray, shape (n_features,)
|
|
626
|
+
Normalized structural signature vector. If NumPy unavailable, returns
|
|
627
|
+
a tuple of features.
|
|
628
|
+
|
|
629
|
+
Notes
|
|
630
|
+
-----
|
|
631
|
+
The signature is normalized to unit length for consistent similarity
|
|
632
|
+
comparisons across different network scales and configurations.
|
|
633
|
+
|
|
634
|
+
Features included:
|
|
635
|
+
1. EPI magnitude (coherence)
|
|
636
|
+
2. νf (structural frequency in Hz_str)
|
|
637
|
+
3. sin(θ), cos(θ) (circular phase representation)
|
|
638
|
+
4. ΔNFR (reorganization gradient)
|
|
639
|
+
5. Normalized degree (relative connectivity)
|
|
640
|
+
6. Local clustering coefficient (triadic closure)
|
|
641
|
+
|
|
642
|
+
Examples
|
|
643
|
+
--------
|
|
644
|
+
>>> G = nx.Graph()
|
|
645
|
+
>>> G.add_node(1, EPI=0.5, vf=1.0, theta=0.2, DNFR=0.1)
|
|
646
|
+
>>> G.add_edge(1, 2)
|
|
647
|
+
>>> sig = compute_structural_signature(G, 1)
|
|
648
|
+
>>> len(sig) # 7 features
|
|
649
|
+
7
|
|
650
|
+
"""
|
|
651
|
+
# Extract TNFR structural attributes
|
|
652
|
+
epi = _as_float(get_attr(G.nodes[node], ALIAS_EPI, 0.0))
|
|
653
|
+
vf = _as_float(get_attr(G.nodes[node], ALIAS_VF, 0.0))
|
|
654
|
+
from ..constants.aliases import ALIAS_THETA
|
|
655
|
+
theta = _as_float(get_attr(G.nodes[node], ALIAS_THETA, 0.0))
|
|
656
|
+
dnfr = _as_float(get_attr(G.nodes[node], ALIAS_DNFR, 0.0))
|
|
657
|
+
|
|
658
|
+
# Compute topological features
|
|
659
|
+
degree = G.degree(node) if G.has_node(node) else 0
|
|
660
|
+
n_nodes = G.number_of_nodes()
|
|
661
|
+
normalized_degree = degree / n_nodes if n_nodes > 0 else 0.0
|
|
662
|
+
|
|
663
|
+
# Local clustering coefficient (requires networkx)
|
|
664
|
+
try:
|
|
665
|
+
nx, _ = _get_networkx_modules()
|
|
666
|
+
clustering = nx.clustering(G, node)
|
|
667
|
+
except Exception:
|
|
668
|
+
clustering = 0.0
|
|
669
|
+
|
|
670
|
+
# Build feature vector
|
|
671
|
+
import math
|
|
672
|
+
features = [
|
|
673
|
+
epi,
|
|
674
|
+
vf,
|
|
675
|
+
math.sin(theta), # Circular phase representation
|
|
676
|
+
math.cos(theta),
|
|
677
|
+
dnfr,
|
|
678
|
+
normalized_degree,
|
|
679
|
+
clustering,
|
|
680
|
+
]
|
|
681
|
+
|
|
682
|
+
# Try to use NumPy for normalization
|
|
683
|
+
np = _get_numpy()
|
|
684
|
+
if np is not None:
|
|
685
|
+
signature = np.array(features, dtype=float)
|
|
686
|
+
norm = np.linalg.norm(signature)
|
|
687
|
+
if norm > 1e-10:
|
|
688
|
+
signature = signature / norm
|
|
689
|
+
return signature
|
|
690
|
+
else:
|
|
691
|
+
# Fallback: manual normalization
|
|
692
|
+
norm = math.sqrt(sum(f * f for f in features))
|
|
693
|
+
if norm > 1e-10:
|
|
694
|
+
features = [f / norm for f in features]
|
|
695
|
+
return tuple(features)
|
|
696
|
+
|
|
697
|
+
|
|
698
|
+
def detect_recursive_patterns(
|
|
699
|
+
G: CommunityGraph,
|
|
700
|
+
threshold: float = 0.75,
|
|
701
|
+
metric: str = "cosine",
|
|
702
|
+
min_cluster_size: int = 2,
|
|
703
|
+
) -> list[list[Hashable]]:
|
|
704
|
+
"""Detect groups of nodes with similar structural patterns.
|
|
705
|
+
|
|
706
|
+
These groups represent the same EPI pattern replicated across different
|
|
707
|
+
nodes/scales in the network. This is fundamental to REMESH's capability
|
|
708
|
+
to recognize and propagate structural identity.
|
|
709
|
+
|
|
710
|
+
Parameters
|
|
711
|
+
----------
|
|
712
|
+
G : TNFRGraph
|
|
713
|
+
Network to analyze
|
|
714
|
+
threshold : float, default=0.75
|
|
715
|
+
Minimum similarity score (0-1) to consider patterns as "same identity".
|
|
716
|
+
Higher values require stricter matching.
|
|
717
|
+
metric : str, default='cosine'
|
|
718
|
+
Similarity metric for signature comparison.
|
|
719
|
+
Options: 'cosine', 'euclidean', 'correlation'
|
|
720
|
+
min_cluster_size : int, default=2
|
|
721
|
+
Minimum number of nodes required to consider as a recursive pattern.
|
|
722
|
+
Single nodes are not patterns.
|
|
723
|
+
|
|
724
|
+
Returns
|
|
725
|
+
-------
|
|
726
|
+
clusters : list of list
|
|
727
|
+
Each cluster contains nodes sharing a structural pattern.
|
|
728
|
+
Clusters are independent - nodes appear in at most one cluster.
|
|
729
|
+
|
|
730
|
+
Notes
|
|
731
|
+
-----
|
|
732
|
+
Algorithm uses greedy clustering:
|
|
733
|
+
1. Compute structural signatures for all nodes
|
|
734
|
+
2. For each unvisited node, find all similar nodes (similarity >= threshold)
|
|
735
|
+
3. Form cluster if size >= min_cluster_size
|
|
736
|
+
4. Mark all cluster members as visited
|
|
737
|
+
|
|
738
|
+
This implements TNFR's principle: "A node can recognize itself in other
|
|
739
|
+
nodes" through structural resonance.
|
|
740
|
+
|
|
741
|
+
Examples
|
|
742
|
+
--------
|
|
743
|
+
>>> # Network with two groups of similar nodes
|
|
744
|
+
>>> clusters = detect_recursive_patterns(G, threshold=0.8)
|
|
745
|
+
>>> len(clusters)
|
|
746
|
+
2
|
|
747
|
+
>>> all(len(c) >= 2 for c in clusters)
|
|
748
|
+
True
|
|
749
|
+
"""
|
|
750
|
+
nodes = list(G.nodes())
|
|
751
|
+
n = len(nodes)
|
|
752
|
+
|
|
753
|
+
if n < min_cluster_size:
|
|
754
|
+
return []
|
|
755
|
+
|
|
756
|
+
# Compute all signatures
|
|
757
|
+
signatures = {}
|
|
758
|
+
for node in nodes:
|
|
759
|
+
signatures[node] = compute_structural_signature(G, node)
|
|
760
|
+
|
|
761
|
+
# Compute similarity matrix (only upper triangle needed)
|
|
762
|
+
np = _get_numpy()
|
|
763
|
+
if np is not None:
|
|
764
|
+
# Use NumPy for efficient computation
|
|
765
|
+
sig_array = np.array([signatures[node] for node in nodes])
|
|
766
|
+
|
|
767
|
+
if metric == "cosine":
|
|
768
|
+
# Cosine similarity matrix
|
|
769
|
+
norms = np.linalg.norm(sig_array, axis=1, keepdims=True)
|
|
770
|
+
norms = np.maximum(norms, 1e-10) # Avoid division by zero
|
|
771
|
+
normalized = sig_array / norms
|
|
772
|
+
similarities = np.dot(normalized, normalized.T)
|
|
773
|
+
elif metric == "euclidean":
|
|
774
|
+
# Euclidean distance -> similarity
|
|
775
|
+
from scipy.spatial.distance import pdist, squareform
|
|
776
|
+
distances = squareform(pdist(sig_array, metric='euclidean'))
|
|
777
|
+
max_dist = np.sqrt(2) # Max distance for unit vectors
|
|
778
|
+
similarities = 1.0 - (distances / max_dist)
|
|
779
|
+
elif metric == "correlation":
|
|
780
|
+
# Pearson correlation
|
|
781
|
+
similarities = np.corrcoef(sig_array)
|
|
782
|
+
# Handle NaN (constant signatures)
|
|
783
|
+
similarities = np.nan_to_num(similarities, nan=0.0)
|
|
784
|
+
else:
|
|
785
|
+
raise ValueError(f"Unknown metric: {metric}")
|
|
786
|
+
else:
|
|
787
|
+
# Fallback: pairwise computation
|
|
788
|
+
similarities = {}
|
|
789
|
+
for i, node1 in enumerate(nodes):
|
|
790
|
+
for j, node2 in enumerate(nodes):
|
|
791
|
+
if i == j:
|
|
792
|
+
similarities[(i, j)] = 1.0
|
|
793
|
+
elif i < j:
|
|
794
|
+
# Use existing structural_similarity function
|
|
795
|
+
sim = structural_similarity(
|
|
796
|
+
signatures[node1],
|
|
797
|
+
signatures[node2],
|
|
798
|
+
metric=metric
|
|
799
|
+
)
|
|
800
|
+
similarities[(i, j)] = sim
|
|
801
|
+
similarities[(j, i)] = sim
|
|
802
|
+
|
|
803
|
+
# Greedy clustering
|
|
804
|
+
visited = set()
|
|
805
|
+
clusters = []
|
|
806
|
+
|
|
807
|
+
for i, node in enumerate(nodes):
|
|
808
|
+
if node in visited:
|
|
809
|
+
continue
|
|
810
|
+
|
|
811
|
+
# Find all similar nodes
|
|
812
|
+
cluster = [node]
|
|
813
|
+
visited.add(node)
|
|
814
|
+
|
|
815
|
+
for j, other_node in enumerate(nodes):
|
|
816
|
+
if other_node in visited:
|
|
817
|
+
continue
|
|
818
|
+
|
|
819
|
+
# Get similarity
|
|
820
|
+
if np is not None:
|
|
821
|
+
sim = similarities[i, j]
|
|
822
|
+
else:
|
|
823
|
+
sim = similarities.get((i, j), 0.0)
|
|
824
|
+
|
|
825
|
+
if sim >= threshold:
|
|
826
|
+
cluster.append(other_node)
|
|
827
|
+
visited.add(other_node)
|
|
828
|
+
|
|
829
|
+
# Only keep clusters meeting minimum size
|
|
830
|
+
if len(cluster) >= min_cluster_size:
|
|
831
|
+
clusters.append(cluster)
|
|
832
|
+
|
|
833
|
+
return clusters
|
|
834
|
+
|
|
835
|
+
|
|
836
|
+
def identify_pattern_origin(
|
|
837
|
+
G: CommunityGraph,
|
|
838
|
+
cluster: Sequence[Hashable],
|
|
839
|
+
) -> Hashable | None:
|
|
840
|
+
"""Identify the origin node of a recursive structural pattern.
|
|
841
|
+
|
|
842
|
+
The origin is the node with the strongest structural manifestation,
|
|
843
|
+
determined by combining coherence (EPI) and reorganization capacity (νf).
|
|
844
|
+
This represents the "source" from which the pattern can most coherently
|
|
845
|
+
propagate.
|
|
846
|
+
|
|
847
|
+
Parameters
|
|
848
|
+
----------
|
|
849
|
+
G : TNFRGraph
|
|
850
|
+
Network containing the cluster
|
|
851
|
+
cluster : sequence of node identifiers
|
|
852
|
+
Nodes sharing the same structural pattern
|
|
853
|
+
|
|
854
|
+
Returns
|
|
855
|
+
-------
|
|
856
|
+
origin_node : Hashable or None
|
|
857
|
+
Node identified as pattern origin (highest structural strength).
|
|
858
|
+
Returns None if cluster is empty.
|
|
859
|
+
|
|
860
|
+
Notes
|
|
861
|
+
-----
|
|
862
|
+
Structural strength score = EPI × νf
|
|
863
|
+
|
|
864
|
+
This metric captures:
|
|
865
|
+
- EPI: How coherent the pattern is (magnitude)
|
|
866
|
+
- νf: How actively the pattern reorganizes (frequency)
|
|
867
|
+
|
|
868
|
+
High score indicates a node that both maintains strong coherence AND
|
|
869
|
+
has high reorganization capacity, making it ideal for propagation.
|
|
870
|
+
|
|
871
|
+
Physical interpretation: The origin node is the "loudest" instance of
|
|
872
|
+
the pattern in the network's structural field.
|
|
873
|
+
|
|
874
|
+
Examples
|
|
875
|
+
--------
|
|
876
|
+
>>> cluster = [1, 2, 3] # Nodes with similar patterns
|
|
877
|
+
>>> origin = identify_pattern_origin(G, cluster)
|
|
878
|
+
>>> # Origin will be node with highest EPI × νf
|
|
879
|
+
"""
|
|
880
|
+
if not cluster:
|
|
881
|
+
return None
|
|
882
|
+
|
|
883
|
+
# Compute structural strength for each node
|
|
884
|
+
scores = []
|
|
885
|
+
for node in cluster:
|
|
886
|
+
epi = _as_float(get_attr(G.nodes[node], ALIAS_EPI, 0.0))
|
|
887
|
+
vf = _as_float(get_attr(G.nodes[node], ALIAS_VF, 0.0))
|
|
888
|
+
# Structural strength = coherence × reorganization capacity
|
|
889
|
+
strength = epi * vf
|
|
890
|
+
scores.append((strength, node))
|
|
891
|
+
|
|
892
|
+
# Return node with maximum strength
|
|
893
|
+
scores.sort(reverse=True)
|
|
894
|
+
return scores[0][1]
|
|
895
|
+
|
|
896
|
+
|
|
897
|
+
def propagate_structural_identity(
|
|
898
|
+
G: CommunityGraph,
|
|
899
|
+
origin_node: Hashable,
|
|
900
|
+
target_nodes: Sequence[Hashable],
|
|
901
|
+
propagation_strength: float = 0.5,
|
|
902
|
+
) -> None:
|
|
903
|
+
"""Propagate structural identity from origin to similar nodes.
|
|
904
|
+
|
|
905
|
+
This implements REMESH's core principle: nodes with similar patterns
|
|
906
|
+
mutually reinforce their coherence through structural resonance.
|
|
907
|
+
The origin's pattern is propagated to targets via weighted interpolation.
|
|
908
|
+
|
|
909
|
+
Parameters
|
|
910
|
+
----------
|
|
911
|
+
G : TNFRGraph
|
|
912
|
+
Network to modify (changes node attributes in-place)
|
|
913
|
+
origin_node : Hashable
|
|
914
|
+
Source node whose pattern to propagate
|
|
915
|
+
target_nodes : sequence
|
|
916
|
+
Nodes that will receive pattern reinforcement
|
|
917
|
+
propagation_strength : float, default=0.5
|
|
918
|
+
Interpolation weight in [0, 1]:
|
|
919
|
+
- 0.0: No effect (targets unchanged)
|
|
920
|
+
- 1.0: Complete copy (targets become identical to origin)
|
|
921
|
+
- 0.5: Balanced blending
|
|
922
|
+
|
|
923
|
+
Notes
|
|
924
|
+
-----
|
|
925
|
+
For each target node, updates:
|
|
926
|
+
- EPI: new = (1-α)×old + α×origin
|
|
927
|
+
- νf: new = (1-α)×old + α×origin
|
|
928
|
+
- θ: new = (1-α)×old + α×origin
|
|
929
|
+
|
|
930
|
+
Where α = propagation_strength.
|
|
931
|
+
|
|
932
|
+
Updates respect structural boundaries (EPI_MIN, EPI_MAX) via
|
|
933
|
+
structural_clip to prevent overflow and maintain physical validity.
|
|
934
|
+
|
|
935
|
+
Records propagation in node's 'structural_lineage' attribute for
|
|
936
|
+
traceability and analysis.
|
|
937
|
+
|
|
938
|
+
**TNFR Physics**: This preserves the nodal equation ∂EPI/∂t = νf·ΔNFR
|
|
939
|
+
by interpolating the structural state rather than imposing it directly.
|
|
940
|
+
|
|
941
|
+
Examples
|
|
942
|
+
--------
|
|
943
|
+
>>> origin = 1
|
|
944
|
+
>>> targets = [2, 3, 4] # Similar nodes
|
|
945
|
+
>>> propagate_structural_identity(G, origin, targets, 0.3)
|
|
946
|
+
>>> # Targets now have patterns 30% closer to origin
|
|
947
|
+
"""
|
|
948
|
+
# Get origin pattern
|
|
949
|
+
origin_epi = _as_float(get_attr(G.nodes[origin_node], ALIAS_EPI, 0.0))
|
|
950
|
+
origin_vf = _as_float(get_attr(G.nodes[origin_node], ALIAS_VF, 0.0))
|
|
951
|
+
from ..constants.aliases import ALIAS_THETA
|
|
952
|
+
origin_theta = _as_float(get_attr(G.nodes[origin_node], ALIAS_THETA, 0.0))
|
|
953
|
+
|
|
954
|
+
# Get structural bounds
|
|
955
|
+
from ..constants import DEFAULTS
|
|
956
|
+
epi_min = float(G.graph.get("EPI_MIN", DEFAULTS.get("EPI_MIN", -1.0)))
|
|
957
|
+
epi_max = float(G.graph.get("EPI_MAX", DEFAULTS.get("EPI_MAX", 1.0)))
|
|
958
|
+
|
|
959
|
+
# Get clip mode
|
|
960
|
+
clip_mode_str = str(G.graph.get("CLIP_MODE", "hard"))
|
|
961
|
+
if clip_mode_str not in ("hard", "soft"):
|
|
962
|
+
clip_mode_str = "hard"
|
|
963
|
+
|
|
964
|
+
# Propagate to each target
|
|
965
|
+
for target in target_nodes:
|
|
966
|
+
if target == origin_node:
|
|
967
|
+
continue # Don't propagate to self
|
|
968
|
+
|
|
969
|
+
# Get current target state
|
|
970
|
+
target_epi = _as_float(get_attr(G.nodes[target], ALIAS_EPI, 0.0))
|
|
971
|
+
target_vf = _as_float(get_attr(G.nodes[target], ALIAS_VF, 0.0))
|
|
972
|
+
target_theta = _as_float(get_attr(G.nodes[target], ALIAS_THETA, 0.0))
|
|
973
|
+
|
|
974
|
+
# Interpolate toward origin pattern
|
|
975
|
+
new_epi = (1.0 - propagation_strength) * target_epi + propagation_strength * origin_epi
|
|
976
|
+
new_vf = (1.0 - propagation_strength) * target_vf + propagation_strength * origin_vf
|
|
977
|
+
new_theta = (1.0 - propagation_strength) * target_theta + propagation_strength * origin_theta
|
|
978
|
+
|
|
979
|
+
# Apply structural clipping to preserve boundaries
|
|
980
|
+
from ..dynamics.structural_clip import structural_clip
|
|
981
|
+
new_epi = structural_clip(new_epi, lo=epi_min, hi=epi_max, mode=clip_mode_str) # type: ignore[arg-type]
|
|
982
|
+
|
|
983
|
+
# Update node attributes
|
|
984
|
+
set_attr(G.nodes[target], ALIAS_EPI, new_epi)
|
|
985
|
+
set_attr(G.nodes[target], ALIAS_VF, new_vf)
|
|
986
|
+
set_attr(G.nodes[target], ALIAS_THETA, new_theta)
|
|
987
|
+
|
|
988
|
+
# Record lineage for traceability
|
|
989
|
+
if 'structural_lineage' not in G.nodes[target]:
|
|
990
|
+
G.nodes[target]['structural_lineage'] = []
|
|
991
|
+
|
|
992
|
+
# Get current step for timestamp
|
|
993
|
+
from ..glyph_history import current_step_idx
|
|
994
|
+
step = current_step_idx(G)
|
|
995
|
+
|
|
996
|
+
G.nodes[target]['structural_lineage'].append({
|
|
997
|
+
'origin': origin_node,
|
|
998
|
+
'step': step,
|
|
999
|
+
'propagation_strength': propagation_strength,
|
|
1000
|
+
'epi_before': target_epi,
|
|
1001
|
+
'epi_after': new_epi,
|
|
1002
|
+
})
|
|
1003
|
+
|
|
1004
|
+
|
|
1005
|
+
@cache
|
|
1006
|
+
def _get_numpy() -> ModuleType | None:
|
|
1007
|
+
"""Get numpy module if available, None otherwise."""
|
|
1008
|
+
return cached_import("numpy")
|
|
1009
|
+
|
|
1010
|
+
|
|
1011
|
+
# ==============================================================================
|
|
1012
|
+
# Phase 2: Coherence Preservation & Fidelity Validation
|
|
1013
|
+
# ==============================================================================
|
|
1014
|
+
|
|
1015
|
+
|
|
1016
|
+
class RemeshCoherenceLossError(Exception):
|
|
1017
|
+
"""Raised when REMESH reorganization loses structural coherence.
|
|
1018
|
+
|
|
1019
|
+
REMESH must preserve coherence during reorganization. This error indicates
|
|
1020
|
+
that the structural fidelity dropped below acceptable thresholds, violating
|
|
1021
|
+
TNFR's requirement that "coherence propagates structurally, not imposed."
|
|
1022
|
+
"""
|
|
1023
|
+
|
|
1024
|
+
def __init__(self, fidelity: float, min_fidelity: float, details: dict[str, Any] | None = None):
|
|
1025
|
+
"""Initialize coherence loss error.
|
|
1026
|
+
|
|
1027
|
+
Parameters
|
|
1028
|
+
----------
|
|
1029
|
+
fidelity : float
|
|
1030
|
+
Measured structural fidelity (coherence_after / coherence_before)
|
|
1031
|
+
min_fidelity : float
|
|
1032
|
+
Minimum required fidelity threshold
|
|
1033
|
+
details : dict, optional
|
|
1034
|
+
Additional diagnostic information
|
|
1035
|
+
"""
|
|
1036
|
+
self.fidelity = fidelity
|
|
1037
|
+
self.min_fidelity = min_fidelity
|
|
1038
|
+
self.details = details or {}
|
|
1039
|
+
|
|
1040
|
+
super().__init__(
|
|
1041
|
+
f"REMESH coherence loss: structural fidelity {fidelity:.2%} "
|
|
1042
|
+
f"< minimum {min_fidelity:.2%}\n"
|
|
1043
|
+
f" Details: {details}"
|
|
1044
|
+
)
|
|
1045
|
+
|
|
1046
|
+
|
|
1047
|
+
def validate_coherence_preservation(
|
|
1048
|
+
G_before: CommunityGraph,
|
|
1049
|
+
G_after: CommunityGraph,
|
|
1050
|
+
*,
|
|
1051
|
+
min_fidelity: float = 0.85,
|
|
1052
|
+
rollback_on_failure: bool = False,
|
|
1053
|
+
) -> float:
|
|
1054
|
+
"""Validate that reorganization preserved structural coherence.
|
|
1055
|
+
|
|
1056
|
+
Implements TNFR requirement that REMESH reorganization must occur
|
|
1057
|
+
"without loss of coherence" - the total structural stability must
|
|
1058
|
+
be maintained within acceptable bounds.
|
|
1059
|
+
|
|
1060
|
+
This function uses the canonical TNFR coherence computation from
|
|
1061
|
+
tnfr.metrics.common.compute_coherence() which is based on ΔNFR and dEPI.
|
|
1062
|
+
|
|
1063
|
+
Parameters
|
|
1064
|
+
----------
|
|
1065
|
+
G_before : TNFRGraph
|
|
1066
|
+
Network state before reorganization
|
|
1067
|
+
G_after : TNFRGraph
|
|
1068
|
+
Network state after reorganization
|
|
1069
|
+
min_fidelity : float, default=0.85
|
|
1070
|
+
Minimum acceptable fidelity (coherence_after / coherence_before)
|
|
1071
|
+
rollback_on_failure : bool, default=False
|
|
1072
|
+
If True and fidelity check fails, raise RemeshCoherenceLossError
|
|
1073
|
+
|
|
1074
|
+
Returns
|
|
1075
|
+
-------
|
|
1076
|
+
float
|
|
1077
|
+
Structural fidelity score (coherence_after / coherence_before)
|
|
1078
|
+
|
|
1079
|
+
Raises
|
|
1080
|
+
------
|
|
1081
|
+
RemeshCoherenceLossError
|
|
1082
|
+
If fidelity < min_fidelity and rollback_on_failure=True
|
|
1083
|
+
|
|
1084
|
+
Notes
|
|
1085
|
+
-----
|
|
1086
|
+
Structural fidelity ≈ 1.0 indicates perfect coherence preservation.
|
|
1087
|
+
Fidelity > 1.0 is possible (reorganization increased coherence).
|
|
1088
|
+
Fidelity < min_fidelity indicates unacceptable coherence loss.
|
|
1089
|
+
|
|
1090
|
+
Uses canonical TNFR coherence: C = 1/(1 + |ΔNFR|_mean + |dEPI|_mean)
|
|
1091
|
+
from tnfr.metrics.common module.
|
|
1092
|
+
"""
|
|
1093
|
+
# Use canonical TNFR coherence computation
|
|
1094
|
+
from ..metrics.common import compute_coherence
|
|
1095
|
+
|
|
1096
|
+
coherence_before = compute_coherence(G_before)
|
|
1097
|
+
coherence_after = compute_coherence(G_after)
|
|
1098
|
+
|
|
1099
|
+
if coherence_before < 1e-10:
|
|
1100
|
+
# Edge case: network had no coherence to begin with
|
|
1101
|
+
return 1.0
|
|
1102
|
+
|
|
1103
|
+
structural_fidelity = coherence_after / coherence_before
|
|
1104
|
+
|
|
1105
|
+
if rollback_on_failure and structural_fidelity < min_fidelity:
|
|
1106
|
+
details = {
|
|
1107
|
+
"coherence_before": coherence_before,
|
|
1108
|
+
"coherence_after": coherence_after,
|
|
1109
|
+
"n_nodes_before": G_before.number_of_nodes(),
|
|
1110
|
+
"n_nodes_after": G_after.number_of_nodes(),
|
|
1111
|
+
}
|
|
1112
|
+
raise RemeshCoherenceLossError(structural_fidelity, min_fidelity, details)
|
|
1113
|
+
|
|
1114
|
+
return structural_fidelity
|
|
1115
|
+
|
|
1116
|
+
|
|
1117
|
+
# ==============================================================================
|
|
1118
|
+
# Original Helper Functions
|
|
1119
|
+
# ==============================================================================
|
|
1120
|
+
|
|
1121
|
+
|
|
1122
|
+
def _as_float(value: Any, default: float = 0.0) -> float:
|
|
1123
|
+
"""Best-effort conversion to ``float`` returning ``default`` on failure."""
|
|
1124
|
+
|
|
1125
|
+
if value is None:
|
|
1126
|
+
return default
|
|
1127
|
+
try:
|
|
1128
|
+
return float(value)
|
|
1129
|
+
except (TypeError, ValueError):
|
|
1130
|
+
return default
|
|
1131
|
+
|
|
1132
|
+
|
|
1133
|
+
def _ordered_edge(u: Hashable, v: Hashable) -> RemeshEdge:
|
|
1134
|
+
"""Return a deterministic ordering for an undirected edge."""
|
|
1135
|
+
|
|
1136
|
+
return (u, v) if repr(u) <= repr(v) else (v, u)
|
|
1137
|
+
|
|
1138
|
+
|
|
1139
|
+
COOLDOWN_KEY = "REMESH_COOLDOWN_WINDOW"
|
|
1140
|
+
|
|
1141
|
+
|
|
1142
|
+
@cache
|
|
1143
|
+
def _get_networkx_modules() -> NetworkxModules:
|
|
1144
|
+
nx = cached_import("networkx")
|
|
1145
|
+
if nx is None:
|
|
1146
|
+
raise ImportError(
|
|
1147
|
+
"networkx is required for network operators; install 'networkx' "
|
|
1148
|
+
"to enable this feature"
|
|
1149
|
+
)
|
|
1150
|
+
nx_comm = cached_import("networkx.algorithms", "community")
|
|
1151
|
+
if nx_comm is None:
|
|
1152
|
+
raise ImportError(
|
|
1153
|
+
"networkx.algorithms.community is required for community-based "
|
|
1154
|
+
"operations; install 'networkx' to enable this feature"
|
|
1155
|
+
)
|
|
1156
|
+
return cast(NetworkxModule, nx), cast(CommunityModule, nx_comm)
|
|
1157
|
+
|
|
1158
|
+
|
|
1159
|
+
def _remesh_alpha_info(G: CommunityGraph) -> tuple[float, str]:
|
|
1160
|
+
"""Return ``(alpha, source)`` with explicit precedence."""
|
|
1161
|
+
if bool(G.graph.get("REMESH_ALPHA_HARD", REMESH_DEFAULTS["REMESH_ALPHA_HARD"])):
|
|
1162
|
+
val = _as_float(
|
|
1163
|
+
G.graph.get("REMESH_ALPHA", REMESH_DEFAULTS["REMESH_ALPHA"]),
|
|
1164
|
+
float(REMESH_DEFAULTS["REMESH_ALPHA"]),
|
|
1165
|
+
)
|
|
1166
|
+
return val, "REMESH_ALPHA"
|
|
1167
|
+
gf = G.graph.get("GLYPH_FACTORS", DEFAULTS.get("GLYPH_FACTORS", {}))
|
|
1168
|
+
if "REMESH_alpha" in gf:
|
|
1169
|
+
return _as_float(gf["REMESH_alpha"]), "GLYPH_FACTORS.REMESH_alpha"
|
|
1170
|
+
if "REMESH_ALPHA" in G.graph:
|
|
1171
|
+
return _as_float(G.graph["REMESH_ALPHA"]), "REMESH_ALPHA"
|
|
1172
|
+
return (
|
|
1173
|
+
float(REMESH_DEFAULTS["REMESH_ALPHA"]),
|
|
1174
|
+
"REMESH_DEFAULTS.REMESH_ALPHA",
|
|
1175
|
+
)
|
|
1176
|
+
|
|
1177
|
+
|
|
1178
|
+
def _snapshot_topology(G: CommunityGraph, nx: NetworkxModule) -> str | None:
|
|
1179
|
+
"""Return a hash representing the current graph topology."""
|
|
1180
|
+
try:
|
|
1181
|
+
n_nodes = G.number_of_nodes()
|
|
1182
|
+
n_edges = G.number_of_edges()
|
|
1183
|
+
degs = sorted(d for _, d in G.degree())
|
|
1184
|
+
topo_str = f"n={n_nodes};m={n_edges};deg=" + ",".join(map(str, degs))
|
|
1185
|
+
return hashlib.blake2b(topo_str.encode(), digest_size=6).hexdigest()
|
|
1186
|
+
except (AttributeError, TypeError, nx.NetworkXError):
|
|
1187
|
+
return None
|
|
1188
|
+
|
|
1189
|
+
|
|
1190
|
+
def _snapshot_epi(G: CommunityGraph) -> tuple[float, str]:
|
|
1191
|
+
"""Return ``(mean, checksum)`` of the node EPI values."""
|
|
1192
|
+
buf = StringIO()
|
|
1193
|
+
values = []
|
|
1194
|
+
for n, data in G.nodes(data=True):
|
|
1195
|
+
v = _as_float(get_attr(data, ALIAS_EPI, 0.0))
|
|
1196
|
+
values.append(v)
|
|
1197
|
+
buf.write(f"{str(n)}:{round(v, 6)};")
|
|
1198
|
+
total = kahan_sum_nd(((v,) for v in values), dims=1)[0]
|
|
1199
|
+
mean_val = total / len(values) if values else 0.0
|
|
1200
|
+
checksum = hashlib.blake2b(buf.getvalue().encode(), digest_size=6).hexdigest()
|
|
1201
|
+
return float(mean_val), checksum
|
|
1202
|
+
|
|
1203
|
+
|
|
1204
|
+
def _log_remesh_event(G: CommunityGraph, meta: RemeshMeta) -> None:
|
|
1205
|
+
"""Store remesh metadata and optionally log and trigger callbacks."""
|
|
1206
|
+
from ..utils import CallbackEvent, callback_manager
|
|
1207
|
+
from ..glyph_history import append_metric
|
|
1208
|
+
|
|
1209
|
+
G.graph["_REMESH_META"] = meta
|
|
1210
|
+
if G.graph.get("REMESH_LOG_EVENTS", REMESH_DEFAULTS["REMESH_LOG_EVENTS"]):
|
|
1211
|
+
hist = G.graph.setdefault("history", {})
|
|
1212
|
+
append_metric(hist, "remesh_events", dict(meta))
|
|
1213
|
+
callback_manager.invoke_callbacks(G, CallbackEvent.ON_REMESH.value, dict(meta))
|
|
1214
|
+
|
|
1215
|
+
|
|
1216
|
+
def apply_network_remesh(G: CommunityGraph) -> None:
|
|
1217
|
+
"""Network-scale REMESH using ``_epi_hist`` with multi-scale memory."""
|
|
1218
|
+
from ..glyph_history import current_step_idx, ensure_history
|
|
1219
|
+
from ..dynamics.structural_clip import structural_clip
|
|
1220
|
+
|
|
1221
|
+
nx, _ = _get_networkx_modules()
|
|
1222
|
+
tau_g = int(get_param(G, "REMESH_TAU_GLOBAL"))
|
|
1223
|
+
tau_l = int(get_param(G, "REMESH_TAU_LOCAL"))
|
|
1224
|
+
tau_req = max(tau_g, tau_l)
|
|
1225
|
+
alpha, alpha_src = _remesh_alpha_info(G)
|
|
1226
|
+
G.graph["_REMESH_ALPHA_SRC"] = alpha_src
|
|
1227
|
+
hist = G.graph.get("_epi_hist", deque())
|
|
1228
|
+
if len(hist) < tau_req + 1:
|
|
1229
|
+
return
|
|
1230
|
+
|
|
1231
|
+
past_g = hist[-(tau_g + 1)]
|
|
1232
|
+
past_l = hist[-(tau_l + 1)]
|
|
1233
|
+
|
|
1234
|
+
topo_hash = _snapshot_topology(G, nx)
|
|
1235
|
+
epi_mean_before, epi_checksum_before = _snapshot_epi(G)
|
|
1236
|
+
|
|
1237
|
+
# Get EPI bounds for structural preservation
|
|
1238
|
+
epi_min = float(G.graph.get("EPI_MIN", DEFAULTS.get("EPI_MIN", -1.0)))
|
|
1239
|
+
epi_max = float(G.graph.get("EPI_MAX", DEFAULTS.get("EPI_MAX", 1.0)))
|
|
1240
|
+
clip_mode_str = str(G.graph.get("CLIP_MODE", "hard"))
|
|
1241
|
+
if clip_mode_str not in ("hard", "soft"):
|
|
1242
|
+
clip_mode_str = "hard"
|
|
1243
|
+
clip_mode = clip_mode_str # type: ignore[assignment]
|
|
1244
|
+
|
|
1245
|
+
for n, nd in G.nodes(data=True):
|
|
1246
|
+
epi_now = _as_float(get_attr(nd, ALIAS_EPI, 0.0))
|
|
1247
|
+
epi_old_l = _as_float(
|
|
1248
|
+
past_l.get(n) if isinstance(past_l, Mapping) else None, epi_now
|
|
1249
|
+
)
|
|
1250
|
+
epi_old_g = _as_float(
|
|
1251
|
+
past_g.get(n) if isinstance(past_g, Mapping) else None, epi_now
|
|
1252
|
+
)
|
|
1253
|
+
mixed = (1 - alpha) * epi_now + alpha * epi_old_l
|
|
1254
|
+
mixed = (1 - alpha) * mixed + alpha * epi_old_g
|
|
1255
|
+
|
|
1256
|
+
# Apply structural boundary preservation to prevent overflow
|
|
1257
|
+
mixed_clipped = structural_clip(mixed, lo=epi_min, hi=epi_max, mode=clip_mode)
|
|
1258
|
+
set_attr(nd, ALIAS_EPI, mixed_clipped)
|
|
1259
|
+
|
|
1260
|
+
epi_mean_after, epi_checksum_after = _snapshot_epi(G)
|
|
1261
|
+
|
|
1262
|
+
step_idx = current_step_idx(G)
|
|
1263
|
+
meta: RemeshMeta = {
|
|
1264
|
+
"alpha": alpha,
|
|
1265
|
+
"alpha_source": alpha_src,
|
|
1266
|
+
"tau_global": tau_g,
|
|
1267
|
+
"tau_local": tau_l,
|
|
1268
|
+
"step": step_idx,
|
|
1269
|
+
"topo_hash": topo_hash,
|
|
1270
|
+
"epi_mean_before": float(epi_mean_before),
|
|
1271
|
+
"epi_mean_after": float(epi_mean_after),
|
|
1272
|
+
"epi_checksum_before": epi_checksum_before,
|
|
1273
|
+
"epi_checksum_after": epi_checksum_after,
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
h = ensure_history(G)
|
|
1277
|
+
if h:
|
|
1278
|
+
if h.get("stable_frac"):
|
|
1279
|
+
meta["stable_frac_last"] = h["stable_frac"][-1]
|
|
1280
|
+
if h.get("phase_sync"):
|
|
1281
|
+
meta["phase_sync_last"] = h["phase_sync"][-1]
|
|
1282
|
+
if h.get("glyph_load_disr"):
|
|
1283
|
+
meta["glyph_disr_last"] = h["glyph_load_disr"][-1]
|
|
1284
|
+
|
|
1285
|
+
_log_remesh_event(G, meta)
|
|
1286
|
+
|
|
1287
|
+
|
|
1288
|
+
def apply_network_remesh_with_memory(
|
|
1289
|
+
G: CommunityGraph,
|
|
1290
|
+
*,
|
|
1291
|
+
enable_structural_memory: bool = True,
|
|
1292
|
+
similarity_threshold: float = 0.75,
|
|
1293
|
+
similarity_metric: str = "cosine",
|
|
1294
|
+
propagation_strength: float = 0.5,
|
|
1295
|
+
min_cluster_size: int = 2,
|
|
1296
|
+
) -> None:
|
|
1297
|
+
"""Apply REMESH with structural field memory activation.
|
|
1298
|
+
|
|
1299
|
+
This extended version of REMESH implements the theoretical capability
|
|
1300
|
+
of "structural memory": nodes can recognize themselves in other nodes
|
|
1301
|
+
through pattern similarity, enabling coherent propagation across scales.
|
|
1302
|
+
|
|
1303
|
+
The function performs:
|
|
1304
|
+
1. Standard REMESH reorganization (temporal EPI mixing)
|
|
1305
|
+
2. Pattern detection (find groups of structurally similar nodes)
|
|
1306
|
+
3. Identity propagation (reinforce shared patterns from origin nodes)
|
|
1307
|
+
|
|
1308
|
+
Parameters
|
|
1309
|
+
----------
|
|
1310
|
+
G : TNFRGraph
|
|
1311
|
+
Network to reorganize (modified in-place)
|
|
1312
|
+
enable_structural_memory : bool, default=True
|
|
1313
|
+
Whether to activate structural memory after standard REMESH.
|
|
1314
|
+
If False, performs only standard REMESH.
|
|
1315
|
+
similarity_threshold : float, default=0.75
|
|
1316
|
+
Minimum similarity [0-1] to recognize patterns as "same identity".
|
|
1317
|
+
Higher = stricter matching. Typical range: 0.7-0.9.
|
|
1318
|
+
similarity_metric : str, default='cosine'
|
|
1319
|
+
Metric for comparing structural signatures.
|
|
1320
|
+
Options: 'cosine', 'euclidean', 'correlation'
|
|
1321
|
+
propagation_strength : float, default=0.5
|
|
1322
|
+
Interpolation weight [0-1] for identity propagation.
|
|
1323
|
+
- 0.0: No propagation (structural memory detection only)
|
|
1324
|
+
- 0.5: Balanced blending (recommended)
|
|
1325
|
+
- 1.0: Full replacement (aggressive, may reduce diversity)
|
|
1326
|
+
min_cluster_size : int, default=2
|
|
1327
|
+
Minimum nodes required to form a recursive pattern.
|
|
1328
|
+
Single isolated nodes are not considered patterns.
|
|
1329
|
+
|
|
1330
|
+
Notes
|
|
1331
|
+
-----
|
|
1332
|
+
**TNFR Physics**: This implements the principle that "coherence propagates
|
|
1333
|
+
structurally, not imposed" (TNFR.pdf § 4.2). Patterns that resonate across
|
|
1334
|
+
the network mutually reinforce through similarity-based coupling.
|
|
1335
|
+
|
|
1336
|
+
**Workflow**:
|
|
1337
|
+
1. `apply_network_remesh(G)` - Standard temporal memory mixing
|
|
1338
|
+
2. `detect_recursive_patterns(G, threshold)` - Find similar node groups
|
|
1339
|
+
3. For each cluster:
|
|
1340
|
+
- `identify_pattern_origin(G, cluster)` - Find strongest instance
|
|
1341
|
+
- `propagate_structural_identity(G, origin, targets)` - Reinforce pattern
|
|
1342
|
+
|
|
1343
|
+
**Telemetry**: Logs structural memory events to G.graph['history']['structural_memory_events']
|
|
1344
|
+
including cluster statistics and propagation metadata.
|
|
1345
|
+
|
|
1346
|
+
**Canonical Relationships**:
|
|
1347
|
+
- Hierarchical REMESH: Combine with IL (coherence) for stable multi-level propagation
|
|
1348
|
+
- Rhizomatic REMESH: Combine with UM (coupling) for decentralized pattern spread
|
|
1349
|
+
- Fractal Harmonic: Combine with RA (resonance) for symmetric amplification
|
|
1350
|
+
|
|
1351
|
+
Examples
|
|
1352
|
+
--------
|
|
1353
|
+
>>> # Standard REMESH with structural memory (recommended)
|
|
1354
|
+
>>> apply_network_remesh_with_memory(G)
|
|
1355
|
+
>>>
|
|
1356
|
+
>>> # Strict pattern matching with gentle propagation
|
|
1357
|
+
>>> apply_network_remesh_with_memory(
|
|
1358
|
+
... G,
|
|
1359
|
+
... similarity_threshold=0.85,
|
|
1360
|
+
... propagation_strength=0.3
|
|
1361
|
+
... )
|
|
1362
|
+
>>>
|
|
1363
|
+
>>> # Disable structural memory (standard REMESH only)
|
|
1364
|
+
>>> apply_network_remesh_with_memory(G, enable_structural_memory=False)
|
|
1365
|
+
"""
|
|
1366
|
+
# Phase 1: Apply standard REMESH (temporal memory)
|
|
1367
|
+
apply_network_remesh(G)
|
|
1368
|
+
|
|
1369
|
+
if not enable_structural_memory:
|
|
1370
|
+
return
|
|
1371
|
+
|
|
1372
|
+
# Phase 2: Structural memory - detect and propagate patterns
|
|
1373
|
+
try:
|
|
1374
|
+
# Detect recursive patterns across network
|
|
1375
|
+
clusters = detect_recursive_patterns(
|
|
1376
|
+
G,
|
|
1377
|
+
threshold=similarity_threshold,
|
|
1378
|
+
metric=similarity_metric,
|
|
1379
|
+
min_cluster_size=min_cluster_size,
|
|
1380
|
+
)
|
|
1381
|
+
|
|
1382
|
+
# Propagate identity from origin to similar nodes
|
|
1383
|
+
propagation_events = []
|
|
1384
|
+
for cluster in clusters:
|
|
1385
|
+
if len(cluster) < min_cluster_size:
|
|
1386
|
+
continue
|
|
1387
|
+
|
|
1388
|
+
# Identify strongest instance of pattern
|
|
1389
|
+
origin = identify_pattern_origin(G, cluster)
|
|
1390
|
+
if origin is None:
|
|
1391
|
+
continue
|
|
1392
|
+
|
|
1393
|
+
# Propagate to other cluster members
|
|
1394
|
+
targets = [n for n in cluster if n != origin]
|
|
1395
|
+
if targets:
|
|
1396
|
+
propagate_structural_identity(
|
|
1397
|
+
G,
|
|
1398
|
+
origin,
|
|
1399
|
+
targets,
|
|
1400
|
+
propagation_strength=propagation_strength,
|
|
1401
|
+
)
|
|
1402
|
+
|
|
1403
|
+
propagation_events.append({
|
|
1404
|
+
'origin': origin,
|
|
1405
|
+
'n_targets': len(targets),
|
|
1406
|
+
'targets': targets[:5], # Sample for telemetry (avoid bloat)
|
|
1407
|
+
})
|
|
1408
|
+
|
|
1409
|
+
# Log structural memory event
|
|
1410
|
+
if G.graph.get("REMESH_LOG_EVENTS", REMESH_DEFAULTS["REMESH_LOG_EVENTS"]):
|
|
1411
|
+
from ..glyph_history import append_metric
|
|
1412
|
+
hist = G.graph.setdefault("history", {})
|
|
1413
|
+
append_metric(
|
|
1414
|
+
hist,
|
|
1415
|
+
"structural_memory_events",
|
|
1416
|
+
{
|
|
1417
|
+
'n_clusters': len(clusters),
|
|
1418
|
+
'cluster_sizes': [len(c) for c in clusters],
|
|
1419
|
+
'n_propagations': len(propagation_events),
|
|
1420
|
+
'propagation_events': propagation_events[:10], # Sample
|
|
1421
|
+
'similarity_threshold': similarity_threshold,
|
|
1422
|
+
'similarity_metric': similarity_metric,
|
|
1423
|
+
'propagation_strength': propagation_strength,
|
|
1424
|
+
'min_cluster_size': min_cluster_size,
|
|
1425
|
+
},
|
|
1426
|
+
)
|
|
1427
|
+
|
|
1428
|
+
except Exception as e:
|
|
1429
|
+
# Graceful degradation: if structural memory fails, REMESH still applied
|
|
1430
|
+
import warnings
|
|
1431
|
+
warnings.warn(
|
|
1432
|
+
f"Structural memory activation failed: {e}. "
|
|
1433
|
+
"Standard REMESH applied successfully.",
|
|
1434
|
+
RuntimeWarning,
|
|
1435
|
+
stacklevel=2,
|
|
1436
|
+
)
|
|
1437
|
+
|
|
1438
|
+
|
|
1439
|
+
def _mst_edges_from_epi(
|
|
1440
|
+
nx: NetworkxModule,
|
|
1441
|
+
nodes: Sequence[Hashable],
|
|
1442
|
+
epi: Mapping[Hashable, float],
|
|
1443
|
+
) -> set[RemeshEdge]:
|
|
1444
|
+
"""Return MST edges based on absolute EPI distance."""
|
|
1445
|
+
H = nx.Graph()
|
|
1446
|
+
H.add_nodes_from(nodes)
|
|
1447
|
+
H.add_weighted_edges_from(
|
|
1448
|
+
(u, v, abs(epi[u] - epi[v])) for u, v in combinations(nodes, 2)
|
|
1449
|
+
)
|
|
1450
|
+
return {_ordered_edge(u, v) for u, v in nx.minimum_spanning_edges(H, data=False)}
|
|
1451
|
+
|
|
1452
|
+
|
|
1453
|
+
def _knn_edges(
|
|
1454
|
+
nodes: Sequence[Hashable],
|
|
1455
|
+
epi: Mapping[Hashable, float],
|
|
1456
|
+
k_val: int,
|
|
1457
|
+
p_rewire: float,
|
|
1458
|
+
rnd: random.Random,
|
|
1459
|
+
) -> set[RemeshEdge]:
|
|
1460
|
+
"""Edges linking each node to its ``k`` nearest neighbours in EPI."""
|
|
1461
|
+
new_edges = set()
|
|
1462
|
+
node_set = set(nodes)
|
|
1463
|
+
for u in nodes:
|
|
1464
|
+
epi_u = epi[u]
|
|
1465
|
+
neighbours = [
|
|
1466
|
+
v
|
|
1467
|
+
for _, v in heapq.nsmallest(
|
|
1468
|
+
k_val,
|
|
1469
|
+
((abs(epi_u - epi[v]), v) for v in nodes if v != u),
|
|
1470
|
+
)
|
|
1471
|
+
]
|
|
1472
|
+
for v in neighbours:
|
|
1473
|
+
if rnd.random() < p_rewire:
|
|
1474
|
+
choices = list(node_set - {u, v})
|
|
1475
|
+
if choices:
|
|
1476
|
+
v = rnd.choice(choices)
|
|
1477
|
+
new_edges.add(_ordered_edge(u, v))
|
|
1478
|
+
return new_edges
|
|
1479
|
+
|
|
1480
|
+
|
|
1481
|
+
def _community_graph(
|
|
1482
|
+
comms: Iterable[Iterable[Hashable]],
|
|
1483
|
+
epi: Mapping[Hashable, float],
|
|
1484
|
+
nx: NetworkxModule,
|
|
1485
|
+
) -> CommunityGraph:
|
|
1486
|
+
"""Return community graph ``C`` with mean EPI per community."""
|
|
1487
|
+
C = nx.Graph()
|
|
1488
|
+
for idx, comm in enumerate(comms):
|
|
1489
|
+
members = list(comm)
|
|
1490
|
+
try:
|
|
1491
|
+
epi_mean = fmean(_as_float(epi.get(n)) for n in members)
|
|
1492
|
+
except StatisticsError:
|
|
1493
|
+
epi_mean = 0.0
|
|
1494
|
+
C.add_node(idx)
|
|
1495
|
+
set_attr(C.nodes[idx], ALIAS_EPI, epi_mean)
|
|
1496
|
+
C.nodes[idx]["members"] = members
|
|
1497
|
+
for i, j in combinations(C.nodes(), 2):
|
|
1498
|
+
w = abs(
|
|
1499
|
+
_as_float(get_attr(C.nodes[i], ALIAS_EPI, 0.0))
|
|
1500
|
+
- _as_float(get_attr(C.nodes[j], ALIAS_EPI, 0.0))
|
|
1501
|
+
)
|
|
1502
|
+
C.add_edge(i, j, weight=w)
|
|
1503
|
+
return cast(CommunityGraph, C)
|
|
1504
|
+
|
|
1505
|
+
|
|
1506
|
+
def _community_k_neighbor_edges(
|
|
1507
|
+
C: CommunityGraph,
|
|
1508
|
+
k_val: int,
|
|
1509
|
+
p_rewire: float,
|
|
1510
|
+
rnd: random.Random,
|
|
1511
|
+
) -> tuple[set[RemeshEdge], dict[int, int], list[tuple[int, int, int]]]:
|
|
1512
|
+
"""Edges linking each community to its ``k`` nearest neighbours."""
|
|
1513
|
+
epi_vals = {n: _as_float(get_attr(C.nodes[n], ALIAS_EPI, 0.0)) for n in C.nodes()}
|
|
1514
|
+
ordered = sorted(C.nodes(), key=lambda v: epi_vals[v])
|
|
1515
|
+
new_edges = set()
|
|
1516
|
+
attempts = {n: 0 for n in C.nodes()}
|
|
1517
|
+
rewired = []
|
|
1518
|
+
node_set = set(C.nodes())
|
|
1519
|
+
for idx, u in enumerate(ordered):
|
|
1520
|
+
epi_u = epi_vals[u]
|
|
1521
|
+
left = idx - 1
|
|
1522
|
+
right = idx + 1
|
|
1523
|
+
added = 0
|
|
1524
|
+
while added < k_val and (left >= 0 or right < len(ordered)):
|
|
1525
|
+
if left < 0:
|
|
1526
|
+
v = ordered[right]
|
|
1527
|
+
right += 1
|
|
1528
|
+
elif right >= len(ordered):
|
|
1529
|
+
v = ordered[left]
|
|
1530
|
+
left -= 1
|
|
1531
|
+
else:
|
|
1532
|
+
if abs(epi_u - epi_vals[ordered[left]]) <= abs(
|
|
1533
|
+
epi_vals[ordered[right]] - epi_u
|
|
1534
|
+
):
|
|
1535
|
+
v = ordered[left]
|
|
1536
|
+
left -= 1
|
|
1537
|
+
else:
|
|
1538
|
+
v = ordered[right]
|
|
1539
|
+
right += 1
|
|
1540
|
+
original_v = v
|
|
1541
|
+
rewired_now = False
|
|
1542
|
+
if rnd.random() < p_rewire:
|
|
1543
|
+
choices = list(node_set - {u, original_v})
|
|
1544
|
+
if choices:
|
|
1545
|
+
v = rnd.choice(choices)
|
|
1546
|
+
rewired_now = True
|
|
1547
|
+
new_edges.add(_ordered_edge(u, v))
|
|
1548
|
+
attempts[u] += 1
|
|
1549
|
+
if rewired_now:
|
|
1550
|
+
rewired.append((u, original_v, v))
|
|
1551
|
+
added += 1
|
|
1552
|
+
return new_edges, attempts, rewired
|
|
1553
|
+
|
|
1554
|
+
|
|
1555
|
+
def _community_remesh(
|
|
1556
|
+
G: CommunityGraph,
|
|
1557
|
+
epi: Mapping[Hashable, float],
|
|
1558
|
+
k_val: int,
|
|
1559
|
+
p_rewire: float,
|
|
1560
|
+
rnd: random.Random,
|
|
1561
|
+
nx: NetworkxModule,
|
|
1562
|
+
nx_comm: CommunityModule,
|
|
1563
|
+
mst_edges: Iterable[RemeshEdge],
|
|
1564
|
+
n_before: int,
|
|
1565
|
+
) -> None:
|
|
1566
|
+
"""Remesh ``G`` replacing nodes by modular communities."""
|
|
1567
|
+
from ..glyph_history import append_metric
|
|
1568
|
+
|
|
1569
|
+
comms = list(nx_comm.greedy_modularity_communities(G))
|
|
1570
|
+
if len(comms) <= 1:
|
|
1571
|
+
with edge_version_update(G):
|
|
1572
|
+
G.clear_edges()
|
|
1573
|
+
G.add_edges_from(mst_edges)
|
|
1574
|
+
return
|
|
1575
|
+
C = _community_graph(comms, epi, nx)
|
|
1576
|
+
mst_c = nx.minimum_spanning_tree(C, weight="weight")
|
|
1577
|
+
new_edges: set[RemeshEdge] = {_ordered_edge(u, v) for u, v in mst_c.edges()}
|
|
1578
|
+
extra_edges, attempts, rewired_edges = _community_k_neighbor_edges(
|
|
1579
|
+
C, k_val, p_rewire, rnd
|
|
1580
|
+
)
|
|
1581
|
+
new_edges |= extra_edges
|
|
1582
|
+
|
|
1583
|
+
extra_degrees = {idx: 0 for idx in C.nodes()}
|
|
1584
|
+
for u, v in extra_edges:
|
|
1585
|
+
extra_degrees[u] += 1
|
|
1586
|
+
extra_degrees[v] += 1
|
|
1587
|
+
|
|
1588
|
+
with edge_version_update(G):
|
|
1589
|
+
G.clear_edges()
|
|
1590
|
+
G.remove_nodes_from(list(G.nodes()))
|
|
1591
|
+
for idx in C.nodes():
|
|
1592
|
+
data = dict(C.nodes[idx])
|
|
1593
|
+
G.add_node(idx, **data)
|
|
1594
|
+
G.add_edges_from(new_edges)
|
|
1595
|
+
|
|
1596
|
+
if G.graph.get("REMESH_LOG_EVENTS", REMESH_DEFAULTS["REMESH_LOG_EVENTS"]):
|
|
1597
|
+
hist = G.graph.setdefault("history", {})
|
|
1598
|
+
mapping = {idx: C.nodes[idx].get("members", []) for idx in C.nodes()}
|
|
1599
|
+
append_metric(
|
|
1600
|
+
hist,
|
|
1601
|
+
"remesh_events",
|
|
1602
|
+
{
|
|
1603
|
+
"mode": "community",
|
|
1604
|
+
"n_before": n_before,
|
|
1605
|
+
"n_after": G.number_of_nodes(),
|
|
1606
|
+
"mapping": mapping,
|
|
1607
|
+
"k": int(k_val),
|
|
1608
|
+
"p_rewire": float(p_rewire),
|
|
1609
|
+
"extra_edges_added": len(extra_edges),
|
|
1610
|
+
"extra_edge_attempts": attempts,
|
|
1611
|
+
"extra_edge_degrees": extra_degrees,
|
|
1612
|
+
"rewired_edges": [
|
|
1613
|
+
{"source": int(u), "from": int(v0), "to": int(v1)}
|
|
1614
|
+
for u, v0, v1 in rewired_edges
|
|
1615
|
+
],
|
|
1616
|
+
},
|
|
1617
|
+
)
|
|
1618
|
+
|
|
1619
|
+
|
|
1620
|
+
def apply_topological_remesh(
|
|
1621
|
+
G: CommunityGraph,
|
|
1622
|
+
mode: str | None = None,
|
|
1623
|
+
*,
|
|
1624
|
+
k: int | None = None,
|
|
1625
|
+
p_rewire: float = 0.2,
|
|
1626
|
+
seed: int | None = None,
|
|
1627
|
+
) -> None:
|
|
1628
|
+
"""Approximate topological remeshing.
|
|
1629
|
+
|
|
1630
|
+
When ``seed`` is ``None`` the RNG draws its base seed from
|
|
1631
|
+
``G.graph['RANDOM_SEED']`` to keep runs reproducible.
|
|
1632
|
+
"""
|
|
1633
|
+
nodes = list(G.nodes())
|
|
1634
|
+
n_before = len(nodes)
|
|
1635
|
+
if n_before <= 1:
|
|
1636
|
+
return
|
|
1637
|
+
if seed is None:
|
|
1638
|
+
base_seed = int(G.graph.get("RANDOM_SEED", 0))
|
|
1639
|
+
else:
|
|
1640
|
+
base_seed = int(seed)
|
|
1641
|
+
rnd = make_rng(base_seed, -2, G)
|
|
1642
|
+
|
|
1643
|
+
if mode is None:
|
|
1644
|
+
mode = str(
|
|
1645
|
+
G.graph.get("REMESH_MODE", REMESH_DEFAULTS.get("REMESH_MODE", "knn"))
|
|
1646
|
+
)
|
|
1647
|
+
mode = str(mode)
|
|
1648
|
+
nx, nx_comm = _get_networkx_modules()
|
|
1649
|
+
epi = {n: _as_float(get_attr(G.nodes[n], ALIAS_EPI, 0.0)) for n in nodes}
|
|
1650
|
+
mst_edges = _mst_edges_from_epi(nx, nodes, epi)
|
|
1651
|
+
default_k = int(
|
|
1652
|
+
G.graph.get("REMESH_COMMUNITY_K", REMESH_DEFAULTS.get("REMESH_COMMUNITY_K", 2))
|
|
1653
|
+
)
|
|
1654
|
+
k_val = max(1, int(k) if k is not None else default_k)
|
|
1655
|
+
|
|
1656
|
+
if mode == "community":
|
|
1657
|
+
_community_remesh(
|
|
1658
|
+
G,
|
|
1659
|
+
epi,
|
|
1660
|
+
k_val,
|
|
1661
|
+
p_rewire,
|
|
1662
|
+
rnd,
|
|
1663
|
+
nx,
|
|
1664
|
+
nx_comm,
|
|
1665
|
+
mst_edges,
|
|
1666
|
+
n_before,
|
|
1667
|
+
)
|
|
1668
|
+
return
|
|
1669
|
+
|
|
1670
|
+
new_edges = set(mst_edges)
|
|
1671
|
+
if mode == "knn":
|
|
1672
|
+
new_edges |= _knn_edges(nodes, epi, k_val, p_rewire, rnd)
|
|
1673
|
+
|
|
1674
|
+
with edge_version_update(G):
|
|
1675
|
+
G.clear_edges()
|
|
1676
|
+
G.add_edges_from(new_edges)
|
|
1677
|
+
|
|
1678
|
+
|
|
1679
|
+
def _extra_gating_ok(
|
|
1680
|
+
hist: MutableMapping[str, Sequence[float]],
|
|
1681
|
+
cfg: Mapping[str, RemeshConfigValue],
|
|
1682
|
+
w_estab: int,
|
|
1683
|
+
) -> bool:
|
|
1684
|
+
"""Check additional stability gating conditions."""
|
|
1685
|
+
checks = [
|
|
1686
|
+
("phase_sync", "REMESH_MIN_PHASE_SYNC", ge),
|
|
1687
|
+
("glyph_load_disr", "REMESH_MAX_GLYPH_DISR", le),
|
|
1688
|
+
("sense_sigma_mag", "REMESH_MIN_SIGMA_MAG", ge),
|
|
1689
|
+
("kuramoto_R", "REMESH_MIN_KURAMOTO_R", ge),
|
|
1690
|
+
("Si_hi_frac", "REMESH_MIN_SI_HI_FRAC", ge),
|
|
1691
|
+
]
|
|
1692
|
+
for hist_key, cfg_key, op in checks:
|
|
1693
|
+
series = hist.get(hist_key)
|
|
1694
|
+
if series is not None and len(series) >= w_estab:
|
|
1695
|
+
win = series[-w_estab:]
|
|
1696
|
+
avg = sum(win) / len(win)
|
|
1697
|
+
threshold = _as_float(cfg[cfg_key])
|
|
1698
|
+
if not op(avg, threshold):
|
|
1699
|
+
return False
|
|
1700
|
+
return True
|
|
1701
|
+
|
|
1702
|
+
|
|
1703
|
+
def apply_remesh_if_globally_stable(
|
|
1704
|
+
G: CommunityGraph,
|
|
1705
|
+
stable_step_window: int | None = None,
|
|
1706
|
+
**kwargs: Any,
|
|
1707
|
+
) -> None:
|
|
1708
|
+
"""Trigger remeshing when global stability indicators satisfy thresholds."""
|
|
1709
|
+
|
|
1710
|
+
from ..glyph_history import ensure_history
|
|
1711
|
+
|
|
1712
|
+
if kwargs:
|
|
1713
|
+
unexpected = ", ".join(sorted(kwargs))
|
|
1714
|
+
raise TypeError(
|
|
1715
|
+
"apply_remesh_if_globally_stable() got unexpected keyword argument(s): "
|
|
1716
|
+
f"{unexpected}"
|
|
1717
|
+
)
|
|
1718
|
+
|
|
1719
|
+
params = [
|
|
1720
|
+
(
|
|
1721
|
+
"REMESH_STABILITY_WINDOW",
|
|
1722
|
+
int,
|
|
1723
|
+
REMESH_DEFAULTS["REMESH_STABILITY_WINDOW"],
|
|
1724
|
+
),
|
|
1725
|
+
(
|
|
1726
|
+
"REMESH_REQUIRE_STABILITY",
|
|
1727
|
+
bool,
|
|
1728
|
+
REMESH_DEFAULTS["REMESH_REQUIRE_STABILITY"],
|
|
1729
|
+
),
|
|
1730
|
+
(
|
|
1731
|
+
"REMESH_MIN_PHASE_SYNC",
|
|
1732
|
+
float,
|
|
1733
|
+
REMESH_DEFAULTS["REMESH_MIN_PHASE_SYNC"],
|
|
1734
|
+
),
|
|
1735
|
+
(
|
|
1736
|
+
"REMESH_MAX_GLYPH_DISR",
|
|
1737
|
+
float,
|
|
1738
|
+
REMESH_DEFAULTS["REMESH_MAX_GLYPH_DISR"],
|
|
1739
|
+
),
|
|
1740
|
+
(
|
|
1741
|
+
"REMESH_MIN_SIGMA_MAG",
|
|
1742
|
+
float,
|
|
1743
|
+
REMESH_DEFAULTS["REMESH_MIN_SIGMA_MAG"],
|
|
1744
|
+
),
|
|
1745
|
+
(
|
|
1746
|
+
"REMESH_MIN_KURAMOTO_R",
|
|
1747
|
+
float,
|
|
1748
|
+
REMESH_DEFAULTS["REMESH_MIN_KURAMOTO_R"],
|
|
1749
|
+
),
|
|
1750
|
+
(
|
|
1751
|
+
"REMESH_MIN_SI_HI_FRAC",
|
|
1752
|
+
float,
|
|
1753
|
+
REMESH_DEFAULTS["REMESH_MIN_SI_HI_FRAC"],
|
|
1754
|
+
),
|
|
1755
|
+
(COOLDOWN_KEY, int, REMESH_DEFAULTS[COOLDOWN_KEY]),
|
|
1756
|
+
("REMESH_COOLDOWN_TS", float, REMESH_DEFAULTS["REMESH_COOLDOWN_TS"]),
|
|
1757
|
+
]
|
|
1758
|
+
cfg = {}
|
|
1759
|
+
for key, conv, _default in params:
|
|
1760
|
+
cfg[key] = conv(get_param(G, key))
|
|
1761
|
+
frac_req = _as_float(get_param(G, "FRACTION_STABLE_REMESH"))
|
|
1762
|
+
w_estab = (
|
|
1763
|
+
stable_step_window
|
|
1764
|
+
if stable_step_window is not None
|
|
1765
|
+
else cfg["REMESH_STABILITY_WINDOW"]
|
|
1766
|
+
)
|
|
1767
|
+
|
|
1768
|
+
hist = ensure_history(G)
|
|
1769
|
+
sf = hist.setdefault("stable_frac", [])
|
|
1770
|
+
if len(sf) < w_estab:
|
|
1771
|
+
return
|
|
1772
|
+
win_sf = sf[-w_estab:]
|
|
1773
|
+
if not all(v >= frac_req for v in win_sf):
|
|
1774
|
+
return
|
|
1775
|
+
if cfg["REMESH_REQUIRE_STABILITY"] and not _extra_gating_ok(hist, cfg, w_estab):
|
|
1776
|
+
return
|
|
1777
|
+
|
|
1778
|
+
last = G.graph.get("_last_remesh_step", -(10**9))
|
|
1779
|
+
step_idx = len(sf)
|
|
1780
|
+
if step_idx - last < cfg[COOLDOWN_KEY]:
|
|
1781
|
+
return
|
|
1782
|
+
t_now = _as_float(G.graph.get("_t", 0.0))
|
|
1783
|
+
last_ts = _as_float(G.graph.get("_last_remesh_ts", -1e12))
|
|
1784
|
+
if cfg["REMESH_COOLDOWN_TS"] > 0 and (t_now - last_ts) < cfg["REMESH_COOLDOWN_TS"]:
|
|
1785
|
+
return
|
|
1786
|
+
|
|
1787
|
+
apply_network_remesh(G)
|
|
1788
|
+
G.graph["_last_remesh_step"] = step_idx
|
|
1789
|
+
G.graph["_last_remesh_ts"] = t_now
|
|
1790
|
+
|
|
1791
|
+
|
|
1792
|
+
__all__ = [
|
|
1793
|
+
# Core remesh functions (existing API)
|
|
1794
|
+
"apply_network_remesh",
|
|
1795
|
+
"apply_topological_remesh",
|
|
1796
|
+
"apply_remesh_if_globally_stable",
|
|
1797
|
+
# Phase 1: Structural memory & pattern recognition
|
|
1798
|
+
"StructuralIdentity",
|
|
1799
|
+
"structural_similarity",
|
|
1800
|
+
"structural_memory_match",
|
|
1801
|
+
"compute_structural_signature",
|
|
1802
|
+
"detect_recursive_patterns",
|
|
1803
|
+
"identify_pattern_origin",
|
|
1804
|
+
"propagate_structural_identity",
|
|
1805
|
+
"apply_network_remesh_with_memory",
|
|
1806
|
+
# Phase 2: Coherence preservation & validation
|
|
1807
|
+
"RemeshCoherenceLossError",
|
|
1808
|
+
"validate_coherence_preservation",
|
|
1809
|
+
]
|