tnfr 4.5.2__py3-none-any.whl → 7.0.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 +275 -51
- 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 +117 -31
- tnfr/alias.pyi +108 -0
- tnfr/cache.py +6 -572
- tnfr/cache.pyi +16 -0
- tnfr/callback_utils.py +16 -38
- tnfr/callback_utils.pyi +79 -0
- tnfr/cli/__init__.py +34 -14
- tnfr/cli/__init__.pyi +26 -0
- tnfr/cli/arguments.py +211 -28
- tnfr/cli/arguments.pyi +27 -0
- tnfr/cli/execution.py +470 -50
- tnfr/cli/execution.pyi +70 -0
- tnfr/cli/utils.py +18 -3
- tnfr/cli/utils.pyi +8 -0
- tnfr/config/__init__.py +13 -0
- tnfr/config/__init__.pyi +10 -0
- tnfr/{constants_glyphs.py → config/constants.py} +26 -20
- tnfr/config/constants.pyi +12 -0
- tnfr/config/feature_flags.py +83 -0
- tnfr/{config.py → config/init.py} +11 -7
- tnfr/config/init.pyi +8 -0
- tnfr/config/operator_names.py +93 -0
- tnfr/config/operator_names.pyi +28 -0
- tnfr/config/presets.py +84 -0
- tnfr/config/presets.pyi +7 -0
- tnfr/constants/__init__.py +80 -29
- tnfr/constants/__init__.pyi +92 -0
- tnfr/constants/aliases.py +31 -0
- tnfr/constants/core.py +4 -4
- tnfr/constants/core.pyi +17 -0
- tnfr/constants/init.py +1 -1
- tnfr/constants/init.pyi +12 -0
- tnfr/constants/metric.py +7 -15
- tnfr/constants/metric.pyi +19 -0
- tnfr/dynamics/__init__.py +165 -633
- tnfr/dynamics/__init__.pyi +82 -0
- tnfr/dynamics/adaptation.py +267 -0
- tnfr/dynamics/aliases.py +23 -0
- tnfr/dynamics/coordination.py +385 -0
- tnfr/dynamics/dnfr.py +2283 -400
- tnfr/dynamics/dnfr.pyi +24 -0
- tnfr/dynamics/integrators.py +406 -98
- tnfr/dynamics/integrators.pyi +34 -0
- tnfr/dynamics/runtime.py +881 -0
- tnfr/dynamics/sampling.py +10 -5
- tnfr/dynamics/sampling.pyi +7 -0
- tnfr/dynamics/selectors.py +719 -0
- tnfr/execution.py +70 -48
- tnfr/execution.pyi +45 -0
- tnfr/flatten.py +13 -9
- tnfr/flatten.pyi +21 -0
- tnfr/gamma.py +66 -53
- tnfr/gamma.pyi +34 -0
- tnfr/glyph_history.py +110 -52
- tnfr/glyph_history.pyi +35 -0
- tnfr/glyph_runtime.py +16 -0
- tnfr/glyph_runtime.pyi +9 -0
- tnfr/immutable.py +69 -28
- tnfr/immutable.pyi +34 -0
- tnfr/initialization.py +16 -16
- tnfr/initialization.pyi +65 -0
- tnfr/io.py +6 -240
- tnfr/io.pyi +16 -0
- tnfr/locking.pyi +7 -0
- tnfr/mathematics/__init__.py +81 -0
- tnfr/mathematics/backend.py +426 -0
- tnfr/mathematics/dynamics.py +398 -0
- tnfr/mathematics/epi.py +254 -0
- tnfr/mathematics/generators.py +222 -0
- tnfr/mathematics/metrics.py +119 -0
- tnfr/mathematics/operators.py +233 -0
- tnfr/mathematics/operators_factory.py +71 -0
- tnfr/mathematics/projection.py +78 -0
- tnfr/mathematics/runtime.py +173 -0
- tnfr/mathematics/spaces.py +247 -0
- tnfr/mathematics/transforms.py +292 -0
- tnfr/metrics/__init__.py +10 -10
- tnfr/metrics/__init__.pyi +20 -0
- tnfr/metrics/coherence.py +993 -324
- tnfr/metrics/common.py +23 -16
- tnfr/metrics/common.pyi +46 -0
- tnfr/metrics/core.py +251 -35
- tnfr/metrics/core.pyi +13 -0
- tnfr/metrics/diagnosis.py +708 -111
- tnfr/metrics/diagnosis.pyi +85 -0
- tnfr/metrics/export.py +27 -15
- tnfr/metrics/glyph_timing.py +232 -42
- tnfr/metrics/reporting.py +33 -22
- tnfr/metrics/reporting.pyi +12 -0
- tnfr/metrics/sense_index.py +987 -43
- tnfr/metrics/sense_index.pyi +9 -0
- tnfr/metrics/trig.py +214 -23
- tnfr/metrics/trig.pyi +13 -0
- tnfr/metrics/trig_cache.py +115 -22
- tnfr/metrics/trig_cache.pyi +10 -0
- tnfr/node.py +542 -136
- tnfr/node.pyi +178 -0
- tnfr/observers.py +152 -35
- tnfr/observers.pyi +31 -0
- tnfr/ontosim.py +23 -19
- tnfr/ontosim.pyi +28 -0
- tnfr/operators/__init__.py +601 -82
- tnfr/operators/__init__.pyi +45 -0
- tnfr/operators/definitions.py +513 -0
- tnfr/operators/definitions.pyi +78 -0
- tnfr/operators/grammar.py +760 -0
- tnfr/operators/jitter.py +107 -38
- tnfr/operators/jitter.pyi +11 -0
- tnfr/operators/registry.py +75 -0
- tnfr/operators/registry.pyi +13 -0
- tnfr/operators/remesh.py +149 -88
- tnfr/py.typed +0 -0
- tnfr/rng.py +46 -143
- tnfr/rng.pyi +14 -0
- tnfr/schemas/__init__.py +8 -0
- tnfr/schemas/grammar.json +94 -0
- tnfr/selector.py +25 -19
- tnfr/selector.pyi +19 -0
- tnfr/sense.py +72 -62
- tnfr/sense.pyi +23 -0
- tnfr/structural.py +522 -262
- tnfr/structural.pyi +69 -0
- tnfr/telemetry/__init__.py +35 -0
- tnfr/telemetry/cache_metrics.py +226 -0
- tnfr/telemetry/nu_f.py +423 -0
- tnfr/telemetry/nu_f.pyi +123 -0
- tnfr/telemetry/verbosity.py +37 -0
- tnfr/tokens.py +1 -3
- tnfr/tokens.pyi +36 -0
- tnfr/trace.py +270 -113
- tnfr/trace.pyi +40 -0
- tnfr/types.py +574 -6
- tnfr/types.pyi +331 -0
- tnfr/units.py +69 -0
- tnfr/units.pyi +16 -0
- tnfr/utils/__init__.py +217 -0
- tnfr/utils/__init__.pyi +202 -0
- tnfr/utils/cache.py +2395 -0
- tnfr/utils/cache.pyi +468 -0
- tnfr/utils/chunks.py +104 -0
- tnfr/utils/chunks.pyi +21 -0
- tnfr/{collections_utils.py → utils/data.py} +147 -90
- tnfr/utils/data.pyi +64 -0
- tnfr/utils/graph.py +85 -0
- tnfr/utils/graph.pyi +10 -0
- tnfr/utils/init.py +770 -0
- tnfr/utils/init.pyi +78 -0
- tnfr/utils/io.py +456 -0
- tnfr/{helpers → utils}/numeric.py +51 -24
- tnfr/utils/numeric.pyi +21 -0
- tnfr/validation/__init__.py +113 -0
- tnfr/validation/__init__.pyi +77 -0
- tnfr/validation/compatibility.py +95 -0
- tnfr/validation/compatibility.pyi +6 -0
- tnfr/validation/grammar.py +71 -0
- tnfr/validation/grammar.pyi +40 -0
- tnfr/validation/graph.py +138 -0
- tnfr/validation/graph.pyi +17 -0
- tnfr/validation/rules.py +281 -0
- tnfr/validation/rules.pyi +55 -0
- tnfr/validation/runtime.py +263 -0
- tnfr/validation/runtime.pyi +31 -0
- tnfr/validation/soft_filters.py +170 -0
- tnfr/validation/soft_filters.pyi +37 -0
- tnfr/validation/spectral.py +159 -0
- tnfr/validation/spectral.pyi +46 -0
- tnfr/validation/syntax.py +40 -0
- tnfr/validation/syntax.pyi +10 -0
- tnfr/validation/window.py +39 -0
- tnfr/validation/window.pyi +1 -0
- tnfr/viz/__init__.py +9 -0
- tnfr/viz/matplotlib.py +246 -0
- tnfr-7.0.0.dist-info/METADATA +179 -0
- tnfr-7.0.0.dist-info/RECORD +185 -0
- {tnfr-4.5.2.dist-info → tnfr-7.0.0.dist-info}/licenses/LICENSE.md +1 -1
- tnfr/grammar.py +0 -344
- tnfr/graph_utils.py +0 -84
- tnfr/helpers/__init__.py +0 -71
- tnfr/import_utils.py +0 -228
- tnfr/json_utils.py +0 -162
- tnfr/logging_utils.py +0 -116
- tnfr/presets.py +0 -60
- tnfr/validators.py +0 -84
- tnfr/value_utils.py +0 -59
- tnfr-4.5.2.dist-info/METADATA +0 -379
- tnfr-4.5.2.dist-info/RECORD +0 -67
- {tnfr-4.5.2.dist-info → tnfr-7.0.0.dist-info}/WHEEL +0 -0
- {tnfr-4.5.2.dist-info → tnfr-7.0.0.dist-info}/entry_points.txt +0 -0
- {tnfr-4.5.2.dist-info → tnfr-7.0.0.dist-info}/top_level.txt +0 -0
tnfr/dynamics/__init__.py
CHANGED
|
@@ -1,658 +1,190 @@
|
|
|
1
|
-
|
|
1
|
+
"""Facade that keeps ΔNFR, νf and phase orchestration coherent across TNFR dynamics.
|
|
2
|
+
|
|
3
|
+
Attributes
|
|
4
|
+
----------
|
|
5
|
+
run : callable
|
|
6
|
+
Callable that fully manages the evolution loop, integrating the nodal
|
|
7
|
+
equation while enforcing ΔNFR hooks, νf adaptation and phase coordination
|
|
8
|
+
on every step.
|
|
9
|
+
step : callable
|
|
10
|
+
Callable entry point for a single iteration that reuses the
|
|
11
|
+
ΔNFR/νf/phase pipeline while letting callers interleave bespoke telemetry
|
|
12
|
+
or operator injections.
|
|
13
|
+
set_delta_nfr_hook : callable
|
|
14
|
+
Callable used to install custom ΔNFR supervision under
|
|
15
|
+
``G.graph['compute_delta_nfr']`` so each operator reorganization stays
|
|
16
|
+
coupled to νf drift and phase targets.
|
|
17
|
+
default_glyph_selector, parametric_glyph_selector : AbstractSelector
|
|
18
|
+
Selector implementations that choose glyphs according to ΔNFR trends,
|
|
19
|
+
νf ranges and phase synchrony, ensuring operator firing reinforces
|
|
20
|
+
coherence.
|
|
21
|
+
coordination, dnfr, integrators : module
|
|
22
|
+
Re-exported modules providing explicit control over phase alignment,
|
|
23
|
+
ΔNFR caches and integrator lifecycles to centralize orchestration.
|
|
24
|
+
ProcessPoolExecutor, apply_glyph, compute_Si : callable
|
|
25
|
+
Re-exported utilities for parallel selector evaluation, explicit glyph
|
|
26
|
+
execution and Si telemetry so ΔNFR, νf and phase traces remain observable.
|
|
27
|
+
|
|
28
|
+
Notes
|
|
29
|
+
-----
|
|
30
|
+
The facade aggregates runtime helpers that preserve canonical TNFR dynamics:
|
|
31
|
+
``dnfr`` manages ΔNFR preparation and caching, ``integrators`` drives the
|
|
32
|
+
numerical updates of νf and EPI, and ``coordination`` synchronizes global and
|
|
33
|
+
local phase. Complementary exports such as
|
|
34
|
+
:func:`~tnfr.dynamics.adaptation.adapt_vf_by_coherence` and
|
|
35
|
+
:func:`~tnfr.dynamics.coordination.coordinate_global_local_phase` allow custom
|
|
36
|
+
feedback loops without breaking operator closure.
|
|
37
|
+
|
|
38
|
+
Examples
|
|
39
|
+
--------
|
|
40
|
+
>>> from tnfr.constants import DNFR_PRIMARY, EPI_PRIMARY, VF_PRIMARY
|
|
41
|
+
>>> from tnfr.structural import Coherence, Emission, Resonance, create_nfr, run_sequence
|
|
42
|
+
>>> from tnfr.dynamics import parametric_glyph_selector, run, set_delta_nfr_hook, step
|
|
43
|
+
>>> G, node = create_nfr("seed", epi=0.22, vf=1.0)
|
|
44
|
+
>>> def regulate_delta(graph, *, n_jobs=None):
|
|
45
|
+
... for _, nd in graph.nodes(data=True):
|
|
46
|
+
... delta = nd[VF_PRIMARY] * 0.08
|
|
47
|
+
... nd[DNFR_PRIMARY] = delta
|
|
48
|
+
... nd[EPI_PRIMARY] += delta
|
|
49
|
+
... nd[VF_PRIMARY] += delta * 0.05
|
|
50
|
+
... return None
|
|
51
|
+
>>> set_delta_nfr_hook(G, regulate_delta, note="ΔNFR guided by νf")
|
|
52
|
+
>>> G.graph["glyph_selector"] = parametric_glyph_selector
|
|
53
|
+
>>> run_sequence(G, node, [Emission(), Resonance(), Coherence()])
|
|
54
|
+
>>> run(G, steps=2, dt=0.05)
|
|
55
|
+
>>> # Automatic integration keeps ΔNFR, νf and phase co-modulated.
|
|
56
|
+
>>> step(G, dt=0.05)
|
|
57
|
+
>>> # Manual control reuses the selector state to consolidate coherence traces.
|
|
58
|
+
"""
|
|
2
59
|
|
|
3
|
-
import
|
|
4
|
-
from collections import deque
|
|
5
|
-
from operator import itemgetter
|
|
6
|
-
from typing import Any
|
|
60
|
+
from __future__ import annotations
|
|
7
61
|
|
|
8
|
-
|
|
9
|
-
# realizar la importación en cada paso de la dinámica. Como los módulos de
|
|
10
|
-
# origen no dependen de ``dynamics``, no se introducen ciclos.
|
|
11
|
-
from ..operators import apply_remesh_if_globally_stable, apply_glyph
|
|
12
|
-
from ..grammar import enforce_canonical_grammar, on_applied_glyph
|
|
13
|
-
from ..types import Glyph
|
|
14
|
-
from ..constants import (
|
|
15
|
-
DEFAULTS,
|
|
16
|
-
METRIC_DEFAULTS,
|
|
17
|
-
get_aliases,
|
|
18
|
-
get_param,
|
|
19
|
-
get_graph_param,
|
|
20
|
-
)
|
|
21
|
-
from ..observers import DEFAULT_GLYPH_LOAD_SPAN, glyph_load, kuramoto_order
|
|
62
|
+
from concurrent.futures import ProcessPoolExecutor
|
|
22
63
|
|
|
23
|
-
from ..helpers.numeric import (
|
|
24
|
-
clamp,
|
|
25
|
-
clamp01,
|
|
26
|
-
angle_diff,
|
|
27
|
-
)
|
|
28
|
-
from ..metrics.trig import neighbor_phase_mean
|
|
29
|
-
from ..alias import (
|
|
30
|
-
get_attr,
|
|
31
|
-
set_vf,
|
|
32
|
-
set_attr,
|
|
33
|
-
set_theta,
|
|
34
|
-
multi_recompute_abs_max,
|
|
35
|
-
)
|
|
36
64
|
from ..metrics.sense_index import compute_Si
|
|
37
|
-
from ..
|
|
38
|
-
from ..
|
|
39
|
-
from ..
|
|
40
|
-
from ..
|
|
41
|
-
from
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
65
|
+
from ..operators import apply_glyph, enforce_canonical_grammar, on_applied_glyph
|
|
66
|
+
from ..types import GlyphCode
|
|
67
|
+
from ..utils import get_numpy
|
|
68
|
+
from ..validation import apply_canonical_clamps, validate_canon
|
|
69
|
+
from . import coordination, dnfr, integrators
|
|
70
|
+
from .adaptation import adapt_vf_by_coherence
|
|
71
|
+
from .aliases import (
|
|
72
|
+
ALIAS_D2EPI,
|
|
73
|
+
ALIAS_DNFR,
|
|
74
|
+
ALIAS_DSI,
|
|
75
|
+
ALIAS_EPI,
|
|
76
|
+
ALIAS_SI,
|
|
77
|
+
ALIAS_VF,
|
|
46
78
|
)
|
|
47
|
-
|
|
48
|
-
from .sampling import update_node_sample as _update_node_sample
|
|
79
|
+
from .coordination import coordinate_global_local_phase
|
|
49
80
|
from .dnfr import (
|
|
50
|
-
|
|
81
|
+
_compute_dnfr,
|
|
82
|
+
_compute_neighbor_means,
|
|
51
83
|
_init_dnfr_cache,
|
|
84
|
+
_prepare_dnfr_data,
|
|
52
85
|
_refresh_dnfr_vectors,
|
|
53
|
-
_compute_neighbor_means,
|
|
54
|
-
_compute_dnfr,
|
|
55
86
|
default_compute_delta_nfr,
|
|
56
|
-
set_delta_nfr_hook,
|
|
57
|
-
dnfr_phase_only,
|
|
58
87
|
dnfr_epi_vf_mixed,
|
|
59
88
|
dnfr_laplacian,
|
|
89
|
+
dnfr_phase_only,
|
|
90
|
+
set_delta_nfr_hook,
|
|
60
91
|
)
|
|
61
92
|
from .integrators import (
|
|
93
|
+
AbstractIntegrator,
|
|
94
|
+
DefaultIntegrator,
|
|
62
95
|
prepare_integration_params,
|
|
63
96
|
update_epi_via_nodal_equation,
|
|
64
97
|
)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
98
|
+
from .runtime import (
|
|
99
|
+
_maybe_remesh,
|
|
100
|
+
_normalize_job_overrides,
|
|
101
|
+
_prepare_dnfr,
|
|
102
|
+
_resolve_jobs_override,
|
|
103
|
+
_run_after_callbacks,
|
|
104
|
+
_run_before_callbacks,
|
|
105
|
+
_run_validators,
|
|
106
|
+
_update_epi_hist,
|
|
107
|
+
_update_nodes,
|
|
108
|
+
run,
|
|
109
|
+
step,
|
|
110
|
+
)
|
|
111
|
+
from .sampling import update_node_sample as _update_node_sample
|
|
112
|
+
from .selectors import (
|
|
113
|
+
AbstractSelector,
|
|
114
|
+
DefaultGlyphSelector,
|
|
115
|
+
ParametricGlyphSelector,
|
|
116
|
+
_apply_glyphs,
|
|
117
|
+
_apply_selector,
|
|
118
|
+
_choose_glyph,
|
|
119
|
+
_collect_selector_metrics,
|
|
120
|
+
_configure_selector_weights,
|
|
121
|
+
_prepare_selector_preselection,
|
|
122
|
+
_resolve_preselected_glyph,
|
|
123
|
+
_selector_parallel_jobs,
|
|
124
|
+
_SelectorPreselection,
|
|
125
|
+
default_glyph_selector,
|
|
126
|
+
parametric_glyph_selector,
|
|
127
|
+
)
|
|
73
128
|
|
|
74
129
|
__all__ = (
|
|
130
|
+
"coordination",
|
|
131
|
+
"dnfr",
|
|
132
|
+
"integrators",
|
|
133
|
+
"ALIAS_D2EPI",
|
|
134
|
+
"ALIAS_DNFR",
|
|
135
|
+
"ALIAS_DSI",
|
|
136
|
+
"ALIAS_EPI",
|
|
137
|
+
"ALIAS_SI",
|
|
138
|
+
"ALIAS_VF",
|
|
139
|
+
"AbstractSelector",
|
|
140
|
+
"DefaultGlyphSelector",
|
|
141
|
+
"ParametricGlyphSelector",
|
|
142
|
+
"GlyphCode",
|
|
143
|
+
"_SelectorPreselection",
|
|
144
|
+
"_apply_glyphs",
|
|
145
|
+
"_apply_selector",
|
|
146
|
+
"_choose_glyph",
|
|
147
|
+
"_collect_selector_metrics",
|
|
148
|
+
"_configure_selector_weights",
|
|
149
|
+
"ProcessPoolExecutor",
|
|
150
|
+
"_maybe_remesh",
|
|
151
|
+
"_normalize_job_overrides",
|
|
152
|
+
"_prepare_dnfr",
|
|
153
|
+
"_prepare_dnfr_data",
|
|
154
|
+
"_prepare_selector_preselection",
|
|
155
|
+
"_resolve_jobs_override",
|
|
156
|
+
"_resolve_preselected_glyph",
|
|
157
|
+
"_run_after_callbacks",
|
|
158
|
+
"_run_before_callbacks",
|
|
159
|
+
"_run_validators",
|
|
160
|
+
"_selector_parallel_jobs",
|
|
161
|
+
"_update_epi_hist",
|
|
162
|
+
"_update_node_sample",
|
|
163
|
+
"_update_nodes",
|
|
164
|
+
"_compute_dnfr",
|
|
165
|
+
"_compute_neighbor_means",
|
|
166
|
+
"_init_dnfr_cache",
|
|
167
|
+
"_refresh_dnfr_vectors",
|
|
168
|
+
"adapt_vf_by_coherence",
|
|
169
|
+
"apply_canonical_clamps",
|
|
170
|
+
"coordinate_global_local_phase",
|
|
171
|
+
"compute_Si",
|
|
75
172
|
"default_compute_delta_nfr",
|
|
76
|
-
"
|
|
77
|
-
"dnfr_phase_only",
|
|
173
|
+
"default_glyph_selector",
|
|
78
174
|
"dnfr_epi_vf_mixed",
|
|
79
175
|
"dnfr_laplacian",
|
|
176
|
+
"dnfr_phase_only",
|
|
177
|
+
"enforce_canonical_grammar",
|
|
178
|
+
"get_numpy",
|
|
179
|
+
"on_applied_glyph",
|
|
180
|
+
"apply_glyph",
|
|
181
|
+
"parametric_glyph_selector",
|
|
182
|
+
"AbstractIntegrator",
|
|
183
|
+
"DefaultIntegrator",
|
|
80
184
|
"prepare_integration_params",
|
|
185
|
+
"run",
|
|
186
|
+
"set_delta_nfr_hook",
|
|
187
|
+
"step",
|
|
81
188
|
"update_epi_via_nodal_equation",
|
|
82
|
-
"apply_canonical_clamps",
|
|
83
189
|
"validate_canon",
|
|
84
|
-
"coordinate_global_local_phase",
|
|
85
|
-
"adapt_vf_by_coherence",
|
|
86
|
-
"default_glyph_selector",
|
|
87
|
-
"parametric_glyph_selector",
|
|
88
|
-
"step",
|
|
89
|
-
"run",
|
|
90
|
-
"_prepare_dnfr_data",
|
|
91
|
-
"_init_dnfr_cache",
|
|
92
|
-
"_refresh_dnfr_vectors",
|
|
93
|
-
"_compute_neighbor_means",
|
|
94
|
-
"_compute_dnfr",
|
|
95
190
|
)
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
def _log_clamp(hist, node, attr, value, lo, hi):
|
|
99
|
-
if value < lo or value > hi:
|
|
100
|
-
hist.append({"node": node, "attr": attr, "value": float(value)})
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def apply_canonical_clamps(nd: dict[str, Any], G=None, node=None) -> None:
|
|
104
|
-
g = G.graph if G is not None else DEFAULTS
|
|
105
|
-
eps_min = float(g.get("EPI_MIN", DEFAULTS["EPI_MIN"]))
|
|
106
|
-
eps_max = float(g.get("EPI_MAX", DEFAULTS["EPI_MAX"]))
|
|
107
|
-
vf_min = float(g.get("VF_MIN", DEFAULTS["VF_MIN"]))
|
|
108
|
-
vf_max = float(g.get("VF_MAX", DEFAULTS["VF_MAX"]))
|
|
109
|
-
theta_wrap = bool(g.get("THETA_WRAP", DEFAULTS["THETA_WRAP"]))
|
|
110
|
-
|
|
111
|
-
epi = get_attr(nd, ALIAS_EPI, 0.0)
|
|
112
|
-
vf = get_attr(nd, ALIAS_VF, 0.0)
|
|
113
|
-
th = get_attr(nd, ALIAS_THETA, 0.0)
|
|
114
|
-
|
|
115
|
-
strict = bool(
|
|
116
|
-
g.get("VALIDATORS_STRICT", DEFAULTS.get("VALIDATORS_STRICT", False))
|
|
117
|
-
)
|
|
118
|
-
if strict and G is not None:
|
|
119
|
-
hist = g.setdefault("history", {}).setdefault("clamp_alerts", [])
|
|
120
|
-
_log_clamp(hist, node, "EPI", epi, eps_min, eps_max)
|
|
121
|
-
_log_clamp(hist, node, "VF", vf, vf_min, vf_max)
|
|
122
|
-
|
|
123
|
-
set_attr(nd, ALIAS_EPI, clamp(epi, eps_min, eps_max))
|
|
124
|
-
|
|
125
|
-
vf_val = clamp(vf, vf_min, vf_max)
|
|
126
|
-
if G is not None and node is not None:
|
|
127
|
-
set_vf(G, node, vf_val, update_max=False)
|
|
128
|
-
else:
|
|
129
|
-
set_attr(nd, ALIAS_VF, vf_val)
|
|
130
|
-
|
|
131
|
-
if theta_wrap:
|
|
132
|
-
new_th = (th + math.pi) % (2 * math.pi) - math.pi
|
|
133
|
-
if G is not None and node is not None:
|
|
134
|
-
set_theta(G, node, new_th)
|
|
135
|
-
else:
|
|
136
|
-
set_attr(nd, ALIAS_THETA, new_th)
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
def validate_canon(G) -> None:
|
|
140
|
-
"""Apply canonical clamps to all nodes of ``G``.
|
|
141
|
-
|
|
142
|
-
Wrap phase and constrain ``EPI`` and ``νf`` to the ranges in ``G.graph``.
|
|
143
|
-
If ``VALIDATORS_STRICT`` is active, alerts are logged in ``history``.
|
|
144
|
-
"""
|
|
145
|
-
for n, nd in G.nodes(data=True):
|
|
146
|
-
apply_canonical_clamps(nd, G, n)
|
|
147
|
-
maxes = multi_recompute_abs_max(G, {"_vfmax": ALIAS_VF})
|
|
148
|
-
G.graph.update(maxes)
|
|
149
|
-
return G
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
def _read_adaptive_params(
|
|
153
|
-
g: dict[str, Any],
|
|
154
|
-
) -> tuple[dict[str, Any], float, float]:
|
|
155
|
-
"""Obtain configuration and current values for phase adaptation."""
|
|
156
|
-
cfg = g.get("PHASE_ADAPT", DEFAULTS.get("PHASE_ADAPT", {}))
|
|
157
|
-
kG = float(g.get("PHASE_K_GLOBAL", DEFAULTS["PHASE_K_GLOBAL"]))
|
|
158
|
-
kL = float(g.get("PHASE_K_LOCAL", DEFAULTS["PHASE_K_LOCAL"]))
|
|
159
|
-
return cfg, kG, kL
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
def _compute_state(G, cfg: dict[str, Any]) -> tuple[str, float, float]:
|
|
163
|
-
"""Return current state (stable/dissonant/transition) and metrics."""
|
|
164
|
-
R = kuramoto_order(G)
|
|
165
|
-
dist = glyph_load(G, window=DEFAULT_GLYPH_LOAD_SPAN)
|
|
166
|
-
disr = float(dist.get("_disruptivos", 0.0)) if dist else 0.0
|
|
167
|
-
|
|
168
|
-
R_hi = float(cfg.get("R_hi", 0.90))
|
|
169
|
-
R_lo = float(cfg.get("R_lo", 0.60))
|
|
170
|
-
disr_hi = float(cfg.get("disr_hi", 0.50))
|
|
171
|
-
disr_lo = float(cfg.get("disr_lo", 0.25))
|
|
172
|
-
if (R >= R_hi) and (disr <= disr_lo):
|
|
173
|
-
state = "estable"
|
|
174
|
-
elif (R <= R_lo) or (disr >= disr_hi):
|
|
175
|
-
state = "disonante"
|
|
176
|
-
else:
|
|
177
|
-
state = "transicion"
|
|
178
|
-
return state, float(R), disr
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
def _smooth_adjust_k(
|
|
182
|
-
kG: float, kL: float, state: str, cfg: dict[str, Any]
|
|
183
|
-
) -> tuple[float, float]:
|
|
184
|
-
"""Smoothly update kG/kL toward targets according to state."""
|
|
185
|
-
kG_min = float(cfg.get("kG_min", 0.01))
|
|
186
|
-
kG_max = float(cfg.get("kG_max", 0.20))
|
|
187
|
-
kL_min = float(cfg.get("kL_min", 0.05))
|
|
188
|
-
kL_max = float(cfg.get("kL_max", 0.25))
|
|
189
|
-
|
|
190
|
-
if state == "disonante":
|
|
191
|
-
kG_t = kG_max
|
|
192
|
-
kL_t = 0.5 * (
|
|
193
|
-
kL_min + kL_max
|
|
194
|
-
) # local medio para no perder plasticidad
|
|
195
|
-
elif state == "estable":
|
|
196
|
-
kG_t = kG_min
|
|
197
|
-
kL_t = kL_min
|
|
198
|
-
else:
|
|
199
|
-
kG_t = 0.5 * (kG_min + kG_max)
|
|
200
|
-
kL_t = 0.5 * (kL_min + kL_max)
|
|
201
|
-
|
|
202
|
-
up = float(cfg.get("up", 0.10))
|
|
203
|
-
down = float(cfg.get("down", 0.07))
|
|
204
|
-
|
|
205
|
-
def _step(curr: float, target: float, mn: float, mx: float) -> float:
|
|
206
|
-
gain = up if target > curr else down
|
|
207
|
-
nxt = curr + gain * (target - curr)
|
|
208
|
-
return max(mn, min(mx, nxt))
|
|
209
|
-
|
|
210
|
-
return _step(kG, kG_t, kG_min, kG_max), _step(kL, kL_t, kL_min, kL_max)
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
def _ensure_hist_deque(hist: dict[str, Any], key: str, maxlen: int) -> deque:
|
|
214
|
-
"""Ensure history entry ``key`` is a deque with ``maxlen``."""
|
|
215
|
-
dq = hist.setdefault(key, deque(maxlen=maxlen))
|
|
216
|
-
if not isinstance(dq, deque):
|
|
217
|
-
dq = deque(dq, maxlen=maxlen)
|
|
218
|
-
hist[key] = dq
|
|
219
|
-
return dq
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
def coordinate_global_local_phase(
|
|
223
|
-
G, global_force: float | None = None, local_force: float | None = None
|
|
224
|
-
) -> None:
|
|
225
|
-
"""
|
|
226
|
-
Ajusta fase con mezcla GLOBAL+VECINAL.
|
|
227
|
-
Si no se pasan fuerzas explícitas, adapta kG/kL según estado
|
|
228
|
-
(disonante / transición / estable).
|
|
229
|
-
Estado se decide por R (Kuramoto) y carga glífica disruptiva reciente.
|
|
230
|
-
"""
|
|
231
|
-
g = G.graph
|
|
232
|
-
defaults = DEFAULTS
|
|
233
|
-
hist = g.setdefault("history", {})
|
|
234
|
-
maxlen = int(
|
|
235
|
-
g.get("PHASE_HISTORY_MAXLEN", METRIC_DEFAULTS["PHASE_HISTORY_MAXLEN"])
|
|
236
|
-
)
|
|
237
|
-
hist_state = _ensure_hist_deque(hist, "phase_state", maxlen)
|
|
238
|
-
hist_R = _ensure_hist_deque(hist, "phase_R", maxlen)
|
|
239
|
-
hist_disr = _ensure_hist_deque(hist, "phase_disr", maxlen)
|
|
240
|
-
# 0) Si hay fuerzas explícitas, usar y salir del modo adaptativo
|
|
241
|
-
if (global_force is not None) or (local_force is not None):
|
|
242
|
-
kG = float(
|
|
243
|
-
global_force
|
|
244
|
-
if global_force is not None
|
|
245
|
-
else g.get("PHASE_K_GLOBAL", defaults["PHASE_K_GLOBAL"])
|
|
246
|
-
)
|
|
247
|
-
kL = float(
|
|
248
|
-
local_force
|
|
249
|
-
if local_force is not None
|
|
250
|
-
else g.get("PHASE_K_LOCAL", defaults["PHASE_K_LOCAL"])
|
|
251
|
-
)
|
|
252
|
-
else:
|
|
253
|
-
cfg, kG, kL = _read_adaptive_params(g)
|
|
254
|
-
|
|
255
|
-
if bool(cfg.get("enabled", False)):
|
|
256
|
-
state, R, disr = _compute_state(G, cfg)
|
|
257
|
-
kG, kL = _smooth_adjust_k(kG, kL, state, cfg)
|
|
258
|
-
|
|
259
|
-
hist_state.append(state)
|
|
260
|
-
hist_R.append(float(R))
|
|
261
|
-
hist_disr.append(float(disr))
|
|
262
|
-
|
|
263
|
-
g["PHASE_K_GLOBAL"] = kG
|
|
264
|
-
g["PHASE_K_LOCAL"] = kL
|
|
265
|
-
append_metric(hist, "phase_kG", float(kG))
|
|
266
|
-
append_metric(hist, "phase_kL", float(kL))
|
|
267
|
-
|
|
268
|
-
# 6) Fase GLOBAL (centroide) para empuje
|
|
269
|
-
trig = compute_theta_trig(G.nodes(data=True))
|
|
270
|
-
num_nodes = G.number_of_nodes()
|
|
271
|
-
if num_nodes:
|
|
272
|
-
mean_cos = sum(trig.cos.values()) / num_nodes
|
|
273
|
-
mean_sin = sum(trig.sin.values()) / num_nodes
|
|
274
|
-
thG = math.atan2(mean_sin, mean_cos)
|
|
275
|
-
else:
|
|
276
|
-
thG = 0.0
|
|
277
|
-
|
|
278
|
-
# 7) Aplicar corrección global+vecinal
|
|
279
|
-
for n, nd in G.nodes(data=True):
|
|
280
|
-
th = get_attr(nd, ALIAS_THETA, 0.0)
|
|
281
|
-
thL = neighbor_phase_mean(G, n)
|
|
282
|
-
dG = angle_diff(thG, th)
|
|
283
|
-
dL = angle_diff(thL, th)
|
|
284
|
-
set_theta(G, n, th + kG * dG + kL * dL)
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
# -------------------------
|
|
288
|
-
# Adaptación de νf por coherencia
|
|
289
|
-
# -------------------------
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
def adapt_vf_by_coherence(G) -> None:
|
|
293
|
-
"""Adjust νf toward neighbour mean in nodes with sustained stability."""
|
|
294
|
-
tau = get_graph_param(G, "VF_ADAPT_TAU", int)
|
|
295
|
-
mu = get_graph_param(G, "VF_ADAPT_MU")
|
|
296
|
-
eps_dnfr = get_graph_param(G, "EPS_DNFR_STABLE")
|
|
297
|
-
thr_sel = get_graph_param(G, "SELECTOR_THRESHOLDS", dict)
|
|
298
|
-
thr_def = get_graph_param(G, "GLYPH_THRESHOLDS", dict)
|
|
299
|
-
si_hi = float(thr_sel.get("si_hi", thr_def.get("hi", 0.66)))
|
|
300
|
-
vf_min = get_graph_param(G, "VF_MIN")
|
|
301
|
-
vf_max = get_graph_param(G, "VF_MAX")
|
|
302
|
-
|
|
303
|
-
updates = {}
|
|
304
|
-
for n, nd in G.nodes(data=True):
|
|
305
|
-
Si = get_attr(nd, ALIAS_SI, 0.0)
|
|
306
|
-
dnfr = abs(get_attr(nd, ALIAS_DNFR, 0.0))
|
|
307
|
-
if Si >= si_hi and dnfr <= eps_dnfr:
|
|
308
|
-
nd["stable_count"] = nd.get("stable_count", 0) + 1
|
|
309
|
-
else:
|
|
310
|
-
nd["stable_count"] = 0
|
|
311
|
-
continue
|
|
312
|
-
|
|
313
|
-
if nd["stable_count"] >= tau:
|
|
314
|
-
vf = get_attr(nd, ALIAS_VF, 0.0)
|
|
315
|
-
neigh = list(G.neighbors(n))
|
|
316
|
-
if neigh:
|
|
317
|
-
total = 0.0
|
|
318
|
-
for v in neigh:
|
|
319
|
-
total += float(get_attr(G.nodes[v], ALIAS_VF, vf))
|
|
320
|
-
vf_bar = total / len(neigh)
|
|
321
|
-
else:
|
|
322
|
-
vf_bar = float(vf)
|
|
323
|
-
updates[n] = vf + mu * (vf_bar - vf)
|
|
324
|
-
|
|
325
|
-
for n, vf_new in updates.items():
|
|
326
|
-
set_vf(G, n, clamp(vf_new, vf_min, vf_max))
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
# -------------------------
|
|
330
|
-
# Selector glífico por defecto
|
|
331
|
-
# -------------------------
|
|
332
|
-
def default_glyph_selector(G, n) -> str:
|
|
333
|
-
nd = G.nodes[n]
|
|
334
|
-
thr = _selector_thresholds(G)
|
|
335
|
-
hi, lo, dnfr_hi = itemgetter("si_hi", "si_lo", "dnfr_hi")(thr)
|
|
336
|
-
# Extract thresholds in one call to reduce dict lookups inside loops.
|
|
337
|
-
|
|
338
|
-
norms = G.graph.get("_sel_norms")
|
|
339
|
-
if norms is None:
|
|
340
|
-
norms = compute_dnfr_accel_max(G)
|
|
341
|
-
G.graph["_sel_norms"] = norms
|
|
342
|
-
dnfr_max = float(norms.get("dnfr_max", 1.0)) or 1.0
|
|
343
|
-
|
|
344
|
-
Si = clamp01(get_attr(nd, ALIAS_SI, 0.5))
|
|
345
|
-
dnfr = abs(get_attr(nd, ALIAS_DNFR, 0.0)) / dnfr_max
|
|
346
|
-
|
|
347
|
-
if Si >= hi:
|
|
348
|
-
return "IL"
|
|
349
|
-
if Si <= lo:
|
|
350
|
-
return "OZ" if dnfr > dnfr_hi else "ZHIR"
|
|
351
|
-
return "NAV" if dnfr > dnfr_hi else "RA"
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
# -------------------------
|
|
355
|
-
# Selector glífico multiobjetivo (paramétrico)
|
|
356
|
-
# -------------------------
|
|
357
|
-
def _soft_grammar_prefilter(G, n, cand, dnfr, accel):
|
|
358
|
-
"""Soft grammar: avoid repetitions before the canonical one."""
|
|
359
|
-
gram = get_graph_param(G, "GRAMMAR", dict)
|
|
360
|
-
gwin = int(gram.get("window", 3))
|
|
361
|
-
avoid = set(gram.get("avoid_repeats", []))
|
|
362
|
-
force_dn = float(gram.get("force_dnfr", 0.60))
|
|
363
|
-
force_ac = float(gram.get("force_accel", 0.60))
|
|
364
|
-
fallbacks = gram.get("fallbacks", {})
|
|
365
|
-
nd = G.nodes[n]
|
|
366
|
-
if cand in avoid and recent_glyph(nd, cand, gwin):
|
|
367
|
-
if not (dnfr >= force_dn or accel >= force_ac):
|
|
368
|
-
cand = fallbacks.get(cand, cand)
|
|
369
|
-
return cand
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
def _selector_normalized_metrics(nd, norms):
|
|
373
|
-
"""Extract and normalise Si, ΔNFR and acceleration for the selector."""
|
|
374
|
-
dnfr_max = float(norms.get("dnfr_max", 1.0)) or 1.0
|
|
375
|
-
acc_max = float(norms.get("accel_max", 1.0)) or 1.0
|
|
376
|
-
Si = clamp01(get_attr(nd, ALIAS_SI, 0.5))
|
|
377
|
-
dnfr = abs(get_attr(nd, ALIAS_DNFR, 0.0)) / dnfr_max
|
|
378
|
-
accel = abs(get_attr(nd, ALIAS_D2EPI, 0.0)) / acc_max
|
|
379
|
-
return Si, dnfr, accel
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
def _selector_base_choice(Si, dnfr, accel, thr):
|
|
383
|
-
"""Base decision according to thresholds of Si, ΔNFR and acceleration."""
|
|
384
|
-
si_hi, si_lo, dnfr_hi, acc_hi = itemgetter(
|
|
385
|
-
"si_hi", "si_lo", "dnfr_hi", "accel_hi"
|
|
386
|
-
)(thr) # Reduce dict lookups inside loops.
|
|
387
|
-
if Si >= si_hi:
|
|
388
|
-
return "IL"
|
|
389
|
-
if Si <= si_lo:
|
|
390
|
-
if accel >= acc_hi:
|
|
391
|
-
return "THOL"
|
|
392
|
-
return "OZ" if dnfr >= dnfr_hi else "ZHIR"
|
|
393
|
-
if dnfr >= dnfr_hi or accel >= acc_hi:
|
|
394
|
-
return "NAV"
|
|
395
|
-
return "RA"
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
def _configure_selector_weights(G) -> dict:
|
|
399
|
-
"""Normalise and store selector weights in ``G.graph``."""
|
|
400
|
-
weights = merge_and_normalize_weights(
|
|
401
|
-
G, "SELECTOR_WEIGHTS", ("w_si", "w_dnfr", "w_accel")
|
|
402
|
-
)
|
|
403
|
-
G.graph["_selector_weights"] = weights
|
|
404
|
-
return weights
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
def _compute_selector_score(G, nd, Si, dnfr, accel, cand):
|
|
408
|
-
"""Compute score and apply stagnation penalties."""
|
|
409
|
-
W = G.graph.get("_selector_weights")
|
|
410
|
-
if W is None:
|
|
411
|
-
W = _configure_selector_weights(G)
|
|
412
|
-
score = _calc_selector_score(Si, dnfr, accel, W)
|
|
413
|
-
hist_prev = nd.get("glyph_history")
|
|
414
|
-
if hist_prev and hist_prev[-1] == cand:
|
|
415
|
-
delta_si = get_attr(nd, ALIAS_DSI, 0.0)
|
|
416
|
-
h = ensure_history(G)
|
|
417
|
-
sig = h.get("sense_sigma_mag", [])
|
|
418
|
-
delta_sigma = sig[-1] - sig[-2] if len(sig) >= 2 else 0.0
|
|
419
|
-
if delta_si <= 0.0 and delta_sigma <= 0.0:
|
|
420
|
-
score -= 0.05
|
|
421
|
-
return score
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
def _apply_score_override(cand, score, dnfr, dnfr_lo):
|
|
425
|
-
"""Adjust final candidate smoothly according to the score."""
|
|
426
|
-
if score >= 0.66 and cand in ("NAV", "RA", "ZHIR", "OZ"):
|
|
427
|
-
cand = "IL"
|
|
428
|
-
elif score <= 0.33 and cand in ("NAV", "RA", "IL"):
|
|
429
|
-
cand = "OZ" if dnfr >= dnfr_lo else "ZHIR"
|
|
430
|
-
return cand
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
def parametric_glyph_selector(G, n) -> str:
|
|
434
|
-
"""Multiobjective: combine Si, |ΔNFR|_norm and |accel|_norm with
|
|
435
|
-
hysteresis.
|
|
436
|
-
|
|
437
|
-
Base rules:
|
|
438
|
-
- High Si ⇒ IL
|
|
439
|
-
- Low Si ⇒ OZ if |ΔNFR| high; ZHIR if |ΔNFR| low;
|
|
440
|
-
THOL if acceleration is high
|
|
441
|
-
- Medium Si ⇒ NAV if |ΔNFR| high (or acceleration high),
|
|
442
|
-
otherwise RA
|
|
443
|
-
"""
|
|
444
|
-
nd = G.nodes[n]
|
|
445
|
-
thr = _selector_thresholds(G)
|
|
446
|
-
margin = get_graph_param(G, "GLYPH_SELECTOR_MARGIN")
|
|
447
|
-
|
|
448
|
-
norms = G.graph.get("_sel_norms") or _norms_para_selector(G)
|
|
449
|
-
Si, dnfr, accel = _selector_normalized_metrics(nd, norms)
|
|
450
|
-
|
|
451
|
-
cand = _selector_base_choice(Si, dnfr, accel, thr)
|
|
452
|
-
|
|
453
|
-
hist_cand = _apply_selector_hysteresis(nd, Si, dnfr, accel, thr, margin)
|
|
454
|
-
if hist_cand is not None:
|
|
455
|
-
return hist_cand
|
|
456
|
-
|
|
457
|
-
score = _compute_selector_score(G, nd, Si, dnfr, accel, cand)
|
|
458
|
-
|
|
459
|
-
cand = _apply_score_override(cand, score, dnfr, thr["dnfr_lo"])
|
|
460
|
-
|
|
461
|
-
return _soft_grammar_prefilter(G, n, cand, dnfr, accel)
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
def _choose_glyph(G, n, selector, use_canon, h_al, h_en, al_max, en_max):
|
|
465
|
-
"""Select the glyph to apply on node ``n``."""
|
|
466
|
-
if h_al[n] > al_max:
|
|
467
|
-
return Glyph.AL
|
|
468
|
-
if h_en[n] > en_max:
|
|
469
|
-
return Glyph.EN
|
|
470
|
-
g = selector(G, n)
|
|
471
|
-
if use_canon:
|
|
472
|
-
g = enforce_canonical_grammar(G, n, g)
|
|
473
|
-
return g
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
# -------------------------
|
|
477
|
-
# Step / run
|
|
478
|
-
# -------------------------
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
def _run_before_callbacks(
|
|
482
|
-
G, *, step_idx: int, dt: float | None, use_Si: bool, apply_glyphs: bool
|
|
483
|
-
) -> None:
|
|
484
|
-
callback_manager.invoke_callbacks(
|
|
485
|
-
G,
|
|
486
|
-
CallbackEvent.BEFORE_STEP.value,
|
|
487
|
-
{
|
|
488
|
-
"step": step_idx,
|
|
489
|
-
"dt": dt,
|
|
490
|
-
"use_Si": use_Si,
|
|
491
|
-
"apply_glyphs": apply_glyphs,
|
|
492
|
-
},
|
|
493
|
-
)
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
def _prepare_dnfr(G, *, use_Si: bool) -> None:
|
|
497
|
-
"""Compute ΔNFR and optionally Si for the current graph state."""
|
|
498
|
-
compute_dnfr_cb = G.graph.get(
|
|
499
|
-
"compute_delta_nfr", default_compute_delta_nfr
|
|
500
|
-
)
|
|
501
|
-
compute_dnfr_cb(G)
|
|
502
|
-
G.graph.pop("_sel_norms", None)
|
|
503
|
-
if use_Si:
|
|
504
|
-
compute_Si(G, inplace=True)
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
def _apply_selector(G):
|
|
508
|
-
"""Configure and return the glyph selector for this step."""
|
|
509
|
-
selector = G.graph.get("glyph_selector", default_glyph_selector)
|
|
510
|
-
if selector is parametric_glyph_selector:
|
|
511
|
-
_norms_para_selector(G)
|
|
512
|
-
_configure_selector_weights(G)
|
|
513
|
-
return selector
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
def _apply_glyphs(G, selector, hist) -> None:
|
|
517
|
-
"""Apply glyphs to nodes using ``selector`` and update history."""
|
|
518
|
-
window = int(get_param(G, "GLYPH_HYSTERESIS_WINDOW"))
|
|
519
|
-
use_canon = bool(
|
|
520
|
-
get_graph_param(G, "GRAMMAR_CANON", dict).get("enabled", False)
|
|
521
|
-
)
|
|
522
|
-
al_max = get_graph_param(G, "AL_MAX_LAG", int)
|
|
523
|
-
en_max = get_graph_param(G, "EN_MAX_LAG", int)
|
|
524
|
-
h_al = hist.setdefault("since_AL", {})
|
|
525
|
-
h_en = hist.setdefault("since_EN", {})
|
|
526
|
-
for n, _ in G.nodes(data=True):
|
|
527
|
-
h_al[n] = int(h_al.get(n, 0)) + 1
|
|
528
|
-
h_en[n] = int(h_en.get(n, 0)) + 1
|
|
529
|
-
g = _choose_glyph(
|
|
530
|
-
G, n, selector, use_canon, h_al, h_en, al_max, en_max
|
|
531
|
-
)
|
|
532
|
-
apply_glyph(G, n, g, window=window)
|
|
533
|
-
if use_canon:
|
|
534
|
-
on_applied_glyph(G, n, g)
|
|
535
|
-
if g == Glyph.AL:
|
|
536
|
-
h_al[n] = 0
|
|
537
|
-
h_en[n] = min(h_en[n], en_max)
|
|
538
|
-
elif g == Glyph.EN:
|
|
539
|
-
h_en[n] = 0
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
def _update_nodes(
|
|
543
|
-
G,
|
|
544
|
-
*,
|
|
545
|
-
dt: float | None,
|
|
546
|
-
use_Si: bool,
|
|
547
|
-
apply_glyphs: bool,
|
|
548
|
-
step_idx: int,
|
|
549
|
-
hist,
|
|
550
|
-
) -> None:
|
|
551
|
-
_update_node_sample(G, step=step_idx)
|
|
552
|
-
_prepare_dnfr(G, use_Si=use_Si)
|
|
553
|
-
selector = _apply_selector(G)
|
|
554
|
-
if apply_glyphs:
|
|
555
|
-
_apply_glyphs(G, selector, hist)
|
|
556
|
-
_dt = get_graph_param(G, "DT") if dt is None else float(dt)
|
|
557
|
-
method = get_graph_param(G, "INTEGRATOR_METHOD", str)
|
|
558
|
-
update_epi_via_nodal_equation(G, dt=_dt, method=method)
|
|
559
|
-
for n, nd in G.nodes(data=True):
|
|
560
|
-
apply_canonical_clamps(nd, G, n)
|
|
561
|
-
coordinate_global_local_phase(G, None, None)
|
|
562
|
-
adapt_vf_by_coherence(G)
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
def _update_epi_hist(G) -> None:
|
|
566
|
-
tau_g = int(get_param(G, "REMESH_TAU_GLOBAL"))
|
|
567
|
-
tau_l = int(get_param(G, "REMESH_TAU_LOCAL"))
|
|
568
|
-
tau = max(tau_g, tau_l)
|
|
569
|
-
maxlen = max(2 * tau + 5, 64)
|
|
570
|
-
epi_hist = G.graph.get("_epi_hist")
|
|
571
|
-
if not isinstance(epi_hist, deque) or epi_hist.maxlen != maxlen:
|
|
572
|
-
epi_hist = deque(list(epi_hist or [])[-maxlen:], maxlen=maxlen)
|
|
573
|
-
G.graph["_epi_hist"] = epi_hist
|
|
574
|
-
epi_hist.append(
|
|
575
|
-
{n: get_attr(nd, ALIAS_EPI, 0.0) for n, nd in G.nodes(data=True)}
|
|
576
|
-
)
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
def _maybe_remesh(G) -> None:
|
|
580
|
-
apply_remesh_if_globally_stable(G)
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
def _run_validators(G) -> None:
|
|
584
|
-
from ..validators import run_validators
|
|
585
|
-
|
|
586
|
-
run_validators(G)
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
def _run_after_callbacks(G, *, step_idx: int) -> None:
|
|
590
|
-
h = ensure_history(G)
|
|
591
|
-
ctx = {"step": step_idx}
|
|
592
|
-
metric_pairs = [
|
|
593
|
-
("C", "C_steps"),
|
|
594
|
-
("stable_frac", "stable_frac"),
|
|
595
|
-
("phase_sync", "phase_sync"),
|
|
596
|
-
("glyph_disr", "glyph_load_disr"),
|
|
597
|
-
("Si_mean", "Si_mean"),
|
|
598
|
-
]
|
|
599
|
-
for dst, src in metric_pairs:
|
|
600
|
-
values = h.get(src)
|
|
601
|
-
if values:
|
|
602
|
-
ctx[dst] = values[-1]
|
|
603
|
-
callback_manager.invoke_callbacks(G, CallbackEvent.AFTER_STEP.value, ctx)
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
def step(
|
|
607
|
-
G,
|
|
608
|
-
*,
|
|
609
|
-
dt: float | None = None,
|
|
610
|
-
use_Si: bool = True,
|
|
611
|
-
apply_glyphs: bool = True,
|
|
612
|
-
) -> None:
|
|
613
|
-
hist = ensure_history(G)
|
|
614
|
-
step_idx = len(hist.setdefault("C_steps", []))
|
|
615
|
-
_run_before_callbacks(
|
|
616
|
-
G, step_idx=step_idx, dt=dt, use_Si=use_Si, apply_glyphs=apply_glyphs
|
|
617
|
-
)
|
|
618
|
-
_update_nodes(
|
|
619
|
-
G,
|
|
620
|
-
dt=dt,
|
|
621
|
-
use_Si=use_Si,
|
|
622
|
-
apply_glyphs=apply_glyphs,
|
|
623
|
-
step_idx=step_idx,
|
|
624
|
-
hist=hist,
|
|
625
|
-
)
|
|
626
|
-
_update_epi_hist(G)
|
|
627
|
-
_maybe_remesh(G)
|
|
628
|
-
_run_validators(G)
|
|
629
|
-
_run_after_callbacks(G, step_idx=step_idx)
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
def run(
|
|
633
|
-
G,
|
|
634
|
-
steps: int,
|
|
635
|
-
*,
|
|
636
|
-
dt: float | None = None,
|
|
637
|
-
use_Si: bool = True,
|
|
638
|
-
apply_glyphs: bool = True,
|
|
639
|
-
) -> None:
|
|
640
|
-
steps_int = int(steps)
|
|
641
|
-
if steps_int < 0:
|
|
642
|
-
raise ValueError("'steps' must be non-negative")
|
|
643
|
-
stop_cfg = get_graph_param(G, "STOP_EARLY", dict)
|
|
644
|
-
stop_enabled = False
|
|
645
|
-
if stop_cfg and stop_cfg.get("enabled", False):
|
|
646
|
-
w = int(stop_cfg.get("window", 25))
|
|
647
|
-
frac = float(stop_cfg.get("fraction", 0.90))
|
|
648
|
-
stop_enabled = True
|
|
649
|
-
for _ in range(steps_int):
|
|
650
|
-
step(G, dt=dt, use_Si=use_Si, apply_glyphs=apply_glyphs)
|
|
651
|
-
# Early-stop opcional
|
|
652
|
-
if stop_enabled:
|
|
653
|
-
history = ensure_history(G)
|
|
654
|
-
series = history.get("stable_frac", [])
|
|
655
|
-
if not isinstance(series, list):
|
|
656
|
-
series = list(series)
|
|
657
|
-
if len(series) >= w and all(v >= frac for v in series[-w:]):
|
|
658
|
-
break
|