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.
- tnfr/__init__.py +270 -90
- tnfr/__init__.pyi +40 -0
- tnfr/_compat.py +11 -0
- tnfr/_version.py +7 -0
- tnfr/_version.pyi +7 -0
- tnfr/alias.py +631 -0
- tnfr/alias.pyi +140 -0
- tnfr/cache.py +732 -0
- tnfr/cache.pyi +232 -0
- tnfr/callback_utils.py +381 -0
- tnfr/callback_utils.pyi +105 -0
- tnfr/cli/__init__.py +89 -0
- tnfr/cli/__init__.pyi +47 -0
- tnfr/cli/arguments.py +199 -0
- tnfr/cli/arguments.pyi +33 -0
- tnfr/cli/execution.py +322 -0
- tnfr/cli/execution.pyi +80 -0
- tnfr/cli/utils.py +34 -0
- 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/init.py +36 -0
- 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 +228 -0
- tnfr/constants/__init__.pyi +104 -0
- tnfr/constants/core.py +158 -0
- tnfr/constants/core.pyi +17 -0
- tnfr/constants/init.py +31 -0
- tnfr/constants/init.pyi +12 -0
- tnfr/constants/metric.py +102 -0
- tnfr/constants/metric.pyi +19 -0
- tnfr/constants_glyphs.py +16 -0
- tnfr/constants_glyphs.pyi +12 -0
- tnfr/dynamics/__init__.py +136 -0
- 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 +2315 -0
- tnfr/dynamics/dnfr.pyi +33 -0
- tnfr/dynamics/integrators.py +561 -0
- tnfr/dynamics/integrators.pyi +35 -0
- tnfr/dynamics/runtime.py +521 -0
- tnfr/dynamics/sampling.py +34 -0
- tnfr/dynamics/sampling.pyi +7 -0
- tnfr/dynamics/selectors.py +680 -0
- tnfr/execution.py +216 -0
- tnfr/execution.pyi +65 -0
- tnfr/flatten.py +283 -0
- tnfr/flatten.pyi +28 -0
- tnfr/gamma.py +320 -89
- tnfr/gamma.pyi +40 -0
- tnfr/glyph_history.py +337 -0
- tnfr/glyph_history.pyi +53 -0
- tnfr/grammar.py +23 -153
- tnfr/grammar.pyi +13 -0
- tnfr/helpers/__init__.py +151 -0
- tnfr/helpers/__init__.pyi +66 -0
- tnfr/helpers/numeric.py +88 -0
- tnfr/helpers/numeric.pyi +12 -0
- tnfr/immutable.py +214 -0
- tnfr/immutable.pyi +37 -0
- tnfr/initialization.py +199 -0
- tnfr/initialization.pyi +73 -0
- tnfr/io.py +311 -0
- tnfr/io.pyi +11 -0
- tnfr/locking.py +37 -0
- tnfr/locking.pyi +7 -0
- tnfr/metrics/__init__.py +41 -0
- tnfr/metrics/__init__.pyi +20 -0
- tnfr/metrics/coherence.py +1469 -0
- tnfr/metrics/common.py +149 -0
- tnfr/metrics/common.pyi +15 -0
- tnfr/metrics/core.py +259 -0
- tnfr/metrics/core.pyi +13 -0
- tnfr/metrics/diagnosis.py +840 -0
- tnfr/metrics/diagnosis.pyi +89 -0
- tnfr/metrics/export.py +151 -0
- tnfr/metrics/glyph_timing.py +369 -0
- tnfr/metrics/reporting.py +152 -0
- tnfr/metrics/reporting.pyi +12 -0
- tnfr/metrics/sense_index.py +294 -0
- tnfr/metrics/sense_index.pyi +9 -0
- tnfr/metrics/trig.py +216 -0
- tnfr/metrics/trig.pyi +12 -0
- tnfr/metrics/trig_cache.py +105 -0
- tnfr/metrics/trig_cache.pyi +10 -0
- tnfr/node.py +255 -177
- tnfr/node.pyi +161 -0
- tnfr/observers.py +154 -150
- tnfr/observers.pyi +46 -0
- tnfr/ontosim.py +135 -134
- tnfr/ontosim.pyi +33 -0
- tnfr/operators/__init__.py +452 -0
- tnfr/operators/__init__.pyi +31 -0
- tnfr/operators/definitions.py +181 -0
- tnfr/operators/definitions.pyi +92 -0
- tnfr/operators/jitter.py +266 -0
- tnfr/operators/jitter.pyi +11 -0
- tnfr/operators/registry.py +80 -0
- tnfr/operators/registry.pyi +15 -0
- tnfr/operators/remesh.py +569 -0
- tnfr/presets.py +10 -23
- tnfr/presets.pyi +7 -0
- tnfr/py.typed +0 -0
- tnfr/rng.py +440 -0
- tnfr/rng.pyi +14 -0
- tnfr/selector.py +217 -0
- tnfr/selector.pyi +19 -0
- tnfr/sense.py +307 -142
- tnfr/sense.pyi +30 -0
- tnfr/structural.py +69 -164
- tnfr/structural.pyi +46 -0
- tnfr/telemetry/__init__.py +13 -0
- tnfr/telemetry/verbosity.py +37 -0
- tnfr/tokens.py +61 -0
- tnfr/tokens.pyi +41 -0
- tnfr/trace.py +520 -95
- tnfr/trace.pyi +68 -0
- tnfr/types.py +382 -17
- 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/utils/data.py +267 -0
- 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/utils/io.py +157 -0
- 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/cli.py +0 -322
- tnfr/config.py +0 -41
- tnfr/constants.py +0 -277
- tnfr/dynamics.py +0 -814
- tnfr/helpers.py +0 -264
- tnfr/main.py +0 -47
- tnfr/metrics.py +0 -597
- tnfr/operators.py +0 -525
- tnfr/program.py +0 -176
- tnfr/scenarios.py +0 -34
- tnfr/validators.py +0 -38
- tnfr-4.5.1.dist-info/METADATA +0 -221
- tnfr-4.5.1.dist-info/RECORD +0 -28
- {tnfr-4.5.1.dist-info → tnfr-6.0.0.dist-info}/WHEEL +0 -0
- {tnfr-4.5.1.dist-info → tnfr-6.0.0.dist-info}/entry_points.txt +0 -0
- {tnfr-4.5.1.dist-info → tnfr-6.0.0.dist-info}/licenses/LICENSE.md +0 -0
- {tnfr-4.5.1.dist-info → tnfr-6.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Trigonometric caches for TNFR metrics.
|
|
2
|
+
|
|
3
|
+
The cosine/sine storage helpers live here to keep :mod:`tnfr.metrics.trig`
|
|
4
|
+
focused on pure mathematical utilities (phase means, compensated sums, etc.).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import math
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from typing import Any, Iterable, Mapping
|
|
12
|
+
|
|
13
|
+
from ..alias import get_theta_attr
|
|
14
|
+
from ..types import GraphLike
|
|
15
|
+
from ..utils import edge_version_cache, get_numpy
|
|
16
|
+
|
|
17
|
+
__all__ = ("TrigCache", "compute_theta_trig", "get_trig_cache", "_compute_trig_python")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(slots=True)
|
|
21
|
+
class TrigCache:
|
|
22
|
+
"""Container for cached trigonometric values per node."""
|
|
23
|
+
|
|
24
|
+
cos: dict[Any, float]
|
|
25
|
+
sin: dict[Any, float]
|
|
26
|
+
theta: dict[Any, float]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _iter_theta_pairs(
|
|
30
|
+
nodes: Iterable[tuple[Any, Mapping[str, Any] | float]],
|
|
31
|
+
) -> Iterable[tuple[Any, float]]:
|
|
32
|
+
"""Yield ``(node, θ)`` pairs from ``nodes``."""
|
|
33
|
+
|
|
34
|
+
for n, data in nodes:
|
|
35
|
+
if isinstance(data, Mapping):
|
|
36
|
+
yield n, get_theta_attr(data, 0.0) or 0.0
|
|
37
|
+
else:
|
|
38
|
+
yield n, float(data)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _compute_trig_python(
|
|
42
|
+
nodes: Iterable[tuple[Any, Mapping[str, Any] | float]],
|
|
43
|
+
) -> TrigCache:
|
|
44
|
+
"""Compute trigonometric mappings using pure Python."""
|
|
45
|
+
|
|
46
|
+
cos_th: dict[Any, float] = {}
|
|
47
|
+
sin_th: dict[Any, float] = {}
|
|
48
|
+
thetas: dict[Any, float] = {}
|
|
49
|
+
for n, th in _iter_theta_pairs(nodes):
|
|
50
|
+
thetas[n] = th
|
|
51
|
+
cos_th[n] = math.cos(th)
|
|
52
|
+
sin_th[n] = math.sin(th)
|
|
53
|
+
return TrigCache(cos=cos_th, sin=sin_th, theta=thetas)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def compute_theta_trig(
|
|
57
|
+
nodes: Iterable[tuple[Any, Mapping[str, Any] | float]],
|
|
58
|
+
np: Any | None = None,
|
|
59
|
+
) -> TrigCache:
|
|
60
|
+
"""Return trigonometric mappings of ``θ`` per node."""
|
|
61
|
+
|
|
62
|
+
if np is None:
|
|
63
|
+
np = get_numpy()
|
|
64
|
+
if np is None or not all(hasattr(np, attr) for attr in ("fromiter", "cos", "sin")):
|
|
65
|
+
return _compute_trig_python(nodes)
|
|
66
|
+
|
|
67
|
+
pairs = list(_iter_theta_pairs(nodes))
|
|
68
|
+
if not pairs:
|
|
69
|
+
return TrigCache(cos={}, sin={}, theta={})
|
|
70
|
+
|
|
71
|
+
node_list, theta_vals = zip(*pairs)
|
|
72
|
+
theta_arr = np.fromiter(theta_vals, dtype=float)
|
|
73
|
+
cos_arr = np.cos(theta_arr)
|
|
74
|
+
sin_arr = np.sin(theta_arr)
|
|
75
|
+
|
|
76
|
+
cos_th = dict(zip(node_list, map(float, cos_arr)))
|
|
77
|
+
sin_th = dict(zip(node_list, map(float, sin_arr)))
|
|
78
|
+
thetas = dict(zip(node_list, map(float, theta_arr)))
|
|
79
|
+
return TrigCache(cos=cos_th, sin=sin_th, theta=thetas)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _build_trig_cache(G: GraphLike, np: Any | None = None) -> TrigCache:
|
|
83
|
+
"""Construct trigonometric cache for ``G``."""
|
|
84
|
+
|
|
85
|
+
return compute_theta_trig(G.nodes(data=True), np=np)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def get_trig_cache(
|
|
89
|
+
G: GraphLike,
|
|
90
|
+
*,
|
|
91
|
+
np: Any | None = None,
|
|
92
|
+
cache_size: int | None = 128,
|
|
93
|
+
) -> TrigCache:
|
|
94
|
+
"""Return cached cosines and sines of ``θ`` per node."""
|
|
95
|
+
|
|
96
|
+
if np is None:
|
|
97
|
+
np = get_numpy()
|
|
98
|
+
version = G.graph.setdefault("_trig_version", 0)
|
|
99
|
+
key = ("_trig", version)
|
|
100
|
+
return edge_version_cache(
|
|
101
|
+
G,
|
|
102
|
+
key,
|
|
103
|
+
lambda: _build_trig_cache(G, np=np),
|
|
104
|
+
max_entries=cache_size,
|
|
105
|
+
)
|
tnfr/node.py
CHANGED
|
@@ -1,202 +1,280 @@
|
|
|
1
|
-
|
|
2
|
-
from dataclasses import dataclass, field
|
|
3
|
-
from typing import Deque, Dict, Iterable, List, Optional, Protocol
|
|
4
|
-
from collections import deque
|
|
5
|
-
|
|
6
|
-
from .constants import DEFAULTS
|
|
7
|
-
from .helpers import push_glifo
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class NodoProtocol(Protocol):
|
|
11
|
-
"""Protocolo mínimo para nodos TNFR."""
|
|
1
|
+
"""Node utilities and structures for TNFR graphs."""
|
|
12
2
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from typing import (
|
|
5
|
+
Any,
|
|
6
|
+
Callable,
|
|
7
|
+
Iterable,
|
|
8
|
+
MutableMapping,
|
|
9
|
+
Optional,
|
|
10
|
+
Protocol,
|
|
11
|
+
SupportsFloat,
|
|
12
|
+
TypeVar,
|
|
13
|
+
)
|
|
14
|
+
from collections.abc import Hashable
|
|
15
|
+
import math
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
|
|
18
|
+
from .constants import get_aliases
|
|
19
|
+
from .alias import (
|
|
20
|
+
get_attr,
|
|
21
|
+
get_theta_attr,
|
|
22
|
+
get_attr_str,
|
|
23
|
+
set_attr,
|
|
24
|
+
set_attr_str,
|
|
25
|
+
set_vf,
|
|
26
|
+
set_dnfr,
|
|
27
|
+
set_theta,
|
|
28
|
+
)
|
|
29
|
+
from .types import (
|
|
30
|
+
CouplingWeight,
|
|
31
|
+
DeltaNFR,
|
|
32
|
+
EPIValue,
|
|
33
|
+
NodeId,
|
|
34
|
+
Phase,
|
|
35
|
+
SecondDerivativeEPI,
|
|
36
|
+
SenseIndex,
|
|
37
|
+
StructuralFrequency,
|
|
38
|
+
TNFRGraph,
|
|
39
|
+
)
|
|
40
|
+
from .utils import (
|
|
41
|
+
cached_node_list,
|
|
42
|
+
ensure_node_offset_map,
|
|
43
|
+
increment_edge_version,
|
|
44
|
+
supports_add_edge,
|
|
45
|
+
)
|
|
46
|
+
from .locking import get_lock
|
|
47
|
+
|
|
48
|
+
ALIAS_EPI = get_aliases("EPI")
|
|
49
|
+
ALIAS_VF = get_aliases("VF")
|
|
50
|
+
ALIAS_THETA = get_aliases("THETA")
|
|
51
|
+
ALIAS_SI = get_aliases("SI")
|
|
52
|
+
ALIAS_EPI_KIND = get_aliases("EPI_KIND")
|
|
53
|
+
ALIAS_DNFR = get_aliases("DNFR")
|
|
54
|
+
ALIAS_D2EPI = get_aliases("D2EPI")
|
|
55
|
+
|
|
56
|
+
T = TypeVar("T")
|
|
57
|
+
|
|
58
|
+
__all__ = ("NodeNX", "NodeProtocol", "add_edge")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@dataclass(frozen=True)
|
|
62
|
+
class AttrSpec:
|
|
63
|
+
"""Configuration required to expose a ``networkx`` node attribute.
|
|
64
|
+
|
|
65
|
+
``AttrSpec`` mirrors the defaults previously used by
|
|
66
|
+
:func:`_nx_attr_property` and centralises the descriptor generation
|
|
67
|
+
logic to keep a single source of truth for NodeNX attribute access.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
aliases: tuple[str, ...]
|
|
71
|
+
default: Any = 0.0
|
|
72
|
+
getter: Callable[[MutableMapping[str, Any], tuple[str, ...], Any], Any] = get_attr
|
|
73
|
+
setter: Callable[..., None] = set_attr
|
|
74
|
+
to_python: Callable[[Any], Any] = float
|
|
75
|
+
to_storage: Callable[[Any], Any] = float
|
|
76
|
+
use_graph_setter: bool = False
|
|
77
|
+
|
|
78
|
+
def build_property(self) -> property:
|
|
79
|
+
"""Create the property descriptor for ``NodeNX`` attributes."""
|
|
80
|
+
|
|
81
|
+
def fget(instance: "NodeNX") -> T:
|
|
82
|
+
return self.to_python(
|
|
83
|
+
self.getter(instance.G.nodes[instance.n], self.aliases, self.default)
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
def fset(instance: "NodeNX", value: T) -> None:
|
|
87
|
+
value = self.to_storage(value)
|
|
88
|
+
if self.use_graph_setter:
|
|
89
|
+
self.setter(instance.G, instance.n, value)
|
|
90
|
+
else:
|
|
91
|
+
self.setter(instance.G.nodes[instance.n], self.aliases, value)
|
|
92
|
+
|
|
93
|
+
return property(fget, fset)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# Mapping of NodeNX attribute specifications used to generate property
|
|
97
|
+
# descriptors. Each entry defines the keyword arguments passed to
|
|
98
|
+
# ``AttrSpec.build_property`` for a given attribute name.
|
|
99
|
+
ATTR_SPECS: dict[str, AttrSpec] = {
|
|
100
|
+
"EPI": AttrSpec(aliases=ALIAS_EPI),
|
|
101
|
+
"vf": AttrSpec(aliases=ALIAS_VF, setter=set_vf, use_graph_setter=True),
|
|
102
|
+
"theta": AttrSpec(
|
|
103
|
+
aliases=ALIAS_THETA,
|
|
104
|
+
getter=lambda mapping, _aliases, default: get_theta_attr(mapping, default),
|
|
105
|
+
setter=set_theta,
|
|
106
|
+
use_graph_setter=True,
|
|
107
|
+
),
|
|
108
|
+
"Si": AttrSpec(aliases=ALIAS_SI),
|
|
109
|
+
"epi_kind": AttrSpec(
|
|
110
|
+
aliases=ALIAS_EPI_KIND,
|
|
111
|
+
default="",
|
|
112
|
+
getter=get_attr_str,
|
|
113
|
+
setter=set_attr_str,
|
|
114
|
+
to_python=str,
|
|
115
|
+
to_storage=str,
|
|
116
|
+
),
|
|
117
|
+
"dnfr": AttrSpec(aliases=ALIAS_DNFR, setter=set_dnfr, use_graph_setter=True),
|
|
118
|
+
"d2EPI": AttrSpec(aliases=ALIAS_D2EPI),
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _add_edge_common(
|
|
123
|
+
n1: NodeId,
|
|
124
|
+
n2: NodeId,
|
|
125
|
+
weight: CouplingWeight | SupportsFloat | str,
|
|
126
|
+
) -> Optional[CouplingWeight]:
|
|
127
|
+
"""Validate basic edge constraints.
|
|
128
|
+
|
|
129
|
+
Returns the parsed weight if the edge can be added. ``None`` is returned
|
|
130
|
+
when the edge should be ignored (e.g. self-connections).
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
if n1 == n2:
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
weight = float(weight)
|
|
137
|
+
if not math.isfinite(weight):
|
|
138
|
+
raise ValueError("Edge weight must be a finite number")
|
|
139
|
+
if weight < 0:
|
|
140
|
+
raise ValueError("Edge weight must be non-negative")
|
|
141
|
+
|
|
142
|
+
return weight
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def add_edge(
|
|
146
|
+
graph: TNFRGraph,
|
|
147
|
+
n1: NodeId,
|
|
148
|
+
n2: NodeId,
|
|
149
|
+
weight: CouplingWeight | SupportsFloat | str,
|
|
150
|
+
overwrite: bool = False,
|
|
151
|
+
) -> None:
|
|
152
|
+
"""Add an edge between ``n1`` and ``n2`` in a ``networkx`` graph."""
|
|
153
|
+
|
|
154
|
+
weight = _add_edge_common(n1, n2, weight)
|
|
155
|
+
if weight is None:
|
|
156
|
+
return
|
|
157
|
+
|
|
158
|
+
if not supports_add_edge(graph):
|
|
159
|
+
raise TypeError("add_edge only supports networkx graphs")
|
|
160
|
+
|
|
161
|
+
if graph.has_edge(n1, n2) and not overwrite:
|
|
162
|
+
return
|
|
163
|
+
|
|
164
|
+
graph.add_edge(n1, n2, weight=weight)
|
|
165
|
+
increment_edge_version(graph)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class NodeProtocol(Protocol):
|
|
169
|
+
"""Minimal protocol for TNFR nodes."""
|
|
170
|
+
|
|
171
|
+
EPI: EPIValue
|
|
172
|
+
vf: StructuralFrequency
|
|
173
|
+
theta: Phase
|
|
174
|
+
Si: SenseIndex
|
|
17
175
|
epi_kind: str
|
|
18
|
-
dnfr:
|
|
19
|
-
d2EPI:
|
|
20
|
-
graph:
|
|
176
|
+
dnfr: DeltaNFR
|
|
177
|
+
d2EPI: SecondDerivativeEPI
|
|
178
|
+
graph: MutableMapping[str, Any]
|
|
21
179
|
|
|
22
|
-
def neighbors(self) -> Iterable[
|
|
180
|
+
def neighbors(self) -> Iterable[NodeProtocol | Hashable]:
|
|
23
181
|
...
|
|
24
182
|
|
|
25
|
-
def
|
|
183
|
+
def _glyph_storage(self) -> MutableMapping[str, object]:
|
|
26
184
|
...
|
|
27
185
|
|
|
28
|
-
def has_edge(self, other: "
|
|
186
|
+
def has_edge(self, other: "NodeProtocol") -> bool:
|
|
29
187
|
...
|
|
30
188
|
|
|
31
|
-
def add_edge(
|
|
189
|
+
def add_edge(
|
|
190
|
+
self,
|
|
191
|
+
other: NodeProtocol,
|
|
192
|
+
weight: CouplingWeight,
|
|
193
|
+
*,
|
|
194
|
+
overwrite: bool = False,
|
|
195
|
+
) -> None:
|
|
32
196
|
...
|
|
33
197
|
|
|
34
198
|
def offset(self) -> int:
|
|
35
199
|
...
|
|
36
200
|
|
|
37
|
-
def all_nodes(self) -> Iterable[
|
|
201
|
+
def all_nodes(self) -> Iterable[NodeProtocol]:
|
|
38
202
|
...
|
|
39
203
|
|
|
40
204
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
def
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
class NodoNX(NodoProtocol):
|
|
88
|
-
"""Adaptador para nodos ``networkx``."""
|
|
89
|
-
|
|
90
|
-
def __init__(self, G, n):
|
|
91
|
-
self.G = G
|
|
92
|
-
self.n = n
|
|
93
|
-
self.graph = G.graph
|
|
94
|
-
|
|
95
|
-
@property
|
|
96
|
-
def EPI(self) -> float:
|
|
97
|
-
from .helpers import _get_attr
|
|
98
|
-
from .constants import ALIAS_EPI
|
|
99
|
-
return float(_get_attr(self.G.nodes[self.n], ALIAS_EPI, 0.0))
|
|
100
|
-
|
|
101
|
-
@EPI.setter
|
|
102
|
-
def EPI(self, v: float) -> None:
|
|
103
|
-
from .helpers import _set_attr
|
|
104
|
-
from .constants import ALIAS_EPI
|
|
105
|
-
_set_attr(self.G.nodes[self.n], ALIAS_EPI, float(v))
|
|
106
|
-
|
|
107
|
-
@property
|
|
108
|
-
def vf(self) -> float:
|
|
109
|
-
from .helpers import _get_attr
|
|
110
|
-
from .constants import ALIAS_VF
|
|
111
|
-
return float(_get_attr(self.G.nodes[self.n], ALIAS_VF, 0.0))
|
|
112
|
-
|
|
113
|
-
@vf.setter
|
|
114
|
-
def vf(self, v: float) -> None:
|
|
115
|
-
from .helpers import _set_attr
|
|
116
|
-
from .constants import ALIAS_VF
|
|
117
|
-
_set_attr(self.G.nodes[self.n], ALIAS_VF, float(v))
|
|
118
|
-
|
|
119
|
-
@property
|
|
120
|
-
def theta(self) -> float:
|
|
121
|
-
from .helpers import _get_attr
|
|
122
|
-
from .constants import ALIAS_THETA
|
|
123
|
-
return float(_get_attr(self.G.nodes[self.n], ALIAS_THETA, 0.0))
|
|
124
|
-
|
|
125
|
-
@theta.setter
|
|
126
|
-
def theta(self, v: float) -> None:
|
|
127
|
-
from .helpers import _set_attr
|
|
128
|
-
from .constants import ALIAS_THETA
|
|
129
|
-
_set_attr(self.G.nodes[self.n], ALIAS_THETA, float(v))
|
|
130
|
-
|
|
131
|
-
@property
|
|
132
|
-
def Si(self) -> float:
|
|
133
|
-
from .helpers import _get_attr
|
|
134
|
-
from .constants import ALIAS_SI
|
|
135
|
-
return float(_get_attr(self.G.nodes[self.n], ALIAS_SI, 0.0))
|
|
136
|
-
|
|
137
|
-
@Si.setter
|
|
138
|
-
def Si(self, v: float) -> None:
|
|
139
|
-
from .helpers import _set_attr
|
|
140
|
-
from .constants import ALIAS_SI
|
|
141
|
-
_set_attr(self.G.nodes[self.n], ALIAS_SI, float(v))
|
|
142
|
-
|
|
143
|
-
@property
|
|
144
|
-
def epi_kind(self) -> str:
|
|
145
|
-
from .helpers import _get_attr_str
|
|
146
|
-
from .constants import ALIAS_EPI_KIND
|
|
147
|
-
return _get_attr_str(self.G.nodes[self.n], ALIAS_EPI_KIND, "")
|
|
148
|
-
|
|
149
|
-
@epi_kind.setter
|
|
150
|
-
def epi_kind(self, v: str) -> None:
|
|
151
|
-
from .helpers import _set_attr_str
|
|
152
|
-
from .constants import ALIAS_EPI_KIND
|
|
153
|
-
_set_attr_str(self.G.nodes[self.n], ALIAS_EPI_KIND, str(v))
|
|
154
|
-
|
|
155
|
-
@property
|
|
156
|
-
def dnfr(self) -> float:
|
|
157
|
-
from .helpers import _get_attr
|
|
158
|
-
from .constants import ALIAS_DNFR
|
|
159
|
-
return float(_get_attr(self.G.nodes[self.n], ALIAS_DNFR, 0.0))
|
|
160
|
-
|
|
161
|
-
@dnfr.setter
|
|
162
|
-
def dnfr(self, v: float) -> None:
|
|
163
|
-
from .helpers import _set_attr
|
|
164
|
-
from .constants import ALIAS_DNFR
|
|
165
|
-
_set_attr(self.G.nodes[self.n], ALIAS_DNFR, float(v))
|
|
166
|
-
|
|
167
|
-
@property
|
|
168
|
-
def d2EPI(self) -> float:
|
|
169
|
-
from .helpers import _get_attr
|
|
170
|
-
from .constants import ALIAS_D2EPI
|
|
171
|
-
return float(_get_attr(self.G.nodes[self.n], ALIAS_D2EPI, 0.0))
|
|
172
|
-
|
|
173
|
-
@d2EPI.setter
|
|
174
|
-
def d2EPI(self, v: float) -> None:
|
|
175
|
-
from .helpers import _set_attr
|
|
176
|
-
from .constants import ALIAS_D2EPI
|
|
177
|
-
_set_attr(self.G.nodes[self.n], ALIAS_D2EPI, float(v))
|
|
178
|
-
|
|
179
|
-
def neighbors(self) -> Iterable[NodoProtocol]:
|
|
180
|
-
return [NodoNX(self.G, v) for v in self.G.neighbors(self.n)]
|
|
181
|
-
|
|
182
|
-
def push_glifo(self, glifo: str, window: int) -> None:
|
|
183
|
-
push_glifo(self.G.nodes[self.n], glifo, window)
|
|
184
|
-
self.epi_kind = glifo
|
|
185
|
-
|
|
186
|
-
def has_edge(self, other: NodoProtocol) -> bool:
|
|
187
|
-
if isinstance(other, NodoNX):
|
|
205
|
+
class NodeNX(NodeProtocol):
|
|
206
|
+
"""Adapter for ``networkx`` nodes."""
|
|
207
|
+
|
|
208
|
+
# Statically defined property descriptors for ``NodeNX`` attributes.
|
|
209
|
+
# Declaring them here makes the attributes discoverable by type checkers
|
|
210
|
+
# and IDEs, avoiding the previous runtime ``setattr`` loop.
|
|
211
|
+
EPI: EPIValue = ATTR_SPECS["EPI"].build_property()
|
|
212
|
+
vf: StructuralFrequency = ATTR_SPECS["vf"].build_property()
|
|
213
|
+
theta: Phase = ATTR_SPECS["theta"].build_property()
|
|
214
|
+
Si: SenseIndex = ATTR_SPECS["Si"].build_property()
|
|
215
|
+
epi_kind: str = ATTR_SPECS["epi_kind"].build_property()
|
|
216
|
+
dnfr: DeltaNFR = ATTR_SPECS["dnfr"].build_property()
|
|
217
|
+
d2EPI: SecondDerivativeEPI = ATTR_SPECS["d2EPI"].build_property()
|
|
218
|
+
|
|
219
|
+
def __init__(self, G: TNFRGraph, n: NodeId) -> None:
|
|
220
|
+
self.G: TNFRGraph = G
|
|
221
|
+
self.n: NodeId = n
|
|
222
|
+
self.graph: MutableMapping[str, Any] = G.graph
|
|
223
|
+
G.graph.setdefault("_node_cache", {})[n] = self
|
|
224
|
+
|
|
225
|
+
def _glyph_storage(self) -> MutableMapping[str, Any]:
|
|
226
|
+
return self.G.nodes[self.n]
|
|
227
|
+
|
|
228
|
+
@classmethod
|
|
229
|
+
def from_graph(cls, G: TNFRGraph, n: NodeId) -> "NodeNX":
|
|
230
|
+
"""Return cached ``NodeNX`` for ``(G, n)`` with thread safety."""
|
|
231
|
+
lock = get_lock(f"node_nx_cache_{id(G)}")
|
|
232
|
+
with lock:
|
|
233
|
+
cache = G.graph.setdefault("_node_cache", {})
|
|
234
|
+
node = cache.get(n)
|
|
235
|
+
if node is None:
|
|
236
|
+
node = cls(G, n)
|
|
237
|
+
return node
|
|
238
|
+
|
|
239
|
+
def neighbors(self) -> Iterable[NodeId]:
|
|
240
|
+
"""Iterate neighbour identifiers (IDs).
|
|
241
|
+
|
|
242
|
+
Wrap each resulting ID with :meth:`from_graph` to obtain the cached
|
|
243
|
+
``NodeNX`` instance when actual node objects are required.
|
|
244
|
+
"""
|
|
245
|
+
return self.G.neighbors(self.n)
|
|
246
|
+
|
|
247
|
+
def has_edge(self, other: NodeProtocol) -> bool:
|
|
248
|
+
if isinstance(other, NodeNX):
|
|
188
249
|
return self.G.has_edge(self.n, other.n)
|
|
189
250
|
raise NotImplementedError
|
|
190
251
|
|
|
191
|
-
def add_edge(
|
|
192
|
-
|
|
193
|
-
|
|
252
|
+
def add_edge(
|
|
253
|
+
self,
|
|
254
|
+
other: NodeProtocol,
|
|
255
|
+
weight: CouplingWeight,
|
|
256
|
+
*,
|
|
257
|
+
overwrite: bool = False,
|
|
258
|
+
) -> None:
|
|
259
|
+
if isinstance(other, NodeNX):
|
|
260
|
+
add_edge(
|
|
261
|
+
self.G,
|
|
262
|
+
self.n,
|
|
263
|
+
other.n,
|
|
264
|
+
weight,
|
|
265
|
+
overwrite,
|
|
266
|
+
)
|
|
194
267
|
else:
|
|
195
268
|
raise NotImplementedError
|
|
196
269
|
|
|
197
270
|
def offset(self) -> int:
|
|
198
|
-
|
|
199
|
-
return
|
|
271
|
+
mapping = ensure_node_offset_map(self.G)
|
|
272
|
+
return mapping.get(self.n, 0)
|
|
273
|
+
|
|
274
|
+
def all_nodes(self) -> Iterable[NodeProtocol]:
|
|
275
|
+
override = self.graph.get("_all_nodes")
|
|
276
|
+
if override is not None:
|
|
277
|
+
return override
|
|
200
278
|
|
|
201
|
-
|
|
202
|
-
return
|
|
279
|
+
nodes = cached_node_list(self.G)
|
|
280
|
+
return tuple(NodeNX.from_graph(self.G, v) for v in nodes)
|