tnfr 4.5.2__py3-none-any.whl → 6.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.
- tnfr/__init__.py +228 -49
- tnfr/__init__.pyi +40 -0
- tnfr/_compat.py +11 -0
- tnfr/_version.py +7 -0
- tnfr/_version.pyi +7 -0
- tnfr/alias.py +106 -21
- tnfr/alias.pyi +140 -0
- tnfr/cache.py +666 -512
- tnfr/cache.pyi +232 -0
- tnfr/callback_utils.py +2 -9
- tnfr/callback_utils.pyi +105 -0
- tnfr/cli/__init__.py +21 -7
- tnfr/cli/__init__.pyi +47 -0
- tnfr/cli/arguments.py +42 -20
- tnfr/cli/arguments.pyi +33 -0
- tnfr/cli/execution.py +54 -20
- tnfr/cli/execution.pyi +80 -0
- tnfr/cli/utils.py +0 -2
- tnfr/cli/utils.pyi +8 -0
- tnfr/config/__init__.py +12 -0
- tnfr/config/__init__.pyi +8 -0
- tnfr/config/constants.py +104 -0
- tnfr/config/constants.pyi +12 -0
- tnfr/{config.py → config/init.py} +11 -7
- tnfr/config/init.pyi +8 -0
- tnfr/config/operator_names.py +106 -0
- tnfr/config/operator_names.pyi +28 -0
- tnfr/config/presets.py +104 -0
- tnfr/config/presets.pyi +7 -0
- tnfr/constants/__init__.py +78 -24
- tnfr/constants/__init__.pyi +104 -0
- tnfr/constants/core.py +1 -2
- tnfr/constants/core.pyi +17 -0
- tnfr/constants/init.pyi +12 -0
- tnfr/constants/metric.py +4 -12
- tnfr/constants/metric.pyi +19 -0
- tnfr/constants_glyphs.py +9 -91
- tnfr/constants_glyphs.pyi +12 -0
- tnfr/dynamics/__init__.py +112 -634
- tnfr/dynamics/__init__.pyi +83 -0
- tnfr/dynamics/adaptation.py +201 -0
- tnfr/dynamics/aliases.py +22 -0
- tnfr/dynamics/coordination.py +343 -0
- tnfr/dynamics/dnfr.py +1936 -354
- tnfr/dynamics/dnfr.pyi +33 -0
- tnfr/dynamics/integrators.py +369 -75
- tnfr/dynamics/integrators.pyi +35 -0
- tnfr/dynamics/runtime.py +521 -0
- tnfr/dynamics/sampling.py +8 -5
- tnfr/dynamics/sampling.pyi +7 -0
- tnfr/dynamics/selectors.py +680 -0
- tnfr/execution.py +56 -41
- tnfr/execution.pyi +65 -0
- tnfr/flatten.py +7 -7
- tnfr/flatten.pyi +28 -0
- tnfr/gamma.py +54 -37
- tnfr/gamma.pyi +40 -0
- tnfr/glyph_history.py +85 -38
- tnfr/glyph_history.pyi +53 -0
- tnfr/grammar.py +19 -338
- tnfr/grammar.pyi +13 -0
- tnfr/helpers/__init__.py +110 -30
- tnfr/helpers/__init__.pyi +66 -0
- tnfr/helpers/numeric.py +1 -0
- tnfr/helpers/numeric.pyi +12 -0
- tnfr/immutable.py +55 -19
- tnfr/immutable.pyi +37 -0
- tnfr/initialization.py +12 -10
- tnfr/initialization.pyi +73 -0
- tnfr/io.py +99 -34
- tnfr/io.pyi +11 -0
- tnfr/locking.pyi +7 -0
- tnfr/metrics/__init__.pyi +20 -0
- tnfr/metrics/coherence.py +934 -294
- tnfr/metrics/common.py +1 -3
- tnfr/metrics/common.pyi +15 -0
- tnfr/metrics/core.py +192 -34
- tnfr/metrics/core.pyi +13 -0
- tnfr/metrics/diagnosis.py +707 -101
- tnfr/metrics/diagnosis.pyi +89 -0
- tnfr/metrics/export.py +27 -13
- tnfr/metrics/glyph_timing.py +218 -38
- tnfr/metrics/reporting.py +22 -18
- tnfr/metrics/reporting.pyi +12 -0
- tnfr/metrics/sense_index.py +199 -25
- tnfr/metrics/sense_index.pyi +9 -0
- tnfr/metrics/trig.py +53 -18
- tnfr/metrics/trig.pyi +12 -0
- tnfr/metrics/trig_cache.py +3 -7
- tnfr/metrics/trig_cache.pyi +10 -0
- tnfr/node.py +148 -125
- tnfr/node.pyi +161 -0
- tnfr/observers.py +44 -30
- tnfr/observers.pyi +46 -0
- tnfr/ontosim.py +14 -13
- tnfr/ontosim.pyi +33 -0
- tnfr/operators/__init__.py +84 -52
- tnfr/operators/__init__.pyi +31 -0
- tnfr/operators/definitions.py +181 -0
- tnfr/operators/definitions.pyi +92 -0
- tnfr/operators/jitter.py +86 -23
- tnfr/operators/jitter.pyi +11 -0
- tnfr/operators/registry.py +80 -0
- tnfr/operators/registry.pyi +15 -0
- tnfr/operators/remesh.py +141 -57
- tnfr/presets.py +9 -54
- tnfr/presets.pyi +7 -0
- tnfr/py.typed +0 -0
- tnfr/rng.py +259 -73
- tnfr/rng.pyi +14 -0
- tnfr/selector.py +24 -17
- tnfr/selector.pyi +19 -0
- tnfr/sense.py +55 -43
- tnfr/sense.pyi +30 -0
- tnfr/structural.py +44 -267
- tnfr/structural.pyi +46 -0
- tnfr/telemetry/__init__.py +13 -0
- tnfr/telemetry/verbosity.py +37 -0
- tnfr/tokens.py +3 -2
- tnfr/tokens.pyi +41 -0
- tnfr/trace.py +272 -82
- tnfr/trace.pyi +68 -0
- tnfr/types.py +345 -6
- tnfr/types.pyi +145 -0
- tnfr/utils/__init__.py +158 -0
- tnfr/utils/__init__.pyi +133 -0
- tnfr/utils/cache.py +755 -0
- tnfr/utils/cache.pyi +156 -0
- tnfr/{collections_utils.py → utils/data.py} +57 -90
- tnfr/utils/data.pyi +73 -0
- tnfr/utils/graph.py +87 -0
- tnfr/utils/graph.pyi +10 -0
- tnfr/utils/init.py +746 -0
- tnfr/utils/init.pyi +85 -0
- tnfr/{json_utils.py → utils/io.py} +13 -18
- tnfr/utils/io.pyi +10 -0
- tnfr/utils/validators.py +130 -0
- tnfr/utils/validators.pyi +19 -0
- tnfr/validation/__init__.py +25 -0
- tnfr/validation/__init__.pyi +17 -0
- tnfr/validation/compatibility.py +59 -0
- tnfr/validation/compatibility.pyi +8 -0
- tnfr/validation/grammar.py +149 -0
- tnfr/validation/grammar.pyi +11 -0
- tnfr/validation/rules.py +194 -0
- tnfr/validation/rules.pyi +18 -0
- tnfr/validation/syntax.py +151 -0
- tnfr/validation/syntax.pyi +7 -0
- tnfr-6.0.0.dist-info/METADATA +135 -0
- tnfr-6.0.0.dist-info/RECORD +157 -0
- tnfr/graph_utils.py +0 -84
- tnfr/import_utils.py +0 -228
- tnfr/logging_utils.py +0 -116
- 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-6.0.0.dist-info}/WHEEL +0 -0
- {tnfr-4.5.2.dist-info → tnfr-6.0.0.dist-info}/entry_points.txt +0 -0
- {tnfr-4.5.2.dist-info → tnfr-6.0.0.dist-info}/licenses/LICENSE.md +0 -0
- {tnfr-4.5.2.dist-info → tnfr-6.0.0.dist-info}/top_level.txt +0 -0
tnfr/operators/remesh.py
CHANGED
|
@@ -1,27 +1,79 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
+
|
|
2
3
|
import hashlib
|
|
3
4
|
import heapq
|
|
5
|
+
import random
|
|
4
6
|
from operator import ge, le
|
|
5
7
|
from functools import cache
|
|
6
8
|
from itertools import combinations
|
|
7
9
|
from io import StringIO
|
|
8
10
|
from collections import deque
|
|
11
|
+
from collections.abc import Hashable, Iterable, Mapping, MutableMapping, Sequence
|
|
9
12
|
from statistics import fmean, StatisticsError
|
|
13
|
+
from types import ModuleType
|
|
14
|
+
from typing import Any, TypedDict, cast
|
|
15
|
+
|
|
16
|
+
from .._compat import TypeAlias
|
|
10
17
|
|
|
11
|
-
from ..cache import edge_version_update
|
|
12
18
|
from ..constants import DEFAULTS, REMESH_DEFAULTS, get_aliases, get_param
|
|
13
19
|
from ..helpers.numeric import kahan_sum_nd
|
|
14
20
|
from ..alias import get_attr, set_attr
|
|
15
21
|
from ..rng import make_rng
|
|
16
22
|
from ..callback_utils import CallbackEvent, callback_manager
|
|
17
23
|
from ..glyph_history import append_metric, ensure_history, current_step_idx
|
|
18
|
-
from ..
|
|
24
|
+
from ..utils import cached_import, edge_version_update
|
|
25
|
+
|
|
26
|
+
CommunityGraph: TypeAlias = Any
|
|
27
|
+
NetworkxModule: TypeAlias = ModuleType
|
|
28
|
+
CommunityModule: TypeAlias = ModuleType
|
|
29
|
+
RemeshEdge: TypeAlias = tuple[Hashable, Hashable]
|
|
30
|
+
NetworkxModules: TypeAlias = tuple[NetworkxModule, CommunityModule]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class RemeshMeta(TypedDict, total=False):
|
|
34
|
+
alpha: float
|
|
35
|
+
alpha_source: str
|
|
36
|
+
tau_global: int
|
|
37
|
+
tau_local: int
|
|
38
|
+
step: int | None
|
|
39
|
+
topo_hash: str | None
|
|
40
|
+
epi_mean_before: float
|
|
41
|
+
epi_mean_after: float
|
|
42
|
+
epi_checksum_before: str
|
|
43
|
+
epi_checksum_after: str
|
|
44
|
+
stable_frac_last: float
|
|
45
|
+
phase_sync_last: float
|
|
46
|
+
glyph_disr_last: float
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
RemeshConfigValue: TypeAlias = bool | float | int
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _as_float(value: Any, default: float = 0.0) -> float:
|
|
53
|
+
"""Best-effort conversion to ``float`` returning ``default`` on failure."""
|
|
54
|
+
|
|
55
|
+
if value is None:
|
|
56
|
+
return default
|
|
57
|
+
try:
|
|
58
|
+
return float(value)
|
|
59
|
+
except (TypeError, ValueError):
|
|
60
|
+
return default
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _ordered_edge(u: Hashable, v: Hashable) -> RemeshEdge:
|
|
64
|
+
"""Return a deterministic ordering for an undirected edge."""
|
|
65
|
+
|
|
66
|
+
return (u, v) if repr(u) <= repr(v) else (v, u)
|
|
67
|
+
|
|
19
68
|
|
|
20
69
|
ALIAS_EPI = get_aliases("EPI")
|
|
21
70
|
|
|
22
71
|
|
|
72
|
+
COOLDOWN_KEY = "REMESH_COOLDOWN_WINDOW"
|
|
73
|
+
|
|
74
|
+
|
|
23
75
|
@cache
|
|
24
|
-
def _get_networkx_modules():
|
|
76
|
+
def _get_networkx_modules() -> NetworkxModules:
|
|
25
77
|
nx = cached_import("networkx")
|
|
26
78
|
if nx is None:
|
|
27
79
|
raise ImportError(
|
|
@@ -34,30 +86,31 @@ def _get_networkx_modules():
|
|
|
34
86
|
"networkx.algorithms.community is required for community-based "
|
|
35
87
|
"operations; install 'networkx' to enable this feature"
|
|
36
88
|
)
|
|
37
|
-
return nx, nx_comm
|
|
89
|
+
return cast(NetworkxModule, nx), cast(CommunityModule, nx_comm)
|
|
38
90
|
|
|
39
91
|
|
|
40
|
-
def _remesh_alpha_info(G):
|
|
92
|
+
def _remesh_alpha_info(G: CommunityGraph) -> tuple[float, str]:
|
|
41
93
|
"""Return ``(alpha, source)`` with explicit precedence."""
|
|
42
94
|
if bool(
|
|
43
95
|
G.graph.get("REMESH_ALPHA_HARD", REMESH_DEFAULTS["REMESH_ALPHA_HARD"])
|
|
44
96
|
):
|
|
45
|
-
val =
|
|
46
|
-
G.graph.get("REMESH_ALPHA", REMESH_DEFAULTS["REMESH_ALPHA"])
|
|
97
|
+
val = _as_float(
|
|
98
|
+
G.graph.get("REMESH_ALPHA", REMESH_DEFAULTS["REMESH_ALPHA"]),
|
|
99
|
+
float(REMESH_DEFAULTS["REMESH_ALPHA"]),
|
|
47
100
|
)
|
|
48
101
|
return val, "REMESH_ALPHA"
|
|
49
102
|
gf = G.graph.get("GLYPH_FACTORS", DEFAULTS.get("GLYPH_FACTORS", {}))
|
|
50
103
|
if "REMESH_alpha" in gf:
|
|
51
|
-
return
|
|
104
|
+
return _as_float(gf["REMESH_alpha"]), "GLYPH_FACTORS.REMESH_alpha"
|
|
52
105
|
if "REMESH_ALPHA" in G.graph:
|
|
53
|
-
return
|
|
106
|
+
return _as_float(G.graph["REMESH_ALPHA"]), "REMESH_ALPHA"
|
|
54
107
|
return (
|
|
55
108
|
float(REMESH_DEFAULTS["REMESH_ALPHA"]),
|
|
56
109
|
"REMESH_DEFAULTS.REMESH_ALPHA",
|
|
57
110
|
)
|
|
58
111
|
|
|
59
112
|
|
|
60
|
-
def _snapshot_topology(G, nx):
|
|
113
|
+
def _snapshot_topology(G: CommunityGraph, nx: NetworkxModule) -> str | None:
|
|
61
114
|
"""Return a hash representing the current graph topology."""
|
|
62
115
|
try:
|
|
63
116
|
n_nodes = G.number_of_nodes()
|
|
@@ -69,12 +122,12 @@ def _snapshot_topology(G, nx):
|
|
|
69
122
|
return None
|
|
70
123
|
|
|
71
124
|
|
|
72
|
-
def _snapshot_epi(G):
|
|
125
|
+
def _snapshot_epi(G: CommunityGraph) -> tuple[float, str]:
|
|
73
126
|
"""Return ``(mean, checksum)`` of the node EPI values."""
|
|
74
127
|
buf = StringIO()
|
|
75
128
|
values = []
|
|
76
129
|
for n, data in G.nodes(data=True):
|
|
77
|
-
v =
|
|
130
|
+
v = _as_float(get_attr(data, ALIAS_EPI, 0.0))
|
|
78
131
|
values.append(v)
|
|
79
132
|
buf.write(f"{str(n)}:{round(v, 6)};")
|
|
80
133
|
total = kahan_sum_nd(((v,) for v in values), dims=1)[0]
|
|
@@ -83,7 +136,7 @@ def _snapshot_epi(G):
|
|
|
83
136
|
return float(mean_val), checksum
|
|
84
137
|
|
|
85
138
|
|
|
86
|
-
def _log_remesh_event(G, meta):
|
|
139
|
+
def _log_remesh_event(G: CommunityGraph, meta: RemeshMeta) -> None:
|
|
87
140
|
"""Store remesh metadata and optionally log and trigger callbacks."""
|
|
88
141
|
G.graph["_REMESH_META"] = meta
|
|
89
142
|
if G.graph.get("REMESH_LOG_EVENTS", REMESH_DEFAULTS["REMESH_LOG_EVENTS"]):
|
|
@@ -94,7 +147,7 @@ def _log_remesh_event(G, meta):
|
|
|
94
147
|
)
|
|
95
148
|
|
|
96
149
|
|
|
97
|
-
def apply_network_remesh(G) -> None:
|
|
150
|
+
def apply_network_remesh(G: CommunityGraph) -> None:
|
|
98
151
|
"""Network-scale REMESH using ``_epi_hist`` with multi-scale memory."""
|
|
99
152
|
nx, _ = _get_networkx_modules()
|
|
100
153
|
tau_g = int(get_param(G, "REMESH_TAU_GLOBAL"))
|
|
@@ -113,9 +166,9 @@ def apply_network_remesh(G) -> None:
|
|
|
113
166
|
epi_mean_before, epi_checksum_before = _snapshot_epi(G)
|
|
114
167
|
|
|
115
168
|
for n, nd in G.nodes(data=True):
|
|
116
|
-
epi_now = get_attr(nd, ALIAS_EPI, 0.0)
|
|
117
|
-
epi_old_l =
|
|
118
|
-
epi_old_g =
|
|
169
|
+
epi_now = _as_float(get_attr(nd, ALIAS_EPI, 0.0))
|
|
170
|
+
epi_old_l = _as_float(past_l.get(n) if isinstance(past_l, Mapping) else None, epi_now)
|
|
171
|
+
epi_old_g = _as_float(past_g.get(n) if isinstance(past_g, Mapping) else None, epi_now)
|
|
119
172
|
mixed = (1 - alpha) * epi_now + alpha * epi_old_l
|
|
120
173
|
mixed = (1 - alpha) * mixed + alpha * epi_old_g
|
|
121
174
|
set_attr(nd, ALIAS_EPI, mixed)
|
|
@@ -123,7 +176,7 @@ def apply_network_remesh(G) -> None:
|
|
|
123
176
|
epi_mean_after, epi_checksum_after = _snapshot_epi(G)
|
|
124
177
|
|
|
125
178
|
step_idx = current_step_idx(G)
|
|
126
|
-
meta = {
|
|
179
|
+
meta: RemeshMeta = {
|
|
127
180
|
"alpha": alpha,
|
|
128
181
|
"alpha_source": alpha_src,
|
|
129
182
|
"tau_global": tau_g,
|
|
@@ -148,7 +201,11 @@ def apply_network_remesh(G) -> None:
|
|
|
148
201
|
_log_remesh_event(G, meta)
|
|
149
202
|
|
|
150
203
|
|
|
151
|
-
def _mst_edges_from_epi(
|
|
204
|
+
def _mst_edges_from_epi(
|
|
205
|
+
nx: NetworkxModule,
|
|
206
|
+
nodes: Sequence[Hashable],
|
|
207
|
+
epi: Mapping[Hashable, float],
|
|
208
|
+
) -> set[RemeshEdge]:
|
|
152
209
|
"""Return MST edges based on absolute EPI distance."""
|
|
153
210
|
H = nx.Graph()
|
|
154
211
|
H.add_nodes_from(nodes)
|
|
@@ -156,12 +213,18 @@ def _mst_edges_from_epi(nx, nodes, epi):
|
|
|
156
213
|
(u, v, abs(epi[u] - epi[v])) for u, v in combinations(nodes, 2)
|
|
157
214
|
)
|
|
158
215
|
return {
|
|
159
|
-
|
|
216
|
+
_ordered_edge(u, v)
|
|
160
217
|
for u, v in nx.minimum_spanning_edges(H, data=False)
|
|
161
218
|
}
|
|
162
219
|
|
|
163
220
|
|
|
164
|
-
def _knn_edges(
|
|
221
|
+
def _knn_edges(
|
|
222
|
+
nodes: Sequence[Hashable],
|
|
223
|
+
epi: Mapping[Hashable, float],
|
|
224
|
+
k_val: int,
|
|
225
|
+
p_rewire: float,
|
|
226
|
+
rnd: random.Random,
|
|
227
|
+
) -> set[RemeshEdge]:
|
|
165
228
|
"""Edges linking each node to its ``k`` nearest neighbours in EPI."""
|
|
166
229
|
new_edges = set()
|
|
167
230
|
node_set = set(nodes)
|
|
@@ -179,17 +242,21 @@ def _knn_edges(nodes, epi, k_val, p_rewire, rnd):
|
|
|
179
242
|
choices = list(node_set - {u, v})
|
|
180
243
|
if choices:
|
|
181
244
|
v = rnd.choice(choices)
|
|
182
|
-
new_edges.add(
|
|
245
|
+
new_edges.add(_ordered_edge(u, v))
|
|
183
246
|
return new_edges
|
|
184
247
|
|
|
185
248
|
|
|
186
|
-
def _community_graph(
|
|
249
|
+
def _community_graph(
|
|
250
|
+
comms: Iterable[Iterable[Hashable]],
|
|
251
|
+
epi: Mapping[Hashable, float],
|
|
252
|
+
nx: NetworkxModule,
|
|
253
|
+
) -> CommunityGraph:
|
|
187
254
|
"""Return community graph ``C`` with mean EPI per community."""
|
|
188
255
|
C = nx.Graph()
|
|
189
256
|
for idx, comm in enumerate(comms):
|
|
190
257
|
members = list(comm)
|
|
191
258
|
try:
|
|
192
|
-
epi_mean = fmean(epi
|
|
259
|
+
epi_mean = fmean(_as_float(epi.get(n)) for n in members)
|
|
193
260
|
except StatisticsError:
|
|
194
261
|
epi_mean = 0.0
|
|
195
262
|
C.add_node(idx)
|
|
@@ -197,16 +264,21 @@ def _community_graph(comms, epi, nx):
|
|
|
197
264
|
C.nodes[idx]["members"] = members
|
|
198
265
|
for i, j in combinations(C.nodes(), 2):
|
|
199
266
|
w = abs(
|
|
200
|
-
get_attr(C.nodes[i], ALIAS_EPI, 0.0)
|
|
201
|
-
- get_attr(C.nodes[j], ALIAS_EPI, 0.0)
|
|
267
|
+
_as_float(get_attr(C.nodes[i], ALIAS_EPI, 0.0))
|
|
268
|
+
- _as_float(get_attr(C.nodes[j], ALIAS_EPI, 0.0))
|
|
202
269
|
)
|
|
203
270
|
C.add_edge(i, j, weight=w)
|
|
204
|
-
return C
|
|
271
|
+
return cast(CommunityGraph, C)
|
|
205
272
|
|
|
206
273
|
|
|
207
|
-
def _community_k_neighbor_edges(
|
|
274
|
+
def _community_k_neighbor_edges(
|
|
275
|
+
C: CommunityGraph,
|
|
276
|
+
k_val: int,
|
|
277
|
+
p_rewire: float,
|
|
278
|
+
rnd: random.Random,
|
|
279
|
+
) -> tuple[set[RemeshEdge], dict[int, int], list[tuple[int, int, int]]]:
|
|
208
280
|
"""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()}
|
|
281
|
+
epi_vals = {n: _as_float(get_attr(C.nodes[n], ALIAS_EPI, 0.0)) for n in C.nodes()}
|
|
210
282
|
ordered = sorted(C.nodes(), key=lambda v: epi_vals[v])
|
|
211
283
|
new_edges = set()
|
|
212
284
|
attempts = {n: 0 for n in C.nodes()}
|
|
@@ -240,7 +312,7 @@ def _community_k_neighbor_edges(C, k_val, p_rewire, rnd):
|
|
|
240
312
|
if choices:
|
|
241
313
|
v = rnd.choice(choices)
|
|
242
314
|
rewired_now = True
|
|
243
|
-
new_edges.add(
|
|
315
|
+
new_edges.add(_ordered_edge(u, v))
|
|
244
316
|
attempts[u] += 1
|
|
245
317
|
if rewired_now:
|
|
246
318
|
rewired.append((u, original_v, v))
|
|
@@ -249,16 +321,16 @@ def _community_k_neighbor_edges(C, k_val, p_rewire, rnd):
|
|
|
249
321
|
|
|
250
322
|
|
|
251
323
|
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
|
-
):
|
|
324
|
+
G: CommunityGraph,
|
|
325
|
+
epi: Mapping[Hashable, float],
|
|
326
|
+
k_val: int,
|
|
327
|
+
p_rewire: float,
|
|
328
|
+
rnd: random.Random,
|
|
329
|
+
nx: NetworkxModule,
|
|
330
|
+
nx_comm: CommunityModule,
|
|
331
|
+
mst_edges: Iterable[RemeshEdge],
|
|
332
|
+
n_before: int,
|
|
333
|
+
) -> None:
|
|
262
334
|
"""Remesh ``G`` replacing nodes by modular communities."""
|
|
263
335
|
comms = list(nx_comm.greedy_modularity_communities(G))
|
|
264
336
|
if len(comms) <= 1:
|
|
@@ -268,7 +340,9 @@ def _community_remesh(
|
|
|
268
340
|
return
|
|
269
341
|
C = _community_graph(comms, epi, nx)
|
|
270
342
|
mst_c = nx.minimum_spanning_tree(C, weight="weight")
|
|
271
|
-
new_edges =
|
|
343
|
+
new_edges: set[RemeshEdge] = {
|
|
344
|
+
_ordered_edge(u, v) for u, v in mst_c.edges()
|
|
345
|
+
}
|
|
272
346
|
extra_edges, attempts, rewired_edges = _community_k_neighbor_edges(
|
|
273
347
|
C, k_val, p_rewire, rnd
|
|
274
348
|
)
|
|
@@ -312,7 +386,7 @@ def _community_remesh(
|
|
|
312
386
|
|
|
313
387
|
|
|
314
388
|
def apply_topological_remesh(
|
|
315
|
-
G,
|
|
389
|
+
G: CommunityGraph,
|
|
316
390
|
mode: str | None = None,
|
|
317
391
|
*,
|
|
318
392
|
k: int | None = None,
|
|
@@ -342,7 +416,7 @@ def apply_topological_remesh(
|
|
|
342
416
|
)
|
|
343
417
|
mode = str(mode)
|
|
344
418
|
nx, nx_comm = _get_networkx_modules()
|
|
345
|
-
epi = {n: get_attr(G.nodes[n], ALIAS_EPI, 0.0) for n in nodes}
|
|
419
|
+
epi = {n: _as_float(get_attr(G.nodes[n], ALIAS_EPI, 0.0)) for n in nodes}
|
|
346
420
|
mst_edges = _mst_edges_from_epi(nx, nodes, epi)
|
|
347
421
|
default_k = int(
|
|
348
422
|
G.graph.get(
|
|
@@ -374,7 +448,11 @@ def apply_topological_remesh(
|
|
|
374
448
|
G.add_edges_from(new_edges)
|
|
375
449
|
|
|
376
450
|
|
|
377
|
-
def _extra_gating_ok(
|
|
451
|
+
def _extra_gating_ok(
|
|
452
|
+
hist: MutableMapping[str, Sequence[float]],
|
|
453
|
+
cfg: Mapping[str, RemeshConfigValue],
|
|
454
|
+
w_estab: int,
|
|
455
|
+
) -> bool:
|
|
378
456
|
"""Check additional stability gating conditions."""
|
|
379
457
|
checks = [
|
|
380
458
|
("phase_sync", "REMESH_MIN_PHASE_SYNC", ge),
|
|
@@ -388,14 +466,24 @@ def _extra_gating_ok(hist, cfg, w_estab):
|
|
|
388
466
|
if series is not None and len(series) >= w_estab:
|
|
389
467
|
win = series[-w_estab:]
|
|
390
468
|
avg = sum(win) / len(win)
|
|
391
|
-
|
|
469
|
+
threshold = _as_float(cfg[cfg_key])
|
|
470
|
+
if not op(avg, threshold):
|
|
392
471
|
return False
|
|
393
472
|
return True
|
|
394
473
|
|
|
395
474
|
|
|
396
475
|
def apply_remesh_if_globally_stable(
|
|
397
|
-
G
|
|
476
|
+
G: CommunityGraph,
|
|
477
|
+
stable_step_window: int | None = None,
|
|
478
|
+
**kwargs: Any,
|
|
398
479
|
) -> None:
|
|
480
|
+
if kwargs:
|
|
481
|
+
unexpected = ", ".join(sorted(kwargs))
|
|
482
|
+
raise TypeError(
|
|
483
|
+
"apply_remesh_if_globally_stable() got unexpected keyword argument(s): "
|
|
484
|
+
f"{unexpected}"
|
|
485
|
+
)
|
|
486
|
+
|
|
399
487
|
params = [
|
|
400
488
|
(
|
|
401
489
|
"REMESH_STABILITY_WINDOW",
|
|
@@ -432,20 +520,16 @@ def apply_remesh_if_globally_stable(
|
|
|
432
520
|
float,
|
|
433
521
|
REMESH_DEFAULTS["REMESH_MIN_SI_HI_FRAC"],
|
|
434
522
|
),
|
|
435
|
-
(
|
|
436
|
-
"REMESH_COOLDOWN_VENTANA",
|
|
437
|
-
int,
|
|
438
|
-
REMESH_DEFAULTS["REMESH_COOLDOWN_VENTANA"],
|
|
439
|
-
),
|
|
523
|
+
(COOLDOWN_KEY, int, REMESH_DEFAULTS[COOLDOWN_KEY]),
|
|
440
524
|
("REMESH_COOLDOWN_TS", float, REMESH_DEFAULTS["REMESH_COOLDOWN_TS"]),
|
|
441
525
|
]
|
|
442
526
|
cfg = {}
|
|
443
527
|
for key, conv, _default in params:
|
|
444
528
|
cfg[key] = conv(get_param(G, key))
|
|
445
|
-
frac_req =
|
|
529
|
+
frac_req = _as_float(get_param(G, "FRACTION_STABLE_REMESH"))
|
|
446
530
|
w_estab = (
|
|
447
|
-
|
|
448
|
-
if
|
|
531
|
+
stable_step_window
|
|
532
|
+
if stable_step_window is not None
|
|
449
533
|
else cfg["REMESH_STABILITY_WINDOW"]
|
|
450
534
|
)
|
|
451
535
|
|
|
@@ -463,10 +547,10 @@ def apply_remesh_if_globally_stable(
|
|
|
463
547
|
|
|
464
548
|
last = G.graph.get("_last_remesh_step", -(10**9))
|
|
465
549
|
step_idx = len(sf)
|
|
466
|
-
if step_idx - last < cfg[
|
|
550
|
+
if step_idx - last < cfg[COOLDOWN_KEY]:
|
|
467
551
|
return
|
|
468
|
-
t_now =
|
|
469
|
-
last_ts =
|
|
552
|
+
t_now = _as_float(G.graph.get("_t", 0.0))
|
|
553
|
+
last_ts = _as_float(G.graph.get("_last_remesh_ts", -1e12))
|
|
470
554
|
if (
|
|
471
555
|
cfg["REMESH_COOLDOWN_TS"] > 0
|
|
472
556
|
and (t_now - last_ts) < cfg["REMESH_COOLDOWN_TS"]
|
tnfr/presets.py
CHANGED
|
@@ -1,60 +1,15 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Backward compatibility shim for configuration presets."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
|
-
from .execution import (
|
|
5
|
-
CANONICAL_PRESET_NAME,
|
|
6
|
-
CANONICAL_PROGRAM_TOKENS,
|
|
7
|
-
block,
|
|
8
|
-
seq,
|
|
9
|
-
wait,
|
|
10
|
-
)
|
|
11
|
-
from .types import Glyph
|
|
12
|
-
|
|
13
|
-
__all__ = ("get_preset",)
|
|
14
4
|
|
|
5
|
+
import warnings
|
|
15
6
|
|
|
16
|
-
|
|
17
|
-
"arranque_resonante": seq(
|
|
18
|
-
Glyph.AL,
|
|
19
|
-
Glyph.EN,
|
|
20
|
-
Glyph.IL,
|
|
21
|
-
Glyph.RA,
|
|
22
|
-
Glyph.VAL,
|
|
23
|
-
Glyph.UM,
|
|
24
|
-
wait(3),
|
|
25
|
-
Glyph.SHA,
|
|
26
|
-
),
|
|
27
|
-
"mutacion_contenida": seq(
|
|
28
|
-
Glyph.AL,
|
|
29
|
-
Glyph.EN,
|
|
30
|
-
block(Glyph.OZ, Glyph.ZHIR, Glyph.IL, repeat=2),
|
|
31
|
-
Glyph.RA,
|
|
32
|
-
Glyph.SHA,
|
|
33
|
-
),
|
|
34
|
-
"exploracion_acople": seq(
|
|
35
|
-
Glyph.AL,
|
|
36
|
-
Glyph.EN,
|
|
37
|
-
Glyph.IL,
|
|
38
|
-
Glyph.VAL,
|
|
39
|
-
Glyph.UM,
|
|
40
|
-
block(Glyph.OZ, Glyph.NAV, Glyph.IL, repeat=1),
|
|
41
|
-
Glyph.RA,
|
|
42
|
-
Glyph.SHA,
|
|
43
|
-
),
|
|
44
|
-
CANONICAL_PRESET_NAME: list(CANONICAL_PROGRAM_TOKENS),
|
|
45
|
-
# Topologías fractales: expansión/contracción modular
|
|
46
|
-
"fractal_expand": seq(
|
|
47
|
-
block(Glyph.THOL, Glyph.VAL, Glyph.UM, repeat=2, close=Glyph.NUL),
|
|
48
|
-
Glyph.RA,
|
|
49
|
-
),
|
|
50
|
-
"fractal_contract": seq(
|
|
51
|
-
block(Glyph.THOL, Glyph.NUL, Glyph.UM, repeat=2, close=Glyph.SHA),
|
|
52
|
-
Glyph.RA,
|
|
53
|
-
),
|
|
54
|
-
}
|
|
7
|
+
from .config.presets import get_preset
|
|
55
8
|
|
|
9
|
+
warnings.warn(
|
|
10
|
+
"'tnfr.presets' is deprecated; use 'tnfr.config.presets' instead",
|
|
11
|
+
DeprecationWarning,
|
|
12
|
+
stacklevel=2,
|
|
13
|
+
)
|
|
56
14
|
|
|
57
|
-
|
|
58
|
-
if name not in _PRESETS:
|
|
59
|
-
raise KeyError(f"Preset no encontrado: {name}")
|
|
60
|
-
return _PRESETS[name]
|
|
15
|
+
__all__ = ("get_preset",)
|
tnfr/presets.pyi
ADDED
tnfr/py.typed
ADDED
|
File without changes
|