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
@@ -0,0 +1,92 @@
1
+ from typing import Any, ClassVar
2
+
3
+ from ..types import Glyph, TNFRGraph
4
+
5
+ __all__ = (
6
+ "Operator",
7
+ "Emission",
8
+ "Reception",
9
+ "Coherence",
10
+ "Dissonance",
11
+ "Coupling",
12
+ "Resonance",
13
+ "Silence",
14
+ "Expansion",
15
+ "Contraction",
16
+ "SelfOrganization",
17
+ "Mutation",
18
+ "Transition",
19
+ "Recursivity",
20
+ )
21
+
22
+
23
+ class Operator:
24
+ name: ClassVar[str]
25
+ glyph: ClassVar[Glyph | None]
26
+
27
+ def __call__(self, G: TNFRGraph, node: Any, **kw: Any) -> None: ...
28
+
29
+
30
+ class Emission(Operator):
31
+ name: ClassVar[str]
32
+ glyph: ClassVar[Glyph]
33
+
34
+
35
+ class Reception(Operator):
36
+ name: ClassVar[str]
37
+ glyph: ClassVar[Glyph]
38
+
39
+
40
+ class Coherence(Operator):
41
+ name: ClassVar[str]
42
+ glyph: ClassVar[Glyph]
43
+
44
+
45
+ class Dissonance(Operator):
46
+ name: ClassVar[str]
47
+ glyph: ClassVar[Glyph]
48
+
49
+
50
+ class Coupling(Operator):
51
+ name: ClassVar[str]
52
+ glyph: ClassVar[Glyph]
53
+
54
+
55
+ class Resonance(Operator):
56
+ name: ClassVar[str]
57
+ glyph: ClassVar[Glyph]
58
+
59
+
60
+ class Silence(Operator):
61
+ name: ClassVar[str]
62
+ glyph: ClassVar[Glyph]
63
+
64
+
65
+ class Expansion(Operator):
66
+ name: ClassVar[str]
67
+ glyph: ClassVar[Glyph]
68
+
69
+
70
+ class Contraction(Operator):
71
+ name: ClassVar[str]
72
+ glyph: ClassVar[Glyph]
73
+
74
+
75
+ class SelfOrganization(Operator):
76
+ name: ClassVar[str]
77
+ glyph: ClassVar[Glyph]
78
+
79
+
80
+ class Mutation(Operator):
81
+ name: ClassVar[str]
82
+ glyph: ClassVar[Glyph]
83
+
84
+
85
+ class Transition(Operator):
86
+ name: ClassVar[str]
87
+ glyph: ClassVar[Glyph]
88
+
89
+
90
+ class Recursivity(Operator):
91
+ name: ClassVar[str]
92
+ glyph: ClassVar[Glyph]
@@ -0,0 +1,266 @@
1
+ from __future__ import annotations
2
+ import threading
3
+
4
+ from typing import Any, TYPE_CHECKING, cast
5
+
6
+ from cachetools import LRUCache
7
+
8
+ from ..rng import (
9
+ ScopedCounterCache,
10
+ make_rng,
11
+ base_seed,
12
+ cache_enabled,
13
+ clear_rng_cache as _clear_rng_cache,
14
+ seed_hash,
15
+ )
16
+ from ..cache import CacheManager
17
+ from ..utils import ensure_node_offset_map, get_nodenx
18
+ from ..types import NodeId, TNFRGraph
19
+
20
+ if TYPE_CHECKING: # pragma: no cover - type checking only
21
+ from ..node import NodeProtocol
22
+
23
+ # Guarded by the cache lock to ensure thread-safe access. ``seq`` stores
24
+ # per-scope jitter sequence counters in an LRU cache bounded to avoid
25
+ # unbounded memory usage.
26
+ _JITTER_MAX_ENTRIES = 1024
27
+
28
+
29
+ class JitterCache:
30
+ """Container for jitter-related caches."""
31
+
32
+ def __init__(
33
+ self,
34
+ max_entries: int = _JITTER_MAX_ENTRIES,
35
+ *,
36
+ manager: CacheManager | None = None,
37
+ ) -> None:
38
+ self._manager = manager or CacheManager()
39
+ if not self._manager.has_override("scoped_counter:jitter"):
40
+ self._manager.configure(
41
+ overrides={"scoped_counter:jitter": int(max_entries)}
42
+ )
43
+ self._sequence = ScopedCounterCache(
44
+ "jitter",
45
+ max_entries=None,
46
+ manager=self._manager,
47
+ default_max_entries=int(max_entries),
48
+ )
49
+ self._settings_key = "jitter_settings"
50
+ self._manager.register(
51
+ self._settings_key,
52
+ lambda: {"max_entries": self._sequence.max_entries},
53
+ reset=self._reset_settings,
54
+ )
55
+
56
+ def _reset_settings(self, settings: dict[str, Any] | None) -> dict[str, Any]:
57
+ return {"max_entries": self._sequence.max_entries}
58
+
59
+ def _refresh_settings(self) -> None:
60
+ self._manager.update(
61
+ self._settings_key,
62
+ lambda _: {"max_entries": self._sequence.max_entries},
63
+ )
64
+
65
+ @property
66
+ def manager(self) -> CacheManager:
67
+ """Expose the cache manager backing this cache."""
68
+
69
+ return self._manager
70
+
71
+ @property
72
+ def seq(self) -> LRUCache[tuple[int, int], int]:
73
+ """Expose the sequence cache for tests and diagnostics."""
74
+
75
+ return self._sequence.cache
76
+
77
+ @property
78
+ def lock(self) -> threading.Lock | threading.RLock:
79
+ """Return the lock protecting the sequence cache."""
80
+
81
+ return self._sequence.lock
82
+
83
+ @property
84
+ def max_entries(self) -> int:
85
+ """Return the maximum number of cached jitter sequences."""
86
+
87
+ return self._sequence.max_entries
88
+
89
+ @max_entries.setter
90
+ def max_entries(self, value: int) -> None:
91
+ """Set the maximum number of cached jitter sequences."""
92
+
93
+ self._sequence.configure(max_entries=int(value))
94
+ self._refresh_settings()
95
+
96
+ @property
97
+ def settings(self) -> dict[str, Any]:
98
+ """Return jitter cache settings stored on the manager."""
99
+
100
+ return cast(dict[str, Any], self._manager.get(self._settings_key))
101
+
102
+ def setup(
103
+ self, force: bool = False, max_entries: int | None = None
104
+ ) -> None:
105
+ """Ensure jitter cache matches the configured size."""
106
+
107
+ self._sequence.configure(force=force, max_entries=max_entries)
108
+ self._refresh_settings()
109
+
110
+ def clear(self) -> None:
111
+ """Clear cached RNGs and jitter state."""
112
+
113
+ _clear_rng_cache()
114
+ self._sequence.clear()
115
+ self._manager.clear(self._settings_key)
116
+
117
+ def bump(self, key: tuple[int, int]) -> int:
118
+ """Return current jitter sequence counter for ``key`` and increment it."""
119
+
120
+ return self._sequence.bump(key)
121
+
122
+
123
+ class JitterCacheManager:
124
+ """Manager exposing the jitter cache without global reassignment."""
125
+
126
+ def __init__(
127
+ self,
128
+ cache: JitterCache | None = None,
129
+ *,
130
+ manager: CacheManager | None = None,
131
+ ) -> None:
132
+ if cache is not None:
133
+ self.cache = cache
134
+ self._manager = cache.manager
135
+ else:
136
+ self._manager = manager or CacheManager()
137
+ self.cache = JitterCache(manager=self._manager)
138
+
139
+ # Convenience passthrough properties
140
+ @property
141
+ def seq(self) -> LRUCache[tuple[int, int], int]:
142
+ return self.cache.seq
143
+
144
+ @property
145
+ def settings(self) -> dict[str, Any]:
146
+ return self.cache.settings
147
+
148
+ @property
149
+ def lock(self) -> threading.Lock | threading.RLock:
150
+ return self.cache.lock
151
+
152
+ @property
153
+ def max_entries(self) -> int:
154
+ """Return the maximum number of cached jitter entries."""
155
+
156
+ return self.cache.max_entries
157
+
158
+ @max_entries.setter
159
+ def max_entries(self, value: int) -> None:
160
+ """Set the maximum number of cached jitter entries."""
161
+
162
+ self.cache.max_entries = value
163
+
164
+ def setup(
165
+ self, force: bool = False, max_entries: int | None = None
166
+ ) -> None:
167
+ """Ensure jitter cache matches the configured size.
168
+
169
+ ``max_entries`` may be provided to explicitly resize the cache.
170
+ When omitted the existing ``cache.max_entries`` is preserved.
171
+ """
172
+
173
+ if max_entries is not None:
174
+ self.cache.setup(force=True, max_entries=max_entries)
175
+ else:
176
+ self.cache.setup(force=force)
177
+
178
+ def clear(self) -> None:
179
+ """Clear cached RNGs and jitter state."""
180
+
181
+ self.cache.clear()
182
+
183
+ def bump(self, key: tuple[int, int]) -> int:
184
+ """Return and increment the jitter sequence counter for ``key``."""
185
+
186
+ return self.cache.bump(key)
187
+
188
+
189
+ # Lazy manager instance
190
+ _JITTER_MANAGER: JitterCacheManager | None = None
191
+
192
+
193
+ def get_jitter_manager() -> JitterCacheManager:
194
+ """Return the singleton jitter manager, initializing on first use."""
195
+ global _JITTER_MANAGER
196
+ if _JITTER_MANAGER is None:
197
+ _JITTER_MANAGER = JitterCacheManager()
198
+ _JITTER_MANAGER.setup(force=True)
199
+ return _JITTER_MANAGER
200
+
201
+
202
+ def reset_jitter_manager() -> None:
203
+ """Reset the global jitter manager (useful for tests)."""
204
+ global _JITTER_MANAGER
205
+ if _JITTER_MANAGER is not None:
206
+ _JITTER_MANAGER.clear()
207
+ _JITTER_MANAGER = None
208
+
209
+
210
+ def _node_offset(G: TNFRGraph, n: NodeId) -> int:
211
+ """Deterministic node index used for jitter seeds."""
212
+ mapping = ensure_node_offset_map(G)
213
+ return int(mapping.get(n, 0))
214
+
215
+
216
+ def _resolve_jitter_seed(node: NodeProtocol) -> tuple[int, int]:
217
+ node_nx_type = get_nodenx()
218
+ if node_nx_type is None:
219
+ raise ImportError("NodeNX is unavailable")
220
+ if isinstance(node, node_nx_type):
221
+ graph = cast(TNFRGraph, getattr(node, "G"))
222
+ node_id = cast(NodeId, getattr(node, "n"))
223
+ return _node_offset(graph, node_id), id(graph)
224
+ uid = getattr(node, "_noise_uid", None)
225
+ if uid is None:
226
+ uid = id(node)
227
+ setattr(node, "_noise_uid", uid)
228
+ graph = cast(TNFRGraph | None, getattr(node, "G", None))
229
+ scope = graph if graph is not None else node
230
+ return int(uid), id(scope)
231
+
232
+
233
+ def random_jitter(
234
+ node: NodeProtocol,
235
+ amplitude: float,
236
+ ) -> float:
237
+ """Return deterministic noise in ``[-amplitude, amplitude]`` for ``node``.
238
+
239
+ The per-node jitter sequences are tracked using the global manager
240
+ returned by :func:`get_jitter_manager`.
241
+ """
242
+ if amplitude < 0:
243
+ raise ValueError("amplitude must be positive")
244
+ if amplitude == 0:
245
+ return 0.0
246
+
247
+ seed_root = base_seed(node.G)
248
+ seed_key, scope_id = _resolve_jitter_seed(node)
249
+
250
+ cache_key = (seed_root, scope_id, seed_key)
251
+ seq = 0
252
+ if cache_enabled(node.G):
253
+ manager = get_jitter_manager()
254
+ seq = manager.bump(cache_key)
255
+ seed = seed_hash(seed_root, scope_id)
256
+ rng = make_rng(seed, seed_key + seq, node.G)
257
+ return rng.uniform(-amplitude, amplitude)
258
+
259
+
260
+ __all__ = [
261
+ "JitterCache",
262
+ "JitterCacheManager",
263
+ "get_jitter_manager",
264
+ "reset_jitter_manager",
265
+ "random_jitter",
266
+ ]
@@ -0,0 +1,11 @@
1
+ from typing import Any
2
+
3
+ __all__: Any
4
+
5
+ def __getattr__(name: str) -> Any: ...
6
+
7
+ JitterCache: Any
8
+ JitterCacheManager: Any
9
+ get_jitter_manager: Any
10
+ random_jitter: Any
11
+ reset_jitter_manager: Any
@@ -0,0 +1,80 @@
1
+ """Registry mapping operator names to their classes."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib
6
+ import pkgutil
7
+ from typing import Any, TYPE_CHECKING
8
+
9
+ from ..config.operator_names import canonical_operator_name
10
+
11
+ if TYPE_CHECKING: # pragma: no cover - type checking only
12
+ from .definitions import Operator
13
+
14
+
15
+ OPERATORS: dict[str, type["Operator"]] = {}
16
+
17
+
18
+ def register_operator(cls: type["Operator"]) -> type["Operator"]:
19
+ """Register ``cls`` under its declared ``name`` in :data:`OPERATORS`."""
20
+
21
+ name = getattr(cls, "name", None)
22
+ if not isinstance(name, str) or not name:
23
+ raise ValueError(
24
+ f"Operator {cls.__name__} must declare a non-empty 'name' attribute"
25
+ )
26
+
27
+ existing = OPERATORS.get(name)
28
+ if existing is not None and existing is not cls:
29
+ raise ValueError(f"Operator '{name}' is already registered")
30
+
31
+ OPERATORS[name] = cls
32
+ return cls
33
+
34
+
35
+ def get_operator_class(name: str) -> type["Operator"]:
36
+ """Return the operator class registered for ``name`` or its canonical alias."""
37
+
38
+ try:
39
+ return OPERATORS[name]
40
+ except KeyError:
41
+ canonical = canonical_operator_name(name)
42
+ if canonical == name:
43
+ raise
44
+ try:
45
+ return OPERATORS[canonical]
46
+ except KeyError as exc: # pragma: no cover - defensive branch
47
+ raise KeyError(name) from exc
48
+
49
+
50
+ def discover_operators() -> None:
51
+ """Import all operator submodules so their decorators run."""
52
+
53
+ package = importlib.import_module("tnfr.operators")
54
+ package_path = getattr(package, "__path__", None)
55
+ if not package_path:
56
+ return
57
+
58
+ if getattr(package, "_operators_discovered", False): # pragma: no cover - cache
59
+ return
60
+
61
+ prefix = f"{package.__name__}."
62
+ for module_info in pkgutil.walk_packages(package_path, prefix):
63
+ if module_info.name == f"{prefix}registry":
64
+ continue
65
+ importlib.import_module(module_info.name)
66
+
67
+ setattr(package, "_operators_discovered", True)
68
+
69
+
70
+ __all__ = ("OPERATORS", "register_operator", "discover_operators", "get_operator_class")
71
+
72
+
73
+ def __getattr__(name: str) -> Any:
74
+ """Provide guidance for legacy registry aliases."""
75
+
76
+ if name == "OPERADORES":
77
+ raise AttributeError(
78
+ f"module '{__name__}' has no attribute '{name}'; use 'OPERATORS' instead."
79
+ )
80
+ raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
@@ -0,0 +1,15 @@
1
+ from typing import Any
2
+
3
+ from .definitions import Operator
4
+
5
+ __all__: Any
6
+
7
+ def __getattr__(name: str) -> Any: ...
8
+
9
+ OPERATORS: dict[str, type[Operator]]
10
+
11
+ def discover_operators() -> None: ...
12
+
13
+ def register_operator(cls: type[Operator]) -> type[Operator]: ...
14
+
15
+ def get_operator_class(name: str) -> type[Operator]: ...