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/dynamics.py
DELETED
|
@@ -1,543 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
dynamics.py — TNFR canónica
|
|
3
|
-
|
|
4
|
-
Bucle de dinámica con la ecuación nodal y utilidades:
|
|
5
|
-
∂EPI/∂t = νf · ΔNFR(t)
|
|
6
|
-
Incluye:
|
|
7
|
-
- default_compute_delta_nfr (mezcla de fase/EPI/νf)
|
|
8
|
-
- update_epi_via_nodal_equation (Euler explícito)
|
|
9
|
-
- aplicar_dnfr_campo, integrar_epi_euler, aplicar_clamps_canonicos
|
|
10
|
-
- coordinar_fase_global_vecinal
|
|
11
|
-
- default_glyph_selector, step, run
|
|
12
|
-
"""
|
|
13
|
-
from __future__ import annotations
|
|
14
|
-
from typing import Dict, Any, Iterable
|
|
15
|
-
import math
|
|
16
|
-
|
|
17
|
-
from observers import sincronía_fase, carga_glifica, orden_kuramoto, sigma_vector
|
|
18
|
-
from operators import aplicar_remesh_si_estabilizacion_global
|
|
19
|
-
from constants import DEFAULTS, ALIAS_VF, ALIAS_THETA, ALIAS_DNFR, ALIAS_EPI, ALIAS_SI, ALIAS_dEPI, ALIAS_D2EPI
|
|
20
|
-
from helpers import (
|
|
21
|
-
clamp, clamp01, list_mean, phase_distance,
|
|
22
|
-
_get_attr, _set_attr, media_vecinal, fase_media,
|
|
23
|
-
invoke_callbacks, reciente_glifo
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
# -------------------------
|
|
27
|
-
# ΔNFR por defecto (campo) + utilidades de hook/metadata
|
|
28
|
-
# -------------------------
|
|
29
|
-
|
|
30
|
-
def _write_dnfr_metadata(G, *, weights: dict, hook_name: str, note: str | None = None) -> None:
|
|
31
|
-
"""Escribe en G.graph un bloque _DNFR_META con la mezcla y el nombre del hook."""
|
|
32
|
-
w_phase = float(weights.get("phase", 0.0))
|
|
33
|
-
w_epi = float(weights.get("epi", 0.0))
|
|
34
|
-
w_vf = float(weights.get("vf", 0.0))
|
|
35
|
-
s = w_phase + w_epi + w_vf
|
|
36
|
-
if s <= 0:
|
|
37
|
-
w_phase = w_epi = w_vf = 1/3
|
|
38
|
-
s = 1.0
|
|
39
|
-
meta = {
|
|
40
|
-
"hook": hook_name,
|
|
41
|
-
"weights_raw": dict(weights),
|
|
42
|
-
"weights_norm": {"phase": w_phase/s, "epi": w_epi/s, "vf": w_vf/s},
|
|
43
|
-
"components": [k for k, v in {"phase":w_phase, "epi":w_epi, "vf":w_vf}.items() if v != 0],
|
|
44
|
-
"doc": "ΔNFR = w_phase·g_phase + w_epi·g_epi + w_vf·g_vf",
|
|
45
|
-
}
|
|
46
|
-
if note:
|
|
47
|
-
meta["note"] = str(note)
|
|
48
|
-
G.graph["_DNFR_META"] = meta
|
|
49
|
-
G.graph["_dnfr_hook_name"] = hook_name # string friendly
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def default_compute_delta_nfr(G) -> None:
|
|
53
|
-
w = G.graph.get("DNFR_WEIGHTS", DEFAULTS["DNFR_WEIGHTS"]) # dict
|
|
54
|
-
w_phase = float(w.get("phase", 0.34))
|
|
55
|
-
w_epi = float(w.get("epi", 0.33))
|
|
56
|
-
w_vf = float(w.get("vf", 0.33))
|
|
57
|
-
s = w_phase + w_epi + w_vf
|
|
58
|
-
if s <= 0:
|
|
59
|
-
w_phase = w_epi = w_vf = 1/3
|
|
60
|
-
else:
|
|
61
|
-
w_phase, w_epi, w_vf = w_phase/s, w_epi/s, w_vf/s
|
|
62
|
-
|
|
63
|
-
# Documentar mezcla y hook activo
|
|
64
|
-
_write_dnfr_metadata(G, weights={"phase":w_phase, "epi":w_epi, "vf":w_vf}, hook_name="default_compute_delta_nfr")
|
|
65
|
-
|
|
66
|
-
for n in G.nodes():
|
|
67
|
-
nd = G.nodes[n]
|
|
68
|
-
th_i = _get_attr(nd, ALIAS_THETA, 0.0)
|
|
69
|
-
th_bar = fase_media(G, n)
|
|
70
|
-
# Gradiente de fase: empuja hacia la fase media (signo envuelto)
|
|
71
|
-
g_phase = - ( (th_i - th_bar + math.pi) % (2*math.pi) - math.pi ) / math.pi # ~[-1,1]
|
|
72
|
-
|
|
73
|
-
epi_i = _get_attr(nd, ALIAS_EPI, 0.0)
|
|
74
|
-
epi_bar = media_vecinal(G, n, ALIAS_EPI, default=epi_i)
|
|
75
|
-
g_epi = (epi_bar - epi_i) # gradiente escalar
|
|
76
|
-
|
|
77
|
-
vf_i = _get_attr(nd, ALIAS_VF, 0.0)
|
|
78
|
-
vf_bar = media_vecinal(G, n, ALIAS_VF, default=vf_i)
|
|
79
|
-
g_vf = (vf_bar - vf_i)
|
|
80
|
-
|
|
81
|
-
dnfr = w_phase*g_phase + w_epi*g_epi + w_vf*g_vf
|
|
82
|
-
_set_attr(nd, ALIAS_DNFR, dnfr)
|
|
83
|
-
|
|
84
|
-
def set_delta_nfr_hook(G, func, *, name: str | None = None, note: str | None = None) -> None:
|
|
85
|
-
"""Fija un hook estable para calcular ΔNFR. Firma requerida: func(G)->None y debe
|
|
86
|
-
escribir ALIAS_DNFR en cada nodo. Actualiza metadatos básicos en G.graph."""
|
|
87
|
-
G.graph["compute_delta_nfr"] = func
|
|
88
|
-
G.graph["_dnfr_hook_name"] = str(name or getattr(func, "__name__", "custom_dnfr"))
|
|
89
|
-
if note:
|
|
90
|
-
meta = G.graph.get("_DNFR_META", {})
|
|
91
|
-
meta["note"] = str(note)
|
|
92
|
-
G.graph["_DNFR_META"] = meta
|
|
93
|
-
|
|
94
|
-
# --- Hooks de ejemplo (opcionales) ---
|
|
95
|
-
def dnfr_phase_only(G) -> None:
|
|
96
|
-
"""Ejemplo: ΔNFR solo desde fase (tipo Kuramoto-like)."""
|
|
97
|
-
for n in G.nodes():
|
|
98
|
-
nd = G.nodes[n]
|
|
99
|
-
th_i = _get_attr(nd, ALIAS_THETA, 0.0)
|
|
100
|
-
th_bar = fase_media(G, n)
|
|
101
|
-
g_phase = - ( (th_i - th_bar + math.pi) % (2*math.pi) - math.pi ) / math.pi
|
|
102
|
-
_set_attr(nd, ALIAS_DNFR, g_phase)
|
|
103
|
-
_write_dnfr_metadata(G, weights={"phase":1.0, "epi":0.0, "vf":0.0}, hook_name="dnfr_phase_only", note="Hook de ejemplo.")
|
|
104
|
-
|
|
105
|
-
def dnfr_epi_vf_mixed(G) -> None:
|
|
106
|
-
"""Ejemplo: ΔNFR sin fase, mezclando EPI y νf."""
|
|
107
|
-
for n in G.nodes():
|
|
108
|
-
nd = G.nodes[n]
|
|
109
|
-
epi_i = _get_attr(nd, ALIAS_EPI, 0.0)
|
|
110
|
-
epi_bar = media_vecinal(G, n, ALIAS_EPI, default=epi_i)
|
|
111
|
-
g_epi = (epi_bar - epi_i)
|
|
112
|
-
vf_i = _get_attr(nd, ALIAS_VF, 0.0)
|
|
113
|
-
vf_bar = media_vecinal(G, n, ALIAS_VF, default=vf_i)
|
|
114
|
-
g_vf = (vf_bar - vf_i)
|
|
115
|
-
_set_attr(nd, ALIAS_DNFR, 0.5*g_epi + 0.5*g_vf)
|
|
116
|
-
_write_dnfr_metadata(G, weights={"phase":0.0, "epi":0.5, "vf":0.5}, hook_name="dnfr_epi_vf_mixed", note="Hook de ejemplo.")
|
|
117
|
-
|
|
118
|
-
# -------------------------
|
|
119
|
-
# Ecuación nodal
|
|
120
|
-
# -------------------------
|
|
121
|
-
|
|
122
|
-
def update_epi_via_nodal_equation(G, *, dt: float = None) -> None:
|
|
123
|
-
if dt is None:
|
|
124
|
-
dt = float(G.graph.get("DT", DEFAULTS["DT"]))
|
|
125
|
-
for n in G.nodes():
|
|
126
|
-
nd = G.nodes[n]
|
|
127
|
-
vf = _get_attr(nd, ALIAS_VF, 0.0)
|
|
128
|
-
dnfr = _get_attr(nd, ALIAS_DNFR, 0.0)
|
|
129
|
-
dEPI_dt_prev = _get_attr(nd, ALIAS_dEPI, 0.0)
|
|
130
|
-
dEPI_dt = vf * dnfr
|
|
131
|
-
epi = _get_attr(nd, ALIAS_EPI, 0.0) + dt * dEPI_dt
|
|
132
|
-
_set_attr(nd, ALIAS_EPI, epi)
|
|
133
|
-
_set_attr(nd, ALIAS_dEPI, dEPI_dt)
|
|
134
|
-
_set_attr(nd, ALIAS_D2EPI, (dEPI_dt - dEPI_dt_prev) / dt if dt != 0 else 0.0)
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
# -------------------------
|
|
138
|
-
# Wrappers nombrados (compatibilidad)
|
|
139
|
-
# -------------------------
|
|
140
|
-
|
|
141
|
-
def aplicar_dnfr_campo(G, w_theta=None, w_epi=None, w_vf=None) -> None:
|
|
142
|
-
if any(v is not None for v in (w_theta, w_epi, w_vf)):
|
|
143
|
-
mix = G.graph.get("DNFR_WEIGHTS", DEFAULTS["DNFR_WEIGHTS"]).copy()
|
|
144
|
-
if w_theta is not None: mix["phase"] = float(w_theta)
|
|
145
|
-
if w_epi is not None: mix["epi"] = float(w_epi)
|
|
146
|
-
if w_vf is not None: mix["vf"] = float(w_vf)
|
|
147
|
-
G.graph["DNFR_WEIGHTS"] = mix
|
|
148
|
-
default_compute_delta_nfr(G)
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
def integrar_epi_euler(G, dt: float | None = None) -> None:
|
|
152
|
-
update_epi_via_nodal_equation(G, dt=dt)
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
def aplicar_clamps_canonicos(nd: Dict[str, Any], G=None) -> None:
|
|
156
|
-
eps_min = float((G.graph.get("EPI_MIN") if G is not None else DEFAULTS["EPI_MIN"]))
|
|
157
|
-
eps_max = float((G.graph.get("EPI_MAX") if G is not None else DEFAULTS["EPI_MAX"]))
|
|
158
|
-
vf_min = float((G.graph.get("VF_MIN") if G is not None else DEFAULTS["VF_MIN"]))
|
|
159
|
-
vf_max = float((G.graph.get("VF_MAX") if G is not None else DEFAULTS["VF_MAX"]))
|
|
160
|
-
|
|
161
|
-
epi = _get_attr(nd, ALIAS_EPI, 0.0)
|
|
162
|
-
vf = _get_attr(nd, ALIAS_VF, 0.0)
|
|
163
|
-
th = _get_attr(nd, ALIAS_THETA, 0.0)
|
|
164
|
-
|
|
165
|
-
_set_attr(nd, ALIAS_EPI, clamp(epi, eps_min, eps_max))
|
|
166
|
-
_set_attr(nd, ALIAS_VF, clamp(vf, vf_min, vf_max))
|
|
167
|
-
if (G.graph.get("THETA_WRAP") if G is not None else DEFAULTS["THETA_WRAP"]):
|
|
168
|
-
# envolver fase
|
|
169
|
-
_set_attr(nd, ALIAS_THETA, ((th + math.pi) % (2*math.pi) - math.pi))
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
def coordinar_fase_global_vecinal(G, fuerza_global: float | None = None, fuerza_vecinal: float | None = None) -> None:
|
|
173
|
-
"""
|
|
174
|
-
Ajusta fase con mezcla GLOBAL+VECINAL.
|
|
175
|
-
Si no se pasan fuerzas explícitas, adapta kG/kL según estado (disonante / transición / estable).
|
|
176
|
-
Estado se decide por R (Kuramoto) y carga glífica disruptiva reciente.
|
|
177
|
-
"""
|
|
178
|
-
# 0) Si hay fuerzas explícitas, usar y salir del modo adaptativo
|
|
179
|
-
if (fuerza_global is not None) or (fuerza_vecinal is not None):
|
|
180
|
-
kG = float(fuerza_global if fuerza_global is not None else G.graph.get("PHASE_K_GLOBAL", DEFAULTS["PHASE_K_GLOBAL"]))
|
|
181
|
-
kL = float(fuerza_vecinal if fuerza_vecinal is not None else G.graph.get("PHASE_K_LOCAL", DEFAULTS["PHASE_K_LOCAL"]))
|
|
182
|
-
else:
|
|
183
|
-
# 1) Lectura de configuración
|
|
184
|
-
cfg = G.graph.get("PHASE_ADAPT", DEFAULTS.get("PHASE_ADAPT", {}))
|
|
185
|
-
kG = float(G.graph.get("PHASE_K_GLOBAL", DEFAULTS["PHASE_K_GLOBAL"]))
|
|
186
|
-
kL = float(G.graph.get("PHASE_K_LOCAL", DEFAULTS["PHASE_K_LOCAL"]))
|
|
187
|
-
|
|
188
|
-
if bool(cfg.get("enabled", False)):
|
|
189
|
-
# 2) Métricas actuales (no dependemos de history)
|
|
190
|
-
R = orden_kuramoto(G)
|
|
191
|
-
win = int(G.graph.get("GLYPH_LOAD_WINDOW", DEFAULTS["GLYPH_LOAD_WINDOW"]))
|
|
192
|
-
dist = carga_glifica(G, window=win)
|
|
193
|
-
disr = float(dist.get("_disruptivos", 0.0)) if dist else 0.0
|
|
194
|
-
|
|
195
|
-
# 3) Decidir estado
|
|
196
|
-
R_hi = float(cfg.get("R_hi", 0.90)); R_lo = float(cfg.get("R_lo", 0.60))
|
|
197
|
-
disr_hi = float(cfg.get("disr_hi", 0.50)); disr_lo = float(cfg.get("disr_lo", 0.25))
|
|
198
|
-
if (R >= R_hi) and (disr <= disr_lo):
|
|
199
|
-
state = "estable"
|
|
200
|
-
elif (R <= R_lo) or (disr >= disr_hi):
|
|
201
|
-
state = "disonante"
|
|
202
|
-
else:
|
|
203
|
-
state = "transicion"
|
|
204
|
-
|
|
205
|
-
# 4) Objetivos y actualización suave (con saturación)
|
|
206
|
-
kG_min = float(cfg.get("kG_min", 0.01)); kG_max = float(cfg.get("kG_max", 0.20))
|
|
207
|
-
kL_min = float(cfg.get("kL_min", 0.05)); kL_max = float(cfg.get("kL_max", 0.25))
|
|
208
|
-
|
|
209
|
-
if state == "disonante":
|
|
210
|
-
kG_t = kG_max
|
|
211
|
-
kL_t = 0.5 * (kL_min + kL_max) # local medio para no perder plasticidad
|
|
212
|
-
elif state == "estable":
|
|
213
|
-
kG_t = kG_min
|
|
214
|
-
kL_t = kL_min
|
|
215
|
-
else:
|
|
216
|
-
kG_t = 0.5 * (kG_min + kG_max)
|
|
217
|
-
kL_t = 0.5 * (kL_min + kL_max)
|
|
218
|
-
|
|
219
|
-
up = float(cfg.get("up", 0.10)); down = float(cfg.get("down", 0.07))
|
|
220
|
-
def _step(curr, target, mn, mx):
|
|
221
|
-
gain = up if target > curr else down
|
|
222
|
-
nxt = curr + gain * (target - curr)
|
|
223
|
-
return max(mn, min(mx, nxt))
|
|
224
|
-
|
|
225
|
-
kG = _step(kG, kG_t, kG_min, kG_max)
|
|
226
|
-
kL = _step(kL, kL_t, kL_min, kL_max)
|
|
227
|
-
|
|
228
|
-
# 5) Persistir en G.graph y log de serie
|
|
229
|
-
G.graph["PHASE_K_GLOBAL"] = kG
|
|
230
|
-
G.graph["PHASE_K_LOCAL"] = kL
|
|
231
|
-
hist = G.graph.setdefault("history", {})
|
|
232
|
-
hist.setdefault("phase_kG", []).append(float(kG))
|
|
233
|
-
hist.setdefault("phase_kL", []).append(float(kL))
|
|
234
|
-
hist.setdefault("phase_state", []).append(state)
|
|
235
|
-
hist.setdefault("phase_R", []).append(float(R))
|
|
236
|
-
hist.setdefault("phase_disr", []).append(float(disr))
|
|
237
|
-
|
|
238
|
-
# 6) Fase GLOBAL (centroide) para empuje
|
|
239
|
-
X = list(math.cos(_get_attr(G.nodes[n], ALIAS_THETA, 0.0)) for n in G.nodes())
|
|
240
|
-
Y = list(math.sin(_get_attr(G.nodes[n], ALIAS_THETA, 0.0)) for n in G.nodes())
|
|
241
|
-
if X:
|
|
242
|
-
thG = math.atan2(sum(Y)/len(Y), sum(X)/len(X))
|
|
243
|
-
else:
|
|
244
|
-
thG = 0.0
|
|
245
|
-
|
|
246
|
-
# 7) Aplicar corrección global+vecinal
|
|
247
|
-
for n in G.nodes():
|
|
248
|
-
nd = G.nodes[n]
|
|
249
|
-
th = _get_attr(nd, ALIAS_THETA, 0.0)
|
|
250
|
-
thL = fase_media(G, n)
|
|
251
|
-
dG = ((thG - th + math.pi) % (2*math.pi) - math.pi)
|
|
252
|
-
dL = ((thL - th + math.pi) % (2*math.pi) - math.pi)
|
|
253
|
-
_set_attr(nd, ALIAS_THETA, th + kG*dG + kL*dL)
|
|
254
|
-
|
|
255
|
-
# -------------------------
|
|
256
|
-
# Selector glífico por defecto
|
|
257
|
-
# -------------------------
|
|
258
|
-
|
|
259
|
-
def default_glyph_selector(G, n) -> str:
|
|
260
|
-
nd = G.nodes[n]
|
|
261
|
-
# Umbrales desde configuración (fallback a DEFAULTS)
|
|
262
|
-
thr = G.graph.get("GLYPH_THRESHOLDS", DEFAULTS.get("GLYPH_THRESHOLDS", {"hi": 0.66, "lo": 0.33, "dnfr": 1e-3}))
|
|
263
|
-
hi = float(thr.get("hi", 0.66))
|
|
264
|
-
lo = float(thr.get("lo", 0.33))
|
|
265
|
-
tdnfr = float(thr.get("dnfr", 1e-3))
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
Si = _get_attr(nd, ALIAS_SI, 0.5)
|
|
269
|
-
dnfr = _get_attr(nd, ALIAS_DNFR, 0.0)
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
if Si >= hi:
|
|
273
|
-
return "I’L" # estabiliza
|
|
274
|
-
if Si <= lo:
|
|
275
|
-
return "O’Z" if abs(dnfr) > tdnfr else "Z’HIR"
|
|
276
|
-
return "NA’V" if abs(dnfr) > tdnfr else "R’A"
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
# -------------------------
|
|
280
|
-
# Selector glífico multiobjetivo (paramétrico)
|
|
281
|
-
# -------------------------
|
|
282
|
-
def _norms_para_selector(G) -> dict:
|
|
283
|
-
"""Calcula y guarda en G.graph los máximos para normalizar |ΔNFR| y |d2EPI/dt2|."""
|
|
284
|
-
dnfr_max = 0.0
|
|
285
|
-
accel_max = 0.0
|
|
286
|
-
for n in G.nodes():
|
|
287
|
-
nd = G.nodes[n]
|
|
288
|
-
dnfr_max = max(dnfr_max, abs(_get_attr(nd, ALIAS_DNFR, 0.0)))
|
|
289
|
-
accel_max = max(accel_max, abs(_get_attr(nd, ALIAS_D2EPI, 0.0)))
|
|
290
|
-
if dnfr_max <= 0: dnfr_max = 1.0
|
|
291
|
-
if accel_max <= 0: accel_max = 1.0
|
|
292
|
-
norms = {"dnfr_max": float(dnfr_max), "accel_max": float(accel_max)}
|
|
293
|
-
G.graph["_sel_norms"] = norms
|
|
294
|
-
return norms
|
|
295
|
-
|
|
296
|
-
def parametric_glyph_selector(G, n) -> str:
|
|
297
|
-
"""Multiobjetivo: combina Si, |ΔNFR|_norm y |accel|_norm + histéresis.
|
|
298
|
-
Reglas base:
|
|
299
|
-
- Si alto ⇒ I’L
|
|
300
|
-
- Si bajo ⇒ O’Z si |ΔNFR| alto; Z’HIR si |ΔNFR| bajo; T’HOL si hay mucha aceleración
|
|
301
|
-
- Si medio ⇒ NA’V si |ΔNFR| alto (o accel alta), si no R’A
|
|
302
|
-
"""
|
|
303
|
-
nd = G.nodes[n]
|
|
304
|
-
thr = G.graph.get("SELECTOR_THRESHOLDS", DEFAULTS["SELECTOR_THRESHOLDS"])
|
|
305
|
-
si_hi, si_lo = float(thr.get("si_hi", 0.66)), float(thr.get("si_lo", 0.33))
|
|
306
|
-
dnfr_hi, dnfr_lo = float(thr.get("dnfr_hi", 0.5)), float(thr.get("dnfr_lo", 0.1))
|
|
307
|
-
acc_hi, acc_lo = float(thr.get("accel_hi", 0.5)), float(thr.get("accel_lo", 0.1))
|
|
308
|
-
margin = float(G.graph.get("GLYPH_SELECTOR_MARGIN", DEFAULTS["GLYPH_SELECTOR_MARGIN"]))
|
|
309
|
-
|
|
310
|
-
# Normalizadores por paso
|
|
311
|
-
norms = G.graph.get("_sel_norms") or _norms_para_selector(G)
|
|
312
|
-
dnfr_max = float(norms.get("dnfr_max", 1.0))
|
|
313
|
-
acc_max = float(norms.get("accel_max", 1.0))
|
|
314
|
-
|
|
315
|
-
# Lecturas nodales
|
|
316
|
-
Si = clamp01(_get_attr(nd, ALIAS_SI, 0.5))
|
|
317
|
-
dnfr = abs(_get_attr(nd, ALIAS_DNFR, 0.0)) / dnfr_max
|
|
318
|
-
accel = abs(_get_attr(nd, ALIAS_D2EPI, 0.0)) / acc_max
|
|
319
|
-
|
|
320
|
-
W = G.graph.get("SELECTOR_WEIGHTS", DEFAULTS["SELECTOR_WEIGHTS"])
|
|
321
|
-
w_si = float(W.get("w_si", 0.5)); w_dn = float(W.get("w_dnfr", 0.3)); w_ac = float(W.get("w_accel", 0.2))
|
|
322
|
-
s = max(1e-9, w_si + w_dn + w_ac)
|
|
323
|
-
w_si, w_dn, w_ac = w_si/s, w_dn/s, w_ac/s
|
|
324
|
-
score = w_si*Si + w_dn*(1.0 - dnfr) + w_ac*(1.0 - accel)
|
|
325
|
-
# usar score como desempate/override suave: si score>0.66 ⇒ inclinar a I’L; <0.33 ⇒ inclinar a O’Z/Z’HIR
|
|
326
|
-
|
|
327
|
-
# Decisión base
|
|
328
|
-
if Si >= si_hi:
|
|
329
|
-
cand = "I’L"
|
|
330
|
-
elif Si <= si_lo:
|
|
331
|
-
if accel >= acc_hi:
|
|
332
|
-
cand = "T’HOL"
|
|
333
|
-
else:
|
|
334
|
-
cand = "O’Z" if dnfr >= dnfr_hi else "Z’HIR"
|
|
335
|
-
else:
|
|
336
|
-
# Zona intermedia: transición si el campo "pide" reorganizar (dnfr/accel altos)
|
|
337
|
-
if dnfr >= dnfr_hi or accel >= acc_hi:
|
|
338
|
-
cand = "NA’V"
|
|
339
|
-
else:
|
|
340
|
-
cand = "R’A"
|
|
341
|
-
|
|
342
|
-
# --- Histéresis del selector: si está cerca de umbrales, conserva el glifo reciente ---
|
|
343
|
-
# Medimos "certeza" como distancia mínima a los umbrales relevantes
|
|
344
|
-
d_si = min(abs(Si - si_hi), abs(Si - si_lo))
|
|
345
|
-
d_dn = min(abs(dnfr - dnfr_hi), abs(dnfr - dnfr_lo))
|
|
346
|
-
d_ac = min(abs(accel - acc_hi), abs(accel - acc_lo))
|
|
347
|
-
certeza = min(d_si, d_dn, d_ac)
|
|
348
|
-
if certeza < margin:
|
|
349
|
-
hist = nd.get("hist_glifos")
|
|
350
|
-
if hist:
|
|
351
|
-
prev = list(hist)[-1]
|
|
352
|
-
if isinstance(prev, str) and prev in ("I’L","O’Z","Z’HIR","T’HOL","NA’V","R’A"):
|
|
353
|
-
return prev
|
|
354
|
-
|
|
355
|
-
# Override suave guiado por score (solo si NO cayó la histéresis arriba)
|
|
356
|
-
# Regla: score>=0.66 inclina a I’L; score<=0.33 inclina a O’Z/Z’HIR
|
|
357
|
-
try:
|
|
358
|
-
if score >= 0.66 and cand in ("NA’V","R’A","Z’HIR","O’Z"):
|
|
359
|
-
cand = "I’L"
|
|
360
|
-
elif score <= 0.33 and cand in ("NA’V","R’A","I’L"):
|
|
361
|
-
cand = "O’Z" if dnfr >= dnfr_lo else "Z’HIR"
|
|
362
|
-
except NameError:
|
|
363
|
-
# por si 'score' no se definió (robustez), no forzamos nada
|
|
364
|
-
pass
|
|
365
|
-
|
|
366
|
-
# --- Gramática glífica suave: evita repeticiones cercanas salvo que el campo lo pida ---
|
|
367
|
-
gram = G.graph.get("GRAMMAR", DEFAULTS.get("GRAMMAR", {}))
|
|
368
|
-
gwin = int(gram.get("window", 3))
|
|
369
|
-
avoid = set(gram.get("avoid_repeats", []))
|
|
370
|
-
force_dn = float(gram.get("force_dnfr", 0.60))
|
|
371
|
-
force_ac = float(gram.get("force_accel", 0.60))
|
|
372
|
-
fallbacks = gram.get("fallbacks", {})
|
|
373
|
-
|
|
374
|
-
if cand in avoid and reciente_glifo(nd, cand, gwin):
|
|
375
|
-
# Solo permitimos repetir si el campo "insiste": dnfr o accel altos (ya normalizados)
|
|
376
|
-
if not (dnfr >= force_dn or accel >= force_ac):
|
|
377
|
-
cand = fallbacks.get(cand, "R’A")
|
|
378
|
-
|
|
379
|
-
return cand
|
|
380
|
-
|
|
381
|
-
# -------------------------
|
|
382
|
-
# Step / run
|
|
383
|
-
# -------------------------
|
|
384
|
-
|
|
385
|
-
def step(G, *, dt: float | None = None, use_Si: bool = True, apply_glyphs: bool = True) -> None:
|
|
386
|
-
# Contexto inicial
|
|
387
|
-
_hist0 = G.graph.setdefault("history", {"C_steps": []})
|
|
388
|
-
step_idx = len(_hist0.get("C_steps", []))
|
|
389
|
-
invoke_callbacks(G, "before_step", {"step": step_idx, "dt": dt, "use_Si": use_Si, "apply_glyphs": apply_glyphs})
|
|
390
|
-
|
|
391
|
-
# 1) ΔNFR (campo)
|
|
392
|
-
compute_dnfr_cb = G.graph.get("compute_delta_nfr", default_compute_delta_nfr)
|
|
393
|
-
compute_dnfr_cb(G)
|
|
394
|
-
|
|
395
|
-
# 2) (opcional) Si
|
|
396
|
-
if use_Si:
|
|
397
|
-
from helpers import compute_Si
|
|
398
|
-
compute_Si(G, inplace=True)
|
|
399
|
-
|
|
400
|
-
# 2b) Normalizadores para selector paramétrico (por paso)
|
|
401
|
-
_norms_para_selector(G) # no molesta si luego se usa el selector por defecto
|
|
402
|
-
|
|
403
|
-
# 3) Selección glífica + aplicación
|
|
404
|
-
if apply_glyphs:
|
|
405
|
-
selector = G.graph.get("glyph_selector", default_glyph_selector)
|
|
406
|
-
from operators import aplicar_glifo
|
|
407
|
-
window = int(G.graph.get("GLYPH_HYSTERESIS_WINDOW", DEFAULTS["GLYPH_HYSTERESIS_WINDOW"]))
|
|
408
|
-
for n in G.nodes():
|
|
409
|
-
g = selector(G, n)
|
|
410
|
-
aplicar_glifo(G, n, g, window=window)
|
|
411
|
-
|
|
412
|
-
# 4) Ecuación nodal
|
|
413
|
-
update_epi_via_nodal_equation(G, dt=dt)
|
|
414
|
-
|
|
415
|
-
# 5) Clamps
|
|
416
|
-
for n in G.nodes():
|
|
417
|
-
aplicar_clamps_canonicos(G.nodes[n], G)
|
|
418
|
-
|
|
419
|
-
# 6) Coordinación de fase
|
|
420
|
-
coordinar_fase_global_vecinal(G, None, None)
|
|
421
|
-
|
|
422
|
-
# 7) Observadores ligeros
|
|
423
|
-
_update_history(G)
|
|
424
|
-
# dynamics.py — dentro de step(), justo antes del punto 8)
|
|
425
|
-
epi_hist = G.graph.setdefault("_epi_hist", [])
|
|
426
|
-
epi_hist.append({n: _get_attr(G.nodes[n], ALIAS_EPI, 0.0) for n in G.nodes()})
|
|
427
|
-
# recorta el buffer para que no crezca sin límite
|
|
428
|
-
tau = int(G.graph.get("REMESH_TAU", DEFAULTS["REMESH_TAU"]))
|
|
429
|
-
maxlen = max(2*tau + 5, 64)
|
|
430
|
-
if len(epi_hist) > maxlen:
|
|
431
|
-
del epi_hist[:-maxlen]
|
|
432
|
-
|
|
433
|
-
# 8) RE’MESH condicionado
|
|
434
|
-
aplicar_remesh_si_estabilizacion_global(G)
|
|
435
|
-
|
|
436
|
-
# Contexto final (últimas métricas del paso)
|
|
437
|
-
h = G.graph.get("history", {})
|
|
438
|
-
ctx = {"step": step_idx}
|
|
439
|
-
if h.get("C_steps"): ctx["C"] = h["C_steps"][-1]
|
|
440
|
-
if h.get("stable_frac"): ctx["stable_frac"] = h["stable_frac"][-1]
|
|
441
|
-
if h.get("phase_sync"): ctx["phase_sync"] = h["phase_sync"][-1]
|
|
442
|
-
if h.get("glyph_load_disr"): ctx["glyph_disr"] = h["glyph_load_disr"][-1]
|
|
443
|
-
if h.get("Si_mean"): ctx["Si_mean"] = h["Si_mean"][-1]
|
|
444
|
-
invoke_callbacks(G, "after_step", ctx)
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
def run(G, steps: int, *, dt: float | None = None, use_Si: bool = True, apply_glyphs: bool = True) -> None:
|
|
448
|
-
for _ in range(int(steps)):
|
|
449
|
-
step(G, dt=dt, use_Si=use_Si, apply_glyphs=apply_glyphs)
|
|
450
|
-
# Early-stop opcional
|
|
451
|
-
stop_cfg = G.graph.get("STOP_EARLY", DEFAULTS.get("STOP_EARLY", {"enabled": False}))
|
|
452
|
-
if stop_cfg and stop_cfg.get("enabled", False):
|
|
453
|
-
w = int(stop_cfg.get("window", 25))
|
|
454
|
-
frac = float(stop_cfg.get("fraction", 0.90))
|
|
455
|
-
hist = G.graph.setdefault("history", {"stable_frac": []})
|
|
456
|
-
series = hist.get("stable_frac", [])
|
|
457
|
-
if len(series) >= w and all(v >= frac for v in series[-w:]):
|
|
458
|
-
break
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
# -------------------------
|
|
462
|
-
# Historial simple
|
|
463
|
-
# -------------------------
|
|
464
|
-
|
|
465
|
-
def _update_history(G) -> None:
|
|
466
|
-
hist = G.graph.setdefault("history", {
|
|
467
|
-
"C_steps": [], "stable_frac": [],
|
|
468
|
-
"phase_sync": [], "glyph_load_estab": [], "glyph_load_disr": [],
|
|
469
|
-
"Si_mean": [], "Si_hi_frac": [], "Si_lo_frac": []
|
|
470
|
-
})
|
|
471
|
-
|
|
472
|
-
# Proxy de coherencia C(t)
|
|
473
|
-
dnfr_mean = list_mean(abs(_get_attr(G.nodes[n], ALIAS_DNFR, 0.0)) for n in G.nodes())
|
|
474
|
-
dEPI_mean = list_mean(abs(_get_attr(G.nodes[n], ALIAS_dEPI, 0.0)) for n in G.nodes())
|
|
475
|
-
C = 1.0 / (1.0 + dnfr_mean + dEPI_mean)
|
|
476
|
-
hist["C_steps"].append(C)
|
|
477
|
-
|
|
478
|
-
# --- W̄: coherencia promedio en ventana ---
|
|
479
|
-
wbar_w = int(G.graph.get("WBAR_WINDOW", DEFAULTS.get("WBAR_WINDOW", 25)))
|
|
480
|
-
cs = hist["C_steps"]
|
|
481
|
-
if cs:
|
|
482
|
-
w = min(len(cs), max(1, wbar_w))
|
|
483
|
-
wbar = sum(cs[-w:]) / w
|
|
484
|
-
hist.setdefault("W_bar", []).append(wbar)
|
|
485
|
-
|
|
486
|
-
eps_dnfr = float(G.graph.get("EPS_DNFR_STABLE", DEFAULTS["EPS_DNFR_STABLE"]))
|
|
487
|
-
eps_depi = float(G.graph.get("EPS_DEPI_STABLE", DEFAULTS["EPS_DEPI_STABLE"]))
|
|
488
|
-
stables = 0
|
|
489
|
-
total = max(1, G.number_of_nodes())
|
|
490
|
-
for n in G.nodes():
|
|
491
|
-
nd = G.nodes[n]
|
|
492
|
-
if abs(_get_attr(nd, ALIAS_DNFR, 0.0)) <= eps_dnfr and abs(_get_attr(nd, ALIAS_dEPI, 0.0)) <= eps_depi:
|
|
493
|
-
stables += 1
|
|
494
|
-
hist["stable_frac"].append(stables/total)
|
|
495
|
-
# --- nuevas series: sincronía de fase y carga glífica ---
|
|
496
|
-
try:
|
|
497
|
-
ps = sincronía_fase(G) # [0,1], más alto = más en fase
|
|
498
|
-
hist["phase_sync"].append(ps)
|
|
499
|
-
R = orden_kuramoto(G)
|
|
500
|
-
hist.setdefault("kuramoto_R", []).append(R)
|
|
501
|
-
win = int(G.graph.get("GLYPH_LOAD_WINDOW", DEFAULTS["GLYPH_LOAD_WINDOW"]))
|
|
502
|
-
gl = carga_glifica(G, window=win) # proporciones
|
|
503
|
-
hist["glyph_load_estab"].append(gl.get("_estabilizadores", 0.0))
|
|
504
|
-
hist["glyph_load_disr"].append(gl.get("_disruptivos", 0.0))
|
|
505
|
-
# --- Σ⃗(t): vector de sentido a partir de la distribución glífica ---
|
|
506
|
-
sig = sigma_vector(G, window=win)
|
|
507
|
-
hist.setdefault("sense_sigma_x", []).append(sig.get("x", 0.0))
|
|
508
|
-
hist.setdefault("sense_sigma_y", []).append(sig.get("y", 0.0))
|
|
509
|
-
hist.setdefault("sense_sigma_mag", []).append(sig.get("mag", 0.0))
|
|
510
|
-
hist.setdefault("sense_sigma_angle", []).append(sig.get("angle", 0.0))
|
|
511
|
-
# --- ι(t): intensidad de activación coherente (proxy) ---
|
|
512
|
-
# Definición operativa: iota = C(t) * stable_frac(t)
|
|
513
|
-
if hist.get("C_steps") and hist.get("stable_frac"):
|
|
514
|
-
hist.setdefault("iota", []).append(hist["C_steps"][-1] * hist["stable_frac"][-1])
|
|
515
|
-
except Exception:
|
|
516
|
-
# observadores son opcionales; si no están, no rompemos el bucle
|
|
517
|
-
pass
|
|
518
|
-
|
|
519
|
-
# --- nuevas series: Si agregado (media y colas) ---
|
|
520
|
-
try:
|
|
521
|
-
import math
|
|
522
|
-
sis = []
|
|
523
|
-
for n in G.nodes():
|
|
524
|
-
sis.append(_get_attr(G.nodes[n], ALIAS_SI, float("nan")))
|
|
525
|
-
sis = [s for s in sis if not math.isnan(s)]
|
|
526
|
-
if sis:
|
|
527
|
-
si_mean = list_mean(sis, 0.0)
|
|
528
|
-
hist["Si_mean"].append(si_mean)
|
|
529
|
-
# umbrales preferentes del selector paramétrico; fallback a los del selector simple
|
|
530
|
-
thr_sel = G.graph.get("SELECTOR_THRESHOLDS", DEFAULTS.get("SELECTOR_THRESHOLDS", {}))
|
|
531
|
-
thr_def = G.graph.get("GLYPH_THRESHOLDS", DEFAULTS.get("GLYPH_THRESHOLDS", {"hi":0.66,"lo":0.33}))
|
|
532
|
-
si_hi = float(thr_sel.get("si_hi", thr_def.get("hi", 0.66)))
|
|
533
|
-
si_lo = float(thr_sel.get("si_lo", thr_def.get("lo", 0.33)))
|
|
534
|
-
n = len(sis)
|
|
535
|
-
hist["Si_hi_frac"].append(sum(1 for s in sis if s >= si_hi) / n)
|
|
536
|
-
hist["Si_lo_frac"].append(sum(1 for s in sis if s <= si_lo) / n)
|
|
537
|
-
else:
|
|
538
|
-
hist["Si_mean"].append(0.0)
|
|
539
|
-
hist["Si_hi_frac"].append(0.0)
|
|
540
|
-
hist["Si_lo_frac"].append(0.0)
|
|
541
|
-
except Exception:
|
|
542
|
-
# si aún no se calculó Si este paso, no interrumpimos
|
|
543
|
-
pass
|