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
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 .constants import ALIAS_DNFR, ALIAS_EPI, ALIAS_THETA, ALIAS_dEPI
12
- from .helpers import _get_attr, list_mean, register_callback
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
- # Observador estándar Γ(R)
49
+ # Standard Γ(R) observer
16
50
  # -------------------------
17
- def _std_log(G, kind: str, ctx: dict):
18
- """Guarda eventos compactos en history['events']."""
19
- h = G.graph.setdefault("history", {})
20
- h.setdefault("events", []).append((kind, dict(ctx)))
21
-
22
- def std_before(G, ctx):
23
- _std_log(G, "before", ctx)
24
-
25
- def std_after(G, ctx):
26
- _std_log(G, "after", ctx)
27
-
28
- def std_on_remesh(G, ctx):
29
- _std_log(G, "remesh", ctx)
30
-
31
- def attach_standard_observer(G):
32
- """Registra callbacks estándar: before_step, after_step, on_remesh."""
33
- register_callback(G, "before_step", std_before)
34
- register_callback(G, "after_step", std_after)
35
- register_callback(G, "on_remesh", std_on_remesh)
36
- G.graph.setdefault("_STD_OBSERVER", "attached")
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
- def coherencia_global(G) -> float:
40
- """Proxy de C(t): alta cuando |ΔNFR| y |dEPI_dt| son pequeños."""
41
- dnfr = list_mean(abs(_get_attr(G.nodes[n], ALIAS_DNFR, 0.0)) for n in G.nodes())
42
- dEPI = list_mean(abs(_get_attr(G.nodes[n], ALIAS_dEPI, 0.0)) for n in G.nodes())
43
- return 1.0 / (1.0 + dnfr + dEPI)
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 sincronía_fase(G) -> float:
47
- X = [math.cos(_get_attr(G.nodes[n], ALIAS_THETA, 0.0)) for n in G.nodes()]
48
- Y = [math.sin(_get_attr(G.nodes[n], ALIAS_THETA, 0.0)) for n in G.nodes()]
49
- if not X:
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
- th = math.atan2(sum(Y) / len(Y), sum(X) / len(X))
52
- # varianza angular aproximada (0 = muy sincronizado)
53
- import statistics as st
54
- var = (
55
- st.pvariance(
56
- [
57
- (
58
- (_get_attr(G.nodes[n], ALIAS_THETA, 0.0) - th + math.pi)
59
- % (2 * math.pi)
60
- - math.pi
61
- )
62
- for n in G.nodes()
63
- ]
64
- )
65
- if len(X) > 1
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
- def orden_kuramoto(G) -> float:
71
- """R en [0,1], 1 = fases perfectamente alineadas."""
72
- X = [math.cos(_get_attr(G.nodes[n], ALIAS_THETA, 0.0)) for n in G.nodes()]
73
- Y = [math.sin(_get_attr(G.nodes[n], ALIAS_THETA, 0.0)) for n in G.nodes()]
74
- if not X:
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 = ((sum(X)**2 + sum(Y)**2) ** 0.5) / max(1, len(X))
125
+ if R is None or psi is None:
126
+ R, psi = kuramoto_metrics(G)
77
127
  return float(R)
78
128
 
79
- def carga_glifica(G, window: int | None = None) -> dict:
80
- """Devuelve distribución de glifos aplicados en la red.
81
- - window: si se indica, cuenta solo los últimos `window` eventos por nodo; si no, usa el maxlen del deque.
82
- Retorna un dict con proporciones por glifo y agregados útiles.
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
- total = Counter()
85
- for n in G.nodes():
86
- nd = G.nodes[n]
87
- hist = nd.get("hist_glifos")
88
- if not hist:
89
- continue
90
- seq = list(hist)
91
- if window is not None and window > 0:
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
- # Proporciones por glifo
102
- dist = {k: v / count for k, v in total.items()}
103
-
104
- # Agregados conceptuales (puedes ajustar categorías)
105
- # Glifos que consolidan la coherencia nodal: I’L estabiliza el flujo (cap. 6),
106
- # R’A propaga la resonancia (cap. 9), U’M acopla nodos en fase (cap. 8)
107
- # y SH’A ofrece silencio regenerativo (cap. 10). Véase manual TNFR,
108
- # sec. 18.19 "Análisis morfosintáctico" para la taxonomía funcional.
109
- estabilizadores = ["I’L", "R’A", "U’M", "SH’A"]
110
-
111
- # Glifos que perturban o reconfiguran la red: O’Z introduce disonancia
112
- # evolutiva (cap. 7), Z’HIR muta la estructura (cap. 14), NA’V marca
113
- # el tránsito entre estados (cap. 15) y T’HOL autoorganiza un nuevo
114
- # orden (cap. 13). Véase manual TNFR, sec. 18.19 para esta clasificación.
115
- disruptivos = ["O’Z", "Z’HIR", "NA’V", "T’HOL"]
116
-
117
-
118
- dist["_estabilizadores"] = sum(dist.get(k, 0.0) for k in estabilizadores)
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 = 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: coherencia instantánea
165
- return coherencia_global(G)
166
- if window is None:
167
- window = int(G.graph.get("WBAR_WINDOW", 25))
168
- w = min(len(cs), max(1, int(window)))
169
- return float(sum(cs[-w:]) / w)
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: ...