tnfr 4.5.1__py3-none-any.whl → 6.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.
- tnfr/__init__.py +270 -90
- tnfr/__init__.pyi +40 -0
- tnfr/_compat.py +11 -0
- tnfr/_version.py +7 -0
- tnfr/_version.pyi +7 -0
- tnfr/alias.py +631 -0
- tnfr/alias.pyi +140 -0
- tnfr/cache.py +732 -0
- tnfr/cache.pyi +232 -0
- tnfr/callback_utils.py +381 -0
- tnfr/callback_utils.pyi +105 -0
- tnfr/cli/__init__.py +89 -0
- tnfr/cli/__init__.pyi +47 -0
- tnfr/cli/arguments.py +199 -0
- tnfr/cli/arguments.pyi +33 -0
- tnfr/cli/execution.py +322 -0
- tnfr/cli/execution.pyi +80 -0
- tnfr/cli/utils.py +34 -0
- tnfr/cli/utils.pyi +8 -0
- tnfr/config/__init__.py +12 -0
- tnfr/config/__init__.pyi +8 -0
- tnfr/config/constants.py +104 -0
- tnfr/config/constants.pyi +12 -0
- tnfr/config/init.py +36 -0
- tnfr/config/init.pyi +8 -0
- tnfr/config/operator_names.py +106 -0
- tnfr/config/operator_names.pyi +28 -0
- tnfr/config/presets.py +104 -0
- tnfr/config/presets.pyi +7 -0
- tnfr/constants/__init__.py +228 -0
- tnfr/constants/__init__.pyi +104 -0
- tnfr/constants/core.py +158 -0
- tnfr/constants/core.pyi +17 -0
- tnfr/constants/init.py +31 -0
- tnfr/constants/init.pyi +12 -0
- tnfr/constants/metric.py +102 -0
- tnfr/constants/metric.pyi +19 -0
- tnfr/constants_glyphs.py +16 -0
- tnfr/constants_glyphs.pyi +12 -0
- tnfr/dynamics/__init__.py +136 -0
- tnfr/dynamics/__init__.pyi +83 -0
- tnfr/dynamics/adaptation.py +201 -0
- tnfr/dynamics/aliases.py +22 -0
- tnfr/dynamics/coordination.py +343 -0
- tnfr/dynamics/dnfr.py +2315 -0
- tnfr/dynamics/dnfr.pyi +33 -0
- tnfr/dynamics/integrators.py +561 -0
- tnfr/dynamics/integrators.pyi +35 -0
- tnfr/dynamics/runtime.py +521 -0
- tnfr/dynamics/sampling.py +34 -0
- tnfr/dynamics/sampling.pyi +7 -0
- tnfr/dynamics/selectors.py +680 -0
- tnfr/execution.py +216 -0
- tnfr/execution.pyi +65 -0
- tnfr/flatten.py +283 -0
- tnfr/flatten.pyi +28 -0
- tnfr/gamma.py +320 -89
- tnfr/gamma.pyi +40 -0
- tnfr/glyph_history.py +337 -0
- tnfr/glyph_history.pyi +53 -0
- tnfr/grammar.py +23 -153
- tnfr/grammar.pyi +13 -0
- tnfr/helpers/__init__.py +151 -0
- tnfr/helpers/__init__.pyi +66 -0
- tnfr/helpers/numeric.py +88 -0
- tnfr/helpers/numeric.pyi +12 -0
- tnfr/immutable.py +214 -0
- tnfr/immutable.pyi +37 -0
- tnfr/initialization.py +199 -0
- tnfr/initialization.pyi +73 -0
- tnfr/io.py +311 -0
- tnfr/io.pyi +11 -0
- tnfr/locking.py +37 -0
- tnfr/locking.pyi +7 -0
- tnfr/metrics/__init__.py +41 -0
- tnfr/metrics/__init__.pyi +20 -0
- tnfr/metrics/coherence.py +1469 -0
- tnfr/metrics/common.py +149 -0
- tnfr/metrics/common.pyi +15 -0
- tnfr/metrics/core.py +259 -0
- tnfr/metrics/core.pyi +13 -0
- tnfr/metrics/diagnosis.py +840 -0
- tnfr/metrics/diagnosis.pyi +89 -0
- tnfr/metrics/export.py +151 -0
- tnfr/metrics/glyph_timing.py +369 -0
- tnfr/metrics/reporting.py +152 -0
- tnfr/metrics/reporting.pyi +12 -0
- tnfr/metrics/sense_index.py +294 -0
- tnfr/metrics/sense_index.pyi +9 -0
- tnfr/metrics/trig.py +216 -0
- tnfr/metrics/trig.pyi +12 -0
- tnfr/metrics/trig_cache.py +105 -0
- tnfr/metrics/trig_cache.pyi +10 -0
- tnfr/node.py +255 -177
- tnfr/node.pyi +161 -0
- tnfr/observers.py +154 -150
- tnfr/observers.pyi +46 -0
- tnfr/ontosim.py +135 -134
- tnfr/ontosim.pyi +33 -0
- tnfr/operators/__init__.py +452 -0
- tnfr/operators/__init__.pyi +31 -0
- tnfr/operators/definitions.py +181 -0
- tnfr/operators/definitions.pyi +92 -0
- tnfr/operators/jitter.py +266 -0
- tnfr/operators/jitter.pyi +11 -0
- tnfr/operators/registry.py +80 -0
- tnfr/operators/registry.pyi +15 -0
- tnfr/operators/remesh.py +569 -0
- tnfr/presets.py +10 -23
- tnfr/presets.pyi +7 -0
- tnfr/py.typed +0 -0
- tnfr/rng.py +440 -0
- tnfr/rng.pyi +14 -0
- tnfr/selector.py +217 -0
- tnfr/selector.pyi +19 -0
- tnfr/sense.py +307 -142
- tnfr/sense.pyi +30 -0
- tnfr/structural.py +69 -164
- tnfr/structural.pyi +46 -0
- tnfr/telemetry/__init__.py +13 -0
- tnfr/telemetry/verbosity.py +37 -0
- tnfr/tokens.py +61 -0
- tnfr/tokens.pyi +41 -0
- tnfr/trace.py +520 -95
- tnfr/trace.pyi +68 -0
- tnfr/types.py +382 -17
- tnfr/types.pyi +145 -0
- tnfr/utils/__init__.py +158 -0
- tnfr/utils/__init__.pyi +133 -0
- tnfr/utils/cache.py +755 -0
- tnfr/utils/cache.pyi +156 -0
- tnfr/utils/data.py +267 -0
- tnfr/utils/data.pyi +73 -0
- tnfr/utils/graph.py +87 -0
- tnfr/utils/graph.pyi +10 -0
- tnfr/utils/init.py +746 -0
- tnfr/utils/init.pyi +85 -0
- tnfr/utils/io.py +157 -0
- tnfr/utils/io.pyi +10 -0
- tnfr/utils/validators.py +130 -0
- tnfr/utils/validators.pyi +19 -0
- tnfr/validation/__init__.py +25 -0
- tnfr/validation/__init__.pyi +17 -0
- tnfr/validation/compatibility.py +59 -0
- tnfr/validation/compatibility.pyi +8 -0
- tnfr/validation/grammar.py +149 -0
- tnfr/validation/grammar.pyi +11 -0
- tnfr/validation/rules.py +194 -0
- tnfr/validation/rules.pyi +18 -0
- tnfr/validation/syntax.py +151 -0
- tnfr/validation/syntax.pyi +7 -0
- tnfr-6.0.0.dist-info/METADATA +135 -0
- tnfr-6.0.0.dist-info/RECORD +157 -0
- tnfr/cli.py +0 -322
- tnfr/config.py +0 -41
- tnfr/constants.py +0 -277
- tnfr/dynamics.py +0 -814
- tnfr/helpers.py +0 -264
- tnfr/main.py +0 -47
- tnfr/metrics.py +0 -597
- tnfr/operators.py +0 -525
- tnfr/program.py +0 -176
- tnfr/scenarios.py +0 -34
- tnfr/validators.py +0 -38
- tnfr-4.5.1.dist-info/METADATA +0 -221
- tnfr-4.5.1.dist-info/RECORD +0 -28
- {tnfr-4.5.1.dist-info → tnfr-6.0.0.dist-info}/WHEEL +0 -0
- {tnfr-4.5.1.dist-info → tnfr-6.0.0.dist-info}/entry_points.txt +0 -0
- {tnfr-4.5.1.dist-info → tnfr-6.0.0.dist-info}/licenses/LICENSE.md +0 -0
- {tnfr-4.5.1.dist-info → tnfr-6.0.0.dist-info}/top_level.txt +0 -0
tnfr/gamma.py
CHANGED
|
@@ -1,127 +1,358 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Gamma registry."""
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from typing import Any, Callable, NamedTuple
|
|
5
|
+
import math
|
|
6
|
+
import logging
|
|
7
|
+
import hashlib
|
|
8
|
+
from collections.abc import Mapping
|
|
9
|
+
from functools import lru_cache
|
|
10
|
+
from types import MappingProxyType
|
|
5
11
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
12
|
+
from .constants import DEFAULTS
|
|
13
|
+
from .alias import get_theta_attr
|
|
14
|
+
from .types import GammaSpec, NodeId, TNFRGraph
|
|
15
|
+
from .utils import (
|
|
16
|
+
edge_version_cache,
|
|
17
|
+
get_graph_mapping,
|
|
18
|
+
get_logger,
|
|
19
|
+
json_dumps,
|
|
20
|
+
node_set_checksum,
|
|
21
|
+
)
|
|
22
|
+
from .metrics.trig_cache import get_trig_cache
|
|
9
23
|
|
|
10
|
-
* ``type`` – modo de acoplamiento (``none``, ``kuramoto_linear``,
|
|
11
|
-
``kuramoto_bandpass``)
|
|
12
|
-
* ``beta`` – ganancia del acoplamiento
|
|
13
|
-
* ``R0`` – umbral de activación (solo lineal)
|
|
14
24
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
25
|
+
logger = get_logger(__name__)
|
|
26
|
+
|
|
27
|
+
DEFAULT_GAMMA: Mapping[str, Any] = MappingProxyType(dict(DEFAULTS["GAMMA"]))
|
|
28
|
+
|
|
29
|
+
__all__ = (
|
|
30
|
+
"kuramoto_R_psi",
|
|
31
|
+
"gamma_none",
|
|
32
|
+
"gamma_kuramoto_linear",
|
|
33
|
+
"gamma_kuramoto_bandpass",
|
|
34
|
+
"gamma_kuramoto_tanh",
|
|
35
|
+
"gamma_harmonic",
|
|
36
|
+
"GammaEntry",
|
|
37
|
+
"GAMMA_REGISTRY",
|
|
38
|
+
"eval_gamma",
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@lru_cache(maxsize=1)
|
|
43
|
+
def _default_gamma_spec() -> tuple[bytes, str]:
|
|
44
|
+
dumped = json_dumps(dict(DEFAULT_GAMMA), sort_keys=True, to_bytes=True)
|
|
45
|
+
hash_ = hashlib.blake2b(dumped, digest_size=16).hexdigest()
|
|
46
|
+
return dumped, hash_
|
|
47
|
+
|
|
24
48
|
|
|
25
|
-
|
|
26
|
-
|
|
49
|
+
def _ensure_kuramoto_cache(G: TNFRGraph, t: float | int) -> None:
|
|
50
|
+
"""Cache ``(R, ψ)`` for the current step ``t`` using
|
|
51
|
+
``edge_version_cache``."""
|
|
52
|
+
checksum = G.graph.get("_dnfr_nodes_checksum")
|
|
53
|
+
if checksum is None:
|
|
54
|
+
# reuse checksum from cached_nodes_and_A when available
|
|
55
|
+
checksum = node_set_checksum(G)
|
|
56
|
+
nodes_sig = (len(G), checksum)
|
|
57
|
+
max_steps = int(G.graph.get("KURAMOTO_CACHE_STEPS", 1))
|
|
27
58
|
|
|
59
|
+
def builder() -> dict[str, float]:
|
|
60
|
+
R, psi = kuramoto_R_psi(G)
|
|
61
|
+
return {"R": R, "psi": psi}
|
|
28
62
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
63
|
+
key = (t, nodes_sig)
|
|
64
|
+
entry = edge_version_cache(G, key, builder, max_entries=max_steps)
|
|
65
|
+
G.graph["_kuramoto_cache"] = entry
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def kuramoto_R_psi(G: TNFRGraph) -> tuple[float, float]:
|
|
69
|
+
"""Return ``(R, ψ)`` for Kuramoto order using θ from all nodes."""
|
|
70
|
+
max_steps = int(G.graph.get("KURAMOTO_CACHE_STEPS", 1))
|
|
71
|
+
trig = get_trig_cache(G, cache_size=max_steps)
|
|
72
|
+
n = len(trig.theta)
|
|
38
73
|
if n == 0:
|
|
39
74
|
return 0.0, 0.0
|
|
40
|
-
|
|
41
|
-
|
|
75
|
+
|
|
76
|
+
cos_sum = sum(trig.cos.values())
|
|
77
|
+
sin_sum = sum(trig.sin.values())
|
|
78
|
+
R = math.hypot(cos_sum, sin_sum) / n
|
|
79
|
+
psi = math.atan2(sin_sum, cos_sum)
|
|
80
|
+
return R, psi
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _kuramoto_common(
|
|
84
|
+
G: TNFRGraph, node: NodeId, _cfg: GammaSpec
|
|
85
|
+
) -> tuple[float, float, float]:
|
|
86
|
+
"""Return ``(θ_i, R, ψ)`` for Kuramoto-based Γ functions.
|
|
87
|
+
|
|
88
|
+
Reads cached global order ``R`` and mean phase ``ψ`` and obtains node
|
|
89
|
+
phase ``θ_i``. ``_cfg`` is accepted only to keep a homogeneous signature
|
|
90
|
+
with Γ evaluators.
|
|
91
|
+
"""
|
|
92
|
+
cache = G.graph.get("_kuramoto_cache", {})
|
|
93
|
+
R = float(cache.get("R", 0.0))
|
|
94
|
+
psi = float(cache.get("psi", 0.0))
|
|
95
|
+
th_val = get_theta_attr(G.nodes[node], 0.0)
|
|
96
|
+
th_i = float(th_val if th_val is not None else 0.0)
|
|
97
|
+
return th_i, R, psi
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _read_gamma_raw(G: TNFRGraph) -> GammaSpec | None:
|
|
101
|
+
"""Return raw Γ specification from ``G.graph['GAMMA']``.
|
|
102
|
+
|
|
103
|
+
The returned value is the direct contents of ``G.graph['GAMMA']`` when
|
|
104
|
+
it is a mapping or the result of :func:`get_graph_mapping` if a path is
|
|
105
|
+
provided. Final validation and caching are handled elsewhere.
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
raw = G.graph.get("GAMMA")
|
|
109
|
+
if raw is None or isinstance(raw, Mapping):
|
|
110
|
+
return raw
|
|
111
|
+
return get_graph_mapping(
|
|
112
|
+
G,
|
|
113
|
+
"GAMMA",
|
|
114
|
+
"G.graph['GAMMA'] is not a mapping; using {'type': 'none'}",
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _get_gamma_spec(G: TNFRGraph) -> GammaSpec:
|
|
119
|
+
"""Return validated Γ specification caching results.
|
|
120
|
+
|
|
121
|
+
The raw value from ``G.graph['GAMMA']`` is cached together with the
|
|
122
|
+
normalized specification and its hash. When the raw value is unchanged,
|
|
123
|
+
the cached spec is returned without re-reading or re-validating,
|
|
124
|
+
preventing repeated warnings or costly hashing.
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
raw = G.graph.get("GAMMA")
|
|
128
|
+
cached_raw = G.graph.get("_gamma_raw")
|
|
129
|
+
cached_spec = G.graph.get("_gamma_spec")
|
|
130
|
+
cached_hash = G.graph.get("_gamma_spec_hash")
|
|
131
|
+
|
|
132
|
+
def _hash_mapping(mapping: GammaSpec) -> str:
|
|
133
|
+
dumped = json_dumps(mapping, sort_keys=True, to_bytes=True)
|
|
134
|
+
return hashlib.blake2b(dumped, digest_size=16).hexdigest()
|
|
135
|
+
|
|
136
|
+
mapping_hash: str | None = None
|
|
137
|
+
if isinstance(raw, Mapping):
|
|
138
|
+
mapping_hash = _hash_mapping(raw)
|
|
139
|
+
if (
|
|
140
|
+
raw is cached_raw
|
|
141
|
+
and cached_spec is not None
|
|
142
|
+
and cached_hash == mapping_hash
|
|
143
|
+
):
|
|
144
|
+
return cached_spec
|
|
145
|
+
elif raw is cached_raw and cached_spec is not None and cached_hash is not None:
|
|
146
|
+
return cached_spec
|
|
147
|
+
|
|
148
|
+
if raw is None:
|
|
149
|
+
spec = DEFAULT_GAMMA
|
|
150
|
+
_, cur_hash = _default_gamma_spec()
|
|
151
|
+
elif isinstance(raw, Mapping):
|
|
152
|
+
spec = raw
|
|
153
|
+
cur_hash = mapping_hash if mapping_hash is not None else _hash_mapping(spec)
|
|
154
|
+
else:
|
|
155
|
+
spec_raw = _read_gamma_raw(G)
|
|
156
|
+
if isinstance(spec_raw, Mapping) and spec_raw is not None:
|
|
157
|
+
spec = spec_raw
|
|
158
|
+
cur_hash = _hash_mapping(spec)
|
|
159
|
+
else:
|
|
160
|
+
spec = DEFAULT_GAMMA
|
|
161
|
+
_, cur_hash = _default_gamma_spec()
|
|
162
|
+
|
|
163
|
+
# Store raw input, validated spec and its hash for future calls
|
|
164
|
+
G.graph["_gamma_raw"] = raw
|
|
165
|
+
G.graph["_gamma_spec"] = spec
|
|
166
|
+
G.graph["_gamma_spec_hash"] = cur_hash
|
|
167
|
+
return spec
|
|
42
168
|
|
|
43
169
|
|
|
44
170
|
# -----------------
|
|
45
|
-
#
|
|
171
|
+
# Helpers
|
|
46
172
|
# -----------------
|
|
47
173
|
|
|
48
174
|
|
|
49
|
-
def
|
|
50
|
-
|
|
175
|
+
def _gamma_params(
|
|
176
|
+
cfg: GammaSpec, **defaults: float
|
|
177
|
+
) -> tuple[float, ...]:
|
|
178
|
+
"""Return normalized Γ parameters from ``cfg``.
|
|
179
|
+
|
|
180
|
+
Parameters are retrieved from ``cfg`` using the keys in ``defaults`` and
|
|
181
|
+
converted to ``float``. If a key is missing, its value from ``defaults`` is
|
|
182
|
+
used. Values convertible to ``float`` (e.g. strings) are accepted.
|
|
51
183
|
|
|
184
|
+
Example
|
|
185
|
+
-------
|
|
186
|
+
>>> beta, R0 = _gamma_params(cfg, beta=0.0, R0=0.0)
|
|
187
|
+
"""
|
|
52
188
|
|
|
53
|
-
|
|
54
|
-
|
|
189
|
+
return tuple(
|
|
190
|
+
float(cfg.get(name, default)) for name, default in defaults.items()
|
|
191
|
+
)
|
|
55
192
|
|
|
56
|
-
Fórmula: Γ = β · (R - R0) · cos(θ_i - ψ)
|
|
57
|
-
- R ∈ [0,1] es el orden global de fase.
|
|
58
|
-
- ψ es la fase media (dirección de coordinación).
|
|
59
|
-
- β, R0 son parámetros (ganancia/umbral).
|
|
60
193
|
|
|
61
|
-
|
|
194
|
+
# -----------------
|
|
195
|
+
# Canonical Γi(R)
|
|
196
|
+
# -----------------
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def gamma_none(
|
|
200
|
+
G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec
|
|
201
|
+
) -> float:
|
|
202
|
+
return 0.0
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _gamma_kuramoto(
|
|
206
|
+
G: TNFRGraph,
|
|
207
|
+
node: NodeId,
|
|
208
|
+
cfg: GammaSpec,
|
|
209
|
+
builder: Callable[..., float],
|
|
210
|
+
**defaults: float,
|
|
211
|
+
) -> float:
|
|
212
|
+
"""Helper for Kuramoto-based Γ functions.
|
|
213
|
+
|
|
214
|
+
``builder`` receives ``(θ_i, R, ψ, *params)`` where ``params`` are
|
|
215
|
+
extracted from ``cfg`` according to ``defaults``.
|
|
62
216
|
"""
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
R, psi =
|
|
66
|
-
|
|
217
|
+
|
|
218
|
+
params = _gamma_params(cfg, **defaults)
|
|
219
|
+
th_i, R, psi = _kuramoto_common(G, node, cfg)
|
|
220
|
+
return builder(th_i, R, psi, *params)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _builder_linear(th_i: float, R: float, psi: float, beta: float, R0: float) -> float:
|
|
67
224
|
return beta * (R - R0) * math.cos(th_i - psi)
|
|
68
225
|
|
|
69
226
|
|
|
70
|
-
def
|
|
71
|
-
"""Γ = β · R(1-R) · sign(cos(θ_i - ψ))"""
|
|
72
|
-
beta = float(cfg.get("beta", 0.0))
|
|
73
|
-
R, psi = kuramoto_R_psi(G)
|
|
74
|
-
th_i = _get_attr(G.nodes[node], ALIAS_THETA, 0.0)
|
|
227
|
+
def _builder_bandpass(th_i: float, R: float, psi: float, beta: float) -> float:
|
|
75
228
|
sgn = 1.0 if math.cos(th_i - psi) >= 0.0 else -1.0
|
|
76
229
|
return beta * R * (1.0 - R) * sgn
|
|
77
230
|
|
|
78
231
|
|
|
79
|
-
def
|
|
80
|
-
|
|
232
|
+
def _builder_tanh(th_i: float, R: float, psi: float, beta: float, k: float, R0: float) -> float:
|
|
233
|
+
return beta * math.tanh(k * (R - R0)) * math.cos(th_i - psi)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def gamma_kuramoto_linear(
|
|
237
|
+
G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec
|
|
238
|
+
) -> float:
|
|
239
|
+
"""Linear Kuramoto coupling for Γi(R).
|
|
240
|
+
|
|
241
|
+
Formula: Γ = β · (R - R0) · cos(θ_i - ψ)
|
|
242
|
+
- R ∈ [0,1] is the global phase order.
|
|
243
|
+
- ψ is the mean phase (coordination direction).
|
|
244
|
+
- β, R0 are parameters (gain/threshold).
|
|
81
245
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
- k: pendiente de la tanh (cuán rápido satura)
|
|
85
|
-
- R0: umbral de activación
|
|
246
|
+
Use: reinforces integration when the network already shows phase
|
|
247
|
+
coherence (R>R0).
|
|
86
248
|
"""
|
|
87
|
-
beta = float(cfg.get("beta", 0.0))
|
|
88
|
-
k = float(cfg.get("k", 1.0))
|
|
89
|
-
R0 = float(cfg.get("R0", 0.0))
|
|
90
|
-
R, psi = kuramoto_R_psi(G)
|
|
91
|
-
th_i = _get_attr(G.nodes[node], ALIAS_THETA, 0.0)
|
|
92
|
-
return beta * math.tanh(k * (R - R0)) * math.cos(th_i - psi)
|
|
93
249
|
|
|
250
|
+
return _gamma_kuramoto(G, node, cfg, _builder_linear, beta=0.0, R0=0.0)
|
|
94
251
|
|
|
95
|
-
def gamma_harmonic(G, node, t, cfg: Dict[str, Any]) -> float:
|
|
96
|
-
"""Forzamiento armónico coherente con el campo global de fase.
|
|
97
252
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
253
|
+
def gamma_kuramoto_bandpass(
|
|
254
|
+
G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec
|
|
255
|
+
) -> float:
|
|
256
|
+
"""Γ = β · R(1-R) · sign(cos(θ_i - ψ))"""
|
|
257
|
+
|
|
258
|
+
return _gamma_kuramoto(G, node, cfg, _builder_bandpass, beta=0.0)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def gamma_kuramoto_tanh(
|
|
262
|
+
G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec
|
|
263
|
+
) -> float:
|
|
264
|
+
"""Saturating tanh coupling for Γi(R).
|
|
265
|
+
|
|
266
|
+
Formula: Γ = β · tanh(k·(R - R0)) · cos(θ_i - ψ)
|
|
267
|
+
- β: coupling gain
|
|
268
|
+
- k: tanh slope (how fast it saturates)
|
|
269
|
+
- R0: activation threshold
|
|
270
|
+
"""
|
|
271
|
+
|
|
272
|
+
return _gamma_kuramoto(G, node, cfg, _builder_tanh, beta=0.0, k=1.0, R0=0.0)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def gamma_harmonic(
|
|
276
|
+
G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec
|
|
277
|
+
) -> float:
|
|
278
|
+
"""Harmonic forcing aligned with the global phase field.
|
|
279
|
+
|
|
280
|
+
Formula: Γ = β · sin(ω·t + φ) · cos(θ_i - ψ)
|
|
281
|
+
- β: coupling gain
|
|
282
|
+
- ω: angular frequency of the forcing
|
|
283
|
+
- φ: initial phase of the forcing
|
|
102
284
|
"""
|
|
103
|
-
beta =
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
285
|
+
beta, omega, phi = _gamma_params(cfg, beta=0.0, omega=1.0, phi=0.0)
|
|
286
|
+
th_i, _, psi = _kuramoto_common(G, node, cfg)
|
|
287
|
+
return beta * math.sin(omega * t + phi) * math.cos(th_i - psi)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
class GammaEntry(NamedTuple):
|
|
291
|
+
fn: Callable[[TNFRGraph, NodeId, float | int, GammaSpec], float]
|
|
292
|
+
needs_kuramoto: bool
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
# ``GAMMA_REGISTRY`` associates each coupling name with a ``GammaEntry`` where
|
|
296
|
+
# ``fn`` is the evaluation function and ``needs_kuramoto`` indicates whether
|
|
297
|
+
# the global phase order must be precomputed.
|
|
298
|
+
GAMMA_REGISTRY: dict[str, GammaEntry] = {
|
|
299
|
+
"none": GammaEntry(gamma_none, False),
|
|
300
|
+
"kuramoto_linear": GammaEntry(gamma_kuramoto_linear, True),
|
|
301
|
+
"kuramoto_bandpass": GammaEntry(gamma_kuramoto_bandpass, True),
|
|
302
|
+
"kuramoto_tanh": GammaEntry(gamma_kuramoto_tanh, True),
|
|
303
|
+
"harmonic": GammaEntry(gamma_harmonic, True),
|
|
117
304
|
}
|
|
118
305
|
|
|
119
306
|
|
|
120
|
-
def eval_gamma(
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
307
|
+
def eval_gamma(
|
|
308
|
+
G: TNFRGraph,
|
|
309
|
+
node: NodeId,
|
|
310
|
+
t: float | int,
|
|
311
|
+
*,
|
|
312
|
+
strict: bool = False,
|
|
313
|
+
log_level: int | None = None,
|
|
314
|
+
) -> float:
|
|
315
|
+
"""Evaluate Γi for ``node`` according to ``G.graph['GAMMA']``
|
|
316
|
+
specification.
|
|
317
|
+
|
|
318
|
+
If ``strict`` is ``True`` exceptions raised during evaluation are
|
|
319
|
+
propagated instead of returning ``0.0``. Likewise, if the specified
|
|
320
|
+
Γ type is not registered a warning is emitted (or ``ValueError`` in
|
|
321
|
+
strict mode) and ``gamma_none`` is used.
|
|
322
|
+
|
|
323
|
+
``log_level`` controls the logging level for captured errors when
|
|
324
|
+
``strict`` is ``False``. If omitted, ``logging.ERROR`` is used in
|
|
325
|
+
strict mode and ``logging.DEBUG`` otherwise.
|
|
326
|
+
"""
|
|
327
|
+
spec = _get_gamma_spec(G)
|
|
328
|
+
spec_type = spec.get("type", "none")
|
|
329
|
+
reg_entry = GAMMA_REGISTRY.get(spec_type)
|
|
330
|
+
if reg_entry is None:
|
|
331
|
+
msg = f"Unknown GAMMA type: {spec_type}"
|
|
332
|
+
if strict:
|
|
333
|
+
raise ValueError(msg)
|
|
334
|
+
logger.warning(msg)
|
|
335
|
+
entry = GammaEntry(gamma_none, False)
|
|
336
|
+
else:
|
|
337
|
+
entry = reg_entry
|
|
338
|
+
if entry.needs_kuramoto:
|
|
339
|
+
_ensure_kuramoto_cache(G, t)
|
|
124
340
|
try:
|
|
125
|
-
return float(fn(G, node, t, spec))
|
|
126
|
-
except
|
|
341
|
+
return float(entry.fn(G, node, t, spec))
|
|
342
|
+
except (ValueError, TypeError, ArithmeticError) as exc:
|
|
343
|
+
level = (
|
|
344
|
+
log_level
|
|
345
|
+
if log_level is not None
|
|
346
|
+
else (logging.ERROR if strict else logging.DEBUG)
|
|
347
|
+
)
|
|
348
|
+
logger.log(
|
|
349
|
+
level,
|
|
350
|
+
"Failed to evaluate Γi for node %s at t=%s: %s: %s",
|
|
351
|
+
node,
|
|
352
|
+
t,
|
|
353
|
+
exc.__class__.__name__,
|
|
354
|
+
exc,
|
|
355
|
+
)
|
|
356
|
+
if strict:
|
|
357
|
+
raise
|
|
127
358
|
return 0.0
|
tnfr/gamma.pyi
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from typing import Callable, NamedTuple
|
|
2
|
+
|
|
3
|
+
from .types import GammaSpec, NodeId, TNFRGraph
|
|
4
|
+
|
|
5
|
+
__all__: tuple[str, ...]
|
|
6
|
+
|
|
7
|
+
class GammaEntry(NamedTuple):
|
|
8
|
+
fn: Callable[[TNFRGraph, NodeId, float | int, GammaSpec], float]
|
|
9
|
+
needs_kuramoto: bool
|
|
10
|
+
|
|
11
|
+
GAMMA_REGISTRY: dict[str, GammaEntry]
|
|
12
|
+
|
|
13
|
+
def kuramoto_R_psi(G: TNFRGraph) -> tuple[float, float]: ...
|
|
14
|
+
|
|
15
|
+
def gamma_none(G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec) -> float: ...
|
|
16
|
+
|
|
17
|
+
def gamma_kuramoto_linear(
|
|
18
|
+
G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec
|
|
19
|
+
) -> float: ...
|
|
20
|
+
|
|
21
|
+
def gamma_kuramoto_bandpass(
|
|
22
|
+
G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec
|
|
23
|
+
) -> float: ...
|
|
24
|
+
|
|
25
|
+
def gamma_kuramoto_tanh(
|
|
26
|
+
G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec
|
|
27
|
+
) -> float: ...
|
|
28
|
+
|
|
29
|
+
def gamma_harmonic(
|
|
30
|
+
G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec
|
|
31
|
+
) -> float: ...
|
|
32
|
+
|
|
33
|
+
def eval_gamma(
|
|
34
|
+
G: TNFRGraph,
|
|
35
|
+
node: NodeId,
|
|
36
|
+
t: float | int,
|
|
37
|
+
*,
|
|
38
|
+
strict: bool = ...,
|
|
39
|
+
log_level: int | None = ...,
|
|
40
|
+
) -> float: ...
|