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
|
@@ -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))
|
tnfr/dynamics/aliases.py
ADDED
|
@@ -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
|
+
)
|