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/metrics/core.py
CHANGED
|
@@ -2,9 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from collections.abc import Mapping
|
|
5
|
+
from collections.abc import Mapping, MutableMapping
|
|
6
6
|
from typing import Any, NamedTuple, cast
|
|
7
7
|
|
|
8
|
+
from ..callback_utils import CallbackEvent, callback_manager
|
|
9
|
+
from ..constants import get_param
|
|
10
|
+
from ..glyph_history import append_metric, ensure_history
|
|
11
|
+
from ..telemetry import ensure_nu_f_telemetry
|
|
12
|
+
from ..units import get_hz_bridge
|
|
13
|
+
from ..telemetry.verbosity import (
|
|
14
|
+
TELEMETRY_VERBOSITY_DEFAULT,
|
|
15
|
+
TELEMETRY_VERBOSITY_LEVELS,
|
|
16
|
+
TelemetryVerbosity,
|
|
17
|
+
)
|
|
8
18
|
from ..types import (
|
|
9
19
|
GlyphSelector,
|
|
10
20
|
NodeId,
|
|
@@ -17,32 +27,23 @@ from ..types import (
|
|
|
17
27
|
TraceFieldMap,
|
|
18
28
|
TraceFieldRegistry,
|
|
19
29
|
)
|
|
20
|
-
|
|
21
|
-
from ..callback_utils import CallbackEvent, callback_manager
|
|
22
|
-
from ..constants import get_param
|
|
23
|
-
from ..glyph_history import append_metric, ensure_history
|
|
24
30
|
from ..utils import get_logger
|
|
25
|
-
from ..telemetry.verbosity import (
|
|
26
|
-
TelemetryVerbosity,
|
|
27
|
-
TELEMETRY_VERBOSITY_DEFAULT,
|
|
28
|
-
TELEMETRY_VERBOSITY_LEVELS,
|
|
29
|
-
)
|
|
30
31
|
from .coherence import (
|
|
32
|
+
GLYPH_LOAD_STABILIZERS_KEY,
|
|
31
33
|
_aggregate_si,
|
|
32
34
|
_track_stability,
|
|
33
35
|
_update_coherence,
|
|
34
36
|
_update_phase_sync,
|
|
35
37
|
_update_sigma,
|
|
36
38
|
register_coherence_callbacks,
|
|
37
|
-
GLYPH_LOAD_STABILIZERS_KEY,
|
|
38
39
|
)
|
|
39
40
|
from .diagnosis import register_diagnosis_callbacks
|
|
40
|
-
from .glyph_timing import
|
|
41
|
+
from .glyph_timing import GlyphMetricsHistory, _compute_advanced_metrics
|
|
41
42
|
from .reporting import (
|
|
42
43
|
Tg_by_node,
|
|
43
44
|
Tg_global,
|
|
44
|
-
glyphogram_series,
|
|
45
45
|
glyph_top,
|
|
46
|
+
glyphogram_series,
|
|
46
47
|
latency_series,
|
|
47
48
|
)
|
|
48
49
|
|
|
@@ -79,6 +80,7 @@ class MetricsVerbositySpec(NamedTuple):
|
|
|
79
80
|
enable_advanced: bool
|
|
80
81
|
attach_coherence_hooks: bool
|
|
81
82
|
attach_diagnosis_hooks: bool
|
|
83
|
+
enable_nu_f: bool
|
|
82
84
|
|
|
83
85
|
|
|
84
86
|
METRICS_VERBOSITY_DEFAULT = TELEMETRY_VERBOSITY_DEFAULT
|
|
@@ -89,7 +91,8 @@ _METRICS_VERBOSITY_PRESETS: dict[str, MetricsVerbositySpec] = {}
|
|
|
89
91
|
def _register_metrics_preset(spec: MetricsVerbositySpec) -> None:
|
|
90
92
|
if spec.name not in TELEMETRY_VERBOSITY_LEVELS:
|
|
91
93
|
raise ValueError(
|
|
92
|
-
"Unknown metrics verbosity '%s'; use %s"
|
|
94
|
+
"Unknown metrics verbosity '%s'; use %s"
|
|
95
|
+
% (
|
|
93
96
|
spec.name,
|
|
94
97
|
", ".join(TELEMETRY_VERBOSITY_LEVELS),
|
|
95
98
|
)
|
|
@@ -106,6 +109,7 @@ _register_metrics_preset(
|
|
|
106
109
|
enable_advanced=False,
|
|
107
110
|
attach_coherence_hooks=False,
|
|
108
111
|
attach_diagnosis_hooks=False,
|
|
112
|
+
enable_nu_f=False,
|
|
109
113
|
)
|
|
110
114
|
)
|
|
111
115
|
|
|
@@ -117,6 +121,7 @@ _detailed_spec = MetricsVerbositySpec(
|
|
|
117
121
|
enable_advanced=False,
|
|
118
122
|
attach_coherence_hooks=True,
|
|
119
123
|
attach_diagnosis_hooks=False,
|
|
124
|
+
enable_nu_f=True,
|
|
120
125
|
)
|
|
121
126
|
_register_metrics_preset(_detailed_spec)
|
|
122
127
|
_register_metrics_preset(
|
|
@@ -124,6 +129,7 @@ _register_metrics_preset(
|
|
|
124
129
|
name=TelemetryVerbosity.DEBUG.value,
|
|
125
130
|
enable_advanced=True,
|
|
126
131
|
attach_diagnosis_hooks=True,
|
|
132
|
+
enable_nu_f=True,
|
|
127
133
|
)
|
|
128
134
|
)
|
|
129
135
|
|
|
@@ -139,6 +145,51 @@ _METRICS_SIGMA_HISTORY_KEYS = (
|
|
|
139
145
|
"sense_sigma_angle",
|
|
140
146
|
)
|
|
141
147
|
_METRICS_SI_HISTORY_KEYS = ("Si_mean", "Si_hi_frac", "Si_lo_frac")
|
|
148
|
+
_METRICS_NU_F_HISTORY_KEYS = (
|
|
149
|
+
"nu_f_rate_hz_str",
|
|
150
|
+
"nu_f_rate_hz",
|
|
151
|
+
"nu_f_ci_lower_hz_str",
|
|
152
|
+
"nu_f_ci_upper_hz_str",
|
|
153
|
+
"nu_f_ci_lower_hz",
|
|
154
|
+
"nu_f_ci_upper_hz",
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _update_nu_f_snapshot(
|
|
159
|
+
G: TNFRGraph,
|
|
160
|
+
hist: MutableMapping[str, Any],
|
|
161
|
+
*,
|
|
162
|
+
record_history: bool,
|
|
163
|
+
) -> None:
|
|
164
|
+
"""Refresh νf telemetry snapshot and optionally persist it in history."""
|
|
165
|
+
|
|
166
|
+
accumulator = ensure_nu_f_telemetry(G, confidence_level=None)
|
|
167
|
+
snapshot = accumulator.snapshot(graph=G)
|
|
168
|
+
payload = snapshot.as_payload()
|
|
169
|
+
bridge: float | None
|
|
170
|
+
try:
|
|
171
|
+
bridge = float(get_hz_bridge(G))
|
|
172
|
+
except (TypeError, ValueError, KeyError):
|
|
173
|
+
bridge = None
|
|
174
|
+
else:
|
|
175
|
+
payload["hz_bridge"] = bridge
|
|
176
|
+
|
|
177
|
+
telemetry = G.graph.setdefault("telemetry", {})
|
|
178
|
+
if not isinstance(telemetry, MutableMapping):
|
|
179
|
+
telemetry = {}
|
|
180
|
+
G.graph["telemetry"] = telemetry
|
|
181
|
+
telemetry["nu_f_snapshot"] = payload
|
|
182
|
+
telemetry["nu_f_bridge"] = bridge
|
|
183
|
+
|
|
184
|
+
if record_history:
|
|
185
|
+
append_metric(hist, "nu_f_rate_hz_str", snapshot.rate_hz_str)
|
|
186
|
+
append_metric(hist, "nu_f_rate_hz", snapshot.rate_hz)
|
|
187
|
+
append_metric(hist, "nu_f_ci_lower_hz_str", snapshot.ci_lower_hz_str)
|
|
188
|
+
append_metric(hist, "nu_f_ci_upper_hz_str", snapshot.ci_upper_hz_str)
|
|
189
|
+
append_metric(hist, "nu_f_ci_lower_hz", snapshot.ci_lower_hz)
|
|
190
|
+
append_metric(hist, "nu_f_ci_upper_hz", snapshot.ci_upper_hz)
|
|
191
|
+
|
|
192
|
+
G.graph["_nu_f_snapshot_payload"] = payload
|
|
142
193
|
|
|
143
194
|
|
|
144
195
|
def _resolve_metrics_verbosity(cfg: Mapping[str, Any]) -> MetricsVerbositySpec:
|
|
@@ -187,6 +238,9 @@ def _metrics_step(G: TNFRGraph, ctx: dict[str, Any] | None = None) -> None:
|
|
|
187
238
|
if spec.enable_aggregate_si:
|
|
188
239
|
for key in _METRICS_SI_HISTORY_KEYS:
|
|
189
240
|
hist.setdefault(key, [])
|
|
241
|
+
if spec.enable_nu_f:
|
|
242
|
+
for key in _METRICS_NU_F_HISTORY_KEYS:
|
|
243
|
+
hist.setdefault(key, [])
|
|
190
244
|
G.graph[metrics_sentinel_key] = history_id
|
|
191
245
|
|
|
192
246
|
dt = float(get_param(G, "DT"))
|
|
@@ -233,6 +287,8 @@ def _metrics_step(G: TNFRGraph, ctx: dict[str, Any] | None = None) -> None:
|
|
|
233
287
|
if spec.enable_aggregate_si:
|
|
234
288
|
_aggregate_si(G, hist, n_jobs=metrics_jobs)
|
|
235
289
|
|
|
290
|
+
_update_nu_f_snapshot(G, hist, record_history=spec.enable_nu_f)
|
|
291
|
+
|
|
236
292
|
if spec.enable_advanced:
|
|
237
293
|
_compute_advanced_metrics(
|
|
238
294
|
G,
|
|
@@ -245,6 +301,8 @@ def _metrics_step(G: TNFRGraph, ctx: dict[str, Any] | None = None) -> None:
|
|
|
245
301
|
|
|
246
302
|
|
|
247
303
|
def register_metrics_callbacks(G: TNFRGraph) -> None:
|
|
304
|
+
"""Attach canonical metrics callbacks according to graph configuration."""
|
|
305
|
+
|
|
248
306
|
cfg = cast(Mapping[str, Any], get_param(G, "METRICS"))
|
|
249
307
|
spec = _resolve_metrics_verbosity(cfg)
|
|
250
308
|
callback_manager.register_callback(
|
tnfr/metrics/diagnosis.py
CHANGED
|
@@ -3,28 +3,28 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import math
|
|
6
|
+
from collections import deque
|
|
7
|
+
from collections.abc import Mapping, MutableMapping, Sequence
|
|
6
8
|
from concurrent.futures import ProcessPoolExecutor
|
|
7
9
|
from dataclasses import dataclass
|
|
8
10
|
from functools import partial
|
|
9
11
|
from operator import ge, le
|
|
10
12
|
from statistics import StatisticsError, fmean
|
|
11
13
|
from typing import Any, Callable, Iterable, cast
|
|
12
|
-
from collections import deque
|
|
13
|
-
from collections.abc import Mapping, MutableMapping, Sequence
|
|
14
14
|
|
|
15
|
+
from ..alias import get_attr
|
|
16
|
+
from ..callback_utils import CallbackEvent, callback_manager
|
|
15
17
|
from ..constants import (
|
|
16
18
|
STATE_DISSONANT,
|
|
17
19
|
STATE_STABLE,
|
|
18
20
|
STATE_TRANSITION,
|
|
19
21
|
VF_KEY,
|
|
20
|
-
get_aliases,
|
|
21
22
|
get_param,
|
|
22
23
|
normalise_state_token,
|
|
23
24
|
)
|
|
24
|
-
from ..
|
|
25
|
+
from ..constants.aliases import ALIAS_DNFR, ALIAS_EPI, ALIAS_SI, ALIAS_VF
|
|
25
26
|
from ..glyph_history import append_metric, ensure_history
|
|
26
|
-
from ..
|
|
27
|
-
from ..helpers.numeric import clamp01, similarity_abs
|
|
27
|
+
from ..utils import clamp01, resolve_chunk_size, similarity_abs
|
|
28
28
|
from ..types import (
|
|
29
29
|
DiagnosisNodeData,
|
|
30
30
|
DiagnosisPayload,
|
|
@@ -36,31 +36,17 @@ from ..types import (
|
|
|
36
36
|
TNFRGraph,
|
|
37
37
|
)
|
|
38
38
|
from ..utils import get_numpy
|
|
39
|
-
from .common import compute_dnfr_accel_max, min_max_range, normalize_dnfr
|
|
40
39
|
from .coherence import CoherenceMatrixPayload, coherence_matrix, local_phase_sync
|
|
40
|
+
from .common import (
|
|
41
|
+
_coerce_jobs,
|
|
42
|
+
compute_dnfr_accel_max,
|
|
43
|
+
min_max_range,
|
|
44
|
+
normalize_dnfr,
|
|
45
|
+
)
|
|
41
46
|
from .trig_cache import compute_theta_trig, get_trig_cache
|
|
42
47
|
|
|
43
|
-
ALIAS_EPI = get_aliases("EPI")
|
|
44
|
-
ALIAS_VF = get_aliases("VF")
|
|
45
|
-
ALIAS_SI = get_aliases("SI")
|
|
46
|
-
ALIAS_DNFR = get_aliases("DNFR")
|
|
47
|
-
|
|
48
48
|
CoherenceSeries = Sequence[CoherenceMatrixPayload | None]
|
|
49
49
|
CoherenceHistory = Mapping[str, CoherenceSeries]
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def _coerce_jobs(raw_jobs: Any | None) -> int | None:
|
|
53
|
-
"""Normalise ``n_jobs`` values coming from user configuration."""
|
|
54
|
-
|
|
55
|
-
try:
|
|
56
|
-
jobs = None if raw_jobs is None else int(raw_jobs)
|
|
57
|
-
except (TypeError, ValueError):
|
|
58
|
-
return None
|
|
59
|
-
if jobs is not None and jobs <= 0:
|
|
60
|
-
return None
|
|
61
|
-
return jobs
|
|
62
|
-
|
|
63
|
-
|
|
64
50
|
def _coherence_matrix_to_numpy(
|
|
65
51
|
weight_matrix: Any,
|
|
66
52
|
size: int,
|
|
@@ -376,7 +362,9 @@ def _node_diagnostics(
|
|
|
376
362
|
|
|
377
363
|
if compute_symmetry:
|
|
378
364
|
epi_bar = node_data.get("neighbor_epi_mean")
|
|
379
|
-
symm =
|
|
365
|
+
symm = (
|
|
366
|
+
1.0 if epi_bar is None else similarity_abs(EPI, epi_bar, epi_min, epi_max)
|
|
367
|
+
)
|
|
380
368
|
else:
|
|
381
369
|
symm = None
|
|
382
370
|
|
|
@@ -505,10 +493,7 @@ def _diagnosis_step(
|
|
|
505
493
|
|
|
506
494
|
if supports_vector:
|
|
507
495
|
epi_arr = np_mod.fromiter(
|
|
508
|
-
(
|
|
509
|
-
cast(float, get_attr(nd, ALIAS_EPI, 0.0))
|
|
510
|
-
for _, nd in nodes_data
|
|
511
|
-
),
|
|
496
|
+
(cast(float, get_attr(nd, ALIAS_EPI, 0.0)) for _, nd in nodes_data),
|
|
512
497
|
dtype=float,
|
|
513
498
|
count=len(nodes_data),
|
|
514
499
|
)
|
|
@@ -518,10 +503,7 @@ def _diagnosis_step(
|
|
|
518
503
|
|
|
519
504
|
si_arr = np_mod.clip(
|
|
520
505
|
np_mod.fromiter(
|
|
521
|
-
(
|
|
522
|
-
cast(float, get_attr(nd, ALIAS_SI, 0.0))
|
|
523
|
-
for _, nd in nodes_data
|
|
524
|
-
),
|
|
506
|
+
(cast(float, get_attr(nd, ALIAS_SI, 0.0)) for _, nd in nodes_data),
|
|
525
507
|
dtype=float,
|
|
526
508
|
count=len(nodes_data),
|
|
527
509
|
),
|
|
@@ -531,10 +513,7 @@ def _diagnosis_step(
|
|
|
531
513
|
si_vals = si_arr.tolist()
|
|
532
514
|
|
|
533
515
|
vf_arr = np_mod.fromiter(
|
|
534
|
-
(
|
|
535
|
-
cast(float, get_attr(nd, ALIAS_VF, 0.0))
|
|
536
|
-
for _, nd in nodes_data
|
|
537
|
-
),
|
|
516
|
+
(cast(float, get_attr(nd, ALIAS_VF, 0.0)) for _, nd in nodes_data),
|
|
538
517
|
dtype=float,
|
|
539
518
|
count=len(nodes_data),
|
|
540
519
|
)
|
|
@@ -595,9 +574,7 @@ def _diagnosis_step(
|
|
|
595
574
|
if supports_vector:
|
|
596
575
|
size = len(coherence_nodes)
|
|
597
576
|
matrix_np = (
|
|
598
|
-
_coherence_matrix_to_numpy(weight_matrix, size, np_mod)
|
|
599
|
-
if size
|
|
600
|
-
else None
|
|
577
|
+
_coherence_matrix_to_numpy(weight_matrix, size, np_mod) if size else None
|
|
601
578
|
)
|
|
602
579
|
if matrix_np is not None and size:
|
|
603
580
|
cos_weight = np_mod.fromiter(
|
|
@@ -617,8 +594,7 @@ def _diagnosis_step(
|
|
|
617
594
|
np_mod,
|
|
618
595
|
)
|
|
619
596
|
rloc_map = {
|
|
620
|
-
coherence_nodes[idx]: float(weighted_sync[idx])
|
|
621
|
-
for idx in range(size)
|
|
597
|
+
coherence_nodes[idx]: float(weighted_sync[idx]) for idx in range(size)
|
|
622
598
|
}
|
|
623
599
|
else:
|
|
624
600
|
rloc_map = {}
|
|
@@ -647,14 +623,19 @@ def _diagnosis_step(
|
|
|
647
623
|
rloc_values = [rloc_map.get(node, 0.0) for node in nodes]
|
|
648
624
|
else:
|
|
649
625
|
if n_jobs and n_jobs > 1 and len(nodes) > 1:
|
|
650
|
-
|
|
626
|
+
approx_chunk = math.ceil(len(nodes) / n_jobs) if n_jobs else None
|
|
627
|
+
chunk_size = resolve_chunk_size(
|
|
628
|
+
approx_chunk,
|
|
629
|
+
len(nodes),
|
|
630
|
+
minimum=1,
|
|
631
|
+
)
|
|
651
632
|
rloc_values = []
|
|
652
633
|
with ProcessPoolExecutor(max_workers=n_jobs) as executor:
|
|
653
634
|
futures = [
|
|
654
635
|
executor.submit(
|
|
655
636
|
_rlocal_worker,
|
|
656
637
|
RLocalWorkerArgs(
|
|
657
|
-
chunk=nodes[idx:idx + chunk_size],
|
|
638
|
+
chunk=nodes[idx : idx + chunk_size],
|
|
658
639
|
coherence_nodes=coherence_nodes,
|
|
659
640
|
weight_matrix=weight_matrix,
|
|
660
641
|
weight_index=weight_index,
|
|
@@ -681,7 +662,9 @@ def _diagnosis_step(
|
|
|
681
662
|
)
|
|
682
663
|
|
|
683
664
|
if isinstance(Wi_last, (list, tuple)) and Wi_last:
|
|
684
|
-
wi_values = [
|
|
665
|
+
wi_values = [
|
|
666
|
+
Wi_last[i] if i < len(Wi_last) else None for i in range(len(nodes))
|
|
667
|
+
]
|
|
685
668
|
else:
|
|
686
669
|
wi_values = [None] * len(nodes)
|
|
687
670
|
|
|
@@ -697,7 +680,12 @@ def _diagnosis_step(
|
|
|
697
680
|
np_mod,
|
|
698
681
|
)
|
|
699
682
|
elif n_jobs and n_jobs > 1 and len(nodes) > 1:
|
|
700
|
-
|
|
683
|
+
approx_chunk = math.ceil(len(nodes) / n_jobs) if n_jobs else None
|
|
684
|
+
chunk_size = resolve_chunk_size(
|
|
685
|
+
approx_chunk,
|
|
686
|
+
len(nodes),
|
|
687
|
+
minimum=1,
|
|
688
|
+
)
|
|
701
689
|
neighbor_means = cast(list[float | None], [])
|
|
702
690
|
with ProcessPoolExecutor(max_workers=n_jobs) as executor:
|
|
703
691
|
submit = cast(Callable[..., Any], executor.submit)
|
|
@@ -708,7 +696,7 @@ def _diagnosis_step(
|
|
|
708
696
|
_neighbor_mean_worker,
|
|
709
697
|
),
|
|
710
698
|
NeighborMeanWorkerArgs(
|
|
711
|
-
chunk=nodes[idx:idx + chunk_size],
|
|
699
|
+
chunk=nodes[idx : idx + chunk_size],
|
|
712
700
|
neighbors_map=neighbors_map,
|
|
713
701
|
epi_map=epi_map,
|
|
714
702
|
),
|
|
@@ -716,9 +704,7 @@ def _diagnosis_step(
|
|
|
716
704
|
for idx in range(0, len(nodes), chunk_size)
|
|
717
705
|
]
|
|
718
706
|
for fut in futures:
|
|
719
|
-
neighbor_means.extend(
|
|
720
|
-
cast(list[float | None], fut.result())
|
|
721
|
-
)
|
|
707
|
+
neighbor_means.extend(cast(list[float | None], fut.result()))
|
|
722
708
|
else:
|
|
723
709
|
neighbor_means = _neighbor_mean_worker(
|
|
724
710
|
NeighborMeanWorkerArgs(
|
|
@@ -754,7 +740,12 @@ def _diagnosis_step(
|
|
|
754
740
|
}
|
|
755
741
|
|
|
756
742
|
if n_jobs and n_jobs > 1 and len(node_payload) > 1:
|
|
757
|
-
|
|
743
|
+
approx_chunk = math.ceil(len(node_payload) / n_jobs) if n_jobs else None
|
|
744
|
+
chunk_size = resolve_chunk_size(
|
|
745
|
+
approx_chunk,
|
|
746
|
+
len(node_payload),
|
|
747
|
+
minimum=1,
|
|
748
|
+
)
|
|
758
749
|
diag_pairs: DiagnosisResultList = []
|
|
759
750
|
with ProcessPoolExecutor(max_workers=n_jobs) as executor:
|
|
760
751
|
submit = cast(Callable[..., Any], executor.submit)
|
|
@@ -767,7 +758,7 @@ def _diagnosis_step(
|
|
|
767
758
|
],
|
|
768
759
|
_diagnosis_worker_chunk,
|
|
769
760
|
),
|
|
770
|
-
node_payload[idx:idx + chunk_size],
|
|
761
|
+
node_payload[idx : idx + chunk_size],
|
|
771
762
|
shared,
|
|
772
763
|
)
|
|
773
764
|
for idx in range(0, len(node_payload), chunk_size)
|
|
@@ -785,9 +776,7 @@ def _diagnosis_step(
|
|
|
785
776
|
append_metric(hist, key, diag)
|
|
786
777
|
|
|
787
778
|
|
|
788
|
-
def dissonance_events(
|
|
789
|
-
G: TNFRGraph, ctx: DiagnosisSharedState | None = None
|
|
790
|
-
) -> None:
|
|
779
|
+
def dissonance_events(G: TNFRGraph, ctx: DiagnosisSharedState | None = None) -> None:
|
|
791
780
|
"""Emit per-node structural dissonance start/end events.
|
|
792
781
|
|
|
793
782
|
Events are recorded as ``"dissonance_start"`` and ``"dissonance_end"``.
|
|
@@ -823,6 +812,8 @@ def dissonance_events(
|
|
|
823
812
|
|
|
824
813
|
|
|
825
814
|
def register_diagnosis_callbacks(G: TNFRGraph) -> None:
|
|
815
|
+
"""Attach diagnosis observers (Si/dissonance tracking) to ``G``."""
|
|
816
|
+
|
|
826
817
|
raw_jobs = G.graph.get("DIAGNOSIS_N_JOBS")
|
|
827
818
|
n_jobs = _coerce_jobs(raw_jobs)
|
|
828
819
|
|
tnfr/metrics/diagnosis.pyi
CHANGED
|
@@ -61,7 +61,6 @@ class RLocalWorkerArgs:
|
|
|
61
61
|
sin_map: Mapping[Any, float],
|
|
62
62
|
) -> None: ...
|
|
63
63
|
|
|
64
|
-
|
|
65
64
|
class NeighborMeanWorkerArgs:
|
|
66
65
|
chunk: Sequence[Any]
|
|
67
66
|
neighbors_map: Mapping[Any, tuple[Any, ...]]
|
|
@@ -74,15 +73,12 @@ class NeighborMeanWorkerArgs:
|
|
|
74
73
|
epi_map: Mapping[Any, float],
|
|
75
74
|
) -> None: ...
|
|
76
75
|
|
|
77
|
-
|
|
78
76
|
def _rlocal_worker(args: RLocalWorkerArgs) -> list[float]: ...
|
|
79
|
-
|
|
80
77
|
def _neighbor_mean_worker(args: NeighborMeanWorkerArgs) -> list[float | None]: ...
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
78
|
+
def _state_from_thresholds(
|
|
79
|
+
Rloc: float, dnfr_n: float, cfg: Mapping[str, Any]
|
|
80
|
+
) -> str: ...
|
|
84
81
|
def _recommendation(state: str, cfg: Mapping[str, Any]) -> list[Any]: ...
|
|
85
|
-
|
|
86
82
|
def _get_last_weights(
|
|
87
83
|
G: TNFRGraph,
|
|
88
84
|
hist: Mapping[str, Sequence[CoherenceMatrixPayload | None]],
|
tnfr/metrics/export.py
CHANGED
|
@@ -5,16 +5,14 @@ from __future__ import annotations
|
|
|
5
5
|
import csv
|
|
6
6
|
import math
|
|
7
7
|
from collections.abc import Iterable, Iterator, Sequence
|
|
8
|
-
from itertools import
|
|
8
|
+
from itertools import tee, zip_longest
|
|
9
9
|
from typing import Mapping, TextIO
|
|
10
10
|
|
|
11
11
|
from ..config.constants import GLYPHS_CANONICAL
|
|
12
12
|
from ..glyph_history import ensure_history
|
|
13
|
-
from ..
|
|
14
|
-
from ..
|
|
15
|
-
from ..types import Graph
|
|
13
|
+
from ..utils import json_dumps, safe_write
|
|
14
|
+
from ..types import Graph, SigmaTrace
|
|
16
15
|
from .core import glyphogram_series
|
|
17
|
-
from .glyph_timing import SigmaTrace
|
|
18
16
|
|
|
19
17
|
|
|
20
18
|
def _write_csv(
|
tnfr/metrics/glyph_timing.py
CHANGED
|
@@ -2,20 +2,38 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import math
|
|
5
6
|
from collections import Counter, defaultdict
|
|
6
7
|
from concurrent.futures import ProcessPoolExecutor
|
|
7
8
|
from dataclasses import dataclass
|
|
8
|
-
import math
|
|
9
9
|
from types import ModuleType
|
|
10
|
-
from typing import
|
|
10
|
+
from typing import (
|
|
11
|
+
Any,
|
|
12
|
+
Callable,
|
|
13
|
+
Mapping,
|
|
14
|
+
MutableMapping,
|
|
15
|
+
Sequence,
|
|
16
|
+
cast,
|
|
17
|
+
)
|
|
11
18
|
|
|
12
19
|
from ..alias import get_attr
|
|
13
|
-
from ..constants import get_aliases, get_param
|
|
14
20
|
from ..config.constants import GLYPH_GROUPS, GLYPHS_CANONICAL
|
|
15
|
-
from ..
|
|
16
|
-
from ..
|
|
17
|
-
|
|
18
|
-
|
|
21
|
+
from ..constants import get_param
|
|
22
|
+
from ..constants.aliases import ALIAS_EPI
|
|
23
|
+
from ..glyph_history import append_metric
|
|
24
|
+
from ..glyph_runtime import last_glyph
|
|
25
|
+
from ..utils import resolve_chunk_size
|
|
26
|
+
from ..types import (
|
|
27
|
+
GlyphCounts,
|
|
28
|
+
GlyphMetricsHistory,
|
|
29
|
+
GlyphMetricsHistoryValue,
|
|
30
|
+
GlyphTimingByNode,
|
|
31
|
+
GlyphTimingTotals,
|
|
32
|
+
GlyphogramRow,
|
|
33
|
+
GraphLike,
|
|
34
|
+
MetricsListHistory,
|
|
35
|
+
SigmaTrace,
|
|
36
|
+
)
|
|
19
37
|
|
|
20
38
|
LATENT_GLYPH: str = "SHA"
|
|
21
39
|
DEFAULT_EPI_SUPPORT_LIMIT = 0.05
|
|
@@ -36,26 +54,6 @@ def _has_numpy_support(np_obj: object) -> bool:
|
|
|
36
54
|
and hasattr(np_obj, "fromiter")
|
|
37
55
|
and hasattr(np_obj, "bincount")
|
|
38
56
|
)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
class SigmaTrace(TypedDict):
|
|
42
|
-
"""Time-aligned σ(t) trace exported alongside glyphograms."""
|
|
43
|
-
|
|
44
|
-
t: list[float]
|
|
45
|
-
sigma_x: list[float]
|
|
46
|
-
sigma_y: list[float]
|
|
47
|
-
mag: list[float]
|
|
48
|
-
angle: list[float]
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
GlyphogramRow = MutableMapping[str, float]
|
|
52
|
-
GlyphTimingTotals = MutableMapping[str, float]
|
|
53
|
-
GlyphTimingByNode = MutableMapping[Any, MutableMapping[str, MutableSequence[float]]]
|
|
54
|
-
GlyphCounts = Mapping[str, int]
|
|
55
|
-
GlyphMetricsHistoryValue = MutableMapping[Any, Any] | MutableSequence[Any]
|
|
56
|
-
GlyphMetricsHistory = MutableMapping[str, GlyphMetricsHistoryValue]
|
|
57
|
-
MetricsListHistory = MutableMapping[str, list[Any]]
|
|
58
|
-
|
|
59
57
|
_GLYPH_TO_INDEX = {glyph: idx for idx, glyph in enumerate(GLYPHS_CANONICAL)}
|
|
60
58
|
|
|
61
59
|
|
|
@@ -70,6 +68,8 @@ def _coerce_float(value: Any) -> float:
|
|
|
70
68
|
|
|
71
69
|
@dataclass
|
|
72
70
|
class GlyphTiming:
|
|
71
|
+
"""Mutable accumulator tracking the active glyph and its dwell time."""
|
|
72
|
+
|
|
73
73
|
curr: str | None = None
|
|
74
74
|
run: float = 0.0
|
|
75
75
|
|
|
@@ -219,11 +219,16 @@ def _update_tg(
|
|
|
219
219
|
}
|
|
220
220
|
)
|
|
221
221
|
elif n_jobs is not None and n_jobs > 1 and len(glyph_sequence) > 1:
|
|
222
|
-
|
|
222
|
+
approx_chunk = math.ceil(len(glyph_sequence) / n_jobs) if n_jobs else None
|
|
223
|
+
chunk_size = resolve_chunk_size(
|
|
224
|
+
approx_chunk,
|
|
225
|
+
len(glyph_sequence),
|
|
226
|
+
minimum=1,
|
|
227
|
+
)
|
|
223
228
|
futures = []
|
|
224
229
|
with ProcessPoolExecutor(max_workers=n_jobs) as executor:
|
|
225
230
|
for start in range(0, len(glyph_sequence), chunk_size):
|
|
226
|
-
chunk = glyph_sequence[start:start + chunk_size]
|
|
231
|
+
chunk = glyph_sequence[start : start + chunk_size]
|
|
227
232
|
futures.append(executor.submit(_count_glyphs_chunk, chunk))
|
|
228
233
|
for future in futures:
|
|
229
234
|
counts.update(future.result())
|
|
@@ -299,12 +304,17 @@ def _update_epi_support(
|
|
|
299
304
|
abs(_coerce_float(get_attr(nd, ALIAS_EPI, 0.0)))
|
|
300
305
|
for _, nd in G.nodes(data=True)
|
|
301
306
|
]
|
|
302
|
-
|
|
307
|
+
approx_chunk = math.ceil(len(values) / n_jobs) if n_jobs else None
|
|
308
|
+
chunk_size = resolve_chunk_size(
|
|
309
|
+
approx_chunk,
|
|
310
|
+
len(values),
|
|
311
|
+
minimum=1,
|
|
312
|
+
)
|
|
303
313
|
totals: list[tuple[float, int]] = []
|
|
304
314
|
with ProcessPoolExecutor(max_workers=n_jobs) as executor:
|
|
305
315
|
futures = []
|
|
306
316
|
for start in range(0, len(values), chunk_size):
|
|
307
|
-
chunk = values[start:start + chunk_size]
|
|
317
|
+
chunk = values[start : start + chunk_size]
|
|
308
318
|
futures.append(executor.submit(_epi_support_chunk, chunk, threshold))
|
|
309
319
|
for future in futures:
|
|
310
320
|
totals.append(future.result())
|
tnfr/metrics/reporting.py
CHANGED
|
@@ -3,14 +3,13 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from collections.abc import Sequence
|
|
6
|
-
from typing import Any
|
|
7
|
-
|
|
8
6
|
from heapq import nlargest
|
|
9
|
-
from statistics import
|
|
7
|
+
from statistics import StatisticsError, fmean, mean
|
|
8
|
+
from typing import Any
|
|
10
9
|
|
|
11
10
|
from ..glyph_history import ensure_history
|
|
12
|
-
from ..types import NodeId, TNFRGraph
|
|
13
11
|
from ..sense import sigma_rose
|
|
12
|
+
from ..types import NodeId, TNFRGraph
|
|
14
13
|
from .glyph_timing import for_each_glyph
|
|
15
14
|
|
|
16
15
|
__all__ = [
|
|
@@ -70,6 +69,8 @@ def Tg_by_node(
|
|
|
70
69
|
|
|
71
70
|
|
|
72
71
|
def latency_series(G: TNFRGraph) -> dict[str, list[float]]:
|
|
72
|
+
"""Return latency samples as ``{"t": [...], "value": [...]}``."""
|
|
73
|
+
|
|
73
74
|
hist = ensure_history(G)
|
|
74
75
|
xs = hist.get("latency_index", [])
|
|
75
76
|
return {
|
|
@@ -79,11 +80,15 @@ def latency_series(G: TNFRGraph) -> dict[str, list[float]]:
|
|
|
79
80
|
|
|
80
81
|
|
|
81
82
|
def glyphogram_series(G: TNFRGraph) -> dict[str, list[float]]:
|
|
83
|
+
"""Return glyphogram time series keyed by glyph label."""
|
|
84
|
+
|
|
82
85
|
hist = ensure_history(G)
|
|
83
86
|
xs = hist.get("glyphogram", [])
|
|
84
87
|
if not xs:
|
|
85
88
|
return {"t": []}
|
|
86
|
-
out: dict[str, list[float]] = {
|
|
89
|
+
out: dict[str, list[float]] = {
|
|
90
|
+
"t": [float(x.get("t", i)) for i, x in enumerate(xs)]
|
|
91
|
+
}
|
|
87
92
|
|
|
88
93
|
def add(g: str) -> None:
|
|
89
94
|
out[g] = [float(x.get(g, 0.0)) for x in xs]
|
|
@@ -104,7 +109,9 @@ def glyph_top(G: TNFRGraph, k: int = 3) -> list[tuple[str, float]]:
|
|
|
104
109
|
|
|
105
110
|
def build_metrics_summary(
|
|
106
111
|
G: TNFRGraph, *, series_limit: int | None = None
|
|
107
|
-
) -> tuple[
|
|
112
|
+
) -> tuple[
|
|
113
|
+
dict[str, float | dict[str, float] | dict[str, list[float]] | dict[str, int]], bool
|
|
114
|
+
]:
|
|
108
115
|
"""Collect a compact metrics summary for CLI reporting.
|
|
109
116
|
|
|
110
117
|
Parameters
|