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/sense.py
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
1
|
"""Sense calculations."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from collections.abc import Iterable, Mapping
|
|
4
|
+
|
|
6
5
|
import math
|
|
7
6
|
from collections import Counter
|
|
7
|
+
from collections.abc import Iterable, Iterator, Mapping
|
|
8
8
|
from itertools import tee
|
|
9
|
+
from typing import Any, Callable, TypeVar
|
|
9
10
|
|
|
10
|
-
import networkx as nx
|
|
11
|
+
import networkx as nx
|
|
11
12
|
|
|
12
|
-
from .constants import get_aliases, get_graph_param
|
|
13
13
|
from .alias import get_attr
|
|
14
|
-
from .helpers.numeric import clamp01, kahan_sum_nd
|
|
15
|
-
from .import_utils import get_numpy
|
|
16
14
|
from .callback_utils import CallbackEvent, callback_manager
|
|
17
|
-
from .
|
|
18
|
-
ensure_history,
|
|
19
|
-
last_glyph,
|
|
20
|
-
count_glyphs,
|
|
21
|
-
append_metric,
|
|
22
|
-
)
|
|
23
|
-
from .constants_glyphs import (
|
|
15
|
+
from .config.constants import (
|
|
24
16
|
ANGLE_MAP,
|
|
25
17
|
GLYPHS_CANONICAL,
|
|
26
18
|
)
|
|
19
|
+
from .constants import get_graph_param
|
|
20
|
+
from .constants.aliases import ALIAS_EPI, ALIAS_SI
|
|
21
|
+
from .glyph_history import append_metric, count_glyphs, ensure_history
|
|
22
|
+
from .glyph_runtime import last_glyph
|
|
23
|
+
from .utils import clamp01, kahan_sum_nd
|
|
24
|
+
from .types import NodeId, SigmaVector, TNFRGraph
|
|
25
|
+
from .utils import get_numpy
|
|
26
|
+
|
|
27
27
|
# -------------------------
|
|
28
|
-
# Canon:
|
|
28
|
+
# Canon: circular glyph order and angles
|
|
29
29
|
# -------------------------
|
|
30
30
|
|
|
31
31
|
GLYPH_UNITS: dict[str, complex] = {
|
|
@@ -45,7 +45,7 @@ __all__ = (
|
|
|
45
45
|
)
|
|
46
46
|
|
|
47
47
|
# -------------------------
|
|
48
|
-
#
|
|
48
|
+
# Basic utilities
|
|
49
49
|
# -------------------------
|
|
50
50
|
|
|
51
51
|
|
|
@@ -58,7 +58,7 @@ def _resolve_glyph(g: str, mapping: Mapping[str, T]) -> T:
|
|
|
58
58
|
try:
|
|
59
59
|
return mapping[g]
|
|
60
60
|
except KeyError as e: # pragma: no cover - small helper
|
|
61
|
-
raise KeyError(f"
|
|
61
|
+
raise KeyError(f"Unknown glyph: {g}") from e
|
|
62
62
|
|
|
63
63
|
|
|
64
64
|
def glyph_angle(g: str) -> float:
|
|
@@ -73,20 +73,19 @@ def glyph_unit(g: str) -> complex:
|
|
|
73
73
|
return _resolve_glyph(g, GLYPH_UNITS)
|
|
74
74
|
|
|
75
75
|
|
|
76
|
-
|
|
77
|
-
ALIAS_EPI = get_aliases("EPI")
|
|
78
|
-
|
|
79
|
-
MODE_FUNCS = {
|
|
76
|
+
MODE_FUNCS: dict[str, Callable[[Mapping[str, Any]], float]] = {
|
|
80
77
|
"Si": lambda nd: clamp01(get_attr(nd, ALIAS_SI, 0.5)),
|
|
81
78
|
"EPI": lambda nd: max(0.0, get_attr(nd, ALIAS_EPI, 0.0)),
|
|
82
79
|
}
|
|
83
80
|
|
|
84
81
|
|
|
85
|
-
def _weight(nd, mode: str) -> float:
|
|
82
|
+
def _weight(nd: Mapping[str, Any], mode: str) -> float:
|
|
86
83
|
return MODE_FUNCS.get(mode, lambda _: 1.0)(nd)
|
|
87
84
|
|
|
88
85
|
|
|
89
|
-
def _node_weight(
|
|
86
|
+
def _node_weight(
|
|
87
|
+
nd: Mapping[str, Any], weight_mode: str
|
|
88
|
+
) -> tuple[str, float, complex] | None:
|
|
90
89
|
"""Return ``(glyph, weight, weighted_unit)`` or ``None`` if no glyph."""
|
|
91
90
|
g = last_glyph(nd)
|
|
92
91
|
if not g:
|
|
@@ -96,7 +95,7 @@ def _node_weight(nd, weight_mode: str) -> tuple[str, float, complex] | None:
|
|
|
96
95
|
return g, w, z
|
|
97
96
|
|
|
98
97
|
|
|
99
|
-
def _sigma_cfg(G):
|
|
98
|
+
def _sigma_cfg(G: TNFRGraph) -> dict[str, Any]:
|
|
100
99
|
return get_graph_param(G, "SIGMA", dict)
|
|
101
100
|
|
|
102
101
|
|
|
@@ -110,7 +109,7 @@ def _to_complex(val: complex | float | int) -> complex:
|
|
|
110
109
|
raise TypeError("values must be an iterable of real or complex numbers")
|
|
111
110
|
|
|
112
111
|
|
|
113
|
-
def _empty_sigma(fallback_angle: float) ->
|
|
112
|
+
def _empty_sigma(fallback_angle: float) -> SigmaVector:
|
|
114
113
|
"""Return an empty σ-vector with ``fallback_angle``.
|
|
115
114
|
|
|
116
115
|
Helps centralise the default structure returned when no values are
|
|
@@ -127,14 +126,14 @@ def _empty_sigma(fallback_angle: float) -> dict[str, float]:
|
|
|
127
126
|
|
|
128
127
|
|
|
129
128
|
# -------------------------
|
|
130
|
-
# σ
|
|
129
|
+
# σ per node and global σ
|
|
131
130
|
# -------------------------
|
|
132
131
|
|
|
133
132
|
|
|
134
133
|
def _sigma_from_iterable(
|
|
135
134
|
values: Iterable[complex | float | int] | complex | float | int,
|
|
136
135
|
fallback_angle: float = 0.0,
|
|
137
|
-
) ->
|
|
136
|
+
) -> SigmaVector:
|
|
138
137
|
"""Normalise vectors in the σ-plane.
|
|
139
138
|
|
|
140
139
|
``values`` may contain complex or real numbers; real inputs are promoted to
|
|
@@ -142,7 +141,9 @@ def _sigma_from_iterable(
|
|
|
142
141
|
number of processed values under the ``"n"`` key.
|
|
143
142
|
"""
|
|
144
143
|
|
|
145
|
-
if isinstance(values, Iterable) and not isinstance(
|
|
144
|
+
if isinstance(values, Iterable) and not isinstance(
|
|
145
|
+
values, (str, bytes, bytearray, Mapping)
|
|
146
|
+
):
|
|
146
147
|
iterator = iter(values)
|
|
147
148
|
else:
|
|
148
149
|
iterator = iter((values,))
|
|
@@ -159,15 +160,15 @@ def _sigma_from_iterable(
|
|
|
159
160
|
mag = float(np.hypot(x, y))
|
|
160
161
|
ang = float(np.arctan2(y, x)) if mag > 0 else float(fallback_angle)
|
|
161
162
|
return {
|
|
162
|
-
"x": x,
|
|
163
|
-
"y": y,
|
|
164
|
-
"mag": mag,
|
|
165
|
-
"angle": ang,
|
|
166
|
-
"n": cnt,
|
|
163
|
+
"x": float(x),
|
|
164
|
+
"y": float(y),
|
|
165
|
+
"mag": float(mag),
|
|
166
|
+
"angle": float(ang),
|
|
167
|
+
"n": int(cnt),
|
|
167
168
|
}
|
|
168
169
|
cnt = 0
|
|
169
170
|
|
|
170
|
-
def pair_iter():
|
|
171
|
+
def pair_iter() -> Iterator[tuple[float, float]]:
|
|
171
172
|
nonlocal cnt
|
|
172
173
|
for val in iterator:
|
|
173
174
|
z = _to_complex(val)
|
|
@@ -188,24 +189,30 @@ def _sigma_from_iterable(
|
|
|
188
189
|
"y": float(y),
|
|
189
190
|
"mag": float(mag),
|
|
190
191
|
"angle": float(ang),
|
|
191
|
-
"n": cnt,
|
|
192
|
+
"n": int(cnt),
|
|
192
193
|
}
|
|
193
194
|
|
|
194
195
|
|
|
195
|
-
def _ema_update(
|
|
196
|
-
prev: dict[str, float], current: dict[str, float], alpha: float
|
|
197
|
-
) -> dict[str, float]:
|
|
196
|
+
def _ema_update(prev: SigmaVector, current: SigmaVector, alpha: float) -> SigmaVector:
|
|
198
197
|
"""Exponential moving average update for σ vectors."""
|
|
199
198
|
x = (1 - alpha) * prev["x"] + alpha * current["x"]
|
|
200
199
|
y = (1 - alpha) * prev["y"] + alpha * current["y"]
|
|
201
200
|
mag = math.hypot(x, y)
|
|
202
201
|
ang = math.atan2(y, x)
|
|
203
|
-
return {
|
|
202
|
+
return {
|
|
203
|
+
"x": float(x),
|
|
204
|
+
"y": float(y),
|
|
205
|
+
"mag": float(mag),
|
|
206
|
+
"angle": float(ang),
|
|
207
|
+
"n": int(current["n"]),
|
|
208
|
+
}
|
|
204
209
|
|
|
205
210
|
|
|
206
211
|
def _sigma_from_nodes(
|
|
207
|
-
nodes: Iterable[
|
|
208
|
-
|
|
212
|
+
nodes: Iterable[Mapping[str, Any]],
|
|
213
|
+
weight_mode: str,
|
|
214
|
+
fallback_angle: float = 0.0,
|
|
215
|
+
) -> tuple[SigmaVector, list[tuple[str, float, complex]]]:
|
|
209
216
|
"""Aggregate weighted glyph vectors for ``nodes``.
|
|
210
217
|
|
|
211
218
|
Returns the aggregated σ vector and the list of ``(glyph, weight, vector)``
|
|
@@ -218,8 +225,10 @@ def _sigma_from_nodes(
|
|
|
218
225
|
|
|
219
226
|
|
|
220
227
|
def sigma_vector_node(
|
|
221
|
-
G, n, weight_mode: str | None = None
|
|
222
|
-
) ->
|
|
228
|
+
G: TNFRGraph, n: NodeId, weight_mode: str | None = None
|
|
229
|
+
) -> SigmaVector | None:
|
|
230
|
+
"""Return the σ vector for node ``n`` using the configured weighting."""
|
|
231
|
+
|
|
223
232
|
cfg = _sigma_cfg(G)
|
|
224
233
|
nd = G.nodes[n]
|
|
225
234
|
weight_mode = weight_mode or cfg.get("weight", "Si")
|
|
@@ -229,11 +238,12 @@ def sigma_vector_node(
|
|
|
229
238
|
g, w, _ = nws[0]
|
|
230
239
|
if sv["mag"] == 0:
|
|
231
240
|
sv["angle"] = glyph_angle(g)
|
|
232
|
-
sv
|
|
241
|
+
sv["glyph"] = g
|
|
242
|
+
sv["w"] = float(w)
|
|
233
243
|
return sv
|
|
234
244
|
|
|
235
245
|
|
|
236
|
-
def sigma_vector(dist:
|
|
246
|
+
def sigma_vector(dist: Mapping[str, float]) -> SigmaVector:
|
|
237
247
|
"""Compute Σ⃗ from a glyph distribution.
|
|
238
248
|
|
|
239
249
|
``dist`` may contain raw counts or proportions. All ``(glyph, weight)``
|
|
@@ -246,8 +256,8 @@ def sigma_vector(dist: dict[str, float]) -> dict[str, float]:
|
|
|
246
256
|
|
|
247
257
|
|
|
248
258
|
def sigma_vector_from_graph(
|
|
249
|
-
G:
|
|
250
|
-
) ->
|
|
259
|
+
G: TNFRGraph, weight_mode: str | None = None
|
|
260
|
+
) -> SigmaVector:
|
|
251
261
|
"""Global vector in the σ sense plane for a graph.
|
|
252
262
|
|
|
253
263
|
Parameters
|
|
@@ -264,34 +274,34 @@ def sigma_vector_from_graph(
|
|
|
264
274
|
"""
|
|
265
275
|
|
|
266
276
|
if not isinstance(G, nx.Graph):
|
|
267
|
-
raise TypeError("sigma_vector_from_graph
|
|
277
|
+
raise TypeError("sigma_vector_from_graph requires a networkx.Graph")
|
|
268
278
|
|
|
269
279
|
cfg = _sigma_cfg(G)
|
|
270
280
|
weight_mode = weight_mode or cfg.get("weight", "Si")
|
|
271
|
-
sv, _ = _sigma_from_nodes(
|
|
272
|
-
(nd for _, nd in G.nodes(data=True)), weight_mode
|
|
273
|
-
)
|
|
281
|
+
sv, _ = _sigma_from_nodes((nd for _, nd in G.nodes(data=True)), weight_mode)
|
|
274
282
|
return sv
|
|
275
283
|
|
|
276
284
|
|
|
277
285
|
# -------------------------
|
|
278
|
-
#
|
|
286
|
+
# History / series
|
|
279
287
|
# -------------------------
|
|
280
288
|
|
|
281
289
|
|
|
282
|
-
def push_sigma_snapshot(G, t: float | None = None) -> None:
|
|
290
|
+
def push_sigma_snapshot(G: TNFRGraph, t: float | None = None) -> None:
|
|
291
|
+
"""Record a global σ snapshot (and optional per-node traces) for ``G``."""
|
|
292
|
+
|
|
283
293
|
cfg = _sigma_cfg(G)
|
|
284
294
|
if not cfg.get("enabled", True):
|
|
285
295
|
return
|
|
286
296
|
|
|
287
|
-
#
|
|
297
|
+
# Local history cache to avoid repeated lookups
|
|
288
298
|
hist = ensure_history(G)
|
|
289
299
|
key = cfg.get("history_key", "sigma_global")
|
|
290
300
|
|
|
291
301
|
weight_mode = cfg.get("weight", "Si")
|
|
292
302
|
sv = sigma_vector_from_graph(G, weight_mode)
|
|
293
303
|
|
|
294
|
-
#
|
|
304
|
+
# Optional exponential smoothing (EMA)
|
|
295
305
|
alpha = float(cfg.get("smooth", 0.0))
|
|
296
306
|
if alpha > 0 and hist.get(key):
|
|
297
307
|
sv = _ema_update(hist[key][-1], sv, alpha)
|
|
@@ -301,11 +311,11 @@ def push_sigma_snapshot(G, t: float | None = None) -> None:
|
|
|
301
311
|
|
|
302
312
|
append_metric(hist, key, sv)
|
|
303
313
|
|
|
304
|
-
#
|
|
314
|
+
# Glyph count per step (useful for the glyph rose)
|
|
305
315
|
counts = count_glyphs(G, last_only=True)
|
|
306
316
|
append_metric(hist, "sigma_counts", {"t": current_t, **counts})
|
|
307
317
|
|
|
308
|
-
#
|
|
318
|
+
# Optional per-node trajectory
|
|
309
319
|
if cfg.get("per_node", False):
|
|
310
320
|
per = hist.setdefault("sigma_per_node", {})
|
|
311
321
|
for n, nd in G.nodes(data=True):
|
|
@@ -317,11 +327,13 @@ def push_sigma_snapshot(G, t: float | None = None) -> None:
|
|
|
317
327
|
|
|
318
328
|
|
|
319
329
|
# -------------------------
|
|
320
|
-
#
|
|
330
|
+
# Register as an automatic callback (after_step)
|
|
321
331
|
# -------------------------
|
|
322
332
|
|
|
323
333
|
|
|
324
|
-
def register_sigma_callback(G) -> None:
|
|
334
|
+
def register_sigma_callback(G: TNFRGraph) -> None:
|
|
335
|
+
"""Attach :func:`push_sigma_snapshot` to the ``AFTER_STEP`` callback bus."""
|
|
336
|
+
|
|
325
337
|
callback_manager.register_callback(
|
|
326
338
|
G,
|
|
327
339
|
event=CallbackEvent.AFTER_STEP.value,
|
|
@@ -330,7 +342,7 @@ def register_sigma_callback(G) -> None:
|
|
|
330
342
|
)
|
|
331
343
|
|
|
332
344
|
|
|
333
|
-
def sigma_rose(G, steps: int | None = None) -> dict[str, int]:
|
|
345
|
+
def sigma_rose(G: TNFRGraph, steps: int | None = None) -> dict[str, int]:
|
|
334
346
|
"""Histogram of glyphs in the last ``steps`` steps (or all)."""
|
|
335
347
|
hist = ensure_history(G)
|
|
336
348
|
counts = hist.get("sigma_counts", [])
|
|
@@ -340,9 +352,7 @@ def sigma_rose(G, steps: int | None = None) -> dict[str, int]:
|
|
|
340
352
|
steps = int(steps)
|
|
341
353
|
if steps < 0:
|
|
342
354
|
raise ValueError("steps must be non-negative")
|
|
343
|
-
rows = (
|
|
344
|
-
counts if steps >= len(counts) else counts[-steps:]
|
|
345
|
-
) # noqa: E203
|
|
355
|
+
rows = counts if steps >= len(counts) else counts[-steps:] # noqa: E203
|
|
346
356
|
else:
|
|
347
357
|
rows = counts
|
|
348
358
|
counter = Counter()
|
tnfr/sense.pyi
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Mapping
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from .types import NodeId, SigmaVector, TNFRGraph
|
|
7
|
+
|
|
8
|
+
__all__: tuple[str, ...]
|
|
9
|
+
|
|
10
|
+
GLYPH_UNITS: dict[str, complex]
|
|
11
|
+
|
|
12
|
+
def glyph_angle(g: str) -> float: ...
|
|
13
|
+
def glyph_unit(g: str) -> complex: ...
|
|
14
|
+
def push_sigma_snapshot(G: TNFRGraph, t: Optional[float] = None) -> None: ...
|
|
15
|
+
def register_sigma_callback(G: TNFRGraph) -> None: ...
|
|
16
|
+
def sigma_rose(G: TNFRGraph, steps: Optional[int] = None) -> dict[str, int]: ...
|
|
17
|
+
def sigma_vector(dist: Mapping[str, float]) -> SigmaVector: ...
|
|
18
|
+
def sigma_vector_from_graph(
|
|
19
|
+
G: TNFRGraph, weight_mode: Optional[str] = None
|
|
20
|
+
) -> SigmaVector: ...
|
|
21
|
+
def sigma_vector_node(
|
|
22
|
+
G: TNFRGraph, n: NodeId, weight_mode: Optional[str] = None
|
|
23
|
+
) -> Optional[SigmaVector]: ...
|