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/coherence.py
CHANGED
|
@@ -7,19 +7,32 @@ from collections.abc import Callable, Iterable, Mapping, Sequence
|
|
|
7
7
|
from concurrent.futures import ProcessPoolExecutor
|
|
8
8
|
from dataclasses import dataclass
|
|
9
9
|
from types import ModuleType
|
|
10
|
-
from typing import Any, MutableMapping,
|
|
10
|
+
from typing import Any, MutableMapping, cast
|
|
11
11
|
|
|
12
12
|
from .._compat import TypeAlias
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
from ..constants import (
|
|
16
|
-
get_aliases,
|
|
17
|
-
get_param,
|
|
18
|
-
)
|
|
13
|
+
from ..alias import collect_attr, collect_theta_attr, get_attr, set_attr
|
|
19
14
|
from ..callback_utils import CallbackEvent, callback_manager
|
|
15
|
+
from ..constants import get_param
|
|
16
|
+
from ..constants.aliases import (
|
|
17
|
+
ALIAS_D2VF,
|
|
18
|
+
ALIAS_DNFR,
|
|
19
|
+
ALIAS_DSI,
|
|
20
|
+
ALIAS_DVF,
|
|
21
|
+
ALIAS_DEPI,
|
|
22
|
+
ALIAS_EPI,
|
|
23
|
+
ALIAS_SI,
|
|
24
|
+
ALIAS_VF,
|
|
25
|
+
)
|
|
20
26
|
from ..glyph_history import append_metric, ensure_history
|
|
21
|
-
from ..
|
|
22
|
-
from ..
|
|
27
|
+
from ..utils import clamp01
|
|
28
|
+
from ..observers import (
|
|
29
|
+
DEFAULT_GLYPH_LOAD_SPAN,
|
|
30
|
+
DEFAULT_WBAR_SPAN,
|
|
31
|
+
glyph_load,
|
|
32
|
+
kuramoto_order,
|
|
33
|
+
phase_sync,
|
|
34
|
+
)
|
|
35
|
+
from ..sense import sigma_vector
|
|
23
36
|
from ..types import (
|
|
24
37
|
CoherenceMetric,
|
|
25
38
|
FloatArray,
|
|
@@ -27,37 +40,22 @@ from ..types import (
|
|
|
27
40
|
GlyphLoadDistribution,
|
|
28
41
|
HistoryState,
|
|
29
42
|
NodeId,
|
|
43
|
+
ParallelWijPayload,
|
|
30
44
|
SigmaVector,
|
|
31
45
|
TNFRGraph,
|
|
32
46
|
)
|
|
33
|
-
from .common import compute_coherence, min_max_range
|
|
34
|
-
from .trig_cache import compute_theta_trig, get_trig_cache
|
|
35
|
-
from ..observers import (
|
|
36
|
-
DEFAULT_GLYPH_LOAD_SPAN,
|
|
37
|
-
DEFAULT_WBAR_SPAN,
|
|
38
|
-
glyph_load,
|
|
39
|
-
kuramoto_order,
|
|
40
|
-
phase_sync,
|
|
41
|
-
)
|
|
42
|
-
from ..sense import sigma_vector
|
|
43
47
|
from ..utils import (
|
|
44
48
|
ensure_node_index_map,
|
|
45
49
|
get_logger,
|
|
46
50
|
get_numpy,
|
|
47
51
|
normalize_weights,
|
|
52
|
+
resolve_chunk_size,
|
|
48
53
|
)
|
|
54
|
+
from .common import compute_coherence, min_max_range
|
|
55
|
+
from .trig_cache import compute_theta_trig, get_trig_cache
|
|
49
56
|
|
|
50
57
|
logger = get_logger(__name__)
|
|
51
58
|
|
|
52
|
-
ALIAS_EPI = get_aliases("EPI")
|
|
53
|
-
ALIAS_VF = get_aliases("VF")
|
|
54
|
-
ALIAS_SI = get_aliases("SI")
|
|
55
|
-
ALIAS_DNFR = get_aliases("DNFR")
|
|
56
|
-
ALIAS_DEPI = get_aliases("DEPI")
|
|
57
|
-
ALIAS_DSI = get_aliases("DSI")
|
|
58
|
-
ALIAS_DVF = get_aliases("DVF")
|
|
59
|
-
ALIAS_D2VF = get_aliases("D2VF")
|
|
60
|
-
|
|
61
59
|
GLYPH_LOAD_STABILIZERS_KEY = "glyph_load_stabilizers"
|
|
62
60
|
|
|
63
61
|
|
|
@@ -81,9 +79,9 @@ PhaseSyncWeights: TypeAlias = (
|
|
|
81
79
|
)
|
|
82
80
|
|
|
83
81
|
SimilarityComponents = tuple[float, float, float, float]
|
|
84
|
-
VectorizedComponents: TypeAlias =
|
|
85
|
-
|
|
86
|
-
|
|
82
|
+
VectorizedComponents: TypeAlias = tuple[
|
|
83
|
+
FloatMatrix, FloatMatrix, FloatMatrix, FloatMatrix
|
|
84
|
+
]
|
|
87
85
|
ScalarOrArray: TypeAlias = float | FloatArray
|
|
88
86
|
StabilityChunkArgs = tuple[
|
|
89
87
|
Sequence[float],
|
|
@@ -110,19 +108,6 @@ StabilityChunkResult = tuple[
|
|
|
110
108
|
MetricValue: TypeAlias = CoherenceMetric
|
|
111
109
|
MetricProvider = Callable[[], MetricValue]
|
|
112
110
|
MetricRecord: TypeAlias = tuple[MetricValue | MetricProvider, str]
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
class ParallelWijPayload(TypedDict):
|
|
116
|
-
epi_vals: Sequence[float]
|
|
117
|
-
vf_vals: Sequence[float]
|
|
118
|
-
si_vals: Sequence[float]
|
|
119
|
-
cos_vals: Sequence[float]
|
|
120
|
-
sin_vals: Sequence[float]
|
|
121
|
-
weights: tuple[float, float, float, float]
|
|
122
|
-
epi_range: float
|
|
123
|
-
vf_range: float
|
|
124
|
-
|
|
125
|
-
|
|
126
111
|
def _compute_wij_phase_epi_vf_si_vectorized(
|
|
127
112
|
epi: FloatArray,
|
|
128
113
|
vf: FloatArray,
|
|
@@ -143,9 +128,7 @@ def _compute_wij_phase_epi_vf_si_vectorized(
|
|
|
143
128
|
epi_range = epi_range if epi_range > 0 else 1.0
|
|
144
129
|
vf_range = vf_range if vf_range > 0 else 1.0
|
|
145
130
|
s_phase = 0.5 * (
|
|
146
|
-
1.0
|
|
147
|
-
+ cos_th[:, None] * cos_th[None, :]
|
|
148
|
-
+ sin_th[:, None] * sin_th[None, :]
|
|
131
|
+
1.0 + cos_th[:, None] * cos_th[None, :] + sin_th[:, None] * sin_th[None, :]
|
|
149
132
|
)
|
|
150
133
|
s_epi = 1.0 - np.abs(epi[:, None] - epi[None, :]) / epi_range
|
|
151
134
|
s_vf = 1.0 - np.abs(vf[:, None] - vf[None, :]) / vf_range
|
|
@@ -369,7 +352,7 @@ def _init_parallel_wij(data: ParallelWijPayload) -> None:
|
|
|
369
352
|
|
|
370
353
|
|
|
371
354
|
def _parallel_wij_worker(
|
|
372
|
-
pairs: Sequence[tuple[int, int]]
|
|
355
|
+
pairs: Sequence[tuple[int, int]],
|
|
373
356
|
) -> list[tuple[int, int, float]]:
|
|
374
357
|
"""Compute coherence weights for ``pairs`` using shared state."""
|
|
375
358
|
|
|
@@ -444,10 +427,7 @@ def _wij_loops(
|
|
|
444
427
|
inputs.si_vals = si_vals
|
|
445
428
|
inputs.cos_vals = cos_vals_list
|
|
446
429
|
inputs.sin_vals = sin_vals_list
|
|
447
|
-
wij = [
|
|
448
|
-
[1.0 if (self_diag and i == j) else 0.0 for j in range(n)]
|
|
449
|
-
for i in range(n)
|
|
450
|
-
]
|
|
430
|
+
wij = [[1.0 if (self_diag and i == j) else 0.0 for j in range(n)] for i in range(n)]
|
|
451
431
|
epi_range = epi_max - epi_min if epi_max > epi_min else 1.0
|
|
452
432
|
vf_range = vf_max - vf_min if vf_max > vf_min else 1.0
|
|
453
433
|
weights = (
|
|
@@ -498,7 +478,12 @@ def _wij_loops(
|
|
|
498
478
|
wij[i][j] = wij[j][i] = wij_ij
|
|
499
479
|
return wij
|
|
500
480
|
|
|
501
|
-
|
|
481
|
+
approx_chunk = math.ceil(total_pairs / max_workers) if max_workers else None
|
|
482
|
+
chunk_size = resolve_chunk_size(
|
|
483
|
+
approx_chunk,
|
|
484
|
+
total_pairs,
|
|
485
|
+
minimum=1,
|
|
486
|
+
)
|
|
502
487
|
payload: ParallelWijPayload = {
|
|
503
488
|
"epi_vals": tuple(epi_vals),
|
|
504
489
|
"vf_vals": tuple(vf_vals),
|
|
@@ -516,7 +501,7 @@ def _wij_loops(
|
|
|
516
501
|
with ProcessPoolExecutor(max_workers=max_workers, initializer=_init) as executor:
|
|
517
502
|
futures = []
|
|
518
503
|
for start in range(0, total_pairs, chunk_size):
|
|
519
|
-
chunk = pair_list[start:start + chunk_size]
|
|
504
|
+
chunk = pair_list[start : start + chunk_size]
|
|
520
505
|
futures.append(executor.submit(_parallel_wij_worker, chunk))
|
|
521
506
|
for future in futures:
|
|
522
507
|
for i, j, value in future.result():
|
|
@@ -589,15 +574,12 @@ def _coherence_numpy(
|
|
|
589
574
|
W = wij.tolist()
|
|
590
575
|
else:
|
|
591
576
|
idx = np.where((wij >= thr) & mask)
|
|
592
|
-
W = [
|
|
593
|
-
(int(i), int(j), float(wij[i, j]))
|
|
594
|
-
for i, j in zip(idx[0], idx[1])
|
|
595
|
-
]
|
|
577
|
+
W = [(int(i), int(j), float(wij[i, j])) for i, j in zip(idx[0], idx[1])]
|
|
596
578
|
return n, values, row_sum, W
|
|
597
579
|
|
|
598
580
|
|
|
599
581
|
def _coherence_python_worker(
|
|
600
|
-
args: tuple[Sequence[Sequence[float]], int, str, float]
|
|
582
|
+
args: tuple[Sequence[Sequence[float]], int, str, float],
|
|
601
583
|
) -> tuple[int, list[float], list[float], CoherenceMatrixSparse]:
|
|
602
584
|
rows, start, mode, thr = args
|
|
603
585
|
values: list[float] = []
|
|
@@ -661,11 +643,16 @@ def _coherence_python(
|
|
|
661
643
|
row_sum[i] += w
|
|
662
644
|
return n, values, row_sum, W if mode == "dense" else W_sparse
|
|
663
645
|
|
|
664
|
-
|
|
646
|
+
approx_chunk = math.ceil(n / max_workers) if max_workers else None
|
|
647
|
+
chunk_size = resolve_chunk_size(
|
|
648
|
+
approx_chunk,
|
|
649
|
+
n,
|
|
650
|
+
minimum=1,
|
|
651
|
+
)
|
|
665
652
|
tasks = []
|
|
666
653
|
with ProcessPoolExecutor(max_workers=max_workers) as executor:
|
|
667
654
|
for start in range(0, n, chunk_size):
|
|
668
|
-
rows = wij[start:start + chunk_size]
|
|
655
|
+
rows = wij[start : start + chunk_size]
|
|
669
656
|
tasks.append(
|
|
670
657
|
executor.submit(
|
|
671
658
|
_coherence_python_worker,
|
|
@@ -675,7 +662,9 @@ def _coherence_python(
|
|
|
675
662
|
results = [task.result() for task in tasks]
|
|
676
663
|
|
|
677
664
|
results.sort(key=lambda item: item[0])
|
|
678
|
-
sparse_entries: list[tuple[int, int, float]] | None =
|
|
665
|
+
sparse_entries: list[tuple[int, int, float]] | None = (
|
|
666
|
+
[] if mode != "dense" else None
|
|
667
|
+
)
|
|
679
668
|
for start, chunk_values, chunk_row_sum, chunk_sparse in results:
|
|
680
669
|
values.extend(chunk_values)
|
|
681
670
|
for offset, total in enumerate(chunk_row_sum):
|
|
@@ -771,9 +760,7 @@ def coherence_matrix(
|
|
|
771
760
|
|
|
772
761
|
# NumPy handling for optional vectorized operations
|
|
773
762
|
np = get_numpy()
|
|
774
|
-
use_np = (
|
|
775
|
-
np is not None if use_numpy is None else (use_numpy and np is not None)
|
|
776
|
-
)
|
|
763
|
+
use_np = np is not None if use_numpy is None else (use_numpy and np is not None)
|
|
777
764
|
|
|
778
765
|
cfg_jobs = cfg.get("n_jobs")
|
|
779
766
|
parallel_jobs = n_jobs if n_jobs is not None else cfg_jobs
|
|
@@ -993,6 +980,8 @@ def _coherence_step(G: TNFRGraph, ctx: dict[str, Any] | None = None) -> None:
|
|
|
993
980
|
|
|
994
981
|
|
|
995
982
|
def register_coherence_callbacks(G: TNFRGraph) -> None:
|
|
983
|
+
"""Attach coherence matrix maintenance to the ``AFTER_STEP`` event."""
|
|
984
|
+
|
|
996
985
|
callback_manager.register_callback(
|
|
997
986
|
G,
|
|
998
987
|
event=CallbackEvent.AFTER_STEP.value,
|
|
@@ -1011,7 +1000,7 @@ def _record_metrics(
|
|
|
1011
1000
|
*pairs: MetricRecord,
|
|
1012
1001
|
evaluate: bool = False,
|
|
1013
1002
|
) -> None:
|
|
1014
|
-
"""
|
|
1003
|
+
"""Record metric values for the trace history."""
|
|
1015
1004
|
|
|
1016
1005
|
metrics = cast(MutableMapping[str, list[Any]], hist)
|
|
1017
1006
|
for payload, key in pairs:
|
|
@@ -1084,9 +1073,7 @@ def _update_sigma(G: TNFRGraph, hist: HistoryState) -> None:
|
|
|
1084
1073
|
(disruptors, "glyph_load_disr"),
|
|
1085
1074
|
)
|
|
1086
1075
|
|
|
1087
|
-
dist: GlyphLoadDistribution = {
|
|
1088
|
-
k: v for k, v in gl.items() if not k.startswith("_")
|
|
1089
|
-
}
|
|
1076
|
+
dist: GlyphLoadDistribution = {k: v for k, v in gl.items() if not k.startswith("_")}
|
|
1090
1077
|
sig: SigmaVector = sigma_vector(dist)
|
|
1091
1078
|
_record_metrics(
|
|
1092
1079
|
hist,
|
|
@@ -1140,7 +1127,10 @@ def _stability_chunk_worker(args: StabilityChunkArgs) -> StabilityChunkResult:
|
|
|
1140
1127
|
B_vals.append(B)
|
|
1141
1128
|
B_sum += B
|
|
1142
1129
|
|
|
1143
|
-
if
|
|
1130
|
+
if (
|
|
1131
|
+
abs(float(dnfr_vals[idx])) <= eps_dnfr
|
|
1132
|
+
and abs(float(depi_vals[idx])) <= eps_depi
|
|
1133
|
+
):
|
|
1144
1134
|
stable += 1
|
|
1145
1135
|
|
|
1146
1136
|
chunk_len = len(si_curr_vals)
|
|
@@ -1196,18 +1186,22 @@ def _track_stability(
|
|
|
1196
1186
|
|
|
1197
1187
|
si_prev_arr = np.asarray(
|
|
1198
1188
|
[
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1189
|
+
(
|
|
1190
|
+
float(prev_si_data[idx])
|
|
1191
|
+
if prev_si_data[idx] is not None
|
|
1192
|
+
else float(si_curr_arr[idx])
|
|
1193
|
+
)
|
|
1202
1194
|
for idx in range(total_nodes)
|
|
1203
1195
|
],
|
|
1204
1196
|
dtype=float,
|
|
1205
1197
|
)
|
|
1206
1198
|
vf_prev_arr = np.asarray(
|
|
1207
1199
|
[
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1200
|
+
(
|
|
1201
|
+
float(prev_vf_data[idx])
|
|
1202
|
+
if prev_vf_data[idx] is not None
|
|
1203
|
+
else float(vf_curr_arr[idx])
|
|
1204
|
+
)
|
|
1211
1205
|
for idx in range(total_nodes)
|
|
1212
1206
|
],
|
|
1213
1207
|
dtype=float,
|
|
@@ -1220,9 +1214,11 @@ def _track_stability(
|
|
|
1220
1214
|
|
|
1221
1215
|
dvf_prev_arr = np.asarray(
|
|
1222
1216
|
[
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1217
|
+
(
|
|
1218
|
+
float(prev_dvf_data[idx])
|
|
1219
|
+
if prev_dvf_data[idx] is not None
|
|
1220
|
+
else float(dvf_dt_arr[idx])
|
|
1221
|
+
)
|
|
1226
1222
|
for idx in range(total_nodes)
|
|
1227
1223
|
],
|
|
1228
1224
|
dtype=float,
|
|
@@ -1268,8 +1264,18 @@ def _track_stability(
|
|
|
1268
1264
|
vf_curr_list = list(vf_curr_vals)
|
|
1269
1265
|
|
|
1270
1266
|
if n_jobs and n_jobs > 1:
|
|
1271
|
-
|
|
1272
|
-
|
|
1267
|
+
approx_chunk = math.ceil(total_nodes / n_jobs) if n_jobs else None
|
|
1268
|
+
chunk_size = resolve_chunk_size(
|
|
1269
|
+
approx_chunk,
|
|
1270
|
+
total_nodes,
|
|
1271
|
+
minimum=1,
|
|
1272
|
+
)
|
|
1273
|
+
chunk_results: list[
|
|
1274
|
+
tuple[
|
|
1275
|
+
int,
|
|
1276
|
+
tuple[int, int, float, float, list[float], list[float], list[float]],
|
|
1277
|
+
]
|
|
1278
|
+
] = []
|
|
1273
1279
|
with ProcessPoolExecutor(max_workers=n_jobs) as executor:
|
|
1274
1280
|
futures: list[tuple[int, Any]] = []
|
|
1275
1281
|
for start in range(0, total_nodes, chunk_size):
|
|
@@ -1286,7 +1292,9 @@ def _track_stability(
|
|
|
1286
1292
|
eps_dnfr,
|
|
1287
1293
|
eps_depi,
|
|
1288
1294
|
)
|
|
1289
|
-
futures.append(
|
|
1295
|
+
futures.append(
|
|
1296
|
+
(start, executor.submit(_stability_chunk_worker, chunk_args))
|
|
1297
|
+
)
|
|
1290
1298
|
|
|
1291
1299
|
for start, fut in futures:
|
|
1292
1300
|
chunk_results.append((start, fut.result()))
|
|
@@ -1349,7 +1357,10 @@ def _track_stability(
|
|
|
1349
1357
|
B_vals_all.append(B_val)
|
|
1350
1358
|
B_sum += B_val
|
|
1351
1359
|
|
|
1352
|
-
if
|
|
1360
|
+
if (
|
|
1361
|
+
abs(float(dnfr_list[idx])) <= eps_dnfr
|
|
1362
|
+
and abs(float(depi_list[idx])) <= eps_depi
|
|
1363
|
+
):
|
|
1353
1364
|
stable_total += 1
|
|
1354
1365
|
|
|
1355
1366
|
total = len(delta_vals_all)
|
|
@@ -1416,10 +1427,30 @@ def _aggregate_si(
|
|
|
1416
1427
|
si_hi = float(thr_sel.get("si_hi", thr_def.get("hi", 0.66)))
|
|
1417
1428
|
si_lo = float(thr_sel.get("si_lo", thr_def.get("lo", 0.33)))
|
|
1418
1429
|
|
|
1430
|
+
node_ids = list(G.nodes)
|
|
1431
|
+
if not node_ids:
|
|
1432
|
+
hist["Si_mean"].append(0.0)
|
|
1433
|
+
hist["Si_hi_frac"].append(0.0)
|
|
1434
|
+
hist["Si_lo_frac"].append(0.0)
|
|
1435
|
+
return
|
|
1436
|
+
|
|
1437
|
+
sis = []
|
|
1438
|
+
for node in node_ids:
|
|
1439
|
+
raw = get_attr(
|
|
1440
|
+
G.nodes[node],
|
|
1441
|
+
ALIAS_SI,
|
|
1442
|
+
None,
|
|
1443
|
+
conv=lambda value: value, # Preserve NaN sentinels
|
|
1444
|
+
)
|
|
1445
|
+
try:
|
|
1446
|
+
sis.append(float(raw) if raw is not None else math.nan)
|
|
1447
|
+
except (TypeError, ValueError):
|
|
1448
|
+
sis.append(math.nan)
|
|
1449
|
+
|
|
1419
1450
|
np_mod = get_numpy()
|
|
1420
1451
|
if np_mod is not None:
|
|
1421
|
-
|
|
1422
|
-
valid =
|
|
1452
|
+
sis_array = np_mod.asarray(sis, dtype=float)
|
|
1453
|
+
valid = sis_array[~np_mod.isnan(sis_array)]
|
|
1423
1454
|
n = int(valid.size)
|
|
1424
1455
|
if n:
|
|
1425
1456
|
hist["Si_mean"].append(float(valid.mean()))
|
|
@@ -1433,19 +1464,17 @@ def _aggregate_si(
|
|
|
1433
1464
|
hist["Si_lo_frac"].append(0.0)
|
|
1434
1465
|
return
|
|
1435
1466
|
|
|
1436
|
-
sis = collect_attr(G, G.nodes, ALIAS_SI, float("nan"))
|
|
1437
|
-
if not sis:
|
|
1438
|
-
hist["Si_mean"].append(0.0)
|
|
1439
|
-
hist["Si_hi_frac"].append(0.0)
|
|
1440
|
-
hist["Si_lo_frac"].append(0.0)
|
|
1441
|
-
return
|
|
1442
|
-
|
|
1443
1467
|
if n_jobs is not None and n_jobs > 1:
|
|
1444
|
-
|
|
1468
|
+
approx_chunk = math.ceil(len(sis) / n_jobs) if n_jobs else None
|
|
1469
|
+
chunk_size = resolve_chunk_size(
|
|
1470
|
+
approx_chunk,
|
|
1471
|
+
len(sis),
|
|
1472
|
+
minimum=1,
|
|
1473
|
+
)
|
|
1445
1474
|
futures = []
|
|
1446
1475
|
with ProcessPoolExecutor(max_workers=n_jobs) as executor:
|
|
1447
1476
|
for idx in range(0, len(sis), chunk_size):
|
|
1448
|
-
chunk = sis[idx:idx + chunk_size]
|
|
1477
|
+
chunk = sis[idx : idx + chunk_size]
|
|
1449
1478
|
futures.append(
|
|
1450
1479
|
executor.submit(_si_chunk_stats, chunk, si_hi, si_lo)
|
|
1451
1480
|
)
|
tnfr/metrics/common.py
CHANGED
|
@@ -6,16 +6,12 @@ from types import MappingProxyType
|
|
|
6
6
|
from typing import Any, Iterable, Mapping, Sequence
|
|
7
7
|
|
|
8
8
|
from ..alias import collect_attr, get_attr, multi_recompute_abs_max
|
|
9
|
-
from ..constants import DEFAULTS
|
|
10
|
-
from ..
|
|
11
|
-
from ..
|
|
9
|
+
from ..constants import DEFAULTS
|
|
10
|
+
from ..constants.aliases import ALIAS_D2EPI, ALIAS_DEPI, ALIAS_DNFR, ALIAS_VF
|
|
11
|
+
from ..utils import clamp01, kahan_sum_nd, normalize_optional_int
|
|
12
|
+
from ..types import GraphLike, NodeAttrMap
|
|
12
13
|
from ..utils import edge_version_cache, get_numpy, normalize_weights
|
|
13
14
|
|
|
14
|
-
ALIAS_DNFR = get_aliases("DNFR")
|
|
15
|
-
ALIAS_D2EPI = get_aliases("D2EPI")
|
|
16
|
-
ALIAS_DEPI = get_aliases("DEPI")
|
|
17
|
-
ALIAS_VF = get_aliases("VF")
|
|
18
|
-
|
|
19
15
|
__all__ = (
|
|
20
16
|
"GraphLike",
|
|
21
17
|
"compute_coherence",
|
|
@@ -25,6 +21,7 @@ __all__ = (
|
|
|
25
21
|
"merge_graph_weights",
|
|
26
22
|
"merge_and_normalize_weights",
|
|
27
23
|
"min_max_range",
|
|
24
|
+
"_coerce_jobs",
|
|
28
25
|
"_get_vf_dnfr_max",
|
|
29
26
|
)
|
|
30
27
|
|
|
@@ -70,7 +67,10 @@ def ensure_neighbors_map(G: GraphLike) -> Mapping[Any, Sequence[Any]]:
|
|
|
70
67
|
def merge_graph_weights(G: GraphLike, key: str) -> dict[str, float]:
|
|
71
68
|
"""Merge default weights for ``key`` with any graph overrides."""
|
|
72
69
|
|
|
73
|
-
|
|
70
|
+
overrides = G.graph.get(key, {})
|
|
71
|
+
if overrides is None or not isinstance(overrides, Mapping):
|
|
72
|
+
overrides = {}
|
|
73
|
+
return {**DEFAULTS[key], **overrides}
|
|
74
74
|
|
|
75
75
|
|
|
76
76
|
def merge_and_normalize_weights(
|
|
@@ -101,7 +101,7 @@ def compute_dnfr_accel_max(G: GraphLike) -> dict[str, float]:
|
|
|
101
101
|
)
|
|
102
102
|
|
|
103
103
|
|
|
104
|
-
def normalize_dnfr(nd:
|
|
104
|
+
def normalize_dnfr(nd: NodeAttrMap, max_val: float) -> float:
|
|
105
105
|
"""Normalise ``|ΔNFR|`` using ``max_val``."""
|
|
106
106
|
|
|
107
107
|
if max_val <= 0:
|
|
@@ -135,9 +135,7 @@ def _get_vf_dnfr_max(G: GraphLike) -> tuple[float, float]:
|
|
|
135
135
|
vfmax = G.graph.get("_vfmax")
|
|
136
136
|
dnfrmax = G.graph.get("_dnfrmax")
|
|
137
137
|
if vfmax is None or dnfrmax is None:
|
|
138
|
-
maxes = multi_recompute_abs_max(
|
|
139
|
-
G, {"_vfmax": ALIAS_VF, "_dnfrmax": ALIAS_DNFR}
|
|
140
|
-
)
|
|
138
|
+
maxes = multi_recompute_abs_max(G, {"_vfmax": ALIAS_VF, "_dnfrmax": ALIAS_DNFR})
|
|
141
139
|
if vfmax is None:
|
|
142
140
|
vfmax = maxes["_vfmax"]
|
|
143
141
|
if dnfrmax is None:
|
|
@@ -147,3 +145,14 @@ def _get_vf_dnfr_max(G: GraphLike) -> tuple[float, float]:
|
|
|
147
145
|
vfmax = 1.0 if vfmax == 0 else vfmax
|
|
148
146
|
dnfrmax = 1.0 if dnfrmax == 0 else dnfrmax
|
|
149
147
|
return float(vfmax), float(dnfrmax)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _coerce_jobs(raw_jobs: Any | None) -> int | None:
|
|
151
|
+
"""Normalise parallel job hints shared by metrics modules."""
|
|
152
|
+
|
|
153
|
+
return normalize_optional_int(
|
|
154
|
+
raw_jobs,
|
|
155
|
+
allow_non_positive=False,
|
|
156
|
+
strict=False,
|
|
157
|
+
sentinels=None,
|
|
158
|
+
)
|
tnfr/metrics/common.pyi
CHANGED
|
@@ -1,15 +1,46 @@
|
|
|
1
|
-
from
|
|
1
|
+
from collections.abc import Iterable, Mapping, Sequence
|
|
2
|
+
from typing import Any, Literal, overload
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
from ..types import GraphLike, NodeAttrMap
|
|
5
|
+
|
|
6
|
+
__all__: tuple[str, ...]
|
|
4
7
|
|
|
5
8
|
def __getattr__(name: str) -> Any: ...
|
|
6
9
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
@overload
|
|
11
|
+
def compute_coherence(G: GraphLike, *, return_means: Literal[False] = ...) -> float: ...
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@overload
|
|
15
|
+
def compute_coherence(
|
|
16
|
+
G: GraphLike, *, return_means: Literal[True]
|
|
17
|
+
) -> tuple[float, float, float]: ...
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def compute_coherence(
|
|
21
|
+
G: GraphLike, *, return_means: bool = ...
|
|
22
|
+
) -> float | tuple[float, float, float]: ...
|
|
23
|
+
|
|
24
|
+
def ensure_neighbors_map(G: GraphLike) -> Mapping[Any, Sequence[Any]]: ...
|
|
25
|
+
|
|
26
|
+
def merge_graph_weights(G: GraphLike, key: str) -> dict[str, float]: ...
|
|
27
|
+
|
|
28
|
+
def merge_and_normalize_weights(
|
|
29
|
+
G: GraphLike,
|
|
30
|
+
key: str,
|
|
31
|
+
fields: Sequence[str],
|
|
32
|
+
*,
|
|
33
|
+
default: float = ...,
|
|
34
|
+
) -> dict[str, float]: ...
|
|
35
|
+
|
|
36
|
+
def compute_dnfr_accel_max(G: GraphLike) -> dict[str, float]: ...
|
|
37
|
+
|
|
38
|
+
def normalize_dnfr(nd: NodeAttrMap, max_val: float) -> float: ...
|
|
39
|
+
|
|
40
|
+
def min_max_range(
|
|
41
|
+
values: Iterable[float], *, default: tuple[float, float] = ...
|
|
42
|
+
) -> tuple[float, float]: ...
|
|
43
|
+
|
|
44
|
+
def _get_vf_dnfr_max(G: GraphLike) -> tuple[float, float]: ...
|
|
45
|
+
|
|
46
|
+
def _coerce_jobs(raw_jobs: Any | None) -> int | None: ...
|