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.

Files changed (195) hide show
  1. tnfr/__init__.py +275 -51
  2. tnfr/__init__.pyi +33 -0
  3. tnfr/_compat.py +10 -0
  4. tnfr/_generated_version.py +34 -0
  5. tnfr/_version.py +49 -0
  6. tnfr/_version.pyi +7 -0
  7. tnfr/alias.py +117 -31
  8. tnfr/alias.pyi +108 -0
  9. tnfr/cache.py +6 -572
  10. tnfr/cache.pyi +16 -0
  11. tnfr/callback_utils.py +16 -38
  12. tnfr/callback_utils.pyi +79 -0
  13. tnfr/cli/__init__.py +34 -14
  14. tnfr/cli/__init__.pyi +26 -0
  15. tnfr/cli/arguments.py +211 -28
  16. tnfr/cli/arguments.pyi +27 -0
  17. tnfr/cli/execution.py +470 -50
  18. tnfr/cli/execution.pyi +70 -0
  19. tnfr/cli/utils.py +18 -3
  20. tnfr/cli/utils.pyi +8 -0
  21. tnfr/config/__init__.py +13 -0
  22. tnfr/config/__init__.pyi +10 -0
  23. tnfr/{constants_glyphs.py → config/constants.py} +26 -20
  24. tnfr/config/constants.pyi +12 -0
  25. tnfr/config/feature_flags.py +83 -0
  26. tnfr/{config.py → config/init.py} +11 -7
  27. tnfr/config/init.pyi +8 -0
  28. tnfr/config/operator_names.py +93 -0
  29. tnfr/config/operator_names.pyi +28 -0
  30. tnfr/config/presets.py +84 -0
  31. tnfr/config/presets.pyi +7 -0
  32. tnfr/constants/__init__.py +80 -29
  33. tnfr/constants/__init__.pyi +92 -0
  34. tnfr/constants/aliases.py +31 -0
  35. tnfr/constants/core.py +4 -4
  36. tnfr/constants/core.pyi +17 -0
  37. tnfr/constants/init.py +1 -1
  38. tnfr/constants/init.pyi +12 -0
  39. tnfr/constants/metric.py +7 -15
  40. tnfr/constants/metric.pyi +19 -0
  41. tnfr/dynamics/__init__.py +165 -633
  42. tnfr/dynamics/__init__.pyi +82 -0
  43. tnfr/dynamics/adaptation.py +267 -0
  44. tnfr/dynamics/aliases.py +23 -0
  45. tnfr/dynamics/coordination.py +385 -0
  46. tnfr/dynamics/dnfr.py +2283 -400
  47. tnfr/dynamics/dnfr.pyi +24 -0
  48. tnfr/dynamics/integrators.py +406 -98
  49. tnfr/dynamics/integrators.pyi +34 -0
  50. tnfr/dynamics/runtime.py +881 -0
  51. tnfr/dynamics/sampling.py +10 -5
  52. tnfr/dynamics/sampling.pyi +7 -0
  53. tnfr/dynamics/selectors.py +719 -0
  54. tnfr/execution.py +70 -48
  55. tnfr/execution.pyi +45 -0
  56. tnfr/flatten.py +13 -9
  57. tnfr/flatten.pyi +21 -0
  58. tnfr/gamma.py +66 -53
  59. tnfr/gamma.pyi +34 -0
  60. tnfr/glyph_history.py +110 -52
  61. tnfr/glyph_history.pyi +35 -0
  62. tnfr/glyph_runtime.py +16 -0
  63. tnfr/glyph_runtime.pyi +9 -0
  64. tnfr/immutable.py +69 -28
  65. tnfr/immutable.pyi +34 -0
  66. tnfr/initialization.py +16 -16
  67. tnfr/initialization.pyi +65 -0
  68. tnfr/io.py +6 -240
  69. tnfr/io.pyi +16 -0
  70. tnfr/locking.pyi +7 -0
  71. tnfr/mathematics/__init__.py +81 -0
  72. tnfr/mathematics/backend.py +426 -0
  73. tnfr/mathematics/dynamics.py +398 -0
  74. tnfr/mathematics/epi.py +254 -0
  75. tnfr/mathematics/generators.py +222 -0
  76. tnfr/mathematics/metrics.py +119 -0
  77. tnfr/mathematics/operators.py +233 -0
  78. tnfr/mathematics/operators_factory.py +71 -0
  79. tnfr/mathematics/projection.py +78 -0
  80. tnfr/mathematics/runtime.py +173 -0
  81. tnfr/mathematics/spaces.py +247 -0
  82. tnfr/mathematics/transforms.py +292 -0
  83. tnfr/metrics/__init__.py +10 -10
  84. tnfr/metrics/__init__.pyi +20 -0
  85. tnfr/metrics/coherence.py +993 -324
  86. tnfr/metrics/common.py +23 -16
  87. tnfr/metrics/common.pyi +46 -0
  88. tnfr/metrics/core.py +251 -35
  89. tnfr/metrics/core.pyi +13 -0
  90. tnfr/metrics/diagnosis.py +708 -111
  91. tnfr/metrics/diagnosis.pyi +85 -0
  92. tnfr/metrics/export.py +27 -15
  93. tnfr/metrics/glyph_timing.py +232 -42
  94. tnfr/metrics/reporting.py +33 -22
  95. tnfr/metrics/reporting.pyi +12 -0
  96. tnfr/metrics/sense_index.py +987 -43
  97. tnfr/metrics/sense_index.pyi +9 -0
  98. tnfr/metrics/trig.py +214 -23
  99. tnfr/metrics/trig.pyi +13 -0
  100. tnfr/metrics/trig_cache.py +115 -22
  101. tnfr/metrics/trig_cache.pyi +10 -0
  102. tnfr/node.py +542 -136
  103. tnfr/node.pyi +178 -0
  104. tnfr/observers.py +152 -35
  105. tnfr/observers.pyi +31 -0
  106. tnfr/ontosim.py +23 -19
  107. tnfr/ontosim.pyi +28 -0
  108. tnfr/operators/__init__.py +601 -82
  109. tnfr/operators/__init__.pyi +45 -0
  110. tnfr/operators/definitions.py +513 -0
  111. tnfr/operators/definitions.pyi +78 -0
  112. tnfr/operators/grammar.py +760 -0
  113. tnfr/operators/jitter.py +107 -38
  114. tnfr/operators/jitter.pyi +11 -0
  115. tnfr/operators/registry.py +75 -0
  116. tnfr/operators/registry.pyi +13 -0
  117. tnfr/operators/remesh.py +149 -88
  118. tnfr/py.typed +0 -0
  119. tnfr/rng.py +46 -143
  120. tnfr/rng.pyi +14 -0
  121. tnfr/schemas/__init__.py +8 -0
  122. tnfr/schemas/grammar.json +94 -0
  123. tnfr/selector.py +25 -19
  124. tnfr/selector.pyi +19 -0
  125. tnfr/sense.py +72 -62
  126. tnfr/sense.pyi +23 -0
  127. tnfr/structural.py +522 -262
  128. tnfr/structural.pyi +69 -0
  129. tnfr/telemetry/__init__.py +35 -0
  130. tnfr/telemetry/cache_metrics.py +226 -0
  131. tnfr/telemetry/nu_f.py +423 -0
  132. tnfr/telemetry/nu_f.pyi +123 -0
  133. tnfr/telemetry/verbosity.py +37 -0
  134. tnfr/tokens.py +1 -3
  135. tnfr/tokens.pyi +36 -0
  136. tnfr/trace.py +270 -113
  137. tnfr/trace.pyi +40 -0
  138. tnfr/types.py +574 -6
  139. tnfr/types.pyi +331 -0
  140. tnfr/units.py +69 -0
  141. tnfr/units.pyi +16 -0
  142. tnfr/utils/__init__.py +217 -0
  143. tnfr/utils/__init__.pyi +202 -0
  144. tnfr/utils/cache.py +2395 -0
  145. tnfr/utils/cache.pyi +468 -0
  146. tnfr/utils/chunks.py +104 -0
  147. tnfr/utils/chunks.pyi +21 -0
  148. tnfr/{collections_utils.py → utils/data.py} +147 -90
  149. tnfr/utils/data.pyi +64 -0
  150. tnfr/utils/graph.py +85 -0
  151. tnfr/utils/graph.pyi +10 -0
  152. tnfr/utils/init.py +770 -0
  153. tnfr/utils/init.pyi +78 -0
  154. tnfr/utils/io.py +456 -0
  155. tnfr/{helpers → utils}/numeric.py +51 -24
  156. tnfr/utils/numeric.pyi +21 -0
  157. tnfr/validation/__init__.py +113 -0
  158. tnfr/validation/__init__.pyi +77 -0
  159. tnfr/validation/compatibility.py +95 -0
  160. tnfr/validation/compatibility.pyi +6 -0
  161. tnfr/validation/grammar.py +71 -0
  162. tnfr/validation/grammar.pyi +40 -0
  163. tnfr/validation/graph.py +138 -0
  164. tnfr/validation/graph.pyi +17 -0
  165. tnfr/validation/rules.py +281 -0
  166. tnfr/validation/rules.pyi +55 -0
  167. tnfr/validation/runtime.py +263 -0
  168. tnfr/validation/runtime.pyi +31 -0
  169. tnfr/validation/soft_filters.py +170 -0
  170. tnfr/validation/soft_filters.pyi +37 -0
  171. tnfr/validation/spectral.py +159 -0
  172. tnfr/validation/spectral.pyi +46 -0
  173. tnfr/validation/syntax.py +40 -0
  174. tnfr/validation/syntax.pyi +10 -0
  175. tnfr/validation/window.py +39 -0
  176. tnfr/validation/window.pyi +1 -0
  177. tnfr/viz/__init__.py +9 -0
  178. tnfr/viz/matplotlib.py +246 -0
  179. tnfr-7.0.0.dist-info/METADATA +179 -0
  180. tnfr-7.0.0.dist-info/RECORD +185 -0
  181. {tnfr-4.5.2.dist-info → tnfr-7.0.0.dist-info}/licenses/LICENSE.md +1 -1
  182. tnfr/grammar.py +0 -344
  183. tnfr/graph_utils.py +0 -84
  184. tnfr/helpers/__init__.py +0 -71
  185. tnfr/import_utils.py +0 -228
  186. tnfr/json_utils.py +0 -162
  187. tnfr/logging_utils.py +0 -116
  188. tnfr/presets.py +0 -60
  189. tnfr/validators.py +0 -84
  190. tnfr/value_utils.py +0 -59
  191. tnfr-4.5.2.dist-info/METADATA +0 -379
  192. tnfr-4.5.2.dist-info/RECORD +0 -67
  193. {tnfr-4.5.2.dist-info → tnfr-7.0.0.dist-info}/WHEEL +0 -0
  194. {tnfr-4.5.2.dist-info → tnfr-7.0.0.dist-info}/entry_points.txt +0 -0
  195. {tnfr-4.5.2.dist-info → tnfr-7.0.0.dist-info}/top_level.txt +0 -0
tnfr/sense.py CHANGED
@@ -1,31 +1,31 @@
1
1
  """Sense calculations."""
2
2
 
3
3
  from __future__ import annotations
4
- from typing import TypeVar
5
- from collections.abc import Iterable, Mapping
4
+
6
5
  import math
7
6
  from collections import Counter
7
+ from collections.abc import Iterable, Iterator, Mapping
8
8
  from itertools import tee
9
+ from typing import Any, Callable, TypeVar
9
10
 
10
- import networkx as nx # type: ignore[import-untyped]
11
+ import networkx as nx
11
12
 
12
- from .constants import get_aliases, get_graph_param
13
13
  from .alias import get_attr
14
- from .helpers.numeric import clamp01, kahan_sum_nd
15
- from .import_utils import get_numpy
16
14
  from .callback_utils import CallbackEvent, callback_manager
17
- from .glyph_history import (
18
- ensure_history,
19
- last_glyph,
20
- count_glyphs,
21
- append_metric,
22
- )
23
- from .constants_glyphs import (
15
+ from .config.constants import (
24
16
  ANGLE_MAP,
25
17
  GLYPHS_CANONICAL,
26
18
  )
19
+ from .constants import get_graph_param
20
+ from .constants.aliases import ALIAS_EPI, ALIAS_SI
21
+ from .glyph_history import append_metric, count_glyphs, ensure_history
22
+ from .glyph_runtime import last_glyph
23
+ from .utils import clamp01, kahan_sum_nd
24
+ from .types import NodeId, SigmaVector, TNFRGraph
25
+ from .utils import get_numpy
26
+
27
27
  # -------------------------
28
- # Canon: orden circular de glyphs y ángulos
28
+ # Canon: circular glyph order and angles
29
29
  # -------------------------
30
30
 
31
31
  GLYPH_UNITS: dict[str, complex] = {
@@ -45,7 +45,7 @@ __all__ = (
45
45
  )
46
46
 
47
47
  # -------------------------
48
- # Utilidades básicas
48
+ # Basic utilities
49
49
  # -------------------------
50
50
 
51
51
 
@@ -58,7 +58,7 @@ def _resolve_glyph(g: str, mapping: Mapping[str, T]) -> T:
58
58
  try:
59
59
  return mapping[g]
60
60
  except KeyError as e: # pragma: no cover - small helper
61
- raise KeyError(f"Glyph desconocido: {g}") from e
61
+ raise KeyError(f"Unknown glyph: {g}") from e
62
62
 
63
63
 
64
64
  def glyph_angle(g: str) -> float:
@@ -73,20 +73,19 @@ def glyph_unit(g: str) -> complex:
73
73
  return _resolve_glyph(g, GLYPH_UNITS)
74
74
 
75
75
 
76
- ALIAS_SI = get_aliases("SI")
77
- ALIAS_EPI = get_aliases("EPI")
78
-
79
- MODE_FUNCS = {
76
+ MODE_FUNCS: dict[str, Callable[[Mapping[str, Any]], float]] = {
80
77
  "Si": lambda nd: clamp01(get_attr(nd, ALIAS_SI, 0.5)),
81
78
  "EPI": lambda nd: max(0.0, get_attr(nd, ALIAS_EPI, 0.0)),
82
79
  }
83
80
 
84
81
 
85
- def _weight(nd, mode: str) -> float:
82
+ def _weight(nd: Mapping[str, Any], mode: str) -> float:
86
83
  return MODE_FUNCS.get(mode, lambda _: 1.0)(nd)
87
84
 
88
85
 
89
- def _node_weight(nd, weight_mode: str) -> tuple[str, float, complex] | None:
86
+ def _node_weight(
87
+ nd: Mapping[str, Any], weight_mode: str
88
+ ) -> tuple[str, float, complex] | None:
90
89
  """Return ``(glyph, weight, weighted_unit)`` or ``None`` if no glyph."""
91
90
  g = last_glyph(nd)
92
91
  if not g:
@@ -96,7 +95,7 @@ def _node_weight(nd, weight_mode: str) -> tuple[str, float, complex] | None:
96
95
  return g, w, z
97
96
 
98
97
 
99
- def _sigma_cfg(G):
98
+ def _sigma_cfg(G: TNFRGraph) -> dict[str, Any]:
100
99
  return get_graph_param(G, "SIGMA", dict)
101
100
 
102
101
 
@@ -110,7 +109,7 @@ def _to_complex(val: complex | float | int) -> complex:
110
109
  raise TypeError("values must be an iterable of real or complex numbers")
111
110
 
112
111
 
113
- def _empty_sigma(fallback_angle: float) -> dict[str, float]:
112
+ def _empty_sigma(fallback_angle: float) -> SigmaVector:
114
113
  """Return an empty σ-vector with ``fallback_angle``.
115
114
 
116
115
  Helps centralise the default structure returned when no values are
@@ -127,14 +126,14 @@ def _empty_sigma(fallback_angle: float) -> dict[str, float]:
127
126
 
128
127
 
129
128
  # -------------------------
130
- # σ por nodo y σ global
129
+ # σ per node and global σ
131
130
  # -------------------------
132
131
 
133
132
 
134
133
  def _sigma_from_iterable(
135
134
  values: Iterable[complex | float | int] | complex | float | int,
136
135
  fallback_angle: float = 0.0,
137
- ) -> dict[str, float]:
136
+ ) -> SigmaVector:
138
137
  """Normalise vectors in the σ-plane.
139
138
 
140
139
  ``values`` may contain complex or real numbers; real inputs are promoted to
@@ -142,7 +141,9 @@ def _sigma_from_iterable(
142
141
  number of processed values under the ``"n"`` key.
143
142
  """
144
143
 
145
- if isinstance(values, Iterable) and not isinstance(values, (str, bytes, bytearray, Mapping)):
144
+ if isinstance(values, Iterable) and not isinstance(
145
+ values, (str, bytes, bytearray, Mapping)
146
+ ):
146
147
  iterator = iter(values)
147
148
  else:
148
149
  iterator = iter((values,))
@@ -159,15 +160,15 @@ def _sigma_from_iterable(
159
160
  mag = float(np.hypot(x, y))
160
161
  ang = float(np.arctan2(y, x)) if mag > 0 else float(fallback_angle)
161
162
  return {
162
- "x": x,
163
- "y": y,
164
- "mag": mag,
165
- "angle": ang,
166
- "n": cnt,
163
+ "x": float(x),
164
+ "y": float(y),
165
+ "mag": float(mag),
166
+ "angle": float(ang),
167
+ "n": int(cnt),
167
168
  }
168
169
  cnt = 0
169
170
 
170
- def pair_iter():
171
+ def pair_iter() -> Iterator[tuple[float, float]]:
171
172
  nonlocal cnt
172
173
  for val in iterator:
173
174
  z = _to_complex(val)
@@ -188,24 +189,30 @@ def _sigma_from_iterable(
188
189
  "y": float(y),
189
190
  "mag": float(mag),
190
191
  "angle": float(ang),
191
- "n": cnt,
192
+ "n": int(cnt),
192
193
  }
193
194
 
194
195
 
195
- def _ema_update(
196
- prev: dict[str, float], current: dict[str, float], alpha: float
197
- ) -> dict[str, float]:
196
+ def _ema_update(prev: SigmaVector, current: SigmaVector, alpha: float) -> SigmaVector:
198
197
  """Exponential moving average update for σ vectors."""
199
198
  x = (1 - alpha) * prev["x"] + alpha * current["x"]
200
199
  y = (1 - alpha) * prev["y"] + alpha * current["y"]
201
200
  mag = math.hypot(x, y)
202
201
  ang = math.atan2(y, x)
203
- return {"x": x, "y": y, "mag": mag, "angle": ang, "n": current.get("n", 0)}
202
+ return {
203
+ "x": float(x),
204
+ "y": float(y),
205
+ "mag": float(mag),
206
+ "angle": float(ang),
207
+ "n": int(current["n"]),
208
+ }
204
209
 
205
210
 
206
211
  def _sigma_from_nodes(
207
- nodes: Iterable[dict], weight_mode: str, fallback_angle: float = 0.0
208
- ) -> tuple[dict[str, float], list[tuple[str, float, complex]]]:
212
+ nodes: Iterable[Mapping[str, Any]],
213
+ weight_mode: str,
214
+ fallback_angle: float = 0.0,
215
+ ) -> tuple[SigmaVector, list[tuple[str, float, complex]]]:
209
216
  """Aggregate weighted glyph vectors for ``nodes``.
210
217
 
211
218
  Returns the aggregated σ vector and the list of ``(glyph, weight, vector)``
@@ -218,8 +225,10 @@ def _sigma_from_nodes(
218
225
 
219
226
 
220
227
  def sigma_vector_node(
221
- G, n, weight_mode: str | None = None
222
- ) -> dict[str, float] | None:
228
+ G: TNFRGraph, n: NodeId, weight_mode: str | None = None
229
+ ) -> SigmaVector | None:
230
+ """Return the σ vector for node ``n`` using the configured weighting."""
231
+
223
232
  cfg = _sigma_cfg(G)
224
233
  nd = G.nodes[n]
225
234
  weight_mode = weight_mode or cfg.get("weight", "Si")
@@ -229,11 +238,12 @@ def sigma_vector_node(
229
238
  g, w, _ = nws[0]
230
239
  if sv["mag"] == 0:
231
240
  sv["angle"] = glyph_angle(g)
232
- sv.update({"glyph": g, "w": float(w)})
241
+ sv["glyph"] = g
242
+ sv["w"] = float(w)
233
243
  return sv
234
244
 
235
245
 
236
- def sigma_vector(dist: dict[str, float]) -> dict[str, float]:
246
+ def sigma_vector(dist: Mapping[str, float]) -> SigmaVector:
237
247
  """Compute Σ⃗ from a glyph distribution.
238
248
 
239
249
  ``dist`` may contain raw counts or proportions. All ``(glyph, weight)``
@@ -246,8 +256,8 @@ def sigma_vector(dist: dict[str, float]) -> dict[str, float]:
246
256
 
247
257
 
248
258
  def sigma_vector_from_graph(
249
- G: nx.Graph, weight_mode: str | None = None
250
- ) -> dict[str, float]:
259
+ G: TNFRGraph, weight_mode: str | None = None
260
+ ) -> SigmaVector:
251
261
  """Global vector in the σ sense plane for a graph.
252
262
 
253
263
  Parameters
@@ -264,34 +274,34 @@ def sigma_vector_from_graph(
264
274
  """
265
275
 
266
276
  if not isinstance(G, nx.Graph):
267
- raise TypeError("sigma_vector_from_graph requiere un networkx.Graph")
277
+ raise TypeError("sigma_vector_from_graph requires a networkx.Graph")
268
278
 
269
279
  cfg = _sigma_cfg(G)
270
280
  weight_mode = weight_mode or cfg.get("weight", "Si")
271
- sv, _ = _sigma_from_nodes(
272
- (nd for _, nd in G.nodes(data=True)), weight_mode
273
- )
281
+ sv, _ = _sigma_from_nodes((nd for _, nd in G.nodes(data=True)), weight_mode)
274
282
  return sv
275
283
 
276
284
 
277
285
  # -------------------------
278
- # Historia / series
286
+ # History / series
279
287
  # -------------------------
280
288
 
281
289
 
282
- def push_sigma_snapshot(G, t: float | None = None) -> None:
290
+ def push_sigma_snapshot(G: TNFRGraph, t: float | None = None) -> None:
291
+ """Record a global σ snapshot (and optional per-node traces) for ``G``."""
292
+
283
293
  cfg = _sigma_cfg(G)
284
294
  if not cfg.get("enabled", True):
285
295
  return
286
296
 
287
- # Cache local de la historia para evitar llamadas repetidas
297
+ # Local history cache to avoid repeated lookups
288
298
  hist = ensure_history(G)
289
299
  key = cfg.get("history_key", "sigma_global")
290
300
 
291
301
  weight_mode = cfg.get("weight", "Si")
292
302
  sv = sigma_vector_from_graph(G, weight_mode)
293
303
 
294
- # Suavizado exponencial (EMA) opcional
304
+ # Optional exponential smoothing (EMA)
295
305
  alpha = float(cfg.get("smooth", 0.0))
296
306
  if alpha > 0 and hist.get(key):
297
307
  sv = _ema_update(hist[key][-1], sv, alpha)
@@ -301,11 +311,11 @@ def push_sigma_snapshot(G, t: float | None = None) -> None:
301
311
 
302
312
  append_metric(hist, key, sv)
303
313
 
304
- # Conteo de glyphs por paso (útil para rosa glífica)
314
+ # Glyph count per step (useful for the glyph rose)
305
315
  counts = count_glyphs(G, last_only=True)
306
316
  append_metric(hist, "sigma_counts", {"t": current_t, **counts})
307
317
 
308
- # Trayectoria por nodo (opcional)
318
+ # Optional per-node trajectory
309
319
  if cfg.get("per_node", False):
310
320
  per = hist.setdefault("sigma_per_node", {})
311
321
  for n, nd in G.nodes(data=True):
@@ -317,11 +327,13 @@ def push_sigma_snapshot(G, t: float | None = None) -> None:
317
327
 
318
328
 
319
329
  # -------------------------
320
- # Registro como callback automático (after_step)
330
+ # Register as an automatic callback (after_step)
321
331
  # -------------------------
322
332
 
323
333
 
324
- def register_sigma_callback(G) -> None:
334
+ def register_sigma_callback(G: TNFRGraph) -> None:
335
+ """Attach :func:`push_sigma_snapshot` to the ``AFTER_STEP`` callback bus."""
336
+
325
337
  callback_manager.register_callback(
326
338
  G,
327
339
  event=CallbackEvent.AFTER_STEP.value,
@@ -330,7 +342,7 @@ def register_sigma_callback(G) -> None:
330
342
  )
331
343
 
332
344
 
333
- def sigma_rose(G, steps: int | None = None) -> dict[str, int]:
345
+ def sigma_rose(G: TNFRGraph, steps: int | None = None) -> dict[str, int]:
334
346
  """Histogram of glyphs in the last ``steps`` steps (or all)."""
335
347
  hist = ensure_history(G)
336
348
  counts = hist.get("sigma_counts", [])
@@ -340,9 +352,7 @@ def sigma_rose(G, steps: int | None = None) -> dict[str, int]:
340
352
  steps = int(steps)
341
353
  if steps < 0:
342
354
  raise ValueError("steps must be non-negative")
343
- rows = (
344
- counts if steps >= len(counts) else counts[-steps:]
345
- ) # noqa: E203
355
+ rows = counts if steps >= len(counts) else counts[-steps:] # noqa: E203
346
356
  else:
347
357
  rows = counts
348
358
  counter = Counter()
tnfr/sense.pyi ADDED
@@ -0,0 +1,23 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Mapping
4
+ from typing import Optional
5
+
6
+ from .types import NodeId, SigmaVector, TNFRGraph
7
+
8
+ __all__: tuple[str, ...]
9
+
10
+ GLYPH_UNITS: dict[str, complex]
11
+
12
+ def glyph_angle(g: str) -> float: ...
13
+ def glyph_unit(g: str) -> complex: ...
14
+ def push_sigma_snapshot(G: TNFRGraph, t: Optional[float] = None) -> None: ...
15
+ def register_sigma_callback(G: TNFRGraph) -> None: ...
16
+ def sigma_rose(G: TNFRGraph, steps: Optional[int] = None) -> dict[str, int]: ...
17
+ def sigma_vector(dist: Mapping[str, float]) -> SigmaVector: ...
18
+ def sigma_vector_from_graph(
19
+ G: TNFRGraph, weight_mode: Optional[str] = None
20
+ ) -> SigmaVector: ...
21
+ def sigma_vector_node(
22
+ G: TNFRGraph, n: NodeId, weight_mode: Optional[str] = None
23
+ ) -> Optional[SigmaVector]: ...