tnfr 4.5.2__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 (161) hide show
  1. tnfr/__init__.py +228 -49
  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 +106 -21
  7. tnfr/alias.pyi +140 -0
  8. tnfr/cache.py +666 -512
  9. tnfr/cache.pyi +232 -0
  10. tnfr/callback_utils.py +2 -9
  11. tnfr/callback_utils.pyi +105 -0
  12. tnfr/cli/__init__.py +21 -7
  13. tnfr/cli/__init__.pyi +47 -0
  14. tnfr/cli/arguments.py +42 -20
  15. tnfr/cli/arguments.pyi +33 -0
  16. tnfr/cli/execution.py +54 -20
  17. tnfr/cli/execution.pyi +80 -0
  18. tnfr/cli/utils.py +0 -2
  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.py → config/init.py} +11 -7
  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 +78 -24
  31. tnfr/constants/__init__.pyi +104 -0
  32. tnfr/constants/core.py +1 -2
  33. tnfr/constants/core.pyi +17 -0
  34. tnfr/constants/init.pyi +12 -0
  35. tnfr/constants/metric.py +4 -12
  36. tnfr/constants/metric.pyi +19 -0
  37. tnfr/constants_glyphs.py +9 -91
  38. tnfr/constants_glyphs.pyi +12 -0
  39. tnfr/dynamics/__init__.py +112 -634
  40. tnfr/dynamics/__init__.pyi +83 -0
  41. tnfr/dynamics/adaptation.py +201 -0
  42. tnfr/dynamics/aliases.py +22 -0
  43. tnfr/dynamics/coordination.py +343 -0
  44. tnfr/dynamics/dnfr.py +1936 -354
  45. tnfr/dynamics/dnfr.pyi +33 -0
  46. tnfr/dynamics/integrators.py +369 -75
  47. tnfr/dynamics/integrators.pyi +35 -0
  48. tnfr/dynamics/runtime.py +521 -0
  49. tnfr/dynamics/sampling.py +8 -5
  50. tnfr/dynamics/sampling.pyi +7 -0
  51. tnfr/dynamics/selectors.py +680 -0
  52. tnfr/execution.py +56 -41
  53. tnfr/execution.pyi +65 -0
  54. tnfr/flatten.py +7 -7
  55. tnfr/flatten.pyi +28 -0
  56. tnfr/gamma.py +54 -37
  57. tnfr/gamma.pyi +40 -0
  58. tnfr/glyph_history.py +85 -38
  59. tnfr/glyph_history.pyi +53 -0
  60. tnfr/grammar.py +19 -338
  61. tnfr/grammar.pyi +13 -0
  62. tnfr/helpers/__init__.py +110 -30
  63. tnfr/helpers/__init__.pyi +66 -0
  64. tnfr/helpers/numeric.py +1 -0
  65. tnfr/helpers/numeric.pyi +12 -0
  66. tnfr/immutable.py +55 -19
  67. tnfr/immutable.pyi +37 -0
  68. tnfr/initialization.py +12 -10
  69. tnfr/initialization.pyi +73 -0
  70. tnfr/io.py +99 -34
  71. tnfr/io.pyi +11 -0
  72. tnfr/locking.pyi +7 -0
  73. tnfr/metrics/__init__.pyi +20 -0
  74. tnfr/metrics/coherence.py +934 -294
  75. tnfr/metrics/common.py +1 -3
  76. tnfr/metrics/common.pyi +15 -0
  77. tnfr/metrics/core.py +192 -34
  78. tnfr/metrics/core.pyi +13 -0
  79. tnfr/metrics/diagnosis.py +707 -101
  80. tnfr/metrics/diagnosis.pyi +89 -0
  81. tnfr/metrics/export.py +27 -13
  82. tnfr/metrics/glyph_timing.py +218 -38
  83. tnfr/metrics/reporting.py +22 -18
  84. tnfr/metrics/reporting.pyi +12 -0
  85. tnfr/metrics/sense_index.py +199 -25
  86. tnfr/metrics/sense_index.pyi +9 -0
  87. tnfr/metrics/trig.py +53 -18
  88. tnfr/metrics/trig.pyi +12 -0
  89. tnfr/metrics/trig_cache.py +3 -7
  90. tnfr/metrics/trig_cache.pyi +10 -0
  91. tnfr/node.py +148 -125
  92. tnfr/node.pyi +161 -0
  93. tnfr/observers.py +44 -30
  94. tnfr/observers.pyi +46 -0
  95. tnfr/ontosim.py +14 -13
  96. tnfr/ontosim.pyi +33 -0
  97. tnfr/operators/__init__.py +84 -52
  98. tnfr/operators/__init__.pyi +31 -0
  99. tnfr/operators/definitions.py +181 -0
  100. tnfr/operators/definitions.pyi +92 -0
  101. tnfr/operators/jitter.py +86 -23
  102. tnfr/operators/jitter.pyi +11 -0
  103. tnfr/operators/registry.py +80 -0
  104. tnfr/operators/registry.pyi +15 -0
  105. tnfr/operators/remesh.py +141 -57
  106. tnfr/presets.py +9 -54
  107. tnfr/presets.pyi +7 -0
  108. tnfr/py.typed +0 -0
  109. tnfr/rng.py +259 -73
  110. tnfr/rng.pyi +14 -0
  111. tnfr/selector.py +24 -17
  112. tnfr/selector.pyi +19 -0
  113. tnfr/sense.py +55 -43
  114. tnfr/sense.pyi +30 -0
  115. tnfr/structural.py +44 -267
  116. tnfr/structural.pyi +46 -0
  117. tnfr/telemetry/__init__.py +13 -0
  118. tnfr/telemetry/verbosity.py +37 -0
  119. tnfr/tokens.py +3 -2
  120. tnfr/tokens.pyi +41 -0
  121. tnfr/trace.py +272 -82
  122. tnfr/trace.pyi +68 -0
  123. tnfr/types.py +345 -6
  124. tnfr/types.pyi +145 -0
  125. tnfr/utils/__init__.py +158 -0
  126. tnfr/utils/__init__.pyi +133 -0
  127. tnfr/utils/cache.py +755 -0
  128. tnfr/utils/cache.pyi +156 -0
  129. tnfr/{collections_utils.py → utils/data.py} +57 -90
  130. tnfr/utils/data.pyi +73 -0
  131. tnfr/utils/graph.py +87 -0
  132. tnfr/utils/graph.pyi +10 -0
  133. tnfr/utils/init.py +746 -0
  134. tnfr/utils/init.pyi +85 -0
  135. tnfr/{json_utils.py → utils/io.py} +13 -18
  136. tnfr/utils/io.pyi +10 -0
  137. tnfr/utils/validators.py +130 -0
  138. tnfr/utils/validators.pyi +19 -0
  139. tnfr/validation/__init__.py +25 -0
  140. tnfr/validation/__init__.pyi +17 -0
  141. tnfr/validation/compatibility.py +59 -0
  142. tnfr/validation/compatibility.pyi +8 -0
  143. tnfr/validation/grammar.py +149 -0
  144. tnfr/validation/grammar.pyi +11 -0
  145. tnfr/validation/rules.py +194 -0
  146. tnfr/validation/rules.pyi +18 -0
  147. tnfr/validation/syntax.py +151 -0
  148. tnfr/validation/syntax.pyi +7 -0
  149. tnfr-6.0.0.dist-info/METADATA +135 -0
  150. tnfr-6.0.0.dist-info/RECORD +157 -0
  151. tnfr/graph_utils.py +0 -84
  152. tnfr/import_utils.py +0 -228
  153. tnfr/logging_utils.py +0 -116
  154. tnfr/validators.py +0 -84
  155. tnfr/value_utils.py +0 -59
  156. tnfr-4.5.2.dist-info/METADATA +0 -379
  157. tnfr-4.5.2.dist-info/RECORD +0 -67
  158. {tnfr-4.5.2.dist-info → tnfr-6.0.0.dist-info}/WHEEL +0 -0
  159. {tnfr-4.5.2.dist-info → tnfr-6.0.0.dist-info}/entry_points.txt +0 -0
  160. {tnfr-4.5.2.dist-info → tnfr-6.0.0.dist-info}/licenses/LICENSE.md +0 -0
  161. {tnfr-4.5.2.dist-info → tnfr-6.0.0.dist-info}/top_level.txt +0 -0
