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.
- tnfr/__init__.py +228 -49
- tnfr/__init__.pyi +40 -0
- tnfr/_compat.py +11 -0
- tnfr/_version.py +7 -0
- tnfr/_version.pyi +7 -0
- tnfr/alias.py +106 -21
- tnfr/alias.pyi +140 -0
- tnfr/cache.py +666 -512
- tnfr/cache.pyi +232 -0
- tnfr/callback_utils.py +2 -9
- tnfr/callback_utils.pyi +105 -0
- tnfr/cli/__init__.py +21 -7
- tnfr/cli/__init__.pyi +47 -0
- tnfr/cli/arguments.py +42 -20
- tnfr/cli/arguments.pyi +33 -0
- tnfr/cli/execution.py +54 -20
- tnfr/cli/execution.pyi +80 -0
- tnfr/cli/utils.py +0 -2
- 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.py → config/init.py} +11 -7
- 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 +78 -24
- tnfr/constants/__init__.pyi +104 -0
- tnfr/constants/core.py +1 -2
- tnfr/constants/core.pyi +17 -0
- tnfr/constants/init.pyi +12 -0
- tnfr/constants/metric.py +4 -12
- tnfr/constants/metric.pyi +19 -0
- tnfr/constants_glyphs.py +9 -91
- tnfr/constants_glyphs.pyi +12 -0
- tnfr/dynamics/__init__.py +112 -634
- 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 +1936 -354
- tnfr/dynamics/dnfr.pyi +33 -0
- tnfr/dynamics/integrators.py +369 -75
- tnfr/dynamics/integrators.pyi +35 -0
- tnfr/dynamics/runtime.py +521 -0
- tnfr/dynamics/sampling.py +8 -5
- tnfr/dynamics/sampling.pyi +7 -0
- tnfr/dynamics/selectors.py +680 -0
- tnfr/execution.py +56 -41
- tnfr/execution.pyi +65 -0
- tnfr/flatten.py +7 -7
- tnfr/flatten.pyi +28 -0
- tnfr/gamma.py +54 -37
- tnfr/gamma.pyi +40 -0
- tnfr/glyph_history.py +85 -38
- tnfr/glyph_history.pyi +53 -0
- tnfr/grammar.py +19 -338
- tnfr/grammar.pyi +13 -0
- tnfr/helpers/__init__.py +110 -30
- tnfr/helpers/__init__.pyi +66 -0
- tnfr/helpers/numeric.py +1 -0
- tnfr/helpers/numeric.pyi +12 -0
- tnfr/immutable.py +55 -19
- tnfr/immutable.pyi +37 -0
- tnfr/initialization.py +12 -10
- tnfr/initialization.pyi +73 -0
- tnfr/io.py +99 -34
- tnfr/io.pyi +11 -0
- tnfr/locking.pyi +7 -0
- tnfr/metrics/__init__.pyi +20 -0
- tnfr/metrics/coherence.py +934 -294
- tnfr/metrics/common.py +1 -3
- tnfr/metrics/common.pyi +15 -0
- tnfr/metrics/core.py +192 -34
- tnfr/metrics/core.pyi +13 -0
- tnfr/metrics/diagnosis.py +707 -101
- tnfr/metrics/diagnosis.pyi +89 -0
- tnfr/metrics/export.py +27 -13
- tnfr/metrics/glyph_timing.py +218 -38
- tnfr/metrics/reporting.py +22 -18
- tnfr/metrics/reporting.pyi +12 -0
- tnfr/metrics/sense_index.py +199 -25
- tnfr/metrics/sense_index.pyi +9 -0
- tnfr/metrics/trig.py +53 -18
- tnfr/metrics/trig.pyi +12 -0
- tnfr/metrics/trig_cache.py +3 -7
- tnfr/metrics/trig_cache.pyi +10 -0
- tnfr/node.py +148 -125
- tnfr/node.pyi +161 -0
- tnfr/observers.py +44 -30
- tnfr/observers.pyi +46 -0
- tnfr/ontosim.py +14 -13
- tnfr/ontosim.pyi +33 -0
- tnfr/operators/__init__.py +84 -52
- tnfr/operators/__init__.pyi +31 -0
- tnfr/operators/definitions.py +181 -0
- tnfr/operators/definitions.pyi +92 -0
- tnfr/operators/jitter.py +86 -23
- tnfr/operators/jitter.pyi +11 -0
- tnfr/operators/registry.py +80 -0
- tnfr/operators/registry.pyi +15 -0
- tnfr/operators/remesh.py +141 -57
- tnfr/presets.py +9 -54
- tnfr/presets.pyi +7 -0
- tnfr/py.typed +0 -0
- tnfr/rng.py +259 -73
- tnfr/rng.pyi +14 -0
- tnfr/selector.py +24 -17
- tnfr/selector.pyi +19 -0
- tnfr/sense.py +55 -43
- tnfr/sense.pyi +30 -0
- tnfr/structural.py +44 -267
- tnfr/structural.pyi +46 -0
- tnfr/telemetry/__init__.py +13 -0
- tnfr/telemetry/verbosity.py +37 -0
- tnfr/tokens.py +3 -2
- tnfr/tokens.pyi +41 -0
- tnfr/trace.py +272 -82
- tnfr/trace.pyi +68 -0
- tnfr/types.py +345 -6
- 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/{collections_utils.py → utils/data.py} +57 -90
- 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/{json_utils.py → utils/io.py} +13 -18
- 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/graph_utils.py +0 -84
- tnfr/import_utils.py +0 -228
- tnfr/logging_utils.py +0 -116
- tnfr/validators.py +0 -84
- tnfr/value_utils.py +0 -59
- tnfr-4.5.2.dist-info/METADATA +0 -379
- tnfr-4.5.2.dist-info/RECORD +0 -67
- {tnfr-4.5.2.dist-info → tnfr-6.0.0.dist-info}/WHEEL +0 -0
- {tnfr-4.5.2.dist-info → tnfr-6.0.0.dist-info}/entry_points.txt +0 -0
- {tnfr-4.5.2.dist-info → tnfr-6.0.0.dist-info}/licenses/LICENSE.md +0 -0
- {tnfr-4.5.2.dist-info → tnfr-6.0.0.dist-info}/top_level.txt +0 -0
tnfr/rng.py
CHANGED
|
@@ -2,17 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
import random
|
|
6
5
|
import hashlib
|
|
6
|
+
import random
|
|
7
7
|
import struct
|
|
8
|
+
import threading
|
|
8
9
|
from collections.abc import Iterator, MutableMapping
|
|
9
|
-
from
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from typing import Any, Generic, Hashable, TypeVar, cast
|
|
10
12
|
|
|
11
13
|
|
|
12
|
-
from cachetools import
|
|
14
|
+
from cachetools import cached # type: ignore[import-untyped]
|
|
13
15
|
from .constants import DEFAULTS, get_param
|
|
14
|
-
from .
|
|
16
|
+
from .cache import CacheManager, InstrumentedLRUCache
|
|
17
|
+
from .utils import get_graph
|
|
15
18
|
from .locking import get_lock
|
|
19
|
+
from .types import GraphLike, TNFRGraph
|
|
16
20
|
|
|
17
21
|
MASK64 = 0xFFFFFFFFFFFFFFFF
|
|
18
22
|
|
|
@@ -22,110 +26,264 @@ _CACHE_MAXSIZE = _DEFAULT_CACHE_MAXSIZE
|
|
|
22
26
|
_CACHE_LOCKED = False
|
|
23
27
|
|
|
24
28
|
K = TypeVar("K", bound=Hashable)
|
|
29
|
+
V = TypeVar("V")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class _SeedCacheState:
|
|
34
|
+
cache: InstrumentedLRUCache[tuple[int, int], int] | None
|
|
35
|
+
maxsize: int
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class _CounterState(Generic[K]):
|
|
40
|
+
cache: InstrumentedLRUCache[K, int]
|
|
41
|
+
locks: dict[K, threading.RLock]
|
|
42
|
+
max_entries: int
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
_RNG_CACHE_MANAGER = CacheManager(default_capacity=_DEFAULT_CACHE_MAXSIZE)
|
|
25
46
|
|
|
26
47
|
|
|
27
48
|
class _SeedHashCache(MutableMapping[tuple[int, int], int]):
|
|
28
49
|
"""Mutable mapping proxy exposing a configurable LRU cache."""
|
|
29
50
|
|
|
30
|
-
def __init__(
|
|
31
|
-
self
|
|
32
|
-
|
|
33
|
-
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
*,
|
|
54
|
+
manager: CacheManager | None = None,
|
|
55
|
+
state_key: str = "seed_hash_cache",
|
|
56
|
+
default_maxsize: int = _DEFAULT_CACHE_MAXSIZE,
|
|
57
|
+
) -> None:
|
|
58
|
+
self._manager = manager or _RNG_CACHE_MANAGER
|
|
59
|
+
self._state_key = state_key
|
|
60
|
+
self._default_maxsize = int(default_maxsize)
|
|
61
|
+
if not self._manager.has_override(self._state_key):
|
|
62
|
+
self._manager.configure(
|
|
63
|
+
overrides={self._state_key: self._default_maxsize}
|
|
64
|
+
)
|
|
65
|
+
self._manager.register(
|
|
66
|
+
self._state_key,
|
|
67
|
+
self._create_state,
|
|
68
|
+
reset=self._reset_state,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
def _resolved_size(self, requested: int | None = None) -> int:
|
|
72
|
+
size = self._manager.get_capacity(
|
|
73
|
+
self._state_key,
|
|
74
|
+
requested=requested,
|
|
75
|
+
fallback=self._default_maxsize,
|
|
76
|
+
)
|
|
77
|
+
if size is None:
|
|
78
|
+
return 0
|
|
79
|
+
return int(size)
|
|
80
|
+
|
|
81
|
+
def _create_state(self) -> _SeedCacheState:
|
|
82
|
+
size = self._resolved_size()
|
|
83
|
+
if size <= 0:
|
|
84
|
+
return _SeedCacheState(cache=None, maxsize=0)
|
|
85
|
+
return _SeedCacheState(
|
|
86
|
+
cache=InstrumentedLRUCache(
|
|
87
|
+
size,
|
|
88
|
+
manager=self._manager,
|
|
89
|
+
metrics_key=self._state_key,
|
|
90
|
+
),
|
|
91
|
+
maxsize=size,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
def _reset_state(self, state: _SeedCacheState | None) -> _SeedCacheState:
|
|
95
|
+
return self._create_state()
|
|
96
|
+
|
|
97
|
+
def _get_state(self, *, create: bool = True) -> _SeedCacheState | None:
|
|
98
|
+
state = self._manager.get(self._state_key, create=create)
|
|
99
|
+
if state is None:
|
|
100
|
+
return None
|
|
101
|
+
if not isinstance(state, _SeedCacheState):
|
|
102
|
+
state = self._create_state()
|
|
103
|
+
self._manager.store(self._state_key, state)
|
|
104
|
+
return state
|
|
34
105
|
|
|
35
106
|
def configure(self, maxsize: int) -> None:
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
else:
|
|
42
|
-
self._cache = LRUCache(maxsize=self._maxsize)
|
|
107
|
+
size = int(maxsize)
|
|
108
|
+
if size < 0:
|
|
109
|
+
raise ValueError("maxsize must be non-negative")
|
|
110
|
+
self._manager.configure(overrides={self._state_key: size})
|
|
111
|
+
self._manager.update(self._state_key, lambda _: self._create_state())
|
|
43
112
|
|
|
44
113
|
def __getitem__(self, key: tuple[int, int]) -> int:
|
|
45
|
-
|
|
114
|
+
state = self._get_state()
|
|
115
|
+
if state is None or state.cache is None:
|
|
46
116
|
raise KeyError(key)
|
|
47
|
-
|
|
117
|
+
value = state.cache[key]
|
|
118
|
+
self._manager.increment_hit(self._state_key)
|
|
119
|
+
return value
|
|
48
120
|
|
|
49
121
|
def __setitem__(self, key: tuple[int, int], value: int) -> None:
|
|
50
|
-
|
|
51
|
-
|
|
122
|
+
state = self._get_state()
|
|
123
|
+
if state is not None and state.cache is not None:
|
|
124
|
+
state.cache[key] = value
|
|
52
125
|
|
|
53
126
|
def __delitem__(self, key: tuple[int, int]) -> None:
|
|
54
|
-
|
|
127
|
+
state = self._get_state()
|
|
128
|
+
if state is None or state.cache is None:
|
|
55
129
|
raise KeyError(key)
|
|
56
|
-
del
|
|
130
|
+
del state.cache[key]
|
|
57
131
|
|
|
58
132
|
def __iter__(self) -> Iterator[tuple[int, int]]:
|
|
59
|
-
|
|
133
|
+
state = self._get_state(create=False)
|
|
134
|
+
if state is None or state.cache is None:
|
|
60
135
|
return iter(())
|
|
61
|
-
return iter(
|
|
136
|
+
return iter(state.cache)
|
|
62
137
|
|
|
63
138
|
def __len__(self) -> int:
|
|
64
|
-
|
|
139
|
+
state = self._get_state(create=False)
|
|
140
|
+
if state is None or state.cache is None:
|
|
65
141
|
return 0
|
|
66
|
-
return len(
|
|
142
|
+
return len(state.cache)
|
|
67
143
|
|
|
68
144
|
def clear(self) -> None: # type: ignore[override]
|
|
69
|
-
|
|
70
|
-
self._cache.clear()
|
|
145
|
+
self._manager.clear(self._state_key)
|
|
71
146
|
|
|
72
147
|
@property
|
|
73
148
|
def maxsize(self) -> int:
|
|
74
|
-
|
|
149
|
+
state = self._get_state()
|
|
150
|
+
return 0 if state is None else state.maxsize
|
|
75
151
|
|
|
76
152
|
@property
|
|
77
153
|
def enabled(self) -> bool:
|
|
78
|
-
|
|
154
|
+
state = self._get_state(create=False)
|
|
155
|
+
return bool(state and state.cache is not None)
|
|
79
156
|
|
|
80
157
|
@property
|
|
81
|
-
def data(self) ->
|
|
158
|
+
def data(self) -> InstrumentedLRUCache[tuple[int, int], int] | None:
|
|
82
159
|
"""Expose the underlying cache for diagnostics/tests."""
|
|
83
160
|
|
|
84
|
-
|
|
161
|
+
state = self._get_state(create=False)
|
|
162
|
+
return None if state is None else state.cache
|
|
85
163
|
|
|
86
164
|
|
|
87
165
|
class ScopedCounterCache(Generic[K]):
|
|
88
166
|
"""Thread-safe LRU cache storing monotonic counters by ``key``."""
|
|
89
167
|
|
|
90
|
-
def __init__(
|
|
91
|
-
|
|
168
|
+
def __init__(
|
|
169
|
+
self,
|
|
170
|
+
name: str,
|
|
171
|
+
max_entries: int | None = None,
|
|
172
|
+
*,
|
|
173
|
+
manager: CacheManager | None = None,
|
|
174
|
+
default_max_entries: int = _DEFAULT_CACHE_MAXSIZE,
|
|
175
|
+
) -> None:
|
|
176
|
+
self._name = name
|
|
177
|
+
self._manager = manager or _RNG_CACHE_MANAGER
|
|
178
|
+
self._state_key = f"scoped_counter:{name}"
|
|
179
|
+
self._default_max_entries = int(default_max_entries)
|
|
180
|
+
requested = None if max_entries is None else int(max_entries)
|
|
181
|
+
if requested is not None and requested < 0:
|
|
92
182
|
raise ValueError("max_entries must be non-negative")
|
|
93
|
-
self.
|
|
94
|
-
|
|
95
|
-
|
|
183
|
+
if not self._manager.has_override(self._state_key):
|
|
184
|
+
fallback = requested
|
|
185
|
+
if fallback is None:
|
|
186
|
+
fallback = self._default_max_entries
|
|
187
|
+
self._manager.configure(overrides={self._state_key: fallback})
|
|
188
|
+
elif requested is not None:
|
|
189
|
+
self._manager.configure(overrides={self._state_key: requested})
|
|
190
|
+
self._manager.register(
|
|
191
|
+
self._state_key,
|
|
192
|
+
self._create_state,
|
|
193
|
+
lock_factory=lambda: get_lock(name),
|
|
194
|
+
reset=self._reset_state,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
def _resolved_entries(self, requested: int | None = None) -> int:
|
|
198
|
+
size = self._manager.get_capacity(
|
|
199
|
+
self._state_key,
|
|
200
|
+
requested=requested,
|
|
201
|
+
fallback=self._default_max_entries,
|
|
202
|
+
)
|
|
203
|
+
if size is None:
|
|
204
|
+
return 0
|
|
205
|
+
return int(size)
|
|
206
|
+
|
|
207
|
+
def _create_state(self, requested: int | None = None) -> _CounterState[K]:
|
|
208
|
+
size = self._resolved_entries(requested)
|
|
209
|
+
locks: dict[K, threading.RLock] = {}
|
|
210
|
+
return _CounterState(
|
|
211
|
+
cache=InstrumentedLRUCache(
|
|
212
|
+
size,
|
|
213
|
+
manager=self._manager,
|
|
214
|
+
metrics_key=self._state_key,
|
|
215
|
+
locks=locks,
|
|
216
|
+
),
|
|
217
|
+
locks=locks,
|
|
218
|
+
max_entries=size,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
def _reset_state(self, state: _CounterState[K] | None) -> _CounterState[K]:
|
|
222
|
+
return self._create_state()
|
|
223
|
+
|
|
224
|
+
def _get_state(self) -> _CounterState[K]:
|
|
225
|
+
state = self._manager.get(self._state_key)
|
|
226
|
+
if not isinstance(state, _CounterState):
|
|
227
|
+
state = self._create_state(0)
|
|
228
|
+
self._manager.store(self._state_key, state)
|
|
229
|
+
return state
|
|
96
230
|
|
|
97
231
|
@property
|
|
98
|
-
def lock(self):
|
|
232
|
+
def lock(self) -> threading.Lock | threading.RLock:
|
|
99
233
|
"""Return the lock guarding access to the underlying cache."""
|
|
100
234
|
|
|
101
|
-
return self.
|
|
235
|
+
return self._manager.get_lock(self._state_key)
|
|
102
236
|
|
|
103
237
|
@property
|
|
104
238
|
def max_entries(self) -> int:
|
|
105
239
|
"""Return the configured maximum number of cached entries."""
|
|
106
240
|
|
|
107
|
-
return self.
|
|
241
|
+
return self._get_state().max_entries
|
|
108
242
|
|
|
109
243
|
@property
|
|
110
|
-
def cache(self) ->
|
|
111
|
-
"""Expose the
|
|
244
|
+
def cache(self) -> InstrumentedLRUCache[K, int]:
|
|
245
|
+
"""Expose the instrumented cache for inspection."""
|
|
112
246
|
|
|
113
|
-
return self.
|
|
247
|
+
return self._get_state().cache
|
|
248
|
+
|
|
249
|
+
@property
|
|
250
|
+
def locks(self) -> dict[K, threading.RLock]:
|
|
251
|
+
"""Return the mapping of per-key locks tracked by the cache."""
|
|
252
|
+
|
|
253
|
+
return self._get_state().locks
|
|
114
254
|
|
|
115
255
|
def configure(
|
|
116
256
|
self, *, force: bool = False, max_entries: int | None = None
|
|
117
257
|
) -> None:
|
|
118
258
|
"""Resize or reset the cache keeping previous settings."""
|
|
119
259
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
260
|
+
if max_entries is None:
|
|
261
|
+
size = self._resolved_entries()
|
|
262
|
+
update_policy = False
|
|
263
|
+
else:
|
|
264
|
+
size = int(max_entries)
|
|
265
|
+
if size < 0:
|
|
266
|
+
raise ValueError("max_entries must be non-negative")
|
|
267
|
+
update_policy = True
|
|
268
|
+
|
|
269
|
+
def _update(state: _CounterState[K] | None) -> _CounterState[K]:
|
|
270
|
+
if not isinstance(state, _CounterState) or force or state.max_entries != size:
|
|
271
|
+
locks: dict[K, threading.RLock] = {}
|
|
272
|
+
return _CounterState(
|
|
273
|
+
cache=InstrumentedLRUCache(
|
|
274
|
+
size,
|
|
275
|
+
manager=self._manager,
|
|
276
|
+
metrics_key=self._state_key,
|
|
277
|
+
locks=locks,
|
|
278
|
+
),
|
|
279
|
+
locks=locks,
|
|
280
|
+
max_entries=size,
|
|
281
|
+
)
|
|
282
|
+
return cast(_CounterState[K], state)
|
|
283
|
+
|
|
284
|
+
if update_policy:
|
|
285
|
+
self._manager.configure(overrides={self._state_key: size})
|
|
286
|
+
self._manager.update(self._state_key, _update)
|
|
129
287
|
|
|
130
288
|
def clear(self) -> None:
|
|
131
289
|
"""Clear stored counters preserving ``max_entries``."""
|
|
@@ -135,22 +293,31 @@ class ScopedCounterCache(Generic[K]):
|
|
|
135
293
|
def bump(self, key: K) -> int:
|
|
136
294
|
"""Return current counter for ``key`` and increment it atomically."""
|
|
137
295
|
|
|
138
|
-
|
|
139
|
-
value = int(self._cache.get(key, 0))
|
|
140
|
-
self._cache[key] = value + 1
|
|
141
|
-
return value
|
|
296
|
+
result: dict[str, Any] = {}
|
|
142
297
|
|
|
143
|
-
|
|
144
|
-
|
|
298
|
+
def _update(state: _CounterState[K] | None) -> _CounterState[K]:
|
|
299
|
+
if not isinstance(state, _CounterState):
|
|
300
|
+
state = self._create_state(0)
|
|
301
|
+
cache = state.cache
|
|
302
|
+
locks = state.locks
|
|
303
|
+
if key not in locks:
|
|
304
|
+
locks[key] = threading.RLock()
|
|
305
|
+
value = int(cache.get(key, 0))
|
|
306
|
+
cache[key] = value + 1
|
|
307
|
+
result["value"] = value
|
|
308
|
+
return state
|
|
145
309
|
|
|
310
|
+
self._manager.update(self._state_key, _update)
|
|
311
|
+
return int(result.get("value", 0))
|
|
146
312
|
|
|
147
|
-
|
|
313
|
+
def __len__(self) -> int:
|
|
314
|
+
return len(self.cache)
|
|
148
315
|
|
|
149
316
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
"""Return a 64-bit hash derived from ``seed_int`` and ``key_int``."""
|
|
317
|
+
_seed_hash_cache = _SeedHashCache()
|
|
318
|
+
|
|
153
319
|
|
|
320
|
+
def _compute_seed_hash(seed_int: int, key_int: int) -> int:
|
|
154
321
|
seed_bytes = struct.pack(
|
|
155
322
|
">QQ",
|
|
156
323
|
seed_int & MASK64,
|
|
@@ -161,7 +328,24 @@ def seed_hash(seed_int: int, key_int: int) -> int:
|
|
|
161
328
|
)
|
|
162
329
|
|
|
163
330
|
|
|
164
|
-
|
|
331
|
+
@cached(cache=_seed_hash_cache, lock=_RNG_LOCK)
|
|
332
|
+
def _cached_seed_hash(seed_int: int, key_int: int) -> int:
|
|
333
|
+
return _compute_seed_hash(seed_int, key_int)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def seed_hash(seed_int: int, key_int: int) -> int:
|
|
337
|
+
"""Return a 64-bit hash derived from ``seed_int`` and ``key_int``."""
|
|
338
|
+
|
|
339
|
+
if _CACHE_MAXSIZE <= 0 or not _seed_hash_cache.enabled:
|
|
340
|
+
return _compute_seed_hash(seed_int, key_int)
|
|
341
|
+
return _cached_seed_hash(seed_int, key_int)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
seed_hash.cache_clear = cast(Any, _cached_seed_hash).cache_clear # type: ignore[attr-defined]
|
|
345
|
+
seed_hash.cache = _seed_hash_cache # type: ignore[attr-defined]
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def _sync_cache_size(G: TNFRGraph | GraphLike | None) -> None:
|
|
165
349
|
"""Synchronise cache size with ``G`` when needed."""
|
|
166
350
|
|
|
167
351
|
global _CACHE_MAXSIZE
|
|
@@ -169,12 +353,14 @@ def _sync_cache_size(G: Any | None) -> None:
|
|
|
169
353
|
return
|
|
170
354
|
size = get_cache_maxsize(G)
|
|
171
355
|
with _RNG_LOCK:
|
|
172
|
-
if size !=
|
|
356
|
+
if size != _seed_hash_cache.maxsize:
|
|
173
357
|
_seed_hash_cache.configure(size)
|
|
174
|
-
_CACHE_MAXSIZE =
|
|
358
|
+
_CACHE_MAXSIZE = _seed_hash_cache.maxsize
|
|
175
359
|
|
|
176
360
|
|
|
177
|
-
def make_rng(
|
|
361
|
+
def make_rng(
|
|
362
|
+
seed: int, key: int, G: TNFRGraph | GraphLike | None = None
|
|
363
|
+
) -> random.Random:
|
|
178
364
|
"""Return a ``random.Random`` for ``seed`` and ``key``.
|
|
179
365
|
|
|
180
366
|
When ``G`` is provided, ``JITTER_CACHE_SIZE`` is read from ``G`` and the
|
|
@@ -188,17 +374,17 @@ def make_rng(seed: int, key: int, G: Any | None = None) -> random.Random:
|
|
|
188
374
|
|
|
189
375
|
def clear_rng_cache() -> None:
|
|
190
376
|
"""Clear cached seed hashes."""
|
|
191
|
-
if
|
|
377
|
+
if _seed_hash_cache.maxsize <= 0 or not _seed_hash_cache.enabled:
|
|
192
378
|
return
|
|
193
|
-
seed_hash.cache_clear()
|
|
379
|
+
seed_hash.cache_clear() # type: ignore[attr-defined]
|
|
194
380
|
|
|
195
381
|
|
|
196
|
-
def get_cache_maxsize(G:
|
|
382
|
+
def get_cache_maxsize(G: TNFRGraph | GraphLike) -> int:
|
|
197
383
|
"""Return RNG cache maximum size for ``G``."""
|
|
198
384
|
return int(get_param(G, "JITTER_CACHE_SIZE"))
|
|
199
385
|
|
|
200
386
|
|
|
201
|
-
def cache_enabled(G:
|
|
387
|
+
def cache_enabled(G: TNFRGraph | GraphLike | None = None) -> bool:
|
|
202
388
|
"""Return ``True`` if RNG caching is enabled.
|
|
203
389
|
|
|
204
390
|
When ``G`` is provided, the cache size is synchronised with
|
|
@@ -207,12 +393,12 @@ def cache_enabled(G: Any | None = None) -> bool:
|
|
|
207
393
|
# Only synchronise the cache size with ``G`` when caching is enabled. This
|
|
208
394
|
# preserves explicit calls to :func:`set_cache_maxsize(0)` which are used in
|
|
209
395
|
# tests to temporarily disable caching regardless of graph defaults.
|
|
210
|
-
if
|
|
396
|
+
if _seed_hash_cache.maxsize > 0:
|
|
211
397
|
_sync_cache_size(G)
|
|
212
|
-
return
|
|
398
|
+
return _seed_hash_cache.maxsize > 0
|
|
213
399
|
|
|
214
400
|
|
|
215
|
-
def base_seed(G:
|
|
401
|
+
def base_seed(G: TNFRGraph | GraphLike) -> int:
|
|
216
402
|
"""Return base RNG seed stored in ``G.graph``."""
|
|
217
403
|
graph = get_graph(G)
|
|
218
404
|
return int(graph.get("RANDOM_SEED", 0))
|
|
@@ -238,7 +424,7 @@ def set_cache_maxsize(size: int) -> None:
|
|
|
238
424
|
raise ValueError("size must be non-negative")
|
|
239
425
|
with _RNG_LOCK:
|
|
240
426
|
_seed_hash_cache.configure(new_size)
|
|
241
|
-
_CACHE_MAXSIZE =
|
|
427
|
+
_CACHE_MAXSIZE = _seed_hash_cache.maxsize
|
|
242
428
|
_CACHE_LOCKED = new_size != _DEFAULT_CACHE_MAXSIZE
|
|
243
429
|
|
|
244
430
|
|
tnfr/rng.pyi
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
__all__: Any
|
|
4
|
+
|
|
5
|
+
def __getattr__(name: str) -> Any: ...
|
|
6
|
+
|
|
7
|
+
ScopedCounterCache: Any
|
|
8
|
+
base_seed: Any
|
|
9
|
+
cache_enabled: Any
|
|
10
|
+
clear_rng_cache: Any
|
|
11
|
+
get_cache_maxsize: Any
|
|
12
|
+
make_rng: Any
|
|
13
|
+
seed_hash: Any
|
|
14
|
+
set_cache_maxsize: Any
|
tnfr/selector.py
CHANGED
|
@@ -8,32 +8,34 @@ from __future__ import annotations
|
|
|
8
8
|
|
|
9
9
|
import threading
|
|
10
10
|
from operator import itemgetter
|
|
11
|
-
from typing import Any, Mapping, TYPE_CHECKING
|
|
11
|
+
from typing import Any, Mapping, TYPE_CHECKING, cast
|
|
12
12
|
from weakref import WeakKeyDictionary
|
|
13
13
|
|
|
14
14
|
if TYPE_CHECKING: # pragma: no cover
|
|
15
|
-
import networkx as nx
|
|
15
|
+
import networkx as nx
|
|
16
16
|
|
|
17
17
|
from .constants import DEFAULTS
|
|
18
18
|
from .constants.core import SELECTOR_THRESHOLD_DEFAULTS
|
|
19
19
|
from .helpers.numeric import clamp01
|
|
20
20
|
from .metrics.common import compute_dnfr_accel_max
|
|
21
|
-
from .
|
|
21
|
+
from .utils import is_non_string_sequence
|
|
22
|
+
from .types import SelectorNorms, SelectorThresholds, SelectorWeights
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
HYSTERESIS_GLYPHS: set[str] = {"IL", "OZ", "ZHIR", "THOL", "NAV", "RA"}
|
|
25
26
|
|
|
26
27
|
__all__ = (
|
|
27
28
|
"_selector_thresholds",
|
|
28
|
-
"
|
|
29
|
+
"_selector_norms",
|
|
29
30
|
"_calc_selector_score",
|
|
30
31
|
"_apply_selector_hysteresis",
|
|
31
32
|
)
|
|
32
33
|
|
|
33
34
|
|
|
35
|
+
_SelectorThresholdItems = tuple[tuple[str, float], ...]
|
|
34
36
|
_SelectorThresholdCacheEntry = tuple[
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
_SelectorThresholdItems,
|
|
38
|
+
SelectorThresholds,
|
|
37
39
|
]
|
|
38
40
|
_SELECTOR_THRESHOLD_CACHE: WeakKeyDictionary[
|
|
39
41
|
"nx.Graph",
|
|
@@ -42,7 +44,7 @@ _SELECTOR_THRESHOLD_CACHE: WeakKeyDictionary[
|
|
|
42
44
|
_SELECTOR_THRESHOLD_CACHE_LOCK = threading.Lock()
|
|
43
45
|
|
|
44
46
|
|
|
45
|
-
def _sorted_items(mapping: Mapping[str, float]) ->
|
|
47
|
+
def _sorted_items(mapping: Mapping[str, float]) -> _SelectorThresholdItems:
|
|
46
48
|
"""Return mapping items sorted by key.
|
|
47
49
|
|
|
48
50
|
Parameters
|
|
@@ -59,8 +61,8 @@ def _sorted_items(mapping: Mapping[str, float]) -> tuple[tuple[str, float], ...]
|
|
|
59
61
|
|
|
60
62
|
|
|
61
63
|
def _compute_selector_thresholds(
|
|
62
|
-
thr_sel_items:
|
|
63
|
-
) ->
|
|
64
|
+
thr_sel_items: _SelectorThresholdItems,
|
|
65
|
+
) -> SelectorThresholds:
|
|
64
66
|
"""Construct selector thresholds for a graph.
|
|
65
67
|
|
|
66
68
|
Parameters
|
|
@@ -79,10 +81,10 @@ def _compute_selector_thresholds(
|
|
|
79
81
|
for key, default in SELECTOR_THRESHOLD_DEFAULTS.items():
|
|
80
82
|
val = thr_sel.get(key, default)
|
|
81
83
|
out[key] = clamp01(float(val))
|
|
82
|
-
return out
|
|
84
|
+
return cast(SelectorThresholds, out)
|
|
83
85
|
|
|
84
86
|
|
|
85
|
-
def _selector_thresholds(G: "nx.Graph") ->
|
|
87
|
+
def _selector_thresholds(G: "nx.Graph") -> SelectorThresholds:
|
|
86
88
|
"""Return normalised thresholds for Si, ΔNFR and acceleration.
|
|
87
89
|
|
|
88
90
|
Parameters
|
|
@@ -114,8 +116,8 @@ def _selector_thresholds(G: "nx.Graph") -> dict[str, float]:
|
|
|
114
116
|
return thresholds
|
|
115
117
|
|
|
116
118
|
|
|
117
|
-
def
|
|
118
|
-
"""Compute and cache norms for ΔNFR and acceleration.
|
|
119
|
+
def _selector_norms(G: "nx.Graph") -> SelectorNorms:
|
|
120
|
+
"""Compute and cache selector norms for ΔNFR and acceleration.
|
|
119
121
|
|
|
120
122
|
Parameters
|
|
121
123
|
----------
|
|
@@ -134,7 +136,7 @@ def _norms_para_selector(G: "nx.Graph") -> dict:
|
|
|
134
136
|
|
|
135
137
|
|
|
136
138
|
def _calc_selector_score(
|
|
137
|
-
Si: float, dnfr: float, accel: float, weights:
|
|
139
|
+
Si: float, dnfr: float, accel: float, weights: SelectorWeights
|
|
138
140
|
) -> float:
|
|
139
141
|
"""Compute weighted selector score.
|
|
140
142
|
|
|
@@ -167,7 +169,7 @@ def _apply_selector_hysteresis(
|
|
|
167
169
|
dnfr: float,
|
|
168
170
|
accel: float,
|
|
169
171
|
thr: dict[str, float],
|
|
170
|
-
margin: float,
|
|
172
|
+
margin: float | None,
|
|
171
173
|
) -> str | None:
|
|
172
174
|
"""Apply hysteresis when values are near thresholds.
|
|
173
175
|
|
|
@@ -183,8 +185,10 @@ def _apply_selector_hysteresis(
|
|
|
183
185
|
Normalised acceleration.
|
|
184
186
|
thr : dict[str, float]
|
|
185
187
|
Thresholds returned by :func:`_selector_thresholds`.
|
|
186
|
-
margin : float
|
|
187
|
-
|
|
188
|
+
margin : float or None
|
|
189
|
+
When positive, distance from thresholds below which the previous
|
|
190
|
+
glyph is reused. Falsy margins disable hysteresis entirely, letting
|
|
191
|
+
selectors bypass the reuse logic.
|
|
188
192
|
|
|
189
193
|
Returns
|
|
190
194
|
-------
|
|
@@ -192,6 +196,9 @@ def _apply_selector_hysteresis(
|
|
|
192
196
|
Previous glyph if hysteresis applies, otherwise ``None``.
|
|
193
197
|
"""
|
|
194
198
|
# Batch extraction reduces dictionary lookups inside loops.
|
|
199
|
+
if not margin:
|
|
200
|
+
return None
|
|
201
|
+
|
|
195
202
|
si_hi, si_lo, dnfr_hi, dnfr_lo, accel_hi, accel_lo = itemgetter(
|
|
196
203
|
"si_hi", "si_lo", "dnfr_hi", "dnfr_lo", "accel_hi", "accel_lo"
|
|
197
204
|
)(thr)
|
tnfr/selector.pyi
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Mapping
|
|
4
|
+
|
|
5
|
+
__all__: Any
|
|
6
|
+
|
|
7
|
+
def __getattr__(name: str) -> Any: ...
|
|
8
|
+
|
|
9
|
+
def _apply_selector_hysteresis(
|
|
10
|
+
nd: dict[str, Any],
|
|
11
|
+
Si: float,
|
|
12
|
+
dnfr: float,
|
|
13
|
+
accel: float,
|
|
14
|
+
thr: Mapping[str, float],
|
|
15
|
+
margin: float | None,
|
|
16
|
+
) -> str | None: ...
|
|
17
|
+
_calc_selector_score: Any
|
|
18
|
+
_selector_norms: Any
|
|
19
|
+
_selector_thresholds: Any
|