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/operators/remesh.py
CHANGED
|
@@ -1,27 +1,61 @@
|
|
|
1
|
+
"""Adaptive remeshing operators preserving TNFR structural coherence."""
|
|
2
|
+
|
|
1
3
|
from __future__ import annotations
|
|
4
|
+
|
|
2
5
|
import hashlib
|
|
3
6
|
import heapq
|
|
4
|
-
|
|
7
|
+
import random
|
|
8
|
+
from collections import deque
|
|
9
|
+
from collections.abc import Hashable, Iterable, Mapping, MutableMapping, Sequence
|
|
5
10
|
from functools import cache
|
|
6
|
-
from itertools import combinations
|
|
7
11
|
from io import StringIO
|
|
8
|
-
from
|
|
9
|
-
from
|
|
12
|
+
from itertools import combinations
|
|
13
|
+
from operator import ge, le
|
|
14
|
+
from statistics import StatisticsError, fmean
|
|
15
|
+
from types import ModuleType
|
|
16
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
10
17
|
|
|
11
|
-
from ..
|
|
12
|
-
from ..constants import DEFAULTS, REMESH_DEFAULTS, get_aliases, get_param
|
|
13
|
-
from ..helpers.numeric import kahan_sum_nd
|
|
18
|
+
from .._compat import TypeAlias
|
|
14
19
|
from ..alias import get_attr, set_attr
|
|
20
|
+
from ..constants import DEFAULTS, REMESH_DEFAULTS, get_param
|
|
21
|
+
from ..constants.aliases import ALIAS_EPI
|
|
15
22
|
from ..rng import make_rng
|
|
16
|
-
from ..
|
|
17
|
-
from ..
|
|
18
|
-
|
|
23
|
+
from ..types import RemeshMeta
|
|
24
|
+
from ..utils import cached_import, edge_version_update, kahan_sum_nd
|
|
25
|
+
|
|
26
|
+
if TYPE_CHECKING: # pragma: no cover - type checking only
|
|
27
|
+
from ..callback_utils import CallbackEvent, CallbackManager
|
|
28
|
+
|
|
29
|
+
CommunityGraph: TypeAlias = Any
|
|
30
|
+
NetworkxModule: TypeAlias = ModuleType
|
|
31
|
+
CommunityModule: TypeAlias = ModuleType
|
|
32
|
+
RemeshEdge: TypeAlias = tuple[Hashable, Hashable]
|
|
33
|
+
NetworkxModules: TypeAlias = tuple[NetworkxModule, CommunityModule]
|
|
34
|
+
RemeshConfigValue: TypeAlias = bool | float | int
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _as_float(value: Any, default: float = 0.0) -> float:
|
|
38
|
+
"""Best-effort conversion to ``float`` returning ``default`` on failure."""
|
|
39
|
+
|
|
40
|
+
if value is None:
|
|
41
|
+
return default
|
|
42
|
+
try:
|
|
43
|
+
return float(value)
|
|
44
|
+
except (TypeError, ValueError):
|
|
45
|
+
return default
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _ordered_edge(u: Hashable, v: Hashable) -> RemeshEdge:
|
|
49
|
+
"""Return a deterministic ordering for an undirected edge."""
|
|
19
50
|
|
|
20
|
-
|
|
51
|
+
return (u, v) if repr(u) <= repr(v) else (v, u)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
COOLDOWN_KEY = "REMESH_COOLDOWN_WINDOW"
|
|
21
55
|
|
|
22
56
|
|
|
23
57
|
@cache
|
|
24
|
-
def _get_networkx_modules():
|
|
58
|
+
def _get_networkx_modules() -> NetworkxModules:
|
|
25
59
|
nx = cached_import("networkx")
|
|
26
60
|
if nx is None:
|
|
27
61
|
raise ImportError(
|
|
@@ -34,30 +68,29 @@ def _get_networkx_modules():
|
|
|
34
68
|
"networkx.algorithms.community is required for community-based "
|
|
35
69
|
"operations; install 'networkx' to enable this feature"
|
|
36
70
|
)
|
|
37
|
-
return nx, nx_comm
|
|
71
|
+
return cast(NetworkxModule, nx), cast(CommunityModule, nx_comm)
|
|
38
72
|
|
|
39
73
|
|
|
40
|
-
def _remesh_alpha_info(G):
|
|
74
|
+
def _remesh_alpha_info(G: CommunityGraph) -> tuple[float, str]:
|
|
41
75
|
"""Return ``(alpha, source)`` with explicit precedence."""
|
|
42
|
-
if bool(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
G.graph.get("REMESH_ALPHA", REMESH_DEFAULTS["REMESH_ALPHA"])
|
|
76
|
+
if bool(G.graph.get("REMESH_ALPHA_HARD", REMESH_DEFAULTS["REMESH_ALPHA_HARD"])):
|
|
77
|
+
val = _as_float(
|
|
78
|
+
G.graph.get("REMESH_ALPHA", REMESH_DEFAULTS["REMESH_ALPHA"]),
|
|
79
|
+
float(REMESH_DEFAULTS["REMESH_ALPHA"]),
|
|
47
80
|
)
|
|
48
81
|
return val, "REMESH_ALPHA"
|
|
49
82
|
gf = G.graph.get("GLYPH_FACTORS", DEFAULTS.get("GLYPH_FACTORS", {}))
|
|
50
83
|
if "REMESH_alpha" in gf:
|
|
51
|
-
return
|
|
84
|
+
return _as_float(gf["REMESH_alpha"]), "GLYPH_FACTORS.REMESH_alpha"
|
|
52
85
|
if "REMESH_ALPHA" in G.graph:
|
|
53
|
-
return
|
|
86
|
+
return _as_float(G.graph["REMESH_ALPHA"]), "REMESH_ALPHA"
|
|
54
87
|
return (
|
|
55
88
|
float(REMESH_DEFAULTS["REMESH_ALPHA"]),
|
|
56
89
|
"REMESH_DEFAULTS.REMESH_ALPHA",
|
|
57
90
|
)
|
|
58
91
|
|
|
59
92
|
|
|
60
|
-
def _snapshot_topology(G, nx):
|
|
93
|
+
def _snapshot_topology(G: CommunityGraph, nx: NetworkxModule) -> str | None:
|
|
61
94
|
"""Return a hash representing the current graph topology."""
|
|
62
95
|
try:
|
|
63
96
|
n_nodes = G.number_of_nodes()
|
|
@@ -69,12 +102,12 @@ def _snapshot_topology(G, nx):
|
|
|
69
102
|
return None
|
|
70
103
|
|
|
71
104
|
|
|
72
|
-
def _snapshot_epi(G):
|
|
105
|
+
def _snapshot_epi(G: CommunityGraph) -> tuple[float, str]:
|
|
73
106
|
"""Return ``(mean, checksum)`` of the node EPI values."""
|
|
74
107
|
buf = StringIO()
|
|
75
108
|
values = []
|
|
76
109
|
for n, data in G.nodes(data=True):
|
|
77
|
-
v =
|
|
110
|
+
v = _as_float(get_attr(data, ALIAS_EPI, 0.0))
|
|
78
111
|
values.append(v)
|
|
79
112
|
buf.write(f"{str(n)}:{round(v, 6)};")
|
|
80
113
|
total = kahan_sum_nd(((v,) for v in values), dims=1)[0]
|
|
@@ -83,19 +116,21 @@ def _snapshot_epi(G):
|
|
|
83
116
|
return float(mean_val), checksum
|
|
84
117
|
|
|
85
118
|
|
|
86
|
-
def _log_remesh_event(G, meta):
|
|
119
|
+
def _log_remesh_event(G: CommunityGraph, meta: RemeshMeta) -> None:
|
|
87
120
|
"""Store remesh metadata and optionally log and trigger callbacks."""
|
|
121
|
+
from ..callback_utils import CallbackEvent, callback_manager
|
|
122
|
+
from ..glyph_history import append_metric
|
|
123
|
+
|
|
88
124
|
G.graph["_REMESH_META"] = meta
|
|
89
125
|
if G.graph.get("REMESH_LOG_EVENTS", REMESH_DEFAULTS["REMESH_LOG_EVENTS"]):
|
|
90
126
|
hist = G.graph.setdefault("history", {})
|
|
91
127
|
append_metric(hist, "remesh_events", dict(meta))
|
|
92
|
-
callback_manager.invoke_callbacks(
|
|
93
|
-
G, CallbackEvent.ON_REMESH.value, dict(meta)
|
|
94
|
-
)
|
|
128
|
+
callback_manager.invoke_callbacks(G, CallbackEvent.ON_REMESH.value, dict(meta))
|
|
95
129
|
|
|
96
130
|
|
|
97
|
-
def apply_network_remesh(G) -> None:
|
|
131
|
+
def apply_network_remesh(G: CommunityGraph) -> None:
|
|
98
132
|
"""Network-scale REMESH using ``_epi_hist`` with multi-scale memory."""
|
|
133
|
+
from ..glyph_history import current_step_idx, ensure_history
|
|
99
134
|
nx, _ = _get_networkx_modules()
|
|
100
135
|
tau_g = int(get_param(G, "REMESH_TAU_GLOBAL"))
|
|
101
136
|
tau_l = int(get_param(G, "REMESH_TAU_LOCAL"))
|
|
@@ -113,9 +148,13 @@ def apply_network_remesh(G) -> None:
|
|
|
113
148
|
epi_mean_before, epi_checksum_before = _snapshot_epi(G)
|
|
114
149
|
|
|
115
150
|
for n, nd in G.nodes(data=True):
|
|
116
|
-
epi_now = get_attr(nd, ALIAS_EPI, 0.0)
|
|
117
|
-
epi_old_l =
|
|
118
|
-
|
|
151
|
+
epi_now = _as_float(get_attr(nd, ALIAS_EPI, 0.0))
|
|
152
|
+
epi_old_l = _as_float(
|
|
153
|
+
past_l.get(n) if isinstance(past_l, Mapping) else None, epi_now
|
|
154
|
+
)
|
|
155
|
+
epi_old_g = _as_float(
|
|
156
|
+
past_g.get(n) if isinstance(past_g, Mapping) else None, epi_now
|
|
157
|
+
)
|
|
119
158
|
mixed = (1 - alpha) * epi_now + alpha * epi_old_l
|
|
120
159
|
mixed = (1 - alpha) * mixed + alpha * epi_old_g
|
|
121
160
|
set_attr(nd, ALIAS_EPI, mixed)
|
|
@@ -123,7 +162,7 @@ def apply_network_remesh(G) -> None:
|
|
|
123
162
|
epi_mean_after, epi_checksum_after = _snapshot_epi(G)
|
|
124
163
|
|
|
125
164
|
step_idx = current_step_idx(G)
|
|
126
|
-
meta = {
|
|
165
|
+
meta: RemeshMeta = {
|
|
127
166
|
"alpha": alpha,
|
|
128
167
|
"alpha_source": alpha_src,
|
|
129
168
|
"tau_global": tau_g,
|
|
@@ -148,20 +187,27 @@ def apply_network_remesh(G) -> None:
|
|
|
148
187
|
_log_remesh_event(G, meta)
|
|
149
188
|
|
|
150
189
|
|
|
151
|
-
def _mst_edges_from_epi(
|
|
190
|
+
def _mst_edges_from_epi(
|
|
191
|
+
nx: NetworkxModule,
|
|
192
|
+
nodes: Sequence[Hashable],
|
|
193
|
+
epi: Mapping[Hashable, float],
|
|
194
|
+
) -> set[RemeshEdge]:
|
|
152
195
|
"""Return MST edges based on absolute EPI distance."""
|
|
153
196
|
H = nx.Graph()
|
|
154
197
|
H.add_nodes_from(nodes)
|
|
155
198
|
H.add_weighted_edges_from(
|
|
156
199
|
(u, v, abs(epi[u] - epi[v])) for u, v in combinations(nodes, 2)
|
|
157
200
|
)
|
|
158
|
-
return {
|
|
159
|
-
tuple(sorted((u, v)))
|
|
160
|
-
for u, v in nx.minimum_spanning_edges(H, data=False)
|
|
161
|
-
}
|
|
201
|
+
return {_ordered_edge(u, v) for u, v in nx.minimum_spanning_edges(H, data=False)}
|
|
162
202
|
|
|
163
203
|
|
|
164
|
-
def _knn_edges(
|
|
204
|
+
def _knn_edges(
|
|
205
|
+
nodes: Sequence[Hashable],
|
|
206
|
+
epi: Mapping[Hashable, float],
|
|
207
|
+
k_val: int,
|
|
208
|
+
p_rewire: float,
|
|
209
|
+
rnd: random.Random,
|
|
210
|
+
) -> set[RemeshEdge]:
|
|
165
211
|
"""Edges linking each node to its ``k`` nearest neighbours in EPI."""
|
|
166
212
|
new_edges = set()
|
|
167
213
|
node_set = set(nodes)
|
|
@@ -179,17 +225,21 @@ def _knn_edges(nodes, epi, k_val, p_rewire, rnd):
|
|
|
179
225
|
choices = list(node_set - {u, v})
|
|
180
226
|
if choices:
|
|
181
227
|
v = rnd.choice(choices)
|
|
182
|
-
new_edges.add(
|
|
228
|
+
new_edges.add(_ordered_edge(u, v))
|
|
183
229
|
return new_edges
|
|
184
230
|
|
|
185
231
|
|
|
186
|
-
def _community_graph(
|
|
232
|
+
def _community_graph(
|
|
233
|
+
comms: Iterable[Iterable[Hashable]],
|
|
234
|
+
epi: Mapping[Hashable, float],
|
|
235
|
+
nx: NetworkxModule,
|
|
236
|
+
) -> CommunityGraph:
|
|
187
237
|
"""Return community graph ``C`` with mean EPI per community."""
|
|
188
238
|
C = nx.Graph()
|
|
189
239
|
for idx, comm in enumerate(comms):
|
|
190
240
|
members = list(comm)
|
|
191
241
|
try:
|
|
192
|
-
epi_mean = fmean(epi
|
|
242
|
+
epi_mean = fmean(_as_float(epi.get(n)) for n in members)
|
|
193
243
|
except StatisticsError:
|
|
194
244
|
epi_mean = 0.0
|
|
195
245
|
C.add_node(idx)
|
|
@@ -197,16 +247,21 @@ def _community_graph(comms, epi, nx):
|
|
|
197
247
|
C.nodes[idx]["members"] = members
|
|
198
248
|
for i, j in combinations(C.nodes(), 2):
|
|
199
249
|
w = abs(
|
|
200
|
-
get_attr(C.nodes[i], ALIAS_EPI, 0.0)
|
|
201
|
-
- get_attr(C.nodes[j], ALIAS_EPI, 0.0)
|
|
250
|
+
_as_float(get_attr(C.nodes[i], ALIAS_EPI, 0.0))
|
|
251
|
+
- _as_float(get_attr(C.nodes[j], ALIAS_EPI, 0.0))
|
|
202
252
|
)
|
|
203
253
|
C.add_edge(i, j, weight=w)
|
|
204
|
-
return C
|
|
254
|
+
return cast(CommunityGraph, C)
|
|
205
255
|
|
|
206
256
|
|
|
207
|
-
def _community_k_neighbor_edges(
|
|
257
|
+
def _community_k_neighbor_edges(
|
|
258
|
+
C: CommunityGraph,
|
|
259
|
+
k_val: int,
|
|
260
|
+
p_rewire: float,
|
|
261
|
+
rnd: random.Random,
|
|
262
|
+
) -> tuple[set[RemeshEdge], dict[int, int], list[tuple[int, int, int]]]:
|
|
208
263
|
"""Edges linking each community to its ``k`` nearest neighbours."""
|
|
209
|
-
epi_vals = {n: get_attr(C.nodes[n], ALIAS_EPI, 0.0) for n in C.nodes()}
|
|
264
|
+
epi_vals = {n: _as_float(get_attr(C.nodes[n], ALIAS_EPI, 0.0)) for n in C.nodes()}
|
|
210
265
|
ordered = sorted(C.nodes(), key=lambda v: epi_vals[v])
|
|
211
266
|
new_edges = set()
|
|
212
267
|
attempts = {n: 0 for n in C.nodes()}
|
|
@@ -240,7 +295,7 @@ def _community_k_neighbor_edges(C, k_val, p_rewire, rnd):
|
|
|
240
295
|
if choices:
|
|
241
296
|
v = rnd.choice(choices)
|
|
242
297
|
rewired_now = True
|
|
243
|
-
new_edges.add(
|
|
298
|
+
new_edges.add(_ordered_edge(u, v))
|
|
244
299
|
attempts[u] += 1
|
|
245
300
|
if rewired_now:
|
|
246
301
|
rewired.append((u, original_v, v))
|
|
@@ -249,17 +304,18 @@ def _community_k_neighbor_edges(C, k_val, p_rewire, rnd):
|
|
|
249
304
|
|
|
250
305
|
|
|
251
306
|
def _community_remesh(
|
|
252
|
-
G,
|
|
253
|
-
epi,
|
|
254
|
-
k_val,
|
|
255
|
-
p_rewire,
|
|
256
|
-
rnd,
|
|
257
|
-
nx,
|
|
258
|
-
nx_comm,
|
|
259
|
-
mst_edges,
|
|
260
|
-
n_before,
|
|
261
|
-
):
|
|
307
|
+
G: CommunityGraph,
|
|
308
|
+
epi: Mapping[Hashable, float],
|
|
309
|
+
k_val: int,
|
|
310
|
+
p_rewire: float,
|
|
311
|
+
rnd: random.Random,
|
|
312
|
+
nx: NetworkxModule,
|
|
313
|
+
nx_comm: CommunityModule,
|
|
314
|
+
mst_edges: Iterable[RemeshEdge],
|
|
315
|
+
n_before: int,
|
|
316
|
+
) -> None:
|
|
262
317
|
"""Remesh ``G`` replacing nodes by modular communities."""
|
|
318
|
+
from ..glyph_history import append_metric
|
|
263
319
|
comms = list(nx_comm.greedy_modularity_communities(G))
|
|
264
320
|
if len(comms) <= 1:
|
|
265
321
|
with edge_version_update(G):
|
|
@@ -268,7 +324,7 @@ def _community_remesh(
|
|
|
268
324
|
return
|
|
269
325
|
C = _community_graph(comms, epi, nx)
|
|
270
326
|
mst_c = nx.minimum_spanning_tree(C, weight="weight")
|
|
271
|
-
new_edges =
|
|
327
|
+
new_edges: set[RemeshEdge] = {_ordered_edge(u, v) for u, v in mst_c.edges()}
|
|
272
328
|
extra_edges, attempts, rewired_edges = _community_k_neighbor_edges(
|
|
273
329
|
C, k_val, p_rewire, rnd
|
|
274
330
|
)
|
|
@@ -312,7 +368,7 @@ def _community_remesh(
|
|
|
312
368
|
|
|
313
369
|
|
|
314
370
|
def apply_topological_remesh(
|
|
315
|
-
G,
|
|
371
|
+
G: CommunityGraph,
|
|
316
372
|
mode: str | None = None,
|
|
317
373
|
*,
|
|
318
374
|
k: int | None = None,
|
|
@@ -324,6 +380,7 @@ def apply_topological_remesh(
|
|
|
324
380
|
When ``seed`` is ``None`` the RNG draws its base seed from
|
|
325
381
|
``G.graph['RANDOM_SEED']`` to keep runs reproducible.
|
|
326
382
|
"""
|
|
383
|
+
from ..glyph_history import append_metric
|
|
327
384
|
nodes = list(G.nodes())
|
|
328
385
|
n_before = len(nodes)
|
|
329
386
|
if n_before <= 1:
|
|
@@ -336,18 +393,14 @@ def apply_topological_remesh(
|
|
|
336
393
|
|
|
337
394
|
if mode is None:
|
|
338
395
|
mode = str(
|
|
339
|
-
G.graph.get(
|
|
340
|
-
"REMESH_MODE", REMESH_DEFAULTS.get("REMESH_MODE", "knn")
|
|
341
|
-
)
|
|
396
|
+
G.graph.get("REMESH_MODE", REMESH_DEFAULTS.get("REMESH_MODE", "knn"))
|
|
342
397
|
)
|
|
343
398
|
mode = str(mode)
|
|
344
399
|
nx, nx_comm = _get_networkx_modules()
|
|
345
|
-
epi = {n: get_attr(G.nodes[n], ALIAS_EPI, 0.0) for n in nodes}
|
|
400
|
+
epi = {n: _as_float(get_attr(G.nodes[n], ALIAS_EPI, 0.0)) for n in nodes}
|
|
346
401
|
mst_edges = _mst_edges_from_epi(nx, nodes, epi)
|
|
347
402
|
default_k = int(
|
|
348
|
-
G.graph.get(
|
|
349
|
-
"REMESH_COMMUNITY_K", REMESH_DEFAULTS.get("REMESH_COMMUNITY_K", 2)
|
|
350
|
-
)
|
|
403
|
+
G.graph.get("REMESH_COMMUNITY_K", REMESH_DEFAULTS.get("REMESH_COMMUNITY_K", 2))
|
|
351
404
|
)
|
|
352
405
|
k_val = max(1, int(k) if k is not None else default_k)
|
|
353
406
|
|
|
@@ -374,7 +427,11 @@ def apply_topological_remesh(
|
|
|
374
427
|
G.add_edges_from(new_edges)
|
|
375
428
|
|
|
376
429
|
|
|
377
|
-
def _extra_gating_ok(
|
|
430
|
+
def _extra_gating_ok(
|
|
431
|
+
hist: MutableMapping[str, Sequence[float]],
|
|
432
|
+
cfg: Mapping[str, RemeshConfigValue],
|
|
433
|
+
w_estab: int,
|
|
434
|
+
) -> bool:
|
|
378
435
|
"""Check additional stability gating conditions."""
|
|
379
436
|
checks = [
|
|
380
437
|
("phase_sync", "REMESH_MIN_PHASE_SYNC", ge),
|
|
@@ -388,14 +445,27 @@ def _extra_gating_ok(hist, cfg, w_estab):
|
|
|
388
445
|
if series is not None and len(series) >= w_estab:
|
|
389
446
|
win = series[-w_estab:]
|
|
390
447
|
avg = sum(win) / len(win)
|
|
391
|
-
|
|
448
|
+
threshold = _as_float(cfg[cfg_key])
|
|
449
|
+
if not op(avg, threshold):
|
|
392
450
|
return False
|
|
393
451
|
return True
|
|
394
452
|
|
|
395
453
|
|
|
396
454
|
def apply_remesh_if_globally_stable(
|
|
397
|
-
G
|
|
455
|
+
G: CommunityGraph,
|
|
456
|
+
stable_step_window: int | None = None,
|
|
457
|
+
**kwargs: Any,
|
|
398
458
|
) -> None:
|
|
459
|
+
"""Trigger remeshing when global stability indicators satisfy thresholds."""
|
|
460
|
+
|
|
461
|
+
from ..glyph_history import ensure_history
|
|
462
|
+
if kwargs:
|
|
463
|
+
unexpected = ", ".join(sorted(kwargs))
|
|
464
|
+
raise TypeError(
|
|
465
|
+
"apply_remesh_if_globally_stable() got unexpected keyword argument(s): "
|
|
466
|
+
f"{unexpected}"
|
|
467
|
+
)
|
|
468
|
+
|
|
399
469
|
params = [
|
|
400
470
|
(
|
|
401
471
|
"REMESH_STABILITY_WINDOW",
|
|
@@ -432,20 +502,16 @@ def apply_remesh_if_globally_stable(
|
|
|
432
502
|
float,
|
|
433
503
|
REMESH_DEFAULTS["REMESH_MIN_SI_HI_FRAC"],
|
|
434
504
|
),
|
|
435
|
-
(
|
|
436
|
-
"REMESH_COOLDOWN_VENTANA",
|
|
437
|
-
int,
|
|
438
|
-
REMESH_DEFAULTS["REMESH_COOLDOWN_VENTANA"],
|
|
439
|
-
),
|
|
505
|
+
(COOLDOWN_KEY, int, REMESH_DEFAULTS[COOLDOWN_KEY]),
|
|
440
506
|
("REMESH_COOLDOWN_TS", float, REMESH_DEFAULTS["REMESH_COOLDOWN_TS"]),
|
|
441
507
|
]
|
|
442
508
|
cfg = {}
|
|
443
509
|
for key, conv, _default in params:
|
|
444
510
|
cfg[key] = conv(get_param(G, key))
|
|
445
|
-
frac_req =
|
|
511
|
+
frac_req = _as_float(get_param(G, "FRACTION_STABLE_REMESH"))
|
|
446
512
|
w_estab = (
|
|
447
|
-
|
|
448
|
-
if
|
|
513
|
+
stable_step_window
|
|
514
|
+
if stable_step_window is not None
|
|
449
515
|
else cfg["REMESH_STABILITY_WINDOW"]
|
|
450
516
|
)
|
|
451
517
|
|
|
@@ -456,21 +522,16 @@ def apply_remesh_if_globally_stable(
|
|
|
456
522
|
win_sf = sf[-w_estab:]
|
|
457
523
|
if not all(v >= frac_req for v in win_sf):
|
|
458
524
|
return
|
|
459
|
-
if cfg["REMESH_REQUIRE_STABILITY"] and not _extra_gating_ok(
|
|
460
|
-
hist, cfg, w_estab
|
|
461
|
-
):
|
|
525
|
+
if cfg["REMESH_REQUIRE_STABILITY"] and not _extra_gating_ok(hist, cfg, w_estab):
|
|
462
526
|
return
|
|
463
527
|
|
|
464
528
|
last = G.graph.get("_last_remesh_step", -(10**9))
|
|
465
529
|
step_idx = len(sf)
|
|
466
|
-
if step_idx - last < cfg[
|
|
530
|
+
if step_idx - last < cfg[COOLDOWN_KEY]:
|
|
467
531
|
return
|
|
468
|
-
t_now =
|
|
469
|
-
last_ts =
|
|
470
|
-
if (
|
|
471
|
-
cfg["REMESH_COOLDOWN_TS"] > 0
|
|
472
|
-
and (t_now - last_ts) < cfg["REMESH_COOLDOWN_TS"]
|
|
473
|
-
):
|
|
532
|
+
t_now = _as_float(G.graph.get("_t", 0.0))
|
|
533
|
+
last_ts = _as_float(G.graph.get("_last_remesh_ts", -1e12))
|
|
534
|
+
if cfg["REMESH_COOLDOWN_TS"] > 0 and (t_now - last_ts) < cfg["REMESH_COOLDOWN_TS"]:
|
|
474
535
|
return
|
|
475
536
|
|
|
476
537
|
apply_network_remesh(G)
|
tnfr/py.typed
ADDED
|
File without changes
|