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.

Files changed (195) hide show
  1. tnfr/__init__.py +275 -51
  2. tnfr/__init__.pyi +33 -0
  3. tnfr/_compat.py +10 -0
  4. tnfr/_generated_version.py +34 -0
  5. tnfr/_version.py +49 -0
  6. tnfr/_version.pyi +7 -0
  7. tnfr/alias.py +117 -31
  8. tnfr/alias.pyi +108 -0
  9. tnfr/cache.py +6 -572
  10. tnfr/cache.pyi +16 -0
  11. tnfr/callback_utils.py +16 -38
  12. tnfr/callback_utils.pyi +79 -0
  13. tnfr/cli/__init__.py +34 -14
  14. tnfr/cli/__init__.pyi +26 -0
  15. tnfr/cli/arguments.py +211 -28
  16. tnfr/cli/arguments.pyi +27 -0
  17. tnfr/cli/execution.py +470 -50
  18. tnfr/cli/execution.pyi +70 -0
  19. tnfr/cli/utils.py +18 -3
  20. tnfr/cli/utils.pyi +8 -0
  21. tnfr/config/__init__.py +13 -0
  22. tnfr/config/__init__.pyi +10 -0
  23. tnfr/{constants_glyphs.py → config/constants.py} +26 -20
  24. tnfr/config/constants.pyi +12 -0
  25. tnfr/config/feature_flags.py +83 -0
  26. tnfr/{config.py → config/init.py} +11 -7
  27. tnfr/config/init.pyi +8 -0
  28. tnfr/config/operator_names.py +93 -0
  29. tnfr/config/operator_names.pyi +28 -0
  30. tnfr/config/presets.py +84 -0
  31. tnfr/config/presets.pyi +7 -0
  32. tnfr/constants/__init__.py +80 -29
  33. tnfr/constants/__init__.pyi +92 -0
  34. tnfr/constants/aliases.py +31 -0
  35. tnfr/constants/core.py +4 -4
  36. tnfr/constants/core.pyi +17 -0
  37. tnfr/constants/init.py +1 -1
  38. tnfr/constants/init.pyi +12 -0
  39. tnfr/constants/metric.py +7 -15
  40. tnfr/constants/metric.pyi +19 -0
  41. tnfr/dynamics/__init__.py +165 -633
  42. tnfr/dynamics/__init__.pyi +82 -0
  43. tnfr/dynamics/adaptation.py +267 -0
  44. tnfr/dynamics/aliases.py +23 -0
  45. tnfr/dynamics/coordination.py +385 -0
  46. tnfr/dynamics/dnfr.py +2283 -400
  47. tnfr/dynamics/dnfr.pyi +24 -0
  48. tnfr/dynamics/integrators.py +406 -98
  49. tnfr/dynamics/integrators.pyi +34 -0
  50. tnfr/dynamics/runtime.py +881 -0
  51. tnfr/dynamics/sampling.py +10 -5
  52. tnfr/dynamics/sampling.pyi +7 -0
  53. tnfr/dynamics/selectors.py +719 -0
  54. tnfr/execution.py +70 -48
  55. tnfr/execution.pyi +45 -0
  56. tnfr/flatten.py +13 -9
  57. tnfr/flatten.pyi +21 -0
  58. tnfr/gamma.py +66 -53
  59. tnfr/gamma.pyi +34 -0
  60. tnfr/glyph_history.py +110 -52
  61. tnfr/glyph_history.pyi +35 -0
  62. tnfr/glyph_runtime.py +16 -0
  63. tnfr/glyph_runtime.pyi +9 -0
  64. tnfr/immutable.py +69 -28
  65. tnfr/immutable.pyi +34 -0
  66. tnfr/initialization.py +16 -16
  67. tnfr/initialization.pyi +65 -0
  68. tnfr/io.py +6 -240
  69. tnfr/io.pyi +16 -0
  70. tnfr/locking.pyi +7 -0
  71. tnfr/mathematics/__init__.py +81 -0
  72. tnfr/mathematics/backend.py +426 -0
  73. tnfr/mathematics/dynamics.py +398 -0
  74. tnfr/mathematics/epi.py +254 -0
  75. tnfr/mathematics/generators.py +222 -0
  76. tnfr/mathematics/metrics.py +119 -0
  77. tnfr/mathematics/operators.py +233 -0
  78. tnfr/mathematics/operators_factory.py +71 -0
  79. tnfr/mathematics/projection.py +78 -0
  80. tnfr/mathematics/runtime.py +173 -0
  81. tnfr/mathematics/spaces.py +247 -0
  82. tnfr/mathematics/transforms.py +292 -0
  83. tnfr/metrics/__init__.py +10 -10
  84. tnfr/metrics/__init__.pyi +20 -0
  85. tnfr/metrics/coherence.py +993 -324
  86. tnfr/metrics/common.py +23 -16
  87. tnfr/metrics/common.pyi +46 -0
  88. tnfr/metrics/core.py +251 -35
  89. tnfr/metrics/core.pyi +13 -0
  90. tnfr/metrics/diagnosis.py +708 -111
  91. tnfr/metrics/diagnosis.pyi +85 -0
  92. tnfr/metrics/export.py +27 -15
  93. tnfr/metrics/glyph_timing.py +232 -42
  94. tnfr/metrics/reporting.py +33 -22
  95. tnfr/metrics/reporting.pyi +12 -0
  96. tnfr/metrics/sense_index.py +987 -43
  97. tnfr/metrics/sense_index.pyi +9 -0
  98. tnfr/metrics/trig.py +214 -23
  99. tnfr/metrics/trig.pyi +13 -0
  100. tnfr/metrics/trig_cache.py +115 -22
  101. tnfr/metrics/trig_cache.pyi +10 -0
  102. tnfr/node.py +542 -136
  103. tnfr/node.pyi +178 -0
  104. tnfr/observers.py +152 -35
  105. tnfr/observers.pyi +31 -0
  106. tnfr/ontosim.py +23 -19
  107. tnfr/ontosim.pyi +28 -0
  108. tnfr/operators/__init__.py +601 -82
  109. tnfr/operators/__init__.pyi +45 -0
  110. tnfr/operators/definitions.py +513 -0
  111. tnfr/operators/definitions.pyi +78 -0
  112. tnfr/operators/grammar.py +760 -0
  113. tnfr/operators/jitter.py +107 -38
  114. tnfr/operators/jitter.pyi +11 -0
  115. tnfr/operators/registry.py +75 -0
  116. tnfr/operators/registry.pyi +13 -0
  117. tnfr/operators/remesh.py +149 -88
  118. tnfr/py.typed +0 -0
  119. tnfr/rng.py +46 -143
  120. tnfr/rng.pyi +14 -0
  121. tnfr/schemas/__init__.py +8 -0
  122. tnfr/schemas/grammar.json +94 -0
  123. tnfr/selector.py +25 -19
  124. tnfr/selector.pyi +19 -0
  125. tnfr/sense.py +72 -62
  126. tnfr/sense.pyi +23 -0
  127. tnfr/structural.py +522 -262
  128. tnfr/structural.pyi +69 -0
  129. tnfr/telemetry/__init__.py +35 -0
  130. tnfr/telemetry/cache_metrics.py +226 -0
  131. tnfr/telemetry/nu_f.py +423 -0
  132. tnfr/telemetry/nu_f.pyi +123 -0
  133. tnfr/telemetry/verbosity.py +37 -0
  134. tnfr/tokens.py +1 -3
  135. tnfr/tokens.pyi +36 -0
  136. tnfr/trace.py +270 -113
  137. tnfr/trace.pyi +40 -0
  138. tnfr/types.py +574 -6
  139. tnfr/types.pyi +331 -0
  140. tnfr/units.py +69 -0
  141. tnfr/units.pyi +16 -0
  142. tnfr/utils/__init__.py +217 -0
  143. tnfr/utils/__init__.pyi +202 -0
  144. tnfr/utils/cache.py +2395 -0
  145. tnfr/utils/cache.pyi +468 -0
  146. tnfr/utils/chunks.py +104 -0
  147. tnfr/utils/chunks.pyi +21 -0
  148. tnfr/{collections_utils.py → utils/data.py} +147 -90
  149. tnfr/utils/data.pyi +64 -0
  150. tnfr/utils/graph.py +85 -0
  151. tnfr/utils/graph.pyi +10 -0
  152. tnfr/utils/init.py +770 -0
  153. tnfr/utils/init.pyi +78 -0
  154. tnfr/utils/io.py +456 -0
  155. tnfr/{helpers → utils}/numeric.py +51 -24
  156. tnfr/utils/numeric.pyi +21 -0
  157. tnfr/validation/__init__.py +113 -0
  158. tnfr/validation/__init__.pyi +77 -0
  159. tnfr/validation/compatibility.py +95 -0
  160. tnfr/validation/compatibility.pyi +6 -0
  161. tnfr/validation/grammar.py +71 -0
  162. tnfr/validation/grammar.pyi +40 -0
  163. tnfr/validation/graph.py +138 -0
  164. tnfr/validation/graph.pyi +17 -0
  165. tnfr/validation/rules.py +281 -0
  166. tnfr/validation/rules.pyi +55 -0
  167. tnfr/validation/runtime.py +263 -0
  168. tnfr/validation/runtime.pyi +31 -0
  169. tnfr/validation/soft_filters.py +170 -0
  170. tnfr/validation/soft_filters.pyi +37 -0
  171. tnfr/validation/spectral.py +159 -0
  172. tnfr/validation/spectral.pyi +46 -0
  173. tnfr/validation/syntax.py +40 -0
  174. tnfr/validation/syntax.pyi +10 -0
  175. tnfr/validation/window.py +39 -0
  176. tnfr/validation/window.pyi +1 -0
  177. tnfr/viz/__init__.py +9 -0
  178. tnfr/viz/matplotlib.py +246 -0
  179. tnfr-7.0.0.dist-info/METADATA +179 -0
  180. tnfr-7.0.0.dist-info/RECORD +185 -0
  181. {tnfr-4.5.2.dist-info → tnfr-7.0.0.dist-info}/licenses/LICENSE.md +1 -1
  182. tnfr/grammar.py +0 -344
  183. tnfr/graph_utils.py +0 -84
  184. tnfr/helpers/__init__.py +0 -71
  185. tnfr/import_utils.py +0 -228
  186. tnfr/json_utils.py +0 -162
  187. tnfr/logging_utils.py +0 -116
  188. tnfr/presets.py +0 -60
  189. tnfr/validators.py +0 -84
  190. tnfr/value_utils.py +0 -59
  191. tnfr-4.5.2.dist-info/METADATA +0 -379
  192. tnfr-4.5.2.dist-info/RECORD +0 -67
  193. {tnfr-4.5.2.dist-info → tnfr-7.0.0.dist-info}/WHEEL +0 -0
  194. {tnfr-4.5.2.dist-info → tnfr-7.0.0.dist-info}/entry_points.txt +0 -0
  195. {tnfr-4.5.2.dist-info → tnfr-7.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,82 @@
1
+ from typing import Any, Literal, Sequence
2
+
3
+ from tnfr.types import GlyphCode, TNFRGraph
4
+ from tnfr.validation import ValidationOutcome
5
+
6
+ __all__: tuple[str, ...]
7
+
8
+ dnfr: Any
9
+ integrators: Any
10
+
11
+ ALIAS_D2EPI: Sequence[str]
12
+ ALIAS_DNFR: Sequence[str]
13
+ ALIAS_DSI: Sequence[str]
14
+ ALIAS_EPI: Sequence[str]
15
+ ALIAS_SI: Sequence[str]
16
+ ALIAS_VF: Sequence[str]
17
+
18
+ AbstractSelector: Any
19
+ DefaultGlyphSelector: Any
20
+ ParametricGlyphSelector: Any
21
+ _SelectorPreselection: Any
22
+ _apply_glyphs: Any
23
+ _apply_selector: Any
24
+ _choose_glyph: Any
25
+ _configure_selector_weights: Any
26
+ ProcessPoolExecutor: Any
27
+ _maybe_remesh: Any
28
+ _normalize_job_overrides: Any
29
+ _prepare_dnfr: Any
30
+ _prepare_dnfr_data: Any
31
+ _prepare_selector_preselection: Any
32
+ _resolve_jobs_override: Any
33
+ _resolve_preselected_glyph: Any
34
+ _run_after_callbacks: Any
35
+ _run_before_callbacks: Any
36
+ _run_validators: Any
37
+ _selector_parallel_jobs: Any
38
+ _update_epi_hist: Any
39
+ _update_node_sample: Any
40
+ _update_nodes: Any
41
+ _compute_dnfr: Any
42
+ _compute_neighbor_means: Any
43
+ _init_dnfr_cache: Any
44
+ _refresh_dnfr_vectors: Any
45
+ adapt_vf_by_coherence: Any
46
+ apply_canonical_clamps: Any
47
+ coordinate_global_local_phase: Any
48
+ default_compute_delta_nfr: Any
49
+ default_glyph_selector: Any
50
+ dnfr_epi_vf_mixed: Any
51
+ dnfr_laplacian: Any
52
+ dnfr_phase_only: Any
53
+ enforce_canonical_grammar: Any
54
+ get_numpy: Any
55
+ on_applied_glyph: Any
56
+ apply_glyph: Any
57
+ parametric_glyph_selector: Any
58
+
59
+ AbstractIntegrator: Any
60
+ DefaultIntegrator: Any
61
+
62
+ def prepare_integration_params(
63
+ G: TNFRGraph,
64
+ dt: float | None = ...,
65
+ t: float | None = ...,
66
+ method: Literal["euler", "rk4"] | None = ...,
67
+ ) -> tuple[float, int, float, Literal["euler", "rk4"]]: ...
68
+
69
+ run: Any
70
+ set_delta_nfr_hook: Any
71
+ step: Any
72
+
73
+ def update_epi_via_nodal_equation(
74
+ G: TNFRGraph,
75
+ *,
76
+ dt: float | None = ...,
77
+ t: float | None = ...,
78
+ method: Literal["euler", "rk4"] | None = ...,
79
+ n_jobs: int | None = ...,
80
+ ) -> None: ...
81
+
82
+ def validate_canon(G: TNFRGraph) -> ValidationOutcome[TNFRGraph]: ...
@@ -0,0 +1,267 @@
1
+ """νf adaptation routines for TNFR dynamics."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ from concurrent.futures import ProcessPoolExecutor
7
+ from typing import Any, cast
8
+
9
+ from ..alias import collect_attr, set_vf
10
+ from ..constants import get_graph_param
11
+ from ..utils import clamp, resolve_chunk_size
12
+ from ..metrics.common import ensure_neighbors_map
13
+ from ..types import CoherenceMetric, DeltaNFR, NodeId, TNFRGraph
14
+ from ..utils import get_numpy
15
+ from .aliases import ALIAS_DNFR, ALIAS_SI, ALIAS_VF
16
+
17
+ __all__ = ("adapt_vf_by_coherence",)
18
+
19
+
20
+ def _vf_adapt_chunk(
21
+ args: tuple[list[tuple[Any, int, tuple[int, ...]]], tuple[float, ...], float],
22
+ ) -> list[tuple[Any, float]]:
23
+ """Return proposed νf updates for ``chunk`` of stable nodes."""
24
+
25
+ chunk, vf_values, mu = args
26
+ updates: list[tuple[Any, float]] = []
27
+ for node, idx, neighbor_idx in chunk:
28
+ vf = vf_values[idx]
29
+ if neighbor_idx:
30
+ mean = math.fsum(vf_values[j] for j in neighbor_idx) / len(neighbor_idx)
31
+ else:
32
+ mean = vf
33
+ updates.append((node, vf + mu * (mean - vf)))
34
+ return updates
35
+
36
+
37
+ def adapt_vf_by_coherence(G: TNFRGraph, n_jobs: int | None = None) -> None:
38
+ """Synchronise νf to the neighbour mean once ΔNFR and Si stay coherent.
39
+
40
+ Parameters
41
+ ----------
42
+ G : TNFRGraph
43
+ Graph that stores the TNFR nodes and configuration required for
44
+ adaptation. The routine reads ``VF_ADAPT_TAU`` (τ) to decide how many
45
+ consecutive stable steps a node must accumulate in ``stable_count``
46
+ before updating. The adaptation weight ``VF_ADAPT_MU`` (μ) controls how
47
+ quickly νf moves toward the neighbour mean. Stability is detected when
48
+ the absolute ΔNFR stays below ``EPS_DNFR_STABLE`` and the sense index Si
49
+ exceeds the selector threshold ``SELECTOR_THRESHOLDS['si_hi']`` (falling
50
+ back to ``GLYPH_THRESHOLDS['hi']``). Only nodes that satisfy both
51
+ thresholds for τ successive evaluations are eligible for μ-weighted
52
+ averaging.
53
+ n_jobs : int or None, optional
54
+ Number of worker processes used for eligible nodes. ``None`` (the
55
+ default) keeps the adaptation serial, ``1`` disables parallelism, and
56
+ any value greater than one dispatches chunks of nodes to a
57
+ :class:`~concurrent.futures.ProcessPoolExecutor` so large graphs can
58
+ adjust νf without blocking the main dynamic loop.
59
+
60
+ Returns
61
+ -------
62
+ None
63
+ The graph is updated in place; no value is returned.
64
+
65
+ Raises
66
+ ------
67
+ KeyError
68
+ Raised when ``G.graph`` lacks the canonical adaptation parameters and
69
+ defaults have not been injected.
70
+
71
+ Examples
72
+ --------
73
+ >>> from tnfr.constants import inject_defaults
74
+ >>> from tnfr.dynamics import adapt_vf_by_coherence
75
+ >>> from tnfr.structural import create_nfr
76
+ >>> G, seed = create_nfr("seed", vf=0.2)
77
+ >>> _, anchor = create_nfr("anchor", graph=G, vf=1.0)
78
+ >>> G.add_edge(seed, anchor)
79
+ >>> inject_defaults(G)
80
+ >>> G.graph["VF_ADAPT_TAU"] = 2 # τ: consecutive stable steps
81
+ >>> G.graph["VF_ADAPT_MU"] = 0.5 # μ: neighbour coupling strength
82
+ >>> G.graph["SELECTOR_THRESHOLDS"] = {"si_hi": 0.8}
83
+ >>> for node in G.nodes:
84
+ ... G.nodes[node]["Si"] = 0.9 # above ΔSi threshold
85
+ ... G.nodes[node]["ΔNFR"] = 0.0 # within |ΔNFR| ≤ eps guard
86
+ ... G.nodes[node]["stable_count"] = 1
87
+ >>> adapt_vf_by_coherence(G)
88
+ >>> round(G.nodes[seed]["νf"], 2), round(G.nodes[anchor]["νf"], 2)
89
+ (0.6, 0.6)
90
+ >>> G.nodes[seed]["stable_count"], G.nodes[anchor]["stable_count"] >= 2
91
+ (2, True)
92
+ """
93
+
94
+ required_keys = ("VF_ADAPT_TAU", "VF_ADAPT_MU")
95
+ missing_keys = [key for key in required_keys if key not in G.graph]
96
+ if missing_keys:
97
+ missing_list = ", ".join(sorted(missing_keys))
98
+ raise KeyError(
99
+ "adapt_vf_by_coherence requires graph parameters "
100
+ f"{missing_list}; call tnfr.constants.inject_defaults(G) "
101
+ "before adaptation."
102
+ )
103
+
104
+ tau = get_graph_param(G, "VF_ADAPT_TAU", int)
105
+ mu = float(get_graph_param(G, "VF_ADAPT_MU"))
106
+ eps_dnfr = cast(DeltaNFR, get_graph_param(G, "EPS_DNFR_STABLE"))
107
+ thr_sel = get_graph_param(G, "SELECTOR_THRESHOLDS", dict)
108
+ thr_def = get_graph_param(G, "GLYPH_THRESHOLDS", dict)
109
+ si_hi = cast(
110
+ CoherenceMetric,
111
+ float(thr_sel.get("si_hi", thr_def.get("hi", 0.66))),
112
+ )
113
+ vf_min = float(get_graph_param(G, "VF_MIN"))
114
+ vf_max = float(get_graph_param(G, "VF_MAX"))
115
+
116
+ nodes = list(G.nodes)
117
+ if not nodes:
118
+ return
119
+
120
+ neighbors_map = ensure_neighbors_map(G)
121
+ node_count = len(nodes)
122
+ node_index = {node: idx for idx, node in enumerate(nodes)}
123
+
124
+ jobs: int | None
125
+ if n_jobs is None:
126
+ jobs = None
127
+ else:
128
+ try:
129
+ jobs = int(n_jobs)
130
+ except (TypeError, ValueError):
131
+ jobs = None
132
+ else:
133
+ if jobs <= 1:
134
+ jobs = None
135
+
136
+ np_mod = get_numpy()
137
+ use_np = np_mod is not None
138
+
139
+ si_values = collect_attr(G, nodes, ALIAS_SI, 0.0, np=np_mod if use_np else None)
140
+ dnfr_values = collect_attr(G, nodes, ALIAS_DNFR, 0.0, np=np_mod if use_np else None)
141
+ vf_values = collect_attr(G, nodes, ALIAS_VF, 0.0, np=np_mod if use_np else None)
142
+
143
+ if use_np:
144
+ np = np_mod # type: ignore[assignment]
145
+ assert np is not None
146
+ si_arr = si_values.astype(float, copy=False)
147
+ dnfr_arr = np.abs(dnfr_values.astype(float, copy=False))
148
+ vf_arr = vf_values.astype(float, copy=False)
149
+
150
+ prev_counts = np.fromiter(
151
+ (int(G.nodes[node].get("stable_count", 0)) for node in nodes),
152
+ dtype=int,
153
+ count=node_count,
154
+ )
155
+ stable_mask = (si_arr >= si_hi) & (dnfr_arr <= eps_dnfr)
156
+ new_counts = np.where(stable_mask, prev_counts + 1, 0)
157
+
158
+ for node, count in zip(nodes, new_counts.tolist()):
159
+ G.nodes[node]["stable_count"] = int(count)
160
+
161
+ eligible_mask = new_counts >= tau
162
+ if not bool(eligible_mask.any()):
163
+ return
164
+
165
+ max_degree = 0
166
+ if node_count:
167
+ degree_counts = np.fromiter(
168
+ (len(neighbors_map.get(node, ())) for node in nodes),
169
+ dtype=int,
170
+ count=node_count,
171
+ )
172
+ if degree_counts.size:
173
+ max_degree = int(degree_counts.max())
174
+
175
+ if max_degree > 0:
176
+ neighbor_indices = np.zeros((node_count, max_degree), dtype=int)
177
+ mask = np.zeros((node_count, max_degree), dtype=bool)
178
+ for idx, node in enumerate(nodes):
179
+ neigh = neighbors_map.get(node, ())
180
+ if not neigh:
181
+ continue
182
+ idxs = [node_index[nbr] for nbr in neigh if nbr in node_index]
183
+ if not idxs:
184
+ continue
185
+ length = len(idxs)
186
+ neighbor_indices[idx, :length] = idxs
187
+ mask[idx, :length] = True
188
+ neighbor_values = vf_arr[neighbor_indices]
189
+ sums = (neighbor_values * mask).sum(axis=1)
190
+ counts = mask.sum(axis=1)
191
+ neighbor_means = np.where(counts > 0, sums / counts, vf_arr)
192
+ else:
193
+ neighbor_means = vf_arr
194
+
195
+ vf_updates = vf_arr + mu * (neighbor_means - vf_arr)
196
+ for idx in np.nonzero(eligible_mask)[0]:
197
+ node = nodes[int(idx)]
198
+ vf_new = clamp(float(vf_updates[int(idx)]), vf_min, vf_max)
199
+ set_vf(G, node, vf_new)
200
+ return
201
+
202
+ si_list = [float(val) for val in si_values]
203
+ dnfr_list = [abs(float(val)) for val in dnfr_values]
204
+ vf_list = [float(val) for val in vf_values]
205
+
206
+ prev_counts = [int(G.nodes[node].get("stable_count", 0)) for node in nodes]
207
+ stable_flags = [
208
+ si >= si_hi and dnfr <= eps_dnfr for si, dnfr in zip(si_list, dnfr_list)
209
+ ]
210
+ new_counts = [
211
+ prev + 1 if flag else 0 for prev, flag in zip(prev_counts, stable_flags)
212
+ ]
213
+
214
+ for node, count in zip(nodes, new_counts):
215
+ G.nodes[node]["stable_count"] = int(count)
216
+
217
+ eligible_nodes = [node for node, count in zip(nodes, new_counts) if count >= tau]
218
+ if not eligible_nodes:
219
+ return
220
+
221
+ if jobs is None:
222
+ for node in eligible_nodes:
223
+ idx = node_index[node]
224
+ neigh_indices = [
225
+ node_index[nbr]
226
+ for nbr in neighbors_map.get(node, ())
227
+ if nbr in node_index
228
+ ]
229
+ if neigh_indices:
230
+ total = math.fsum(vf_list[i] for i in neigh_indices)
231
+ mean = total / len(neigh_indices)
232
+ else:
233
+ mean = vf_list[idx]
234
+ vf_new = vf_list[idx] + mu * (mean - vf_list[idx])
235
+ set_vf(G, node, clamp(float(vf_new), vf_min, vf_max))
236
+ return
237
+
238
+ work_items: list[tuple[Any, int, tuple[int, ...]]] = []
239
+ for node in eligible_nodes:
240
+ idx = node_index[node]
241
+ neigh_indices = tuple(
242
+ node_index[nbr] for nbr in neighbors_map.get(node, ()) if nbr in node_index
243
+ )
244
+ work_items.append((node, idx, neigh_indices))
245
+
246
+ approx_chunk = math.ceil(len(work_items) / jobs) if jobs else None
247
+ chunk_size = resolve_chunk_size(
248
+ approx_chunk,
249
+ len(work_items),
250
+ minimum=1,
251
+ )
252
+ chunks = [
253
+ work_items[i : i + chunk_size] for i in range(0, len(work_items), chunk_size)
254
+ ]
255
+ vf_tuple = tuple(vf_list)
256
+ updates: dict[Any, float] = {}
257
+ with ProcessPoolExecutor(max_workers=jobs) as executor:
258
+ args = ((chunk, vf_tuple, mu) for chunk in chunks)
259
+ for chunk_updates in executor.map(_vf_adapt_chunk, args):
260
+ for node, value in chunk_updates:
261
+ updates[node] = float(value)
262
+
263
+ for node in eligible_nodes:
264
+ vf_new = updates.get(node)
265
+ if vf_new is None:
266
+ continue
267
+ set_vf(G, node, clamp(float(vf_new), vf_min, vf_max))
@@ -0,0 +1,23 @@
1
+ """Shared alias tokens used across TNFR dynamics submodules."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from ..constants.aliases import (
6
+ ALIAS_D2EPI,
7
+ ALIAS_DNFR,
8
+ ALIAS_DSI,
9
+ ALIAS_EPI,
10
+ ALIAS_SI,
11
+ ALIAS_THETA,
12
+ ALIAS_VF,
13
+ )
14
+
15
+ __all__ = (
16
+ "ALIAS_VF",
17
+ "ALIAS_DNFR",
18
+ "ALIAS_EPI",
19
+ "ALIAS_SI",
20
+ "ALIAS_THETA",
21
+ "ALIAS_D2EPI",
22
+ "ALIAS_DSI",
23
+ )