tnfr/execution.py CHANGED
@@ -4,28 +4,25 @@ from __future__ import annotations
4
4
 
5
5
  from collections import deque
6
6
  from collections.abc import Callable, Iterable, Sequence
7
- from typing import Any, Optional
7
+ from typing import Any, Optional, cast
8
8
 
9
- import networkx as nx # networkx is used at runtime
9
+ from ._compat import TypeAlias
10
10
 
11
- from .collections_utils import (
12
- MAX_MATERIALIZE_DEFAULT,
13
- ensure_collection,
14
- is_non_string_sequence,
15
- )
11
+ from .utils import MAX_MATERIALIZE_DEFAULT, ensure_collection, is_non_string_sequence
16
12
  from .constants import get_param
17
13
  from .dynamics import step
18
14
  from .flatten import _flatten
19
15
  from .glyph_history import ensure_history
20
- from .grammar import apply_glyph_with_grammar
16
+ from .validation.grammar import apply_glyph_with_grammar
21
17
  from .tokens import OpTag, TARGET, THOL, WAIT, Token
22
- from .types import Glyph
18
+ from .types import Glyph, NodeId, TNFRGraph
23
19
 
24
- Node = Any
25
- AdvanceFn = Callable[[Any], None]
20
+ AdvanceFn = Callable[[TNFRGraph], None]
21
+ TraceEntry = dict[str, Any]
22
+ ProgramTrace: TypeAlias = deque[TraceEntry]
26
23
  HandlerFn = Callable[
27
- [nx.Graph, Any, Optional[list[Node]], deque, AdvanceFn],
28
- Optional[list[Node]],
24
+ [TNFRGraph, Any, Optional[Sequence[NodeId]], ProgramTrace, AdvanceFn],
25
+ Optional[Sequence[NodeId]],
29
26
  ]
