tnfr 4.5.1__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 (170) hide show
  1. tnfr/__init__.py +270 -90
  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 +631 -0
  7. tnfr/alias.pyi +140 -0
  8. tnfr/cache.py +732 -0
  9. tnfr/cache.pyi +232 -0
  10. tnfr/callback_utils.py +381 -0
  11. tnfr/callback_utils.pyi +105 -0
  12. tnfr/cli/__init__.py +89 -0
  13. tnfr/cli/__init__.pyi +47 -0
  14. tnfr/cli/arguments.py +199 -0
  15. tnfr/cli/arguments.pyi +33 -0
  16. tnfr/cli/execution.py +322 -0
  17. tnfr/cli/execution.pyi +80 -0
  18. tnfr/cli/utils.py +34 -0
  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/init.py +36 -0
  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 +228 -0
  31. tnfr/constants/__init__.pyi +104 -0
  32. tnfr/constants/core.py +158 -0
  33. tnfr/constants/core.pyi +17 -0
  34. tnfr/constants/init.py +31 -0
  35. tnfr/constants/init.pyi +12 -0
  36. tnfr/constants/metric.py +102 -0
  37. tnfr/constants/metric.pyi +19 -0
  38. tnfr/constants_glyphs.py +16 -0
  39. tnfr/constants_glyphs.pyi +12 -0
  40. tnfr/dynamics/__init__.py +136 -0
  41. tnfr/dynamics/__init__.pyi +83 -0
  42. tnfr/dynamics/adaptation.py +201 -0
  43. tnfr/dynamics/aliases.py +22 -0
  44. tnfr/dynamics/coordination.py +343 -0
  45. tnfr/dynamics/dnfr.py +2315 -0
  46. tnfr/dynamics/dnfr.pyi +33 -0
  47. tnfr/dynamics/integrators.py +561 -0
  48. tnfr/dynamics/integrators.pyi +35 -0
  49. tnfr/dynamics/runtime.py +521 -0
  50. tnfr/dynamics/sampling.py +34 -0
  51. tnfr/dynamics/sampling.pyi +7 -0
  52. tnfr/dynamics/selectors.py +680 -0
  53. tnfr/execution.py +216 -0
  54. tnfr/execution.pyi +65 -0
  55. tnfr/flatten.py +283 -0
  56. tnfr/flatten.pyi +28 -0
  57. tnfr/gamma.py +320 -89
  58. tnfr/gamma.pyi +40 -0
  59. tnfr/glyph_history.py +337 -0
  60. tnfr/glyph_history.pyi +53 -0
  61. tnfr/grammar.py +23 -153
  62. tnfr/grammar.pyi +13 -0
  63. tnfr/helpers/__init__.py +151 -0
  64. tnfr/helpers/__init__.pyi +66 -0
  65. tnfr/helpers/numeric.py +88 -0
  66. tnfr/helpers/numeric.pyi +12 -0
  67. tnfr/immutable.py +214 -0
  68. tnfr/immutable.pyi +37 -0
  69. tnfr/initialization.py +199 -0
  70. tnfr/initialization.pyi +73 -0
  71. tnfr/io.py +311 -0
  72. tnfr/io.pyi +11 -0
  73. tnfr/locking.py +37 -0
  74. tnfr/locking.pyi +7 -0
  75. tnfr/metrics/__init__.py +41 -0
  76. tnfr/metrics/__init__.pyi +20 -0
  77. tnfr/metrics/coherence.py +1469 -0
  78. tnfr/metrics/common.py +149 -0
  79. tnfr/metrics/common.pyi +15 -0
  80. tnfr/metrics/core.py +259 -0
  81. tnfr/metrics/core.pyi +13 -0
  82. tnfr/metrics/diagnosis.py +840 -0
  83. tnfr/metrics/diagnosis.pyi +89 -0
  84. tnfr/metrics/export.py +151 -0
  85. tnfr/metrics/glyph_timing.py +369 -0
  86. tnfr/metrics/reporting.py +152 -0
  87. tnfr/metrics/reporting.pyi +12 -0
  88. tnfr/metrics/sense_index.py +294 -0
  89. tnfr/metrics/sense_index.pyi +9 -0
  90. tnfr/metrics/trig.py +216 -0
  91. tnfr/metrics/trig.pyi +12 -0
  92. tnfr/metrics/trig_cache.py +105 -0
  93. tnfr/metrics/trig_cache.pyi +10 -0
  94. tnfr/node.py +255 -177
  95. tnfr/node.pyi +161 -0
  96. tnfr/observers.py +154 -150
  97. tnfr/observers.pyi +46 -0
  98. tnfr/ontosim.py +135 -134
  99. tnfr/ontosim.pyi +33 -0
  100. tnfr/operators/__init__.py +452 -0
  101. tnfr/operators/__init__.pyi +31 -0
  102. tnfr/operators/definitions.py +181 -0
  103. tnfr/operators/definitions.pyi +92 -0
  104. tnfr/operators/jitter.py +266 -0
  105. tnfr/operators/jitter.pyi +11 -0
  106. tnfr/operators/registry.py +80 -0
  107. tnfr/operators/registry.pyi +15 -0
  108. tnfr/operators/remesh.py +569 -0
  109. tnfr/presets.py +10 -23
  110. tnfr/presets.pyi +7 -0
  111. tnfr/py.typed +0 -0
  112. tnfr/rng.py +440 -0
  113. tnfr/rng.pyi +14 -0
  114. tnfr/selector.py +217 -0
  115. tnfr/selector.pyi +19 -0
  116. tnfr/sense.py +307 -142
  117. tnfr/sense.pyi +30 -0
  118. tnfr/structural.py +69 -164
  119. tnfr/structural.pyi +46 -0
  120. tnfr/telemetry/__init__.py +13 -0
  121. tnfr/telemetry/verbosity.py +37 -0
  122. tnfr/tokens.py +61 -0
  123. tnfr/tokens.pyi +41 -0
  124. tnfr/trace.py +520 -95
  125. tnfr/trace.pyi +68 -0
  126. tnfr/types.py +382 -17
  127. tnfr/types.pyi +145 -0
  128. tnfr/utils/__init__.py +158 -0
  129. tnfr/utils/__init__.pyi +133 -0
  130. tnfr/utils/cache.py +755 -0
  131. tnfr/utils/cache.pyi +156 -0
  132. tnfr/utils/data.py +267 -0
  133. tnfr/utils/data.pyi +73 -0
  134. tnfr/utils/graph.py +87 -0
  135. tnfr/utils/graph.pyi +10 -0
  136. tnfr/utils/init.py +746 -0
  137. tnfr/utils/init.pyi +85 -0
  138. tnfr/utils/io.py +157 -0
  139. tnfr/utils/io.pyi +10 -0
  140. tnfr/utils/validators.py +130 -0
  141. tnfr/utils/validators.pyi +19 -0
  142. tnfr/validation/__init__.py +25 -0
  143. tnfr/validation/__init__.pyi +17 -0
  144. tnfr/validation/compatibility.py +59 -0
  145. tnfr/validation/compatibility.pyi +8 -0
  146. tnfr/validation/grammar.py +149 -0
  147. tnfr/validation/grammar.pyi +11 -0
  148. tnfr/validation/rules.py +194 -0
  149. tnfr/validation/rules.pyi +18 -0
  150. tnfr/validation/syntax.py +151 -0
  151. tnfr/validation/syntax.pyi +7 -0
  152. tnfr-6.0.0.dist-info/METADATA +135 -0
  153. tnfr-6.0.0.dist-info/RECORD +157 -0
  154. tnfr/cli.py +0 -322
  155. tnfr/config.py +0 -41
  156. tnfr/constants.py +0 -277
  157. tnfr/dynamics.py +0 -814
  158. tnfr/helpers.py +0 -264
  159. tnfr/main.py +0 -47
  160. tnfr/metrics.py +0 -597
  161. tnfr/operators.py +0 -525
  162. tnfr/program.py +0 -176
  163. tnfr/scenarios.py +0 -34
  164. tnfr/validators.py +0 -38
  165. tnfr-4.5.1.dist-info/METADATA +0 -221
  166. tnfr-4.5.1.dist-info/RECORD +0 -28
  167. {tnfr-4.5.1.dist-info → tnfr-6.0.0.dist-info}/WHEEL +0 -0
  168. {tnfr-4.5.1.dist-info → tnfr-6.0.0.dist-info}/entry_points.txt +0 -0
  169. {tnfr-4.5.1.dist-info → tnfr-6.0.0.dist-info}/licenses/LICENSE.md +0 -0
  170. {tnfr-4.5.1.dist-info → tnfr-6.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,12 @@
1
+ from typing import Any
2
+
3
+ __all__: Any
4
+
5
+ def __getattr__(name: str) -> Any: ...
6
+
7
+ ANGLE_MAP: Any
8
+ DISRUPTORS: Any
9
+ STABILIZERS: Any
10
+ GLYPHS_CANONICAL: Any
11
+ GLYPHS_CANONICAL_SET: Any
12
+ GLYPH_GROUPS: Any
@@ -0,0 +1,136 @@
1
+ """Facade for TNFR dynamics submodules."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from concurrent.futures import ProcessPoolExecutor
6
+
7
+ from . import coordination, dnfr, integrators
8
+ from .adaptation import adapt_vf_by_coherence
9
+ from .aliases import (
10
+ ALIAS_D2EPI,
11
+ ALIAS_DNFR,
12
+ ALIAS_DSI,
13
+ ALIAS_EPI,
14
+ ALIAS_SI,
15
+ ALIAS_VF,
16
+ )
17
+ from .coordination import coordinate_global_local_phase
18
+ from .dnfr import (
19
+ _compute_dnfr,
20
+ _compute_neighbor_means,
21
+ _init_dnfr_cache,
22
+ _prepare_dnfr_data,
23
+ _refresh_dnfr_vectors,
24
+ default_compute_delta_nfr,
25
+ dnfr_epi_vf_mixed,
26
+ dnfr_laplacian,
27
+ dnfr_phase_only,
28
+ set_delta_nfr_hook,
29
+ )
30
+ from .integrators import (
31
+ AbstractIntegrator,
32
+ DefaultIntegrator,
33
+ prepare_integration_params,
34
+ update_epi_via_nodal_equation,
35
+ )
36
+ from .runtime import (
37
+ _maybe_remesh,
38
+ _normalize_job_overrides,
39
+ _prepare_dnfr,
40
+ _resolve_jobs_override,
41
+ _run_after_callbacks,
42
+ _run_before_callbacks,
43
+ _run_validators,
44
+ _update_epi_hist,
45
+ _update_nodes,
46
+ apply_canonical_clamps,
47
+ run,
48
+ step,
49
+ validate_canon,
50
+ )
51
+ from .sampling import update_node_sample as _update_node_sample
52
+ from .selectors import (
53
+ AbstractSelector,
54
+ DefaultGlyphSelector,
55
+ GlyphCode,
56
+ ParametricGlyphSelector,
57
+ _SelectorPreselection,
58
+ _apply_glyphs,
59
+ _apply_selector,
60
+ _choose_glyph,
61
+ _collect_selector_metrics,
62
+ _configure_selector_weights,
63
+ _prepare_selector_preselection,
64
+ _resolve_preselected_glyph,
65
+ _selector_parallel_jobs,
66
+ default_glyph_selector,
67
+ parametric_glyph_selector,
68
+ )
69
+ from ..operators import apply_glyph
70
+ from ..metrics.sense_index import compute_Si
71
+ from ..utils import get_numpy
72
+ from ..validation.grammar import enforce_canonical_grammar, on_applied_glyph
73
+
74
+ __all__ = (
75
+ "coordination",
76
+ "dnfr",
77
+ "integrators",
78
+ "ALIAS_D2EPI",
79
+ "ALIAS_DNFR",
80
+ "ALIAS_DSI",
81
+ "ALIAS_EPI",
82
+ "ALIAS_SI",
83
+ "ALIAS_VF",
84
+ "AbstractSelector",
85
+ "DefaultGlyphSelector",
86
+ "ParametricGlyphSelector",
87
+ "GlyphCode",
88
+ "_SelectorPreselection",
89
+ "_apply_glyphs",
90
+ "_apply_selector",
91
+ "_choose_glyph",
92
+ "_collect_selector_metrics",
93
+ "_configure_selector_weights",
94
+ "ProcessPoolExecutor",
95
+ "_maybe_remesh",
96
+ "_normalize_job_overrides",
97
+ "_prepare_dnfr",
98
+ "_prepare_dnfr_data",
99
+ "_prepare_selector_preselection",
100
+ "_resolve_jobs_override",
101
+ "_resolve_preselected_glyph",
102
+ "_run_after_callbacks",
103
+ "_run_before_callbacks",
104
+ "_run_validators",
105
+ "_selector_parallel_jobs",
106
+ "_update_epi_hist",
107
+ "_update_node_sample",
108
+ "_update_nodes",
109
+ "_compute_dnfr",
110
+ "_compute_neighbor_means",
111
+ "_init_dnfr_cache",
112
+ "_refresh_dnfr_vectors",
113
+ "adapt_vf_by_coherence",
114
+ "apply_canonical_clamps",
115
+ "coordinate_global_local_phase",
116
+ "compute_Si",
117
+ "default_compute_delta_nfr",
118
+ "default_glyph_selector",
119
+ "dnfr_epi_vf_mixed",
120
+ "dnfr_laplacian",
121
+ "dnfr_phase_only",
122
+ "enforce_canonical_grammar",
123
+ "get_numpy",
124
+ "on_applied_glyph",
125
+ "apply_glyph",
126
+ "parametric_glyph_selector",
127
+ "AbstractIntegrator",
128
+ "DefaultIntegrator",
129
+ "prepare_integration_params",
130
+ "run",
131
+ "set_delta_nfr_hook",
132
+ "step",
133
+ "update_epi_via_nodal_equation",
134
+ "validate_canon",
135
+ )
136
+
@@ -0,0 +1,83 @@
1
+ from typing import Any, Literal, Sequence
2
+
3
+ from tnfr.types import TNFRGraph
4
+
5
+ __all__: tuple[str, ...]
6
+
7
+ dnfr: Any
8
+ integrators: Any
9
+
10
+ ALIAS_D2EPI: Sequence[str]
11
+ ALIAS_DNFR: Sequence[str]
12
+ ALIAS_DSI: Sequence[str]
13
+ ALIAS_EPI: Sequence[str]
14
+ ALIAS_SI: Sequence[str]
15
+ ALIAS_VF: Sequence[str]
16
+
17
+ AbstractSelector: Any
18
+ DefaultGlyphSelector: Any
19
+ GlyphCode: 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
+ validate_canon: Any
83
+
@@ -0,0 +1,201 @@
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 ..helpers.numeric import clamp
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
+ """Adjust νf toward neighbour mean in nodes with sustained stability."""
39
+
40
+ tau = get_graph_param(G, "VF_ADAPT_TAU", int)
41
+ mu = float(get_graph_param(G, "VF_ADAPT_MU"))
42
+ eps_dnfr = cast(DeltaNFR, get_graph_param(G, "EPS_DNFR_STABLE"))
43
+ thr_sel = get_graph_param(G, "SELECTOR_THRESHOLDS", dict)
44
+ thr_def = get_graph_param(G, "GLYPH_THRESHOLDS", dict)
45
+ si_hi = cast(
46
+ CoherenceMetric,
47
+ float(thr_sel.get("si_hi", thr_def.get("hi", 0.66))),
48
+ )
49
+ vf_min = float(get_graph_param(G, "VF_MIN"))
50
+ vf_max = float(get_graph_param(G, "VF_MAX"))
51
+
52
+ nodes = list(G.nodes)
53
+ if not nodes:
54
+ return
55
+
56
+ neighbors_map = ensure_neighbors_map(G)
57
+ node_count = len(nodes)
58
+ node_index = {node: idx for idx, node in enumerate(nodes)}
59
+
60
+ jobs: int | None
61
+ if n_jobs is None:
62
+ jobs = None
63
+ else:
64
+ try:
65
+ jobs = int(n_jobs)
66
+ except (TypeError, ValueError):
67
+ jobs = None
68
+ else:
69
+ if jobs <= 1:
70
+ jobs = None
71
+
72
+ np_mod = get_numpy()
73
+ use_np = np_mod is not None
74
+
75
+ si_values = collect_attr(G, nodes, ALIAS_SI, 0.0, np=np_mod if use_np else None)
76
+ dnfr_values = collect_attr(G, nodes, ALIAS_DNFR, 0.0, np=np_mod if use_np else None)
77
+ vf_values = collect_attr(G, nodes, ALIAS_VF, 0.0, np=np_mod if use_np else None)
78
+
79
+ if use_np:
80
+ np = np_mod # type: ignore[assignment]
81
+ assert np is not None
82
+ si_arr = si_values.astype(float, copy=False)
83
+ dnfr_arr = np.abs(dnfr_values.astype(float, copy=False))
84
+ vf_arr = vf_values.astype(float, copy=False)
85
+
86
+ prev_counts = np.fromiter(
87
+ (int(G.nodes[node].get("stable_count", 0)) for node in nodes),
88
+ dtype=int,
89
+ count=node_count,
90
+ )
91
+ stable_mask = (si_arr >= si_hi) & (dnfr_arr <= eps_dnfr)
92
+ new_counts = np.where(stable_mask, prev_counts + 1, 0)
93
+
94
+ for node, count in zip(nodes, new_counts.tolist()):
95
+ G.nodes[node]["stable_count"] = int(count)
96
+
97
+ eligible_mask = new_counts >= tau
98
+ if not bool(eligible_mask.any()):
99
+ return
100
+
101
+ max_degree = 0
102
+ if node_count:
103
+ degree_counts = np.fromiter(
104
+ (len(neighbors_map.get(node, ())) for node in nodes),
105
+ dtype=int,
106
+ count=node_count,
107
+ )
108
+ if degree_counts.size:
109
+ max_degree = int(degree_counts.max())
110
+
111
+ if max_degree > 0:
112
+ neighbor_indices = np.zeros((node_count, max_degree), dtype=int)
113
+ mask = np.zeros((node_count, max_degree), dtype=bool)
114
+ for idx, node in enumerate(nodes):
115
+ neigh = neighbors_map.get(node, ())
116
+ if not neigh:
117
+ continue
118
+ idxs = [node_index[nbr] for nbr in neigh if nbr in node_index]
119
+ if not idxs:
120
+ continue
121
+ length = len(idxs)
122
+ neighbor_indices[idx, :length] = idxs
123
+ mask[idx, :length] = True
124
+ neighbor_values = vf_arr[neighbor_indices]
125
+ sums = (neighbor_values * mask).sum(axis=1)
126
+ counts = mask.sum(axis=1)
127
+ neighbor_means = np.where(counts > 0, sums / counts, vf_arr)
128
+ else:
129
+ neighbor_means = vf_arr
130
+
131
+ vf_updates = vf_arr + mu * (neighbor_means - vf_arr)
132
+ for idx in np.nonzero(eligible_mask)[0]:
133
+ node = nodes[int(idx)]
134
+ vf_new = clamp(float(vf_updates[int(idx)]), vf_min, vf_max)
135
+ set_vf(G, node, vf_new)
136
+ return
137
+
138
+ si_list = [float(val) for val in si_values]
139
+ dnfr_list = [abs(float(val)) for val in dnfr_values]
140
+ vf_list = [float(val) for val in vf_values]
141
+
142
+ prev_counts = [int(G.nodes[node].get("stable_count", 0)) for node in nodes]
143
+ stable_flags = [
144
+ si >= si_hi and dnfr <= eps_dnfr
145
+ for si, dnfr in zip(si_list, dnfr_list)
146
+ ]
147
+ new_counts = [prev + 1 if flag else 0 for prev, flag in zip(prev_counts, stable_flags)]
148
+
149
+ for node, count in zip(nodes, new_counts):
150
+ G.nodes[node]["stable_count"] = int(count)
151
+
152
+ eligible_nodes = [node for node, count in zip(nodes, new_counts) if count >= tau]
153
+ if not eligible_nodes:
154
+ return
155
+
156
+ if jobs is None:
157
+ for node in eligible_nodes:
158
+ idx = node_index[node]
159
+ neigh_indices = [
160
+ node_index[nbr]
161
+ for nbr in neighbors_map.get(node, ())
162
+ if nbr in node_index
163
+ ]
164
+ if neigh_indices:
165
+ total = math.fsum(vf_list[i] for i in neigh_indices)
166
+ mean = total / len(neigh_indices)
167
+ else:
168
+ mean = vf_list[idx]
169
+ vf_new = vf_list[idx] + mu * (mean - vf_list[idx])
170
+ set_vf(G, node, clamp(float(vf_new), vf_min, vf_max))
171
+ return
172
+
173
+ work_items: list[tuple[Any, int, tuple[int, ...]]] = []
174
+ for node in eligible_nodes:
175
+ idx = node_index[node]
176
+ neigh_indices = tuple(
177
+ node_index[nbr]
178
+ for nbr in neighbors_map.get(node, ())
179
+ if nbr in node_index
180
+ )
181
+ work_items.append((node, idx, neigh_indices))
182
+
183
+ chunk_size = max(1, math.ceil(len(work_items) / jobs))
184
+ chunks = [
185
+ work_items[i : i + chunk_size]
186
+ for i in range(0, len(work_items), chunk_size)
187
+ ]
188
+ vf_tuple = tuple(vf_list)
189
+ updates: dict[Any, float] = {}
190
+ with ProcessPoolExecutor(max_workers=jobs) as executor:
191
+ args = ((chunk, vf_tuple, mu) for chunk in chunks)
192
+ for chunk_updates in executor.map(_vf_adapt_chunk, args):
193
+ for node, value in chunk_updates:
194
+ updates[node] = float(value)
195
+
196
+ for node in eligible_nodes:
197
+ vf_new = updates.get(node)
198
+ if vf_new is None:
199
+ continue
200
+ set_vf(G, node, clamp(float(vf_new), vf_min, vf_max))
201
+
@@ -0,0 +1,22 @@
1
+ """Shared alias tokens used across TNFR dynamics submodules."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from ..constants import get_aliases
6
+
7
+ ALIAS_VF = get_aliases("VF")
8
+ ALIAS_DNFR = get_aliases("DNFR")
9
+ ALIAS_EPI = get_aliases("EPI")
10
+ ALIAS_SI = get_aliases("SI")
11
+ ALIAS_D2EPI = get_aliases("D2EPI")
12
+ ALIAS_DSI = get_aliases("DSI")
13
+
14
+ __all__ = (
15
+ "ALIAS_VF",
16
+ "ALIAS_DNFR",
17
+ "ALIAS_EPI",
18
+ "ALIAS_SI",
19
+ "ALIAS_D2EPI",
20
+ "ALIAS_DSI",
21
+ )
22
+