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
|
@@ -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]
|
tnfr/operators/jitter.py
ADDED
|
@@ -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,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]: ...
|