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
tnfr/node.pyi
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Hashable, Iterable, MutableMapping
|
|
4
|
+
from typing import Any, Callable, Optional, Protocol, SupportsFloat, TypeVar
|
|
5
|
+
|
|
6
|
+
from .types import (
|
|
7
|
+
CouplingWeight,
|
|
8
|
+
DeltaNFR,
|
|
9
|
+
EPIValue,
|
|
10
|
+
NodeId,
|
|
11
|
+
Phase,
|
|
12
|
+
SecondDerivativeEPI,
|
|
13
|
+
SenseIndex,
|
|
14
|
+
StructuralFrequency,
|
|
15
|
+
TNFRGraph,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
T = TypeVar("T")
|
|
19
|
+
|
|
20
|
+
__all__ = ("NodeNX", "NodeProtocol", "add_edge")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class AttrSpec:
|
|
24
|
+
aliases: tuple[str, ...]
|
|
25
|
+
default: Any
|
|
26
|
+
getter: Callable[[MutableMapping[str, Any], tuple[str, ...], Any], Any]
|
|
27
|
+
setter: Callable[..., None]
|
|
28
|
+
to_python: Callable[[Any], Any]
|
|
29
|
+
to_storage: Callable[[Any], Any]
|
|
30
|
+
use_graph_setter: bool
|
|
31
|
+
|
|
32
|
+
def build_property(self) -> property: ...
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
ALIAS_EPI: tuple[str, ...]
|
|
36
|
+
ALIAS_VF: tuple[str, ...]
|
|
37
|
+
ALIAS_THETA: tuple[str, ...]
|
|
38
|
+
ALIAS_SI: tuple[str, ...]
|
|
39
|
+
ALIAS_EPI_KIND: tuple[str, ...]
|
|
40
|
+
ALIAS_DNFR: tuple[str, ...]
|
|
41
|
+
ALIAS_D2EPI: tuple[str, ...]
|
|
42
|
+
|
|
43
|
+
ATTR_SPECS: dict[str, AttrSpec]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _add_edge_common(
|
|
47
|
+
n1: NodeId,
|
|
48
|
+
n2: NodeId,
|
|
49
|
+
weight: CouplingWeight | SupportsFloat | str,
|
|
50
|
+
) -> Optional[CouplingWeight]: ...
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def add_edge(
|
|
54
|
+
graph: TNFRGraph,
|
|
55
|
+
n1: NodeId,
|
|
56
|
+
n2: NodeId,
|
|
57
|
+
weight: CouplingWeight | SupportsFloat | str,
|
|
58
|
+
overwrite: bool = ...,
|
|
59
|
+
) -> None: ...
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class NodeProtocol(Protocol):
|
|
63
|
+
EPI: EPIValue
|
|
64
|
+
vf: StructuralFrequency
|
|
65
|
+
theta: Phase
|
|
66
|
+
Si: SenseIndex
|
|
67
|
+
epi_kind: str
|
|
68
|
+
dnfr: DeltaNFR
|
|
69
|
+
d2EPI: SecondDerivativeEPI
|
|
70
|
+
graph: MutableMapping[str, Any]
|
|
71
|
+
|
|
72
|
+
def neighbors(self) -> Iterable[NodeProtocol | Hashable]: ...
|
|
73
|
+
|
|
74
|
+
def _glyph_storage(self) -> MutableMapping[str, object]: ...
|
|
75
|
+
|
|
76
|
+
def has_edge(self, other: NodeProtocol) -> bool: ...
|
|
77
|
+
|
|
78
|
+
def add_edge(
|
|
79
|
+
self,
|
|
80
|
+
other: NodeProtocol,
|
|
81
|
+
weight: CouplingWeight,
|
|
82
|
+
*,
|
|
83
|
+
overwrite: bool = ...,
|
|
84
|
+
) -> None: ...
|
|
85
|
+
|
|
86
|
+
def offset(self) -> int: ...
|
|
87
|
+
|
|
88
|
+
def all_nodes(self) -> Iterable[NodeProtocol]: ...
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class NodeNX(NodeProtocol):
|
|
92
|
+
G: TNFRGraph
|
|
93
|
+
n: NodeId
|
|
94
|
+
graph: MutableMapping[str, Any]
|
|
95
|
+
|
|
96
|
+
def __init__(self, G: TNFRGraph, n: NodeId) -> None: ...
|
|
97
|
+
|
|
98
|
+
@classmethod
|
|
99
|
+
def from_graph(cls, G: TNFRGraph, n: NodeId) -> "NodeNX": ...
|
|
100
|
+
|
|
101
|
+
def _glyph_storage(self) -> MutableMapping[str, Any]: ...
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def EPI(self) -> EPIValue: ...
|
|
105
|
+
|
|
106
|
+
@EPI.setter
|
|
107
|
+
def EPI(self, value: EPIValue) -> None: ...
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def vf(self) -> StructuralFrequency: ...
|
|
111
|
+
|
|
112
|
+
@vf.setter
|
|
113
|
+
def vf(self, value: StructuralFrequency) -> None: ...
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def theta(self) -> Phase: ...
|
|
117
|
+
|
|
118
|
+
@theta.setter
|
|
119
|
+
def theta(self, value: Phase) -> None: ...
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def Si(self) -> SenseIndex: ...
|
|
123
|
+
|
|
124
|
+
@Si.setter
|
|
125
|
+
def Si(self, value: SenseIndex) -> None: ...
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def epi_kind(self) -> str: ...
|
|
129
|
+
|
|
130
|
+
@epi_kind.setter
|
|
131
|
+
def epi_kind(self, value: str) -> None: ...
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def dnfr(self) -> DeltaNFR: ...
|
|
135
|
+
|
|
136
|
+
@dnfr.setter
|
|
137
|
+
def dnfr(self, value: DeltaNFR) -> None: ...
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def d2EPI(self) -> SecondDerivativeEPI: ...
|
|
141
|
+
|
|
142
|
+
@d2EPI.setter
|
|
143
|
+
def d2EPI(self, value: SecondDerivativeEPI) -> None: ...
|
|
144
|
+
|
|
145
|
+
def neighbors(self) -> Iterable[NodeId]: ...
|
|
146
|
+
|
|
147
|
+
def has_edge(self, other: NodeProtocol) -> bool: ...
|
|
148
|
+
|
|
149
|
+
def add_edge(
|
|
150
|
+
self,
|
|
151
|
+
other: NodeProtocol,
|
|
152
|
+
weight: CouplingWeight,
|
|
153
|
+
*,
|
|
154
|
+
overwrite: bool = ...,
|
|
155
|
+
) -> None: ...
|
|
156
|
+
|
|
157
|
+
def offset(self) -> int: ...
|
|
158
|
+
|
|
159
|
+
def all_nodes(self) -> Iterable[NodeProtocol]: ...
|
|
160
|
+
|
|
161
|
+
|
tnfr/observers.py
CHANGED
|
@@ -1,169 +1,173 @@
|
|
|
1
|
-
"""
|
|
2
|
-
observers.py — TNFR canónica
|
|
1
|
+
"""Observer management."""
|
|
3
2
|
|
|
4
|
-
Observadores y métricas auxiliares.
|
|
5
|
-
"""
|
|
6
3
|
from __future__ import annotations
|
|
7
|
-
from collections import Counter
|
|
8
|
-
from typing import Dict, Any
|
|
9
|
-
import math
|
|
10
4
|
|
|
11
|
-
from .
|
|
12
|
-
from
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from functools import partial
|
|
7
|
+
import statistics
|
|
8
|
+
from statistics import StatisticsError, pvariance
|
|
9
|
+
|
|
10
|
+
from .alias import get_theta_attr
|
|
11
|
+
from .helpers.numeric import angle_diff
|
|
12
|
+
from .callback_utils import CallbackEvent, callback_manager
|
|
13
|
+
from .glyph_history import (
|
|
14
|
+
ensure_history,
|
|
15
|
+
count_glyphs,
|
|
16
|
+
append_metric,
|
|
17
|
+
)
|
|
18
|
+
from .types import Glyph, GlyphLoadDistribution, TNFRGraph
|
|
19
|
+
from .utils import (
|
|
20
|
+
get_logger,
|
|
21
|
+
get_numpy,
|
|
22
|
+
mix_groups,
|
|
23
|
+
normalize_counter,
|
|
24
|
+
validate_window,
|
|
25
|
+
)
|
|
26
|
+
from .config.constants import GLYPH_GROUPS
|
|
27
|
+
from .gamma import kuramoto_R_psi
|
|
28
|
+
from .metrics.common import compute_coherence
|
|
29
|
+
|
|
30
|
+
__all__ = (
|
|
31
|
+
"attach_standard_observer",
|
|
32
|
+
"kuramoto_metrics",
|
|
33
|
+
"phase_sync",
|
|
34
|
+
"kuramoto_order",
|
|
35
|
+
"glyph_load",
|
|
36
|
+
"wbar",
|
|
37
|
+
"DEFAULT_GLYPH_LOAD_SPAN",
|
|
38
|
+
"DEFAULT_WBAR_SPAN",
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
logger = get_logger(__name__)
|
|
43
|
+
|
|
44
|
+
DEFAULT_GLYPH_LOAD_SPAN = 50
|
|
45
|
+
DEFAULT_WBAR_SPAN = 25
|
|
46
|
+
|
|
13
47
|
|
|
14
48
|
# -------------------------
|
|
15
|
-
#
|
|
49
|
+
# Standard Γ(R) observer
|
|
16
50
|
# -------------------------
|
|
17
|
-
def _std_log(
|
|
18
|
-
"""
|
|
19
|
-
h = G
|
|
20
|
-
h
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
_std_log
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
G.graph
|
|
51
|
+
def _std_log(kind: str, G: TNFRGraph, ctx: Mapping[str, object]) -> None:
|
|
52
|
+
"""Store compact events in ``history['events']``."""
|
|
53
|
+
h = ensure_history(G)
|
|
54
|
+
append_metric(h, "events", (kind, dict(ctx)))
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
_STD_CALLBACKS = {
|
|
58
|
+
CallbackEvent.BEFORE_STEP.value: partial(_std_log, "before"),
|
|
59
|
+
CallbackEvent.AFTER_STEP.value: partial(_std_log, "after"),
|
|
60
|
+
CallbackEvent.ON_REMESH.value: partial(_std_log, "remesh"),
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def attach_standard_observer(G: TNFRGraph) -> TNFRGraph:
|
|
65
|
+
"""Register standard callbacks: before_step, after_step, on_remesh."""
|
|
66
|
+
if G.graph.get("_STD_OBSERVER"):
|
|
67
|
+
return G
|
|
68
|
+
for event, fn in _STD_CALLBACKS.items():
|
|
69
|
+
callback_manager.register_callback(G, event, fn)
|
|
70
|
+
G.graph["_STD_OBSERVER"] = "attached"
|
|
37
71
|
return G
|
|
38
72
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
73
|
+
|
|
74
|
+
def _ensure_nodes(G: TNFRGraph) -> bool:
|
|
75
|
+
"""Return ``True`` when the graph has nodes."""
|
|
76
|
+
return bool(G.number_of_nodes())
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def kuramoto_metrics(G: TNFRGraph) -> tuple[float, float]:
|
|
80
|
+
"""Return Kuramoto order ``R`` and mean phase ``ψ``.
|
|
81
|
+
|
|
82
|
+
Delegates to :func:`kuramoto_R_psi` and performs the computation exactly
|
|
83
|
+
once per invocation.
|
|
84
|
+
"""
|
|
85
|
+
return kuramoto_R_psi(G)
|
|
44
86
|
|
|
45
87
|
|
|
46
|
-
def
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
88
|
+
def phase_sync(
|
|
89
|
+
G: TNFRGraph,
|
|
90
|
+
R: float | None = None,
|
|
91
|
+
psi: float | None = None,
|
|
92
|
+
) -> float:
|
|
93
|
+
if not _ensure_nodes(G):
|
|
50
94
|
return 1.0
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
else 0.0
|
|
67
|
-
|
|
95
|
+
if R is None or psi is None:
|
|
96
|
+
R_calc, psi_calc = kuramoto_metrics(G)
|
|
97
|
+
if R is None:
|
|
98
|
+
R = R_calc
|
|
99
|
+
if psi is None:
|
|
100
|
+
psi = psi_calc
|
|
101
|
+
def _theta(nd: Mapping[str, object]) -> float:
|
|
102
|
+
value = get_theta_attr(nd, 0.0)
|
|
103
|
+
return float(value) if value is not None else 0.0
|
|
104
|
+
|
|
105
|
+
diffs = (angle_diff(_theta(data), psi) for _, data in G.nodes(data=True))
|
|
106
|
+
# Try NumPy for a vectorised population variance
|
|
107
|
+
np = get_numpy()
|
|
108
|
+
if np is not None:
|
|
109
|
+
arr = np.fromiter(diffs, dtype=float)
|
|
110
|
+
var = float(np.var(arr)) if arr.size else 0.0
|
|
111
|
+
else:
|
|
112
|
+
try:
|
|
113
|
+
var = pvariance(diffs)
|
|
114
|
+
except StatisticsError:
|
|
115
|
+
var = 0.0
|
|
68
116
|
return 1.0 / (1.0 + var)
|
|
69
117
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
118
|
+
|
|
119
|
+
def kuramoto_order(
|
|
120
|
+
G: TNFRGraph, R: float | None = None, psi: float | None = None
|
|
121
|
+
) -> float:
|
|
122
|
+
"""R in [0,1], 1 means perfectly aligned phases."""
|
|
123
|
+
if not _ensure_nodes(G):
|
|
75
124
|
return 1.0
|
|
76
|
-
R
|
|
125
|
+
if R is None or psi is None:
|
|
126
|
+
R, psi = kuramoto_metrics(G)
|
|
77
127
|
return float(R)
|
|
78
128
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
129
|
+
|
|
130
|
+
def glyph_load(G: TNFRGraph, window: int | None = None) -> GlyphLoadDistribution:
|
|
131
|
+
"""Return distribution of glyphs applied in the network.
|
|
132
|
+
|
|
133
|
+
- ``window``: if provided, count only the last ``window`` events per node;
|
|
134
|
+
otherwise use :data:`DEFAULT_GLYPH_LOAD_SPAN`.
|
|
135
|
+
Returns a dict with proportions per glyph and useful aggregates.
|
|
83
136
|
"""
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
seq = seq[-window:]
|
|
93
|
-
total.update(seq)
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
count = sum(total.values())
|
|
137
|
+
if window == 0:
|
|
138
|
+
return {"_count": 0.0}
|
|
139
|
+
if window is None:
|
|
140
|
+
window_int = DEFAULT_GLYPH_LOAD_SPAN
|
|
141
|
+
else:
|
|
142
|
+
window_int = validate_window(window, positive=True)
|
|
143
|
+
total = count_glyphs(G, window=window_int, last_only=(window_int == 1))
|
|
144
|
+
dist_raw, count = normalize_counter(total)
|
|
97
145
|
if count == 0:
|
|
98
|
-
return {"_count": 0}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
dist["_disruptivos"] = sum(dist.get(k, 0.0) for k in disruptivos)
|
|
120
|
-
dist["_count"] = count
|
|
121
|
-
return dist
|
|
122
|
-
|
|
123
|
-
def sigma_vector(G, window: int | None = None) -> dict:
|
|
124
|
-
"""Vector de sentido Σ⃗ a partir de la distribución glífica reciente.
|
|
125
|
-
Devuelve dict con x, y, mag (0..1) y angle (rad)."""
|
|
126
|
-
# Distribución glífica (proporciones)
|
|
127
|
-
dist = carga_glifica(G, window=window)
|
|
128
|
-
if not dist or dist.get("_count", 0) == 0:
|
|
129
|
-
return {"x": 0.0, "y": 0.0, "mag": 0.0, "angle": 0.0}
|
|
130
|
-
|
|
131
|
-
# Mapeo polar de glifos principales en el plano de sentido
|
|
132
|
-
# (ordenado estabilización→expansión→acoplamiento→silencio→disonancia→mutación→transición→autoorg.)
|
|
133
|
-
angles = {
|
|
134
|
-
"I’L": 0.0,
|
|
135
|
-
"R’A": math.pi/4,
|
|
136
|
-
"U’M": math.pi/2,
|
|
137
|
-
"SH’A": 3*math.pi/4,
|
|
138
|
-
"O’Z": math.pi,
|
|
139
|
-
"Z’HIR": 5*math.pi/4,
|
|
140
|
-
"NA’V": 3*math.pi/2,
|
|
141
|
-
"T’HOL": 7*math.pi/4,
|
|
142
|
-
}
|
|
143
|
-
# Normaliza solo sobre glifos mapeados
|
|
144
|
-
total = sum(dist.get(k, 0.0) for k in angles.keys())
|
|
145
|
-
if total <= 0:
|
|
146
|
-
return {"x": 0.0, "y": 0.0, "mag": 0.0, "angle": 0.0}
|
|
147
|
-
|
|
148
|
-
x = 0.0
|
|
149
|
-
y = 0.0
|
|
150
|
-
for k, a in angles.items():
|
|
151
|
-
p = dist.get(k, 0.0) / total
|
|
152
|
-
x += p * math.cos(a)
|
|
153
|
-
y += p * math.sin(a)
|
|
154
|
-
|
|
155
|
-
mag = (x*x + y*y) ** 0.5
|
|
156
|
-
ang = math.atan2(y, x)
|
|
157
|
-
return {"x": float(x), "y": float(y), "mag": float(mag), "angle": float(ang)}
|
|
158
|
-
|
|
159
|
-
def wbar(G, window: int | None = None) -> float:
|
|
160
|
-
"""Devuelve W̄ = media de C(t) en una ventana reciente."""
|
|
161
|
-
hist = G.graph.get("history", {})
|
|
162
|
-
cs = hist.get("C_steps", [])
|
|
146
|
+
return {"_count": 0.0}
|
|
147
|
+
dist = mix_groups(dist_raw, GLYPH_GROUPS)
|
|
148
|
+
glyph_dist: GlyphLoadDistribution = {}
|
|
149
|
+
for key, value in dist.items():
|
|
150
|
+
try:
|
|
151
|
+
glyph_key: Glyph | str = Glyph(key)
|
|
152
|
+
except ValueError:
|
|
153
|
+
glyph_key = key
|
|
154
|
+
glyph_dist[glyph_key] = value
|
|
155
|
+
glyph_dist["_count"] = float(count)
|
|
156
|
+
return glyph_dist
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def wbar(G: TNFRGraph, window: int | None = None) -> float:
|
|
160
|
+
"""Return W̄ = mean of ``C(t)`` over a recent window.
|
|
161
|
+
|
|
162
|
+
Uses :func:`ensure_history` to obtain ``G.graph['history']`` and falls back
|
|
163
|
+
to the instantaneous coherence when ``"C_steps"`` is missing or empty.
|
|
164
|
+
"""
|
|
165
|
+
hist = ensure_history(G)
|
|
166
|
+
cs = list(hist.get("C_steps", []))
|
|
163
167
|
if not cs:
|
|
164
|
-
# fallback:
|
|
165
|
-
return
|
|
166
|
-
if window is None
|
|
167
|
-
|
|
168
|
-
w = min(len(cs),
|
|
169
|
-
return float(
|
|
168
|
+
# fallback: instantaneous coherence
|
|
169
|
+
return compute_coherence(G)
|
|
170
|
+
w_param = DEFAULT_WBAR_SPAN if window is None else window
|
|
171
|
+
w = validate_window(w_param, positive=True)
|
|
172
|
+
w = min(len(cs), w)
|
|
173
|
+
return float(statistics.fmean(cs[-w:]))
|
tnfr/observers.pyi
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Mapping
|
|
4
|
+
from typing import Final
|
|
5
|
+
|
|
6
|
+
from .types import GlyphLoadDistribution, TNFRGraph
|
|
7
|
+
|
|
8
|
+
__all__: tuple[str, ...]
|
|
9
|
+
|
|
10
|
+
DEFAULT_GLYPH_LOAD_SPAN: Final[int]
|
|
11
|
+
DEFAULT_WBAR_SPAN: Final[int]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _std_log(kind: str, G: TNFRGraph, ctx: Mapping[str, object]) -> None: ...
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def attach_standard_observer(G: TNFRGraph) -> TNFRGraph: ...
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _ensure_nodes(G: TNFRGraph) -> bool: ...
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def kuramoto_metrics(G: TNFRGraph) -> tuple[float, float]: ...
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def phase_sync(
|
|
27
|
+
G: TNFRGraph,
|
|
28
|
+
R: float | None = ...,
|
|
29
|
+
psi: float | None = ...,
|
|
30
|
+
) -> float: ...
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def kuramoto_order(
|
|
34
|
+
G: TNFRGraph,
|
|
35
|
+
R: float | None = ...,
|
|
36
|
+
psi: float | None = ...,
|
|
37
|
+
) -> float: ...
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def glyph_load(
|
|
41
|
+
G: TNFRGraph,
|
|
42
|
+
window: int | None = ...,
|
|
43
|
+
) -> GlyphLoadDistribution: ...
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def wbar(G: TNFRGraph, window: int | None = ...) -> float: ...
|