30
27
 
31
28
  __all__ = [
@@ -45,7 +42,7 @@ __all__ = [
45
42
  ]
46
43
 
47
44
 
48
- CANONICAL_PRESET_NAME = "ejemplo_canonico"
45
+ CANONICAL_PRESET_NAME = "canonical_example"
49
46
  CANONICAL_PROGRAM_TOKENS: tuple[Token, ...] = (
50
47
  Glyph.SHA,
51
48
  Glyph.AL,
@@ -56,13 +53,13 @@ CANONICAL_PROGRAM_TOKENS: tuple[Token, ...] = (
56
53
  )
57
54
 
58
55
 
59
- def _window(G) -> int:
56
+ def _window(G: TNFRGraph) -> int:
60
57
  return int(get_param(G, "GLYPH_HYSTERESIS_WINDOW"))
61
58
 
62
59
 
63
60
  def _apply_glyph_to_targets(
64
- G, g: Glyph | str, nodes: Optional[Iterable[Node]] = None
65
- ):
61
+ G: TNFRGraph, g: Glyph | str, nodes: Optional[Iterable[NodeId]] = None
62
+ ) -> None:
66
63
  """Apply ``g`` to ``nodes`` (or all nodes) respecting the grammar."""
67
64
 
68
65
  nodes_iter = G.nodes() if nodes is None else nodes
@@ -70,22 +67,22 @@ def _apply_glyph_to_targets(
70
67
  apply_glyph_with_grammar(G, nodes_iter, g, w)
71
68
 
72
69
 
73
- def _advance(G, step_fn: AdvanceFn):
70
+ def _advance(G: TNFRGraph, step_fn: AdvanceFn) -> None:
74
71
  step_fn(G)
75
72
 
76
73
 
77
- def _record_trace(trace: deque, G, op: OpTag, **data) -> None:
74
+ def _record_trace(trace: ProgramTrace, G: TNFRGraph, op: OpTag, **data: Any) -> None:
78
75
  trace.append({"t": float(G.graph.get("_t", 0.0)), "op": op.name, **data})
79
76
 
80
77
 
81
78
  def _advance_and_record(
82
- G,
83
- trace: deque,
79
+ G: TNFRGraph,
80
+ trace: ProgramTrace,
84
81
  label: OpTag,
85
82
  step_fn: AdvanceFn,
86
83
  *,
87
84
  times: int = 1,
88
- **data,
85
+ **data: Any,
89
86
  ) -> None:
90
87
  for _ in range(times):
91
88
  _advance(G, step_fn)
@@ -93,40 +90,55 @@ def _advance_and_record(
93
90
 
94
91
 
95
92
  def _handle_target(
96
- G, payload: TARGET, _curr_target, trace: deque, _step_fn: AdvanceFn
97
- ):
93
+ G: TNFRGraph,
94
+ payload: TARGET,
95
+ _curr_target: Optional[Sequence[NodeId]],
96
+ trace: ProgramTrace,
97
+ _step_fn: AdvanceFn,
98
+ ) -> Sequence[NodeId]:
98
99
  """Handle a ``TARGET`` token and return the active node set."""
99
100
 
100
101
  nodes_src = G.nodes() if payload.nodes is None else payload.nodes
101
102
  nodes = ensure_collection(nodes_src, max_materialize=None)
102
- curr_target = nodes if is_non_string_sequence(nodes) else tuple(nodes)
103
+ if is_non_string_sequence(nodes):
104
+ curr_target = cast(Sequence[NodeId], nodes)
105
+ else:
106
+ curr_target = tuple(nodes)
103
107
  _record_trace(trace, G, OpTag.TARGET, n=len(curr_target))
104
108
  return curr_target
105
109
 
106
110
 
107
111
  def _handle_wait(
108
- G, steps: int, curr_target, trace: deque, step_fn: AdvanceFn
109
- ):
112
+ G: TNFRGraph,
113
+ steps: int,
114
+ curr_target: Optional[Sequence[NodeId]],
115
+ trace: ProgramTrace,
116
+ step_fn: AdvanceFn,
117
+ ) -> Optional[Sequence[NodeId]]:
110
118
  _advance_and_record(G, trace, OpTag.WAIT, step_fn, times=steps, k=steps)
111
119
  return curr_target
112
120
 
113
121
 
114
122
  def _handle_glyph(
115
- G,
116
- g: str,
117
- curr_target,
118
- trace: deque,
123
+ G: TNFRGraph,
124
+ g: Glyph | str,
125
+ curr_target: Optional[Sequence[NodeId]],
126
+ trace: ProgramTrace,
119
127
  step_fn: AdvanceFn,
120
128
  label: OpTag = OpTag.GLYPH,
121
- ):
129
+ ) -> Optional[Sequence[NodeId]]:
122
130
  _apply_glyph_to_targets(G, g, curr_target)
