tnfr 6.0.0__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 +50 -5
- tnfr/__init__.pyi +0 -7
- tnfr/_compat.py +0 -1
- tnfr/_generated_version.py +34 -0
- tnfr/_version.py +44 -2
- tnfr/alias.py +14 -13
- tnfr/alias.pyi +5 -37
- tnfr/cache.py +9 -729
- tnfr/cache.pyi +8 -224
- tnfr/callback_utils.py +16 -31
- tnfr/callback_utils.pyi +3 -29
- tnfr/cli/__init__.py +17 -11
- tnfr/cli/__init__.pyi +0 -21
- tnfr/cli/arguments.py +175 -14
- tnfr/cli/arguments.pyi +5 -11
- tnfr/cli/execution.py +434 -48
- tnfr/cli/execution.pyi +14 -24
- tnfr/cli/utils.py +20 -3
- tnfr/cli/utils.pyi +5 -5
- tnfr/config/__init__.py +2 -1
- tnfr/config/__init__.pyi +2 -0
- tnfr/config/feature_flags.py +83 -0
- tnfr/config/init.py +1 -1
- tnfr/config/operator_names.py +1 -14
- tnfr/config/presets.py +6 -26
- tnfr/constants/__init__.py +10 -13
- tnfr/constants/__init__.pyi +10 -22
- tnfr/constants/aliases.py +31 -0
- tnfr/constants/core.py +4 -3
- tnfr/constants/init.py +1 -1
- tnfr/constants/metric.py +3 -3
- tnfr/dynamics/__init__.py +64 -10
- tnfr/dynamics/__init__.pyi +3 -4
- tnfr/dynamics/adaptation.py +79 -13
- tnfr/dynamics/aliases.py +10 -9
- tnfr/dynamics/coordination.py +77 -35
- tnfr/dynamics/dnfr.py +575 -274
- tnfr/dynamics/dnfr.pyi +1 -10
- tnfr/dynamics/integrators.py +47 -33
- tnfr/dynamics/integrators.pyi +0 -1
- tnfr/dynamics/runtime.py +489 -129
- tnfr/dynamics/sampling.py +2 -0
- tnfr/dynamics/selectors.py +101 -62
- tnfr/execution.py +15 -8
- tnfr/execution.pyi +5 -25
- tnfr/flatten.py +7 -3
- tnfr/flatten.pyi +1 -8
- tnfr/gamma.py +22 -26
- tnfr/gamma.pyi +0 -6
- tnfr/glyph_history.py +37 -26
- tnfr/glyph_history.pyi +1 -19
- tnfr/glyph_runtime.py +16 -0
- tnfr/glyph_runtime.pyi +9 -0
- tnfr/immutable.py +20 -15
- tnfr/immutable.pyi +4 -7
- tnfr/initialization.py +5 -7
- tnfr/initialization.pyi +1 -9
- tnfr/io.py +6 -305
- tnfr/io.pyi +13 -8
- 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/coherence.py +123 -94
- tnfr/metrics/common.py +22 -13
- tnfr/metrics/common.pyi +42 -11
- tnfr/metrics/core.py +72 -14
- tnfr/metrics/diagnosis.py +48 -57
- tnfr/metrics/diagnosis.pyi +3 -7
- tnfr/metrics/export.py +3 -5
- tnfr/metrics/glyph_timing.py +41 -31
- tnfr/metrics/reporting.py +13 -6
- tnfr/metrics/sense_index.py +884 -114
- tnfr/metrics/trig.py +167 -11
- tnfr/metrics/trig.pyi +1 -0
- tnfr/metrics/trig_cache.py +112 -15
- tnfr/node.py +400 -17
- tnfr/node.pyi +55 -38
- tnfr/observers.py +111 -8
- tnfr/observers.pyi +0 -15
- tnfr/ontosim.py +9 -6
- tnfr/ontosim.pyi +0 -5
- tnfr/operators/__init__.py +529 -42
- tnfr/operators/__init__.pyi +14 -0
- tnfr/operators/definitions.py +350 -18
- tnfr/operators/definitions.pyi +0 -14
- tnfr/operators/grammar.py +760 -0
- tnfr/operators/jitter.py +28 -22
- tnfr/operators/registry.py +7 -12
- tnfr/operators/registry.pyi +0 -2
- tnfr/operators/remesh.py +38 -61
- tnfr/rng.py +17 -300
- tnfr/schemas/__init__.py +8 -0
- tnfr/schemas/grammar.json +94 -0
- tnfr/selector.py +3 -4
- tnfr/selector.pyi +1 -1
- tnfr/sense.py +22 -24
- tnfr/sense.pyi +0 -7
- tnfr/structural.py +504 -21
- tnfr/structural.pyi +41 -18
- tnfr/telemetry/__init__.py +23 -1
- tnfr/telemetry/cache_metrics.py +226 -0
- tnfr/telemetry/nu_f.py +423 -0
- tnfr/telemetry/nu_f.pyi +123 -0
- tnfr/tokens.py +1 -4
- tnfr/tokens.pyi +1 -6
- tnfr/trace.py +20 -53
- tnfr/trace.pyi +9 -37
- tnfr/types.py +244 -15
- tnfr/types.pyi +200 -14
- tnfr/units.py +69 -0
- tnfr/units.pyi +16 -0
- tnfr/utils/__init__.py +107 -48
- tnfr/utils/__init__.pyi +80 -11
- tnfr/utils/cache.py +1705 -65
- tnfr/utils/cache.pyi +370 -58
- tnfr/utils/chunks.py +104 -0
- tnfr/utils/chunks.pyi +21 -0
- tnfr/utils/data.py +95 -5
- tnfr/utils/data.pyi +8 -17
- tnfr/utils/graph.py +2 -4
- tnfr/utils/init.py +31 -7
- tnfr/utils/init.pyi +4 -11
- tnfr/utils/io.py +313 -14
- tnfr/{helpers → utils}/numeric.py +50 -24
- tnfr/utils/numeric.pyi +21 -0
- tnfr/validation/__init__.py +92 -4
- tnfr/validation/__init__.pyi +77 -17
- tnfr/validation/compatibility.py +79 -43
- tnfr/validation/compatibility.pyi +4 -6
- tnfr/validation/grammar.py +55 -133
- tnfr/validation/grammar.pyi +37 -8
- tnfr/validation/graph.py +138 -0
- tnfr/validation/graph.pyi +17 -0
- tnfr/validation/rules.py +161 -74
- tnfr/validation/rules.pyi +55 -18
- 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 +28 -139
- tnfr/validation/syntax.pyi +7 -4
- 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-6.0.0.dist-info → tnfr-7.0.0.dist-info}/METADATA +63 -19
- tnfr-7.0.0.dist-info/RECORD +185 -0
- {tnfr-6.0.0.dist-info → tnfr-7.0.0.dist-info}/licenses/LICENSE.md +1 -1
- tnfr/constants_glyphs.py +0 -16
- tnfr/constants_glyphs.pyi +0 -12
- tnfr/grammar.py +0 -25
- tnfr/grammar.pyi +0 -13
- tnfr/helpers/__init__.py +0 -151
- tnfr/helpers/__init__.pyi +0 -66
- tnfr/helpers/numeric.pyi +0 -12
- tnfr/presets.py +0 -15
- tnfr/presets.pyi +0 -7
- tnfr/utils/io.pyi +0 -10
- tnfr/utils/validators.py +0 -130
- tnfr/utils/validators.pyi +0 -19
- tnfr-6.0.0.dist-info/RECORD +0 -157
- {tnfr-6.0.0.dist-info → tnfr-7.0.0.dist-info}/WHEEL +0 -0
- {tnfr-6.0.0.dist-info → tnfr-7.0.0.dist-info}/entry_points.txt +0 -0
- {tnfr-6.0.0.dist-info → tnfr-7.0.0.dist-info}/top_level.txt +0 -0
tnfr/gamma.py
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
"""Gamma registry."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import math
|
|
6
|
-
import logging
|
|
4
|
+
|
|
7
5
|
import hashlib
|
|
6
|
+
import logging
|
|
7
|
+
import math
|
|
8
8
|
from collections.abc import Mapping
|
|
9
9
|
from functools import lru_cache
|
|
10
10
|
from types import MappingProxyType
|
|
11
|
+
from typing import Any, Callable, NamedTuple
|
|
11
12
|
|
|
12
|
-
from .constants import DEFAULTS
|
|
13
13
|
from .alias import get_theta_attr
|
|
14
|
+
from .constants import DEFAULTS
|
|
15
|
+
from .utils import json_dumps
|
|
16
|
+
from .metrics.trig_cache import get_trig_cache
|
|
14
17
|
from .types import GammaSpec, NodeId, TNFRGraph
|
|
15
18
|
from .utils import (
|
|
16
19
|
edge_version_cache,
|
|
17
20
|
get_graph_mapping,
|
|
18
21
|
get_logger,
|
|
19
|
-
json_dumps,
|
|
20
22
|
node_set_checksum,
|
|
21
23
|
)
|
|
22
|
-
from .metrics.trig_cache import get_trig_cache
|
|
23
|
-
|
|
24
24
|
|
|
25
25
|
logger = get_logger(__name__)
|
|
26
26
|
|
|
@@ -47,8 +47,7 @@ def _default_gamma_spec() -> tuple[bytes, str]:
|
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
def _ensure_kuramoto_cache(G: TNFRGraph, t: float | int) -> None:
|
|
50
|
-
"""Cache ``(R, ψ)`` for the current step ``t`` using
|
|
51
|
-
``edge_version_cache``."""
|
|
50
|
+
"""Cache ``(R, ψ)`` for the current step ``t`` using ``edge_version_cache``."""
|
|
52
51
|
checksum = G.graph.get("_dnfr_nodes_checksum")
|
|
53
52
|
if checksum is None:
|
|
54
53
|
# reuse checksum from cached_nodes_and_A when available
|
|
@@ -172,9 +171,7 @@ def _get_gamma_spec(G: TNFRGraph) -> GammaSpec:
|
|
|
172
171
|
# -----------------
|
|
173
172
|
|
|
174
173
|
|
|
175
|
-
def _gamma_params(
|
|
176
|
-
cfg: GammaSpec, **defaults: float
|
|
177
|
-
) -> tuple[float, ...]:
|
|
174
|
+
def _gamma_params(cfg: GammaSpec, **defaults: float) -> tuple[float, ...]:
|
|
178
175
|
"""Return normalized Γ parameters from ``cfg``.
|
|
179
176
|
|
|
180
177
|
Parameters are retrieved from ``cfg`` using the keys in ``defaults`` and
|
|
@@ -186,9 +183,7 @@ def _gamma_params(
|
|
|
186
183
|
>>> beta, R0 = _gamma_params(cfg, beta=0.0, R0=0.0)
|
|
187
184
|
"""
|
|
188
185
|
|
|
189
|
-
return tuple(
|
|
190
|
-
float(cfg.get(name, default)) for name, default in defaults.items()
|
|
191
|
-
)
|
|
186
|
+
return tuple(float(cfg.get(name, default)) for name, default in defaults.items())
|
|
192
187
|
|
|
193
188
|
|
|
194
189
|
# -----------------
|
|
@@ -196,9 +191,9 @@ def _gamma_params(
|
|
|
196
191
|
# -----------------
|
|
197
192
|
|
|
198
193
|
|
|
199
|
-
def gamma_none(
|
|
200
|
-
|
|
201
|
-
|
|
194
|
+
def gamma_none(G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec) -> float:
|
|
195
|
+
"""Return ``0.0`` to disable Γ forcing for the given node."""
|
|
196
|
+
|
|
202
197
|
return 0.0
|
|
203
198
|
|
|
204
199
|
|
|
@@ -209,7 +204,7 @@ def _gamma_kuramoto(
|
|
|
209
204
|
builder: Callable[..., float],
|
|
210
205
|
**defaults: float,
|
|
211
206
|
) -> float:
|
|
212
|
-
"""
|
|
207
|
+
"""Construct a Kuramoto-based Γ function.
|
|
213
208
|
|
|
214
209
|
``builder`` receives ``(θ_i, R, ψ, *params)`` where ``params`` are
|
|
215
210
|
extracted from ``cfg`` according to ``defaults``.
|
|
@@ -229,7 +224,9 @@ def _builder_bandpass(th_i: float, R: float, psi: float, beta: float) -> float:
|
|
|
229
224
|
return beta * R * (1.0 - R) * sgn
|
|
230
225
|
|
|
231
226
|
|
|
232
|
-
def _builder_tanh(
|
|
227
|
+
def _builder_tanh(
|
|
228
|
+
th_i: float, R: float, psi: float, beta: float, k: float, R0: float
|
|
229
|
+
) -> float:
|
|
233
230
|
return beta * math.tanh(k * (R - R0)) * math.cos(th_i - psi)
|
|
234
231
|
|
|
235
232
|
|
|
@@ -253,7 +250,7 @@ def gamma_kuramoto_linear(
|
|
|
253
250
|
def gamma_kuramoto_bandpass(
|
|
254
251
|
G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec
|
|
255
252
|
) -> float:
|
|
256
|
-
"""Γ = β · R(1-R) · sign(cos(θ_i - ψ))"""
|
|
253
|
+
"""Compute Γ = β · R(1-R) · sign(cos(θ_i - ψ))."""
|
|
257
254
|
|
|
258
255
|
return _gamma_kuramoto(G, node, cfg, _builder_bandpass, beta=0.0)
|
|
259
256
|
|
|
@@ -272,9 +269,7 @@ def gamma_kuramoto_tanh(
|
|
|
272
269
|
return _gamma_kuramoto(G, node, cfg, _builder_tanh, beta=0.0, k=1.0, R0=0.0)
|
|
273
270
|
|
|
274
271
|
|
|
275
|
-
def gamma_harmonic(
|
|
276
|
-
G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec
|
|
277
|
-
) -> float:
|
|
272
|
+
def gamma_harmonic(G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec) -> float:
|
|
278
273
|
"""Harmonic forcing aligned with the global phase field.
|
|
279
274
|
|
|
280
275
|
Formula: Γ = β · sin(ω·t + φ) · cos(θ_i - ψ)
|
|
@@ -288,6 +283,8 @@ def gamma_harmonic(
|
|
|
288
283
|
|
|
289
284
|
|
|
290
285
|
class GammaEntry(NamedTuple):
|
|
286
|
+
"""Lookup entry linking Γ evaluators with their preconditions."""
|
|
287
|
+
|
|
291
288
|
fn: Callable[[TNFRGraph, NodeId, float | int, GammaSpec], float]
|
|
292
289
|
needs_kuramoto: bool
|
|
293
290
|
|
|
@@ -312,8 +309,7 @@ def eval_gamma(
|
|
|
312
309
|
strict: bool = False,
|
|
313
310
|
log_level: int | None = None,
|
|
314
311
|
) -> float:
|
|
315
|
-
"""Evaluate Γi for ``node``
|
|
316
|
-
specification.
|
|
312
|
+
"""Evaluate Γi for ``node`` using ``G.graph['GAMMA']`` specification.
|
|
317
313
|
|
|
318
314
|
If ``strict`` is ``True`` exceptions raised during evaluation are
|
|
319
315
|
propagated instead of returning ``0.0``. Likewise, if the specified
|
tnfr/gamma.pyi
CHANGED
|
@@ -11,25 +11,19 @@ class GammaEntry(NamedTuple):
|
|
|
11
11
|
GAMMA_REGISTRY: dict[str, GammaEntry]
|
|
12
12
|
|
|
13
13
|
def kuramoto_R_psi(G: TNFRGraph) -> tuple[float, float]: ...
|
|
14
|
-
|
|
15
14
|
def gamma_none(G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec) -> float: ...
|
|
16
|
-
|
|
17
15
|
def gamma_kuramoto_linear(
|
|
18
16
|
G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec
|
|
19
17
|
) -> float: ...
|
|
20
|
-
|
|
21
18
|
def gamma_kuramoto_bandpass(
|
|
22
19
|
G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec
|
|
23
20
|
) -> float: ...
|
|
24
|
-
|
|
25
21
|
def gamma_kuramoto_tanh(
|
|
26
22
|
G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec
|
|
27
23
|
) -> float: ...
|
|
28
|
-
|
|
29
24
|
def gamma_harmonic(
|
|
30
25
|
G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec
|
|
31
26
|
) -> float: ...
|
|
32
|
-
|
|
33
27
|
def eval_gamma(
|
|
34
28
|
G: TNFRGraph,
|
|
35
29
|
node: NodeId,
|
tnfr/glyph_history.py
CHANGED
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from
|
|
6
|
-
from collections import deque, Counter
|
|
7
|
-
from itertools import islice
|
|
5
|
+
from collections import Counter, deque
|
|
8
6
|
from collections.abc import Iterable, Mapping, MutableMapping
|
|
7
|
+
from itertools import islice
|
|
8
|
+
from typing import Any, cast
|
|
9
9
|
|
|
10
10
|
from .constants import get_param, normalise_state_token
|
|
11
|
-
from .
|
|
11
|
+
from .glyph_runtime import last_glyph
|
|
12
12
|
from .types import TNFRGraph
|
|
13
|
+
from .utils import ensure_collection, get_logger
|
|
13
14
|
|
|
14
15
|
logger = get_logger(__name__)
|
|
15
16
|
|
|
@@ -20,16 +21,27 @@ __all__ = (
|
|
|
20
21
|
"ensure_history",
|
|
21
22
|
"current_step_idx",
|
|
22
23
|
"append_metric",
|
|
23
|
-
"last_glyph",
|
|
24
24
|
"count_glyphs",
|
|
25
25
|
)
|
|
26
26
|
|
|
27
27
|
|
|
28
|
+
_NU_F_HISTORY_KEYS = (
|
|
29
|
+
"nu_f_rate_hz_str",
|
|
30
|
+
"nu_f_rate_hz",
|
|
31
|
+
"nu_f_ci_lower_hz_str",
|
|
32
|
+
"nu_f_ci_upper_hz_str",
|
|
33
|
+
"nu_f_ci_lower_hz",
|
|
34
|
+
"nu_f_ci_upper_hz",
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
28
38
|
def _ensure_history(
|
|
29
39
|
nd: MutableMapping[str, Any], window: int, *, create_zero: bool = False
|
|
30
40
|
) -> tuple[int, deque[str] | None]:
|
|
31
41
|
"""Validate ``window`` and ensure ``nd['glyph_history']`` deque."""
|
|
32
42
|
|
|
43
|
+
from tnfr.validation.window import validate_window
|
|
44
|
+
|
|
33
45
|
v_window = validate_window(window)
|
|
34
46
|
if v_window == 0 and not create_zero:
|
|
35
47
|
return v_window, None
|
|
@@ -42,9 +54,7 @@ def _ensure_history(
|
|
|
42
54
|
try:
|
|
43
55
|
items = ensure_collection(hist, max_materialize=None)
|
|
44
56
|
except TypeError:
|
|
45
|
-
logger.debug(
|
|
46
|
-
"Discarding non-iterable glyph history value %r", hist
|
|
47
|
-
)
|
|
57
|
+
logger.debug("Discarding non-iterable glyph history value %r", hist)
|
|
48
58
|
items = ()
|
|
49
59
|
hist = deque((str(item) for item in items), maxlen=v_window)
|
|
50
60
|
nd["glyph_history"] = hist
|
|
@@ -62,9 +72,7 @@ def push_glyph(nd: MutableMapping[str, Any], glyph: str, window: int) -> None:
|
|
|
62
72
|
hist.append(str(glyph))
|
|
63
73
|
|
|
64
74
|
|
|
65
|
-
def recent_glyph(
|
|
66
|
-
nd: MutableMapping[str, Any], glyph: str, window: int
|
|
67
|
-
) -> bool:
|
|
75
|
+
def recent_glyph(nd: MutableMapping[str, Any], glyph: str, window: int) -> bool:
|
|
68
76
|
"""Return ``True`` if ``glyph`` appeared in last ``window`` emissions.
|
|
69
77
|
|
|
70
78
|
``window`` validation and deque creation are handled by
|
|
@@ -145,26 +153,36 @@ class HistoryDict(dict[str, Any]):
|
|
|
145
153
|
return val
|
|
146
154
|
|
|
147
155
|
def get_increment(self, key: str, default: Any = None) -> Any:
|
|
156
|
+
"""Return value for ``key`` and increment its usage counter."""
|
|
157
|
+
|
|
148
158
|
insert = key not in self
|
|
149
159
|
val = self._resolve_value(key, default, insert=insert)
|
|
150
160
|
self._increment(key)
|
|
151
161
|
return val
|
|
152
162
|
|
|
153
163
|
def __getitem__(self, key: str) -> Any: # type: ignore[override]
|
|
164
|
+
"""Return the tracked value for ``key`` ensuring deque normalisation."""
|
|
165
|
+
|
|
154
166
|
return self._resolve_value(key, None, insert=False)
|
|
155
167
|
|
|
156
168
|
def get(self, key: str, default: Any | None = None) -> Any: # type: ignore[override]
|
|
169
|
+
"""Return ``key`` when present; otherwise fall back to ``default``."""
|
|
170
|
+
|
|
157
171
|
try:
|
|
158
172
|
return self._resolve_value(key, None, insert=False)
|
|
159
173
|
except KeyError:
|
|
160
174
|
return default
|
|
161
175
|
|
|
162
176
|
def __setitem__(self, key: str, value: Any) -> None: # type: ignore[override]
|
|
177
|
+
"""Store ``value`` for ``key`` while initialising usage tracking."""
|
|
178
|
+
|
|
163
179
|
super().__setitem__(key, value)
|
|
164
180
|
if key not in self._counts:
|
|
165
181
|
self._counts[key] = 0
|
|
166
182
|
|
|
167
183
|
def setdefault(self, key: str, default: Any | None = None) -> Any: # type: ignore[override]
|
|
184
|
+
"""Return existing value for ``key`` or insert ``default`` when absent."""
|
|
185
|
+
|
|
168
186
|
insert = key not in self
|
|
169
187
|
val = self._resolve_value(key, default, insert=insert)
|
|
170
188
|
if insert:
|
|
@@ -181,6 +199,8 @@ class HistoryDict(dict[str, Any]):
|
|
|
181
199
|
raise KeyError("HistoryDict is empty; cannot pop least used")
|
|
182
200
|
|
|
183
201
|
def pop_least_used_batch(self, k: int) -> None:
|
|
202
|
+
"""Remove up to ``k`` least-used entries from the history."""
|
|
203
|
+
|
|
184
204
|
for _ in range(max(0, int(k))):
|
|
185
205
|
try:
|
|
186
206
|
self.pop_least_used()
|
|
@@ -213,10 +233,7 @@ def ensure_history(G: TNFRGraph) -> HistoryDict | dict[str, Any]:
|
|
|
213
233
|
if isinstance(hist, MutableMapping):
|
|
214
234
|
_normalise_state_streams(hist)
|
|
215
235
|
return hist
|
|
216
|
-
if (
|
|
217
|
-
not isinstance(hist, HistoryDict)
|
|
218
|
-
or hist._maxlen != maxlen
|
|
219
|
-
):
|
|
236
|
+
if not isinstance(hist, HistoryDict) or hist._maxlen != maxlen:
|
|
220
237
|
hist = HistoryDict(hist, maxlen=maxlen)
|
|
221
238
|
G.graph["history"] = hist
|
|
222
239
|
replaced = True
|
|
@@ -226,6 +243,8 @@ def ensure_history(G: TNFRGraph) -> HistoryDict | dict[str, Any]:
|
|
|
226
243
|
if replaced:
|
|
227
244
|
G.graph.pop(sentinel_key, None)
|
|
228
245
|
_normalise_state_streams(cast(MutableMapping[str, Any], hist))
|
|
246
|
+
for key in _NU_F_HISTORY_KEYS:
|
|
247
|
+
hist.setdefault(key, [])
|
|
229
248
|
return hist
|
|
230
249
|
|
|
231
250
|
|
|
@@ -236,9 +255,7 @@ def current_step_idx(G: TNFRGraph | Mapping[str, Any]) -> int:
|
|
|
236
255
|
return len(graph.get("history", {}).get("C_steps", []))
|
|
237
256
|
|
|
238
257
|
|
|
239
|
-
def append_metric(
|
|
240
|
-
hist: MutableMapping[str, list[Any]], key: str, value: Any
|
|
241
|
-
) -> None:
|
|
258
|
+
def append_metric(hist: MutableMapping[str, list[Any]], key: str, value: Any) -> None:
|
|
242
259
|
"""Append ``value`` to ``hist[key]`` list, creating it if missing."""
|
|
243
260
|
if key == "phase_state" and isinstance(value, str):
|
|
244
261
|
value = normalise_state_token(value)
|
|
@@ -260,14 +277,6 @@ def append_metric(
|
|
|
260
277
|
return
|
|
261
278
|
|
|
262
279
|
hist.setdefault(key, []).append(value)
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
def last_glyph(nd: Mapping[str, Any]) -> str | None:
|
|
266
|
-
"""Return the most recent glyph for node or ``None``."""
|
|
267
|
-
hist = nd.get("glyph_history")
|
|
268
|
-
return hist[-1] if hist else None
|
|
269
|
-
|
|
270
|
-
|
|
271
280
|
def count_glyphs(
|
|
272
281
|
G: TNFRGraph, window: int | None = None, *, last_only: bool = False
|
|
273
282
|
) -> Counter[str]:
|
|
@@ -279,6 +288,8 @@ def count_glyphs(
|
|
|
279
288
|
"""
|
|
280
289
|
|
|
281
290
|
if window is not None:
|
|
291
|
+
from tnfr.validation.window import validate_window
|
|
292
|
+
|
|
282
293
|
window = validate_window(window)
|
|
283
294
|
if window == 0:
|
|
284
295
|
return Counter()
|
tnfr/glyph_history.pyi
CHANGED
|
@@ -8,7 +8,6 @@ from .types import TNFRGraph
|
|
|
8
8
|
|
|
9
9
|
__all__: tuple[str, ...]
|
|
10
10
|
|
|
11
|
-
|
|
12
11
|
class HistoryDict(dict[str, Any]):
|
|
13
12
|
_maxlen: int
|
|
14
13
|
_counts: Counter[str]
|
|
@@ -16,7 +15,6 @@ class HistoryDict(dict[str, Any]):
|
|
|
16
15
|
def __init__(
|
|
17
16
|
self, data: Mapping[str, Any] | None = ..., *, maxlen: int = ...
|
|
18
17
|
) -> None: ...
|
|
19
|
-
|
|
20
18
|
def get_increment(self, key: str, default: Any = ...) -> Any: ...
|
|
21
19
|
def __getitem__(self, key: str) -> Any: ...
|
|
22
20
|
def get(self, key: str, default: Any | None = ...) -> Any: ...
|
|
@@ -25,29 +23,13 @@ class HistoryDict(dict[str, Any]):
|
|
|
25
23
|
def pop_least_used(self) -> Any: ...
|
|
26
24
|
def pop_least_used_batch(self, k: int) -> None: ...
|
|
27
25
|
|
|
28
|
-
|
|
29
26
|
def push_glyph(nd: MutableMapping[str, Any], glyph: str, window: int) -> None: ...
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def recent_glyph(
|
|
33
|
-
nd: MutableMapping[str, Any], glyph: str, window: int
|
|
34
|
-
) -> bool: ...
|
|
35
|
-
|
|
36
|
-
|
|
27
|
+
def recent_glyph(nd: MutableMapping[str, Any], glyph: str, window: int) -> bool: ...
|
|
37
28
|
def ensure_history(G: TNFRGraph) -> HistoryDict | dict[str, Any]: ...
|
|
38
|
-
|
|
39
|
-
|
|
40
29
|
def current_step_idx(G: TNFRGraph | Mapping[str, Any]) -> int: ...
|
|
41
|
-
|
|
42
|
-
|
|
43
30
|
def append_metric(
|
|
44
31
|
hist: MutableMapping[str, list[Any]], key: str, value: Any
|
|
45
32
|
) -> None: ...
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def last_glyph(nd: Mapping[str, Any]) -> str | None: ...
|
|
49
|
-
|
|
50
|
-
|
|
51
33
|
def count_glyphs(
|
|
52
34
|
G: TNFRGraph, window: int | None = ..., *, last_only: bool = ...
|
|
53
35
|
) -> Counter[str]: ...
|
tnfr/glyph_runtime.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Runtime glyph helpers decoupled from validation internals."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
__all__ = ("last_glyph",)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def last_glyph(nd: Mapping[str, Any]) -> str | None:
|
|
12
|
+
"""Return the most recent glyph for node or ``None``."""
|
|
13
|
+
|
|
14
|
+
hist = nd.get("glyph_history")
|
|
15
|
+
return hist[-1] if hist else None
|
|
16
|
+
|
tnfr/glyph_runtime.pyi
ADDED
tnfr/immutable.py
CHANGED
|
@@ -7,21 +7,19 @@ encountered.
|
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
|
+
import threading
|
|
11
|
+
import weakref
|
|
12
|
+
from collections.abc import Mapping
|
|
10
13
|
from contextlib import contextmanager
|
|
11
14
|
from dataclasses import asdict, is_dataclass
|
|
12
15
|
from functools import lru_cache, partial, singledispatch, wraps
|
|
13
|
-
from typing import Any, Callable, Iterable, Iterator, cast
|
|
14
|
-
from collections.abc import Mapping
|
|
15
16
|
from types import MappingProxyType
|
|
16
|
-
import
|
|
17
|
-
import weakref
|
|
17
|
+
from typing import Any, Callable, Iterable, Iterator, cast
|
|
18
18
|
|
|
19
19
|
from ._compat import TypeAlias
|
|
20
20
|
|
|
21
21
|
# Types considered immutable without further inspection
|
|
22
|
-
IMMUTABLE_SIMPLE = frozenset(
|
|
23
|
-
{int, float, complex, str, bool, bytes, type(None)}
|
|
24
|
-
)
|
|
22
|
+
IMMUTABLE_SIMPLE = frozenset({int, float, complex, str, bool, bytes, type(None)})
|
|
25
23
|
|
|
26
24
|
|
|
27
25
|
FrozenPrimitive: TypeAlias = int | float | complex | str | bool | bytes | None
|
|
@@ -40,7 +38,10 @@ FrozenTaggedMapping: TypeAlias = tuple[str, FrozenMappingItems]
|
|
|
40
38
|
"""Tagged mapping snapshot identifying the original mapping flavour."""
|
|
41
39
|
|
|
42
40
|
FrozenSnapshot: TypeAlias = (
|
|
43
|
-
FrozenPrimitive
|
|
41
|
+
FrozenPrimitive
|
|
42
|
+
| FrozenCollectionItems
|
|
43
|
+
| FrozenTaggedCollection
|
|
44
|
+
| FrozenTaggedMapping
|
|
44
45
|
)
|
|
45
46
|
"""Union describing the immutable snapshot returned by :func:`_freeze`."""
|
|
46
47
|
|
|
@@ -61,9 +62,9 @@ def _cycle_guard(value: Any, seen: set[int] | None = None) -> Iterator[set[int]]
|
|
|
61
62
|
|
|
62
63
|
|
|
63
64
|
def _check_cycle(
|
|
64
|
-
func: Callable[[Any, set[int] | None], FrozenSnapshot]
|
|
65
|
+
func: Callable[[Any, set[int] | None], FrozenSnapshot],
|
|
65
66
|
) -> Callable[[Any, set[int] | None], FrozenSnapshot]:
|
|
66
|
-
"""
|
|
67
|
+
"""Apply :func:`_cycle_guard` to ``func``."""
|
|
67
68
|
|
|
68
69
|
@wraps(func)
|
|
69
70
|
def wrapper(value: Any, seen: set[int] | None = None) -> FrozenSnapshot:
|
|
@@ -95,7 +96,9 @@ def _freeze(value: Any, seen: set[int] | None = None) -> FrozenSnapshot:
|
|
|
95
96
|
|
|
96
97
|
@_freeze.register(tuple)
|
|
97
98
|
@_check_cycle
|
|
98
|
-
def _freeze_tuple(
|
|
99
|
+
def _freeze_tuple(
|
|
100
|
+
value: tuple[Any, ...], seen: set[int] | None = None
|
|
101
|
+
) -> FrozenCollectionItems: # noqa: F401
|
|
99
102
|
assert seen is not None
|
|
100
103
|
return tuple(_freeze(v, seen) for v in value)
|
|
101
104
|
|
|
@@ -115,7 +118,9 @@ def _freeze_iterable_with_tag(
|
|
|
115
118
|
|
|
116
119
|
def _register_iterable(cls: type, tag: str) -> None:
|
|
117
120
|
handler = _check_cycle(partial(_freeze_iterable_with_tag, tag=tag))
|
|
118
|
-
_freeze.register(cls)(
|
|
121
|
+
_freeze.register(cls)(
|
|
122
|
+
cast(Callable[[Any, set[int] | None], FrozenSnapshot], handler)
|
|
123
|
+
)
|
|
119
124
|
|
|
120
125
|
|
|
121
126
|
for _cls, _tag in (
|
|
@@ -159,6 +164,8 @@ _IMMUTABLE_TAG_DISPATCH: Mapping[str, ImmutableTagHandler] = MappingProxyType(
|
|
|
159
164
|
@lru_cache(maxsize=1024)
|
|
160
165
|
@singledispatch
|
|
161
166
|
def _is_immutable_inner(value: Any) -> bool:
|
|
167
|
+
"""Return ``True`` when ``value`` belongs to the canonical immutable set."""
|
|
168
|
+
|
|
162
169
|
return type(value) in IMMUTABLE_SIMPLE
|
|
163
170
|
|
|
164
171
|
|
|
@@ -176,9 +183,7 @@ def _is_immutable_inner_frozenset(value: frozenset[Any]) -> bool: # noqa: F401
|
|
|
176
183
|
return _all_immutable(value)
|
|
177
184
|
|
|
178
185
|
|
|
179
|
-
_IMMUTABLE_CACHE: weakref.WeakKeyDictionary[Any, bool] = (
|
|
180
|
-
weakref.WeakKeyDictionary()
|
|
181
|
-
)
|
|
186
|
+
_IMMUTABLE_CACHE: weakref.WeakKeyDictionary[Any, bool] = weakref.WeakKeyDictionary()
|
|
182
187
|
_IMMUTABLE_CACHE_LOCK = threading.Lock()
|
|
183
188
|
|
|
184
189
|
|
tnfr/immutable.pyi
CHANGED
|
@@ -8,29 +8,26 @@ FrozenMappingItems: TypeAlias = tuple[tuple[Any, "FrozenSnapshot"], ...]
|
|
|
8
8
|
FrozenTaggedCollection: TypeAlias = tuple[str, FrozenCollectionItems]
|
|
9
9
|
FrozenTaggedMapping: TypeAlias = tuple[str, FrozenMappingItems]
|
|
10
10
|
FrozenSnapshot: TypeAlias = (
|
|
11
|
-
FrozenPrimitive
|
|
11
|
+
FrozenPrimitive
|
|
12
|
+
| FrozenCollectionItems
|
|
13
|
+
| FrozenTaggedCollection
|
|
14
|
+
| FrozenTaggedMapping
|
|
12
15
|
)
|
|
13
16
|
ImmutableTagHandler: TypeAlias = Callable[[tuple[Any, ...]], bool]
|
|
14
17
|
|
|
15
18
|
__all__: tuple[str, ...]
|
|
16
19
|
|
|
17
20
|
def __getattr__(name: str) -> Any: ...
|
|
18
|
-
|
|
19
21
|
def _cycle_guard(value: Any, seen: set[int] | None = ...) -> Iterator[set[int]]: ...
|
|
20
|
-
|
|
21
22
|
def _check_cycle(
|
|
22
23
|
func: Callable[[Any, set[int] | None], FrozenSnapshot],
|
|
23
24
|
) -> Callable[[Any, set[int] | None], FrozenSnapshot]: ...
|
|
24
|
-
|
|
25
25
|
def _freeze(value: Any, seen: set[int] | None = ...) -> FrozenSnapshot: ...
|
|
26
|
-
|
|
27
26
|
def _freeze_mapping(
|
|
28
27
|
value: Mapping[Any, Any],
|
|
29
28
|
seen: set[int] | None = ...,
|
|
30
29
|
) -> FrozenTaggedMapping: ...
|
|
31
|
-
|
|
32
30
|
def _is_immutable(value: Any) -> bool: ...
|
|
33
|
-
|
|
34
31
|
def _is_immutable_inner(value: Any) -> bool: ...
|
|
35
32
|
|
|
36
33
|
_IMMUTABLE_CACHE: Any
|
tnfr/initialization.py
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
"""Node initialization."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
|
-
import random
|
|
5
|
-
from typing import TYPE_CHECKING, cast
|
|
6
4
|
|
|
5
|
+
import random
|
|
7
6
|
from dataclasses import dataclass
|
|
7
|
+
from typing import TYPE_CHECKING, cast
|
|
8
8
|
|
|
9
|
-
from .constants import
|
|
10
|
-
from .
|
|
9
|
+
from .constants import THETA_KEY, VF_KEY, get_graph_param
|
|
10
|
+
from .utils import clamp
|
|
11
11
|
from .rng import make_rng
|
|
12
12
|
from .types import NodeInitAttrMap
|
|
13
13
|
|
|
@@ -53,9 +53,7 @@ class InitParams:
|
|
|
53
53
|
vf_uniform_max=get_graph_param(G, "INIT_VF_MAX"),
|
|
54
54
|
vf_mean=get_graph_param(G, "INIT_VF_MEAN"),
|
|
55
55
|
vf_std=get_graph_param(G, "INIT_VF_STD"),
|
|
56
|
-
clamp_to_limits=get_graph_param(
|
|
57
|
-
G, "INIT_VF_CLAMP_TO_LIMITS", bool
|
|
58
|
-
),
|
|
56
|
+
clamp_to_limits=get_graph_param(G, "INIT_VF_CLAMP_TO_LIMITS", bool),
|
|
59
57
|
si_min=get_graph_param(G, "INIT_SI_MIN"),
|
|
60
58
|
si_max=get_graph_param(G, "INIT_SI_MAX"),
|
|
61
59
|
epi_val=get_graph_param(G, "INIT_EPI_VALUE"),
|
tnfr/initialization.pyi
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from dataclasses import dataclass
|
|
4
3
|
import random
|
|
4
|
+
from dataclasses import dataclass
|
|
5
5
|
|
|
6
6
|
import networkx as nx
|
|
7
7
|
|
|
@@ -9,7 +9,6 @@ from .types import NodeInitAttrMap
|
|
|
9
9
|
|
|
10
10
|
__all__: tuple[str, str] = ("InitParams", "init_node_attrs")
|
|
11
11
|
|
|
12
|
-
|
|
13
12
|
@dataclass
|
|
14
13
|
class InitParams:
|
|
15
14
|
seed: int | None
|
|
@@ -31,7 +30,6 @@ class InitParams:
|
|
|
31
30
|
@classmethod
|
|
32
31
|
def from_graph(cls, G: nx.Graph) -> InitParams: ...
|
|
33
32
|
|
|
34
|
-
|
|
35
33
|
def _init_phase(
|
|
36
34
|
nd: NodeInitAttrMap,
|
|
37
35
|
rng: random.Random,
|
|
@@ -41,8 +39,6 @@ def _init_phase(
|
|
|
41
39
|
th_min: float,
|
|
42
40
|
th_max: float,
|
|
43
41
|
) -> None: ...
|
|
44
|
-
|
|
45
|
-
|
|
46
42
|
def _init_vf(
|
|
47
43
|
nd: NodeInitAttrMap,
|
|
48
44
|
rng: random.Random,
|
|
@@ -57,8 +53,6 @@ def _init_vf(
|
|
|
57
53
|
vf_max_lim: float,
|
|
58
54
|
clamp_to_limits: bool,
|
|
59
55
|
) -> None: ...
|
|
60
|
-
|
|
61
|
-
|
|
62
56
|
def _init_si_epi(
|
|
63
57
|
nd: NodeInitAttrMap,
|
|
64
58
|
rng: random.Random,
|
|
@@ -68,6 +62,4 @@ def _init_si_epi(
|
|
|
68
62
|
si_max: float,
|
|
69
63
|
epi_val: float,
|
|
70
64
|
) -> None: ...
|
|
71
|
-
|
|
72
|
-
|
|
73
65
|
def init_node_attrs(G: nx.Graph, *, override: bool = True) -> nx.Graph: ...
|