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.
Files changed (161) hide show
  1. tnfr/__init__.py +228 -49
  2. tnfr/__init__.pyi +40 -0
  3. tnfr/_compat.py +11 -0
  4. tnfr/_version.py +7 -0
  5. tnfr/_version.pyi +7 -0
  6. tnfr/alias.py +106 -21
  7. tnfr/alias.pyi +140 -0
  8. tnfr/cache.py +666 -512
  9. tnfr/cache.pyi +232 -0
  10. tnfr/callback_utils.py +2 -9
  11. tnfr/callback_utils.pyi +105 -0
  12. tnfr/cli/__init__.py +21 -7
  13. tnfr/cli/__init__.pyi +47 -0
  14. tnfr/cli/arguments.py +42 -20
  15. tnfr/cli/arguments.pyi +33 -0
  16. tnfr/cli/execution.py +54 -20
  17. tnfr/cli/execution.pyi +80 -0
  18. tnfr/cli/utils.py +0 -2
  19. tnfr/cli/utils.pyi +8 -0
  20. tnfr/config/__init__.py +12 -0
  21. tnfr/config/__init__.pyi +8 -0
  22. tnfr/config/constants.py +104 -0
  23. tnfr/config/constants.pyi +12 -0
  24. tnfr/{config.py → config/init.py} +11 -7
  25. tnfr/config/init.pyi +8 -0
  26. tnfr/config/operator_names.py +106 -0
  27. tnfr/config/operator_names.pyi +28 -0
  28. tnfr/config/presets.py +104 -0
  29. tnfr/config/presets.pyi +7 -0
  30. tnfr/constants/__init__.py +78 -24
  31. tnfr/constants/__init__.pyi +104 -0
  32. tnfr/constants/core.py +1 -2
  33. tnfr/constants/core.pyi +17 -0
  34. tnfr/constants/init.pyi +12 -0
  35. tnfr/constants/metric.py +4 -12
  36. tnfr/constants/metric.pyi +19 -0
  37. tnfr/constants_glyphs.py +9 -91
  38. tnfr/constants_glyphs.pyi +12 -0
  39. tnfr/dynamics/__init__.py +112 -634
  40. tnfr/dynamics/__init__.pyi +83 -0
  41. tnfr/dynamics/adaptation.py +201 -0
  42. tnfr/dynamics/aliases.py +22 -0
  43. tnfr/dynamics/coordination.py +343 -0
  44. tnfr/dynamics/dnfr.py +1936 -354
  45. tnfr/dynamics/dnfr.pyi +33 -0
  46. tnfr/dynamics/integrators.py +369 -75
  47. tnfr/dynamics/integrators.pyi +35 -0
  48. tnfr/dynamics/runtime.py +521 -0
  49. tnfr/dynamics/sampling.py +8 -5
  50. tnfr/dynamics/sampling.pyi +7 -0
  51. tnfr/dynamics/selectors.py +680 -0
  52. tnfr/execution.py +56 -41
  53. tnfr/execution.pyi +65 -0
  54. tnfr/flatten.py +7 -7
  55. tnfr/flatten.pyi +28 -0
  56. tnfr/gamma.py +54 -37
  57. tnfr/gamma.pyi +40 -0
  58. tnfr/glyph_history.py +85 -38
  59. tnfr/glyph_history.pyi +53 -0
  60. tnfr/grammar.py +19 -338
  61. tnfr/grammar.pyi +13 -0
  62. tnfr/helpers/__init__.py +110 -30
  63. tnfr/helpers/__init__.pyi +66 -0
  64. tnfr/helpers/numeric.py +1 -0
  65. tnfr/helpers/numeric.pyi +12 -0
  66. tnfr/immutable.py +55 -19
  67. tnfr/immutable.pyi +37 -0
  68. tnfr/initialization.py +12 -10
  69. tnfr/initialization.pyi +73 -0
  70. tnfr/io.py +99 -34
  71. tnfr/io.pyi +11 -0
  72. tnfr/locking.pyi +7 -0
  73. tnfr/metrics/__init__.pyi +20 -0
  74. tnfr/metrics/coherence.py +934 -294
  75. tnfr/metrics/common.py +1 -3
  76. tnfr/metrics/common.pyi +15 -0
  77. tnfr/metrics/core.py +192 -34
  78. tnfr/metrics/core.pyi +13 -0
  79. tnfr/metrics/diagnosis.py +707 -101
  80. tnfr/metrics/diagnosis.pyi +89 -0
  81. tnfr/metrics/export.py +27 -13
  82. tnfr/metrics/glyph_timing.py +218 -38
  83. tnfr/metrics/reporting.py +22 -18
  84. tnfr/metrics/reporting.pyi +12 -0
  85. tnfr/metrics/sense_index.py +199 -25
  86. tnfr/metrics/sense_index.pyi +9 -0
  87. tnfr/metrics/trig.py +53 -18
  88. tnfr/metrics/trig.pyi +12 -0
  89. tnfr/metrics/trig_cache.py +3 -7
  90. tnfr/metrics/trig_cache.pyi +10 -0
  91. tnfr/node.py +148 -125
  92. tnfr/node.pyi +161 -0
  93. tnfr/observers.py +44 -30
  94. tnfr/observers.pyi +46 -0
  95. tnfr/ontosim.py +14 -13
  96. tnfr/ontosim.pyi +33 -0
  97. tnfr/operators/__init__.py +84 -52
  98. tnfr/operators/__init__.pyi +31 -0
  99. tnfr/operators/definitions.py +181 -0
  100. tnfr/operators/definitions.pyi +92 -0
  101. tnfr/operators/jitter.py +86 -23
  102. tnfr/operators/jitter.pyi +11 -0
  103. tnfr/operators/registry.py +80 -0
  104. tnfr/operators/registry.pyi +15 -0
  105. tnfr/operators/remesh.py +141 -57
  106. tnfr/presets.py +9 -54
  107. tnfr/presets.pyi +7 -0
  108. tnfr/py.typed +0 -0
  109. tnfr/rng.py +259 -73
  110. tnfr/rng.pyi +14 -0
  111. tnfr/selector.py +24 -17
  112. tnfr/selector.pyi +19 -0
  113. tnfr/sense.py +55 -43
  114. tnfr/sense.pyi +30 -0
  115. tnfr/structural.py +44 -267
  116. tnfr/structural.pyi +46 -0
  117. tnfr/telemetry/__init__.py +13 -0
  118. tnfr/telemetry/verbosity.py +37 -0
  119. tnfr/tokens.py +3 -2
  120. tnfr/tokens.pyi +41 -0
  121. tnfr/trace.py +272 -82
  122. tnfr/trace.pyi +68 -0
  123. tnfr/types.py +345 -6
  124. tnfr/types.pyi +145 -0
  125. tnfr/utils/__init__.py +158 -0
  126. tnfr/utils/__init__.pyi +133 -0
  127. tnfr/utils/cache.py +755 -0
  128. tnfr/utils/cache.pyi +156 -0
  129. tnfr/{collections_utils.py → utils/data.py} +57 -90
  130. tnfr/utils/data.pyi +73 -0
  131. tnfr/utils/graph.py +87 -0
  132. tnfr/utils/graph.pyi +10 -0
  133. tnfr/utils/init.py +746 -0
  134. tnfr/utils/init.pyi +85 -0
  135. tnfr/{json_utils.py → utils/io.py} +13 -18
  136. tnfr/utils/io.pyi +10 -0
  137. tnfr/utils/validators.py +130 -0
  138. tnfr/utils/validators.pyi +19 -0
  139. tnfr/validation/__init__.py +25 -0
  140. tnfr/validation/__init__.pyi +17 -0
  141. tnfr/validation/compatibility.py +59 -0
  142. tnfr/validation/compatibility.pyi +8 -0
  143. tnfr/validation/grammar.py +149 -0
  144. tnfr/validation/grammar.pyi +11 -0
  145. tnfr/validation/rules.py +194 -0
  146. tnfr/validation/rules.pyi +18 -0
  147. tnfr/validation/syntax.py +151 -0
  148. tnfr/validation/syntax.pyi +7 -0
  149. tnfr-6.0.0.dist-info/METADATA +135 -0
  150. tnfr-6.0.0.dist-info/RECORD +157 -0
  151. tnfr/graph_utils.py +0 -84
  152. tnfr/import_utils.py +0 -228
  153. tnfr/logging_utils.py +0 -116
  154. tnfr/validators.py +0 -84
  155. tnfr/value_utils.py +0 -59
  156. tnfr-4.5.2.dist-info/METADATA +0 -379
  157. tnfr-4.5.2.dist-info/RECORD +0 -67
  158. {tnfr-4.5.2.dist-info → tnfr-6.0.0.dist-info}/WHEEL +0 -0
  159. {tnfr-4.5.2.dist-info → tnfr-6.0.0.dist-info}/entry_points.txt +0 -0
  160. {tnfr-4.5.2.dist-info → tnfr-6.0.0.dist-info}/licenses/LICENSE.md +0 -0
  161. {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 ..import_utils import cached_import
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 = float(
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 float(gf["REMESH_alpha"]), "GLYPH_FACTORS.REMESH_alpha"
104
+ return _as_float(gf["REMESH_alpha"]), "GLYPH_FACTORS.REMESH_alpha"
52
105
  if "REMESH_ALPHA" in G.graph:
53
- return float(G.graph["REMESH_ALPHA"]), "REMESH_ALPHA"
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 = float(get_attr(data, ALIAS_EPI, 0.0))
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 = float(past_l.get(n, epi_now))
118
- epi_old_g = float(past_g.get(n, epi_now))
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(nx, nodes, 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
- tuple(sorted((u, v)))
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(nodes, epi, k_val, p_rewire, rnd):
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(tuple(sorted((u, v))))
245
+ new_edges.add(_ordered_edge(u, v))
183
246
  return new_edges
184
247
 
185
248
 
186
- def _community_graph(comms, epi, nx):
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[n] for n in members)
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(C, k_val, p_rewire, rnd):
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(tuple(sorted((u, v))))
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 = set(mst_c.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(hist, cfg, w_estab):
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
- if not op(avg, cfg[cfg_key]):
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, pasos_estables_consecutivos: int | None = None
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 = float(get_param(G, "FRACTION_STABLE_REMESH"))
529
+ frac_req = _as_float(get_param(G, "FRACTION_STABLE_REMESH"))
446
530
  w_estab = (
447
- pasos_estables_consecutivos
448
- if pasos_estables_consecutivos is not None
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["REMESH_COOLDOWN_VENTANA"]:
550
+ if step_idx - last < cfg[COOLDOWN_KEY]:
467
551
  return
468
- t_now = float(G.graph.get("_t", 0.0))
469
- last_ts = float(G.graph.get("_last_remesh_ts", -1e12))
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
- """Predefined configurations."""
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
- _PRESETS = {
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
- def get_preset(name: str):
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
@@ -0,0 +1,7 @@
1
+ from typing import Any
2
+
3
+ __all__: Any
4
+
5
+ def __getattr__(name: str) -> Any: ...
6
+
7
+ get_preset: Any
tnfr/py.typed ADDED
File without changes