123
131
  _advance_and_record(G, trace, label, step_fn, g=g)
124
132
  return curr_target
125
133
 
126
134
 
127
135
  def _handle_thol(
128
- G, g, curr_target, trace: deque, step_fn: AdvanceFn
129
- ):
136
+ G: TNFRGraph,
137
+ g: Glyph | str | None,
138
+ curr_target: Optional[Sequence[NodeId]],
139
+ trace: ProgramTrace,
140
+ step_fn: AdvanceFn,
141
+ ) -> Optional[Sequence[NodeId]]:
130
142
  return _handle_glyph(
131
143
  G, g or Glyph.THOL.value, curr_target, trace, step_fn, label=OpTag.THOL
132
144
  )
@@ -141,20 +153,23 @@ HANDLERS: dict[OpTag, HandlerFn] = {
141
153
 
142
154
 
143
155
  def play(
144
- G, sequence: Sequence[Token], step_fn: Optional[AdvanceFn] = None
156
+ G: TNFRGraph, sequence: Sequence[Token], step_fn: Optional[AdvanceFn] = None
145
157
  ) -> None:
146
158
  """Execute a canonical sequence on graph ``G``."""
147
159
 
148
160
  step_fn = step_fn or step
149
161
 
150
- curr_target: Optional[list[Node]] = None
162
+ curr_target: Optional[Sequence[NodeId]] = None
151
163
 
152
164
  history = ensure_history(G)
153
165
  maxlen = int(get_param(G, "PROGRAM_TRACE_MAXLEN"))
154
- trace = history.get("program_trace")
155
- if not isinstance(trace, deque) or trace.maxlen != maxlen:
156
- trace = deque(trace or [], maxlen=maxlen)
166
+ trace_obj = history.get("program_trace")
167
+ trace: ProgramTrace
168
+ if not isinstance(trace_obj, deque) or trace_obj.maxlen != maxlen:
169
+ trace = cast(ProgramTrace, deque(trace_obj or [], maxlen=maxlen))
157
170
  history["program_trace"] = trace
171
+ else:
172
+ trace = cast(ProgramTrace, trace_obj)
158
173
 
159
174
  for op, payload in _flatten(sequence):
160
175
  handler: HandlerFn | None = HANDLERS.get(op)
@@ -183,7 +198,7 @@ def block(
183
198
  return THOL(body=list(tokens), repeat=repeat, force_close=close)
184
199
 
185
200
 
186
- def target(nodes: Optional[Iterable[Node]] = None) -> TARGET:
201
+ def target(nodes: Optional[Iterable[NodeId]] = None) -> TARGET:
187
202
  return TARGET(nodes=nodes)
188
203
 
189
204
 
@@ -195,7 +210,7 @@ def basic_canonical_example() -> list[Token]:
195
210
  """Reference canonical sequence.
196
211
 
197
212
  Returns a copy of the canonical preset tokens to keep CLI defaults aligned
198
- with :func:`tnfr.presets.get_preset`.
213
+ with :func:`tnfr.config.presets.get_preset`.
199
214
  """
200
215
 
201
216
  return list(CANONICAL_PROGRAM_TOKENS)
tnfr/execution.pyi ADDED
@@ -0,0 +1,65 @@
1
+ from __future__ import annotations
2
+
3
+ from collections import deque
4
+ from collections.abc import Callable, Iterable, Sequence
5
+ from typing import Any, Optional
6
+
7
+ from ._compat import TypeAlias
8
+
9
+ from .tokens import OpTag, TARGET, THOL, WAIT, Token
10
+ from .types import Glyph, NodeId, TNFRGraph
11
+
12
+ __all__: list[str]
13
+
14
+
15
+ def __getattr__(name: str) -> Any: ...
16
+
17
+
18
+ AdvanceFn = Callable[[TNFRGraph], None]
19
+ TraceEntry = dict[str, Any]
20
+ ProgramTrace: TypeAlias = deque[TraceEntry]
21
+ HandlerFn = Callable[
22
+ [TNFRGraph, Any, Sequence[NodeId] | None, ProgramTrace, AdvanceFn],
23
+ Sequence[NodeId] | None,
24
+ ]
25
+
26
+ CANONICAL_PRESET_NAME: str
27
+ CANONICAL_PROGRAM_TOKENS: tuple[Token, ...]
28
+ HANDLERS: dict[OpTag, HandlerFn]
29
+
30
+
31
+ def _apply_glyph_to_targets(
32
+ G: TNFRGraph, g: Glyph | str, nodes: Iterable[NodeId] | None = ...
33
+ ) -> None: ...
34
+
35
+
36
+ def _record_trace(trace: ProgramTrace, G: TNFRGraph, op: OpTag, **data: Any) -> None: ...
37
+
38
+
39
+ def compile_sequence(
40
+ sequence: Iterable[Token] | Sequence[Token] | Any,
41
+ *,
42
+ max_materialize: int | None = ...,
43
+ ) -> list[tuple[OpTag, Any]]: ...
44
+
45
+
46
+ def play(
47
+ G: TNFRGraph, sequence: Sequence[Token], step_fn: Optional[AdvanceFn] = ...
48
+ ) -> None: ...
49
+
50
+
51
+ def seq(*tokens: Token) -> list[Token]: ...
52
+
53
+
54
+ def block(
55
+ *tokens: Token, repeat: int = ..., close: Glyph | None = ...
56
+ ) -> THOL: ...
57
+
58
+
59
+ def target(nodes: Iterable[NodeId] | None = ...) -> TARGET: ...
60
+
61
+
62
+ def wait(steps: int = ...) -> WAIT: ...
63
+
64
+
65
+ def basic_canonical_example() -> list[Token]: ...
tnfr/flatten.py CHANGED
@@ -7,14 +7,14 @@ from dataclasses import dataclass
7
7
  from itertools import chain
8
8
  from typing import Any, Callable
9
9
 
10
- from .collections_utils import (
10
+ from .utils import (
11
11
  MAX_MATERIALIZE_DEFAULT,
12
+ STRING_TYPES,
12
13
  ensure_collection,
13
14
  flatten_structure,
14
- STRING_TYPES,
15
15
  normalize_materialize_limit,
16
16
  )
17
- from .constants_glyphs import GLYPHS_CANONICAL_SET
17
+ from .config.constants import GLYPHS_CANONICAL_SET
18
18
  from .tokens import THOL, TARGET, WAIT, OpTag, THOL_SENTINEL, Token
19
19
  from .types import Glyph
20
20
 
@@ -126,7 +126,7 @@ class THOLEvaluator:
126
126
  def __iter__(self) -> "THOLEvaluator":
127
127
  return self
128
128
 
129
- def __next__(self):
129
+ def __next__(self) -> Token | object:
130
130
  if not self._started:
131
131
  self._started = True
132
132
  return THOL_SENTINEL
@@ -211,7 +211,7 @@ def _coerce_mapping_token(
211
211
  if isinstance(close, str):
212
212
  close_enum = Glyph.__members__.get(close)
213
213
  if close_enum is None:
214
- raise ValueError(f"Glyph de cierre desconocido: {close!r}")
214
+ raise ValueError(f"Unknown closing glyph: {close!r}")
215
215
  close = close_enum
216
216
  elif close is not None and not isinstance(close, Glyph):
217
217
  raise TypeError("THOL close glyph must be a Glyph or string name")
@@ -235,7 +235,7 @@ def parse_program_tokens(
235
235
 
236
236
  sequence = _iter_source(obj, max_materialize=max_materialize)
237
237
 
238
- def _expand(item: Any):
238
+ def _expand(item: Any) -> Iterable[Any] | None:
239
239
  if isinstance(item, Mapping):
240
240
  return (_coerce_mapping_token(item, max_materialize=max_materialize),)
241
241
  return None
@@ -259,7 +259,7 @@ def _flatten(
259
259
  ops: list[tuple[OpTag, Any]] = []
260
260
  sequence = _iter_source(seq, max_materialize=max_materialize)
261
261
 
262
- def _expand(item: Any):
262
+ def _expand(item: Any) -> Iterable[Any] | None:
263
263
  if isinstance(item, THOL):
264
264
  return THOLEvaluator(item, max_materialize=max_materialize)
265
265
  if isinstance(item, Mapping):
tnfr/flatten.pyi ADDED
@@ -0,0 +1,28 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Iterable, Iterator, Sequence
4
+ from typing import Any
5
+
6
+ from .tokens import THOL, Token
7
+
8
+ __all__: list[str]
9
+
10
+
11
+ def __getattr__(name: str) -> Any: ...
12
+
13
+
14
+ class THOLEvaluator(Iterator[Token | object]):
15
+ def __init__(
16
+ self, item: THOL, *, max_materialize: int | None = ...
17
+ ) -> None: ...
18
+
19
+ def __iter__(self) -> THOLEvaluator: ...
20
+
21
+ def __next__(self) -> Token | object: ...
22
+
23
+
24
+ def parse_program_tokens(
25
+ obj: Iterable[Any] | Sequence[Any] | Any,
26
+ *,
27
+ max_materialize: int | None = ...,
28
+ ) -> list[Token]: ...
tnfr/gamma.py CHANGED
@@ -9,16 +9,18 @@ from collections.abc import Mapping
9
9
  from functools import lru_cache
10
10
  from types import MappingProxyType
11
11
 
12
- from .constants import DEFAULTS, get_aliases
13
- from .alias import get_attr
14
- from .graph_utils import get_graph_mapping
15
- from .cache import edge_version_cache, node_set_checksum
16
- from .json_utils import json_dumps
17
- from .logging_utils import get_logger
12
+ from .constants import DEFAULTS
13
+ from .alias import get_theta_attr
14
+ from .types import GammaSpec, NodeId, TNFRGraph
15
+ from .utils import (
16
+ edge_version_cache,
17
+ get_graph_mapping,
18
+ get_logger,
19
+ json_dumps,
20
+ node_set_checksum,
21
+ )
18
22
  from .metrics.trig_cache import get_trig_cache
19
23
 
20
- ALIAS_THETA = get_aliases("THETA")
21
-
22
24
 
23
25
  logger = get_logger(__name__)
24
26
 
@@ -44,7 +46,7 @@ def _default_gamma_spec() -> tuple[bytes, str]:
44
46
  return dumped, hash_
45
47
 
46
48
 
47
- def _ensure_kuramoto_cache(G, t) -> None:
49
+ def _ensure_kuramoto_cache(G: TNFRGraph, t: float | int) -> None:
48
50
  """Cache ``(R, ψ)`` for the current step ``t`` using
49
51
  ``edge_version_cache``."""
50
52
  checksum = G.graph.get("_dnfr_nodes_checksum")
@@ -63,7 +65,7 @@ def _ensure_kuramoto_cache(G, t) -> None:
63
65
  G.graph["_kuramoto_cache"] = entry
64
66
 
65
67
 
66
- def kuramoto_R_psi(G) -> tuple[float, float]:
68
+ def kuramoto_R_psi(G: TNFRGraph) -> tuple[float, float]:
67
69
  """Return ``(R, ψ)`` for Kuramoto order using θ from all nodes."""
68
70
  max_steps = int(G.graph.get("KURAMOTO_CACHE_STEPS", 1))
69
71
  trig = get_trig_cache(G, cache_size=max_steps)
@@ -78,7 +80,9 @@ def kuramoto_R_psi(G) -> tuple[float, float]:
78
80
  return R, psi
79
81
 
80
82
 
81
- def _kuramoto_common(G, node, _cfg):
83
+ def _kuramoto_common(
84
+ G: TNFRGraph, node: NodeId, _cfg: GammaSpec
85
+ ) -> tuple[float, float, float]:
82
86
  """Return ``(θ_i, R, ψ)`` for Kuramoto-based Γ functions.
83
87
 
84
88
  Reads cached global order ``R`` and mean phase ``ψ`` and obtains node
@@ -88,11 +92,12 @@ def _kuramoto_common(G, node, _cfg):
88
92
  cache = G.graph.get("_kuramoto_cache", {})
89
93
  R = float(cache.get("R", 0.0))
90
94
  psi = float(cache.get("psi", 0.0))
91
- th_i = get_attr(G.nodes[node], ALIAS_THETA, 0.0)
95
+ th_val = get_theta_attr(G.nodes[node], 0.0)
96
+ th_i = float(th_val if th_val is not None else 0.0)
92
97
  return th_i, R, psi
93
98
 
94
99
 
95
- def _read_gamma_raw(G) -> Mapping[str, Any] | None:
100
+ def _read_gamma_raw(G: TNFRGraph) -> GammaSpec | None:
96
101
  """Return raw Γ specification from ``G.graph['GAMMA']``.
97
102
 
98
103
  The returned value is the direct contents of ``G.graph['GAMMA']`` when
@@ -104,11 +109,13 @@ def _read_gamma_raw(G) -> Mapping[str, Any] | None:
104
109
  if raw is None or isinstance(raw, Mapping):
105
110
  return raw
106
111
  return get_graph_mapping(
107
- G, "GAMMA", "G.graph['GAMMA'] no es un mapeo; se usa {'type': 'none'}"
112
+ G,
113
+ "GAMMA",
114
+ "G.graph['GAMMA'] is not a mapping; using {'type': 'none'}",
108
115
  )
109
116
 
110
117
 
111
- def _get_gamma_spec(G) -> Mapping[str, Any]:
118
+ def _get_gamma_spec(G: TNFRGraph) -> GammaSpec:
112
119
  """Return validated Γ specification caching results.
113
120
 
114
121
  The raw value from ``G.graph['GAMMA']`` is cached together with the
@@ -122,7 +129,7 @@ def _get_gamma_spec(G) -> Mapping[str, Any]:
122
129
  cached_spec = G.graph.get("_gamma_spec")
123
130
  cached_hash = G.graph.get("_gamma_spec_hash")
124
131
 
125
- def _hash_mapping(mapping: Mapping[str, Any]) -> str:
132
+ def _hash_mapping(mapping: GammaSpec) -> str:
126
133
  dumped = json_dumps(mapping, sort_keys=True, to_bytes=True)
127
134
  return hashlib.blake2b(dumped, digest_size=16).hexdigest()
128
135
 
@@ -166,7 +173,7 @@ def _get_gamma_spec(G) -> Mapping[str, Any]:
166
173
 
167
174
 
168
175
  def _gamma_params(
169
- cfg: Mapping[str, Any], **defaults: float
176
+ cfg: GammaSpec, **defaults: float
170
177
  ) -> tuple[float, ...]:
171
178
  """Return normalized Γ parameters from ``cfg``.
172
179
 
@@ -185,18 +192,20 @@ def _gamma_params(
185
192
 
186
193
 
187
194
  # -----------------
188
- # Γi(R) canónicos
195
+ # Canonical Γi(R)
189
196
  # -----------------
190
197
 
191
198
 
192
- def gamma_none(G, node, t, cfg: dict[str, Any]) -> float:
199
+ def gamma_none(
200
+ G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec
201
+ ) -> float:
193
202
  return 0.0
194
203
 
195
204
 
196
205
  def _gamma_kuramoto(
197
- G,
198
- node,
199
- cfg: Mapping[str, Any],
206
+ G: TNFRGraph,
207
+ node: NodeId,
208
+ cfg: GammaSpec,
200
209
  builder: Callable[..., float],
201
210
  **defaults: float,
202
211
  ) -> float:
@@ -224,7 +233,9 @@ def _builder_tanh(th_i: float, R: float, psi: float, beta: float, k: float, R0:
224
233
  return beta * math.tanh(k * (R - R0)) * math.cos(th_i - psi)
225
234
 
226
235
 
227
- def gamma_kuramoto_linear(G, node, t, cfg: dict[str, Any]) -> float:
236
+ def gamma_kuramoto_linear(
237
+ G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec
238
+ ) -> float:
228
239
  """Linear Kuramoto coupling for Γi(R).
229
240
 
230
241
  Formula: Γ = β · (R - R0) · cos(θ_i - ψ)
@@ -239,13 +250,17 @@ def gamma_kuramoto_linear(G, node, t, cfg: dict[str, Any]) -> float:
239
250
  return _gamma_kuramoto(G, node, cfg, _builder_linear, beta=0.0, R0=0.0)
240
251
 
241
252
 
242
- def gamma_kuramoto_bandpass(G, node, t, cfg: dict[str, Any]) -> float:
253
+ def gamma_kuramoto_bandpass(
254
+ G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec
255
+ ) -> float:
243
256
  """Γ = β · R(1-R) · sign(cos(θ_i - ψ))"""
244
257
 
245
258
  return _gamma_kuramoto(G, node, cfg, _builder_bandpass, beta=0.0)
246
259
 
247
260
 
248
- def gamma_kuramoto_tanh(G, node, t, cfg: dict[str, Any]) -> float:
261
+ def gamma_kuramoto_tanh(
262
+ G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec
263
+ ) -> float:
249
264
  """Saturating tanh coupling for Γi(R).
250
265
 
251
266
  Formula: Γ = β · tanh(k·(R - R0)) · cos(θ_i - ψ)
@@ -257,7 +272,9 @@ def gamma_kuramoto_tanh(G, node, t, cfg: dict[str, Any]) -> float:
257
272
  return _gamma_kuramoto(G, node, cfg, _builder_tanh, beta=0.0, k=1.0, R0=0.0)
258
273
 
259
274
 
260
- def gamma_harmonic(G, node, t, cfg: dict[str, Any]) -> float:
275
+ def gamma_harmonic(
276
+ G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec
277
+ ) -> float:
261
278
  """Harmonic forcing aligned with the global phase field.
262
279
 
263
280
  Formula: Γ = β · sin(ω·t + φ) · cos(θ_i - ψ)
@@ -271,13 +288,13 @@ def gamma_harmonic(G, node, t, cfg: dict[str, Any]) -> float:
271
288
 
272
289
 
273
290
  class GammaEntry(NamedTuple):
274
- fn: Callable[[Any, Any, Any, dict[str, Any]], float]
291
+ fn: Callable[[TNFRGraph, NodeId, float | int, GammaSpec], float]
275
292
  needs_kuramoto: bool
276
293
 
277
294
 
278
- # ``GAMMA_REGISTRY`` asocia el nombre del acoplamiento con un
279
- # ``GammaEntry`` donde ``fn`` es la función evaluadora y
280
- # ``needs_kuramoto`` indica si requiere precomputar el orden global de fase.
295
+ # ``GAMMA_REGISTRY`` associates each coupling name with a ``GammaEntry`` where
296
+ # ``fn`` is the evaluation function and ``needs_kuramoto`` indicates whether
297
+ # the global phase order must be precomputed.
281
298
  GAMMA_REGISTRY: dict[str, GammaEntry] = {
282
299
  "none": GammaEntry(gamma_none, False),
283
300
  "kuramoto_linear": GammaEntry(gamma_kuramoto_linear, True),
@@ -288,9 +305,9 @@ GAMMA_REGISTRY: dict[str, GammaEntry] = {
288
305
 
289
306
 
290
307
  def eval_gamma(
291
- G,
292
- node,
293
- t,
308
+ G: TNFRGraph,
309
+ node: NodeId,
310
+ t: float | int,
294
311
  *,
295
312
  strict: bool = False,
296
313
  log_level: int | None = None,
@@ -300,8 +317,8 @@ def eval_gamma(
300
317
 
301
318
  If ``strict`` is ``True`` exceptions raised during evaluation are
302
319
  propagated instead of returning ``0.0``. Likewise, if the specified
303
- Γ type is not registered a warning is emitted (o ``ValueError`` en
304
- modo estricto) y se usa ``gamma_none``.
320
+ Γ type is not registered a warning is emitted (or ``ValueError`` in
321
+ strict mode) and ``gamma_none`` is used.
305
322
 
306
323
  ``log_level`` controls the logging level for captured errors when
307
324
  ``strict`` is ``False``. If omitted, ``logging.ERROR`` is used in
@@ -311,7 +328,7 @@ def eval_gamma(
311
328
  spec_type = spec.get("type", "none")
312
329
  reg_entry = GAMMA_REGISTRY.get(spec_type)
313
330
  if reg_entry is None:
314
- msg = f"Tipo GAMMA desconocido: {spec_type}"
331
+ msg = f"Unknown GAMMA type: {spec_type}"
315
332
  if strict:
316
333
  raise ValueError(msg)
317
334
  logger.warning(msg)
@@ -330,7 +347,7 @@ def eval_gamma(
330
347
  )
331
348
  logger.log(
332
349
  level,
333
- "Fallo al evaluar Γi para nodo %s en t=%s: %s: %s",
350
+ "Failed to evaluate Γi for node %s at t=%s: %s: %s",
334
351
  node,
335
352
  t,
336
353
  exc.__class__.__name__,
tnfr/gamma.pyi ADDED
@@ -0,0 +1,40 @@
1
+ from typing import Callable, NamedTuple
2
+
3
+ from .types import GammaSpec, NodeId, TNFRGraph
4
+
5
+ __all__: tuple[str, ...]
6
+
7
+ class GammaEntry(NamedTuple):
8
+ fn: Callable[[TNFRGraph, NodeId, float | int, GammaSpec], float]
9
+ needs_kuramoto: bool
10
+
11
+ GAMMA_REGISTRY: dict[str, GammaEntry]
12
+
13
+ def kuramoto_R_psi(G: TNFRGraph) -> tuple[float, float]: ...
14
+
15
+ def gamma_none(G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec) -> float: ...
16
+
17
+ def gamma_kuramoto_linear(
18
+ G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec
19
+ ) -> float: ...
20
+
21
+ def gamma_kuramoto_bandpass(
22
+ G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec
23
+ ) -> float: ...
24
+
25
+ def gamma_kuramoto_tanh(
26
+ G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec
27
+ ) -> float: ...
28
+
29
+ def gamma_harmonic(
30
+ G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec
31
+ ) -> float: ...
32
+
33
+ def eval_gamma(
34
+ G: TNFRGraph,
35
+ node: NodeId,
36
+ t: float | int,
37
+ *,
38
+ strict: bool = ...,
39
+ log_level: int | None = ...,
40
+ ) -> float: ...