tnfr 6.0.0__py3-none-any.whl → 7.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of tnfr might be problematic. Click here for more details.
- tnfr/__init__.py +50 -5
- tnfr/__init__.pyi +0 -7
- tnfr/_compat.py +0 -1
- tnfr/_generated_version.py +34 -0
- tnfr/_version.py +44 -2
- tnfr/alias.py +14 -13
- tnfr/alias.pyi +5 -37
- tnfr/cache.py +9 -729
- tnfr/cache.pyi +8 -224
- tnfr/callback_utils.py +16 -31
- tnfr/callback_utils.pyi +3 -29
- tnfr/cli/__init__.py +17 -11
- tnfr/cli/__init__.pyi +0 -21
- tnfr/cli/arguments.py +175 -14
- tnfr/cli/arguments.pyi +5 -11
- tnfr/cli/execution.py +434 -48
- tnfr/cli/execution.pyi +14 -24
- tnfr/cli/utils.py +20 -3
- tnfr/cli/utils.pyi +5 -5
- tnfr/config/__init__.py +2 -1
- tnfr/config/__init__.pyi +2 -0
- tnfr/config/feature_flags.py +83 -0
- tnfr/config/init.py +1 -1
- tnfr/config/operator_names.py +1 -14
- tnfr/config/presets.py +6 -26
- tnfr/constants/__init__.py +10 -13
- tnfr/constants/__init__.pyi +10 -22
- tnfr/constants/aliases.py +31 -0
- tnfr/constants/core.py +4 -3
- tnfr/constants/init.py +1 -1
- tnfr/constants/metric.py +3 -3
- tnfr/dynamics/__init__.py +64 -10
- tnfr/dynamics/__init__.pyi +3 -4
- tnfr/dynamics/adaptation.py +79 -13
- tnfr/dynamics/aliases.py +10 -9
- tnfr/dynamics/coordination.py +77 -35
- tnfr/dynamics/dnfr.py +575 -274
- tnfr/dynamics/dnfr.pyi +1 -10
- tnfr/dynamics/integrators.py +47 -33
- tnfr/dynamics/integrators.pyi +0 -1
- tnfr/dynamics/runtime.py +489 -129
- tnfr/dynamics/sampling.py +2 -0
- tnfr/dynamics/selectors.py +101 -62
- tnfr/execution.py +15 -8
- tnfr/execution.pyi +5 -25
- tnfr/flatten.py +7 -3
- tnfr/flatten.pyi +1 -8
- tnfr/gamma.py +22 -26
- tnfr/gamma.pyi +0 -6
- tnfr/glyph_history.py +37 -26
- tnfr/glyph_history.pyi +1 -19
- tnfr/glyph_runtime.py +16 -0
- tnfr/glyph_runtime.pyi +9 -0
- tnfr/immutable.py +20 -15
- tnfr/immutable.pyi +4 -7
- tnfr/initialization.py +5 -7
- tnfr/initialization.pyi +1 -9
- tnfr/io.py +6 -305
- tnfr/io.pyi +13 -8
- tnfr/mathematics/__init__.py +81 -0
- tnfr/mathematics/backend.py +426 -0
- tnfr/mathematics/dynamics.py +398 -0
- tnfr/mathematics/epi.py +254 -0
- tnfr/mathematics/generators.py +222 -0
- tnfr/mathematics/metrics.py +119 -0
- tnfr/mathematics/operators.py +233 -0
- tnfr/mathematics/operators_factory.py +71 -0
- tnfr/mathematics/projection.py +78 -0
- tnfr/mathematics/runtime.py +173 -0
- tnfr/mathematics/spaces.py +247 -0
- tnfr/mathematics/transforms.py +292 -0
- tnfr/metrics/__init__.py +10 -10
- tnfr/metrics/coherence.py +123 -94
- tnfr/metrics/common.py +22 -13
- tnfr/metrics/common.pyi +42 -11
- tnfr/metrics/core.py +72 -14
- tnfr/metrics/diagnosis.py +48 -57
- tnfr/metrics/diagnosis.pyi +3 -7
- tnfr/metrics/export.py +3 -5
- tnfr/metrics/glyph_timing.py +41 -31
- tnfr/metrics/reporting.py +13 -6
- tnfr/metrics/sense_index.py +884 -114
- tnfr/metrics/trig.py +167 -11
- tnfr/metrics/trig.pyi +1 -0
- tnfr/metrics/trig_cache.py +112 -15
- tnfr/node.py +400 -17
- tnfr/node.pyi +55 -38
- tnfr/observers.py +111 -8
- tnfr/observers.pyi +0 -15
- tnfr/ontosim.py +9 -6
- tnfr/ontosim.pyi +0 -5
- tnfr/operators/__init__.py +529 -42
- tnfr/operators/__init__.pyi +14 -0
- tnfr/operators/definitions.py +350 -18
- tnfr/operators/definitions.pyi +0 -14
- tnfr/operators/grammar.py +760 -0
- tnfr/operators/jitter.py +28 -22
- tnfr/operators/registry.py +7 -12
- tnfr/operators/registry.pyi +0 -2
- tnfr/operators/remesh.py +38 -61
- tnfr/rng.py +17 -300
- tnfr/schemas/__init__.py +8 -0
- tnfr/schemas/grammar.json +94 -0
- tnfr/selector.py +3 -4
- tnfr/selector.pyi +1 -1
- tnfr/sense.py +22 -24
- tnfr/sense.pyi +0 -7
- tnfr/structural.py +504 -21
- tnfr/structural.pyi +41 -18
- tnfr/telemetry/__init__.py +23 -1
- tnfr/telemetry/cache_metrics.py +226 -0
- tnfr/telemetry/nu_f.py +423 -0
- tnfr/telemetry/nu_f.pyi +123 -0
- tnfr/tokens.py +1 -4
- tnfr/tokens.pyi +1 -6
- tnfr/trace.py +20 -53
- tnfr/trace.pyi +9 -37
- tnfr/types.py +244 -15
- tnfr/types.pyi +200 -14
- tnfr/units.py +69 -0
- tnfr/units.pyi +16 -0
- tnfr/utils/__init__.py +107 -48
- tnfr/utils/__init__.pyi +80 -11
- tnfr/utils/cache.py +1705 -65
- tnfr/utils/cache.pyi +370 -58
- tnfr/utils/chunks.py +104 -0
- tnfr/utils/chunks.pyi +21 -0
- tnfr/utils/data.py +95 -5
- tnfr/utils/data.pyi +8 -17
- tnfr/utils/graph.py +2 -4
- tnfr/utils/init.py +31 -7
- tnfr/utils/init.pyi +4 -11
- tnfr/utils/io.py +313 -14
- tnfr/{helpers → utils}/numeric.py +50 -24
- tnfr/utils/numeric.pyi +21 -0
- tnfr/validation/__init__.py +92 -4
- tnfr/validation/__init__.pyi +77 -17
- tnfr/validation/compatibility.py +79 -43
- tnfr/validation/compatibility.pyi +4 -6
- tnfr/validation/grammar.py +55 -133
- tnfr/validation/grammar.pyi +37 -8
- tnfr/validation/graph.py +138 -0
- tnfr/validation/graph.pyi +17 -0
- tnfr/validation/rules.py +161 -74
- tnfr/validation/rules.pyi +55 -18
- tnfr/validation/runtime.py +263 -0
- tnfr/validation/runtime.pyi +31 -0
- tnfr/validation/soft_filters.py +170 -0
- tnfr/validation/soft_filters.pyi +37 -0
- tnfr/validation/spectral.py +159 -0
- tnfr/validation/spectral.pyi +46 -0
- tnfr/validation/syntax.py +28 -139
- tnfr/validation/syntax.pyi +7 -4
- tnfr/validation/window.py +39 -0
- tnfr/validation/window.pyi +1 -0
- tnfr/viz/__init__.py +9 -0
- tnfr/viz/matplotlib.py +246 -0
- {tnfr-6.0.0.dist-info → tnfr-7.0.0.dist-info}/METADATA +63 -19
- tnfr-7.0.0.dist-info/RECORD +185 -0
- {tnfr-6.0.0.dist-info → tnfr-7.0.0.dist-info}/licenses/LICENSE.md +1 -1
- tnfr/constants_glyphs.py +0 -16
- tnfr/constants_glyphs.pyi +0 -12
- tnfr/grammar.py +0 -25
- tnfr/grammar.pyi +0 -13
- tnfr/helpers/__init__.py +0 -151
- tnfr/helpers/__init__.pyi +0 -66
- tnfr/helpers/numeric.pyi +0 -12
- tnfr/presets.py +0 -15
- tnfr/presets.pyi +0 -7
- tnfr/utils/io.pyi +0 -10
- tnfr/utils/validators.py +0 -130
- tnfr/utils/validators.pyi +0 -19
- tnfr-6.0.0.dist-info/RECORD +0 -157
- {tnfr-6.0.0.dist-info → tnfr-7.0.0.dist-info}/WHEEL +0 -0
- {tnfr-6.0.0.dist-info → tnfr-7.0.0.dist-info}/entry_points.txt +0 -0
- {tnfr-6.0.0.dist-info → tnfr-7.0.0.dist-info}/top_level.txt +0 -0
tnfr/rng.py
CHANGED
|
@@ -5,18 +5,19 @@ from __future__ import annotations
|
|
|
5
5
|
import hashlib
|
|
6
6
|
import random
|
|
7
7
|
import struct
|
|
8
|
-
import
|
|
9
|
-
from collections.abc import Iterator, MutableMapping
|
|
10
|
-
from dataclasses import dataclass
|
|
11
|
-
from typing import Any, Generic, Hashable, TypeVar, cast
|
|
12
|
-
|
|
8
|
+
from typing import Any, cast
|
|
13
9
|
|
|
14
10
|
from cachetools import cached # type: ignore[import-untyped]
|
|
11
|
+
|
|
15
12
|
from .constants import DEFAULTS, get_param
|
|
16
|
-
from .cache import CacheManager, InstrumentedLRUCache
|
|
17
|
-
from .utils import get_graph
|
|
18
13
|
from .locking import get_lock
|
|
19
14
|
from .types import GraphLike, TNFRGraph
|
|
15
|
+
from .utils import (
|
|
16
|
+
ScopedCounterCache,
|
|
17
|
+
_SeedHashCache,
|
|
18
|
+
build_cache_manager,
|
|
19
|
+
get_graph,
|
|
20
|
+
)
|
|
20
21
|
|
|
21
22
|
MASK64 = 0xFFFFFFFFFFFFFFFF
|
|
22
23
|
|
|
@@ -25,296 +26,14 @@ _DEFAULT_CACHE_MAXSIZE = int(DEFAULTS.get("JITTER_CACHE_SIZE", 128))
|
|
|
25
26
|
_CACHE_MAXSIZE = _DEFAULT_CACHE_MAXSIZE
|
|
26
27
|
_CACHE_LOCKED = False
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
class _SeedHashCache(MutableMapping[tuple[int, int], int]):
|
|
49
|
-
"""Mutable mapping proxy exposing a configurable LRU cache."""
|
|
50
|
-
|
|
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
|
|
105
|
-
|
|
106
|
-
def configure(self, maxsize: int) -> None:
|
|
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())
|
|
112
|
-
|
|
113
|
-
def __getitem__(self, key: tuple[int, int]) -> int:
|
|
114
|
-
state = self._get_state()
|
|
115
|
-
if state is None or state.cache is None:
|
|
116
|
-
raise KeyError(key)
|
|
117
|
-
value = state.cache[key]
|
|
118
|
-
self._manager.increment_hit(self._state_key)
|
|
119
|
-
return value
|
|
120
|
-
|
|
121
|
-
def __setitem__(self, key: tuple[int, int], value: int) -> None:
|
|
122
|
-
state = self._get_state()
|
|
123
|
-
if state is not None and state.cache is not None:
|
|
124
|
-
state.cache[key] = value
|
|
125
|
-
|
|
126
|
-
def __delitem__(self, key: tuple[int, int]) -> None:
|
|
127
|
-
state = self._get_state()
|
|
128
|
-
if state is None or state.cache is None:
|
|
129
|
-
raise KeyError(key)
|
|
130
|
-
del state.cache[key]
|
|
131
|
-
|
|
132
|
-
def __iter__(self) -> Iterator[tuple[int, int]]:
|
|
133
|
-
state = self._get_state(create=False)
|
|
134
|
-
if state is None or state.cache is None:
|
|
135
|
-
return iter(())
|
|
136
|
-
return iter(state.cache)
|
|
137
|
-
|
|
138
|
-
def __len__(self) -> int:
|
|
139
|
-
state = self._get_state(create=False)
|
|
140
|
-
if state is None or state.cache is None:
|
|
141
|
-
return 0
|
|
142
|
-
return len(state.cache)
|
|
143
|
-
|
|
144
|
-
def clear(self) -> None: # type: ignore[override]
|
|
145
|
-
self._manager.clear(self._state_key)
|
|
146
|
-
|
|
147
|
-
@property
|
|
148
|
-
def maxsize(self) -> int:
|
|
149
|
-
state = self._get_state()
|
|
150
|
-
return 0 if state is None else state.maxsize
|
|
151
|
-
|
|
152
|
-
@property
|
|
153
|
-
def enabled(self) -> bool:
|
|
154
|
-
state = self._get_state(create=False)
|
|
155
|
-
return bool(state and state.cache is not None)
|
|
156
|
-
|
|
157
|
-
@property
|
|
158
|
-
def data(self) -> InstrumentedLRUCache[tuple[int, int], int] | None:
|
|
159
|
-
"""Expose the underlying cache for diagnostics/tests."""
|
|
160
|
-
|
|
161
|
-
state = self._get_state(create=False)
|
|
162
|
-
return None if state is None else state.cache
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
class ScopedCounterCache(Generic[K]):
|
|
166
|
-
"""Thread-safe LRU cache storing monotonic counters by ``key``."""
|
|
167
|
-
|
|
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:
|
|
182
|
-
raise ValueError("max_entries must be non-negative")
|
|
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
|
|
230
|
-
|
|
231
|
-
@property
|
|
232
|
-
def lock(self) -> threading.Lock | threading.RLock:
|
|
233
|
-
"""Return the lock guarding access to the underlying cache."""
|
|
234
|
-
|
|
235
|
-
return self._manager.get_lock(self._state_key)
|
|
236
|
-
|
|
237
|
-
@property
|
|
238
|
-
def max_entries(self) -> int:
|
|
239
|
-
"""Return the configured maximum number of cached entries."""
|
|
240
|
-
|
|
241
|
-
return self._get_state().max_entries
|
|
242
|
-
|
|
243
|
-
@property
|
|
244
|
-
def cache(self) -> InstrumentedLRUCache[K, int]:
|
|
245
|
-
"""Expose the instrumented cache for inspection."""
|
|
246
|
-
|
|
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
|
|
254
|
-
|
|
255
|
-
def configure(
|
|
256
|
-
self, *, force: bool = False, max_entries: int | None = None
|
|
257
|
-
) -> None:
|
|
258
|
-
"""Resize or reset the cache keeping previous settings."""
|
|
259
|
-
|
|
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)
|
|
287
|
-
|
|
288
|
-
def clear(self) -> None:
|
|
289
|
-
"""Clear stored counters preserving ``max_entries``."""
|
|
290
|
-
|
|
291
|
-
self.configure(force=True)
|
|
292
|
-
|
|
293
|
-
def bump(self, key: K) -> int:
|
|
294
|
-
"""Return current counter for ``key`` and increment it atomically."""
|
|
295
|
-
|
|
296
|
-
result: dict[str, Any] = {}
|
|
297
|
-
|
|
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
|
|
309
|
-
|
|
310
|
-
self._manager.update(self._state_key, _update)
|
|
311
|
-
return int(result.get("value", 0))
|
|
312
|
-
|
|
313
|
-
def __len__(self) -> int:
|
|
314
|
-
return len(self.cache)
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
_seed_hash_cache = _SeedHashCache()
|
|
29
|
+
|
|
30
|
+
_RNG_CACHE_MANAGER = build_cache_manager(default_capacity=_DEFAULT_CACHE_MAXSIZE)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
_seed_hash_cache = _SeedHashCache(
|
|
34
|
+
manager=_RNG_CACHE_MANAGER,
|
|
35
|
+
default_maxsize=_DEFAULT_CACHE_MAXSIZE,
|
|
36
|
+
)
|
|
318
37
|
|
|
319
38
|
|
|
320
39
|
def _compute_seed_hash(seed_int: int, key_int: int) -> int:
|
|
@@ -323,9 +42,7 @@ def _compute_seed_hash(seed_int: int, key_int: int) -> int:
|
|
|
323
42
|
seed_int & MASK64,
|
|
324
43
|
key_int & MASK64,
|
|
325
44
|
)
|
|
326
|
-
return int.from_bytes(
|
|
327
|
-
hashlib.blake2b(seed_bytes, digest_size=8).digest(), "big"
|
|
328
|
-
)
|
|
45
|
+
return int.from_bytes(hashlib.blake2b(seed_bytes, digest_size=8).digest(), "big")
|
|
329
46
|
|
|
330
47
|
|
|
331
48
|
@cached(cache=_seed_hash_cache, lock=_RNG_LOCK)
|
tnfr/schemas/__init__.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://tnfr.io/schemas/grammar.json",
|
|
4
|
+
"title": "TNFR Grammar Configuration",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"definitions": {
|
|
7
|
+
"glyphCode": {
|
|
8
|
+
"description": "Canonical glyph identifier in uppercase notation.",
|
|
9
|
+
"type": "string",
|
|
10
|
+
"pattern": "^[A-Z][A-Z_]*$"
|
|
11
|
+
},
|
|
12
|
+
"probability": {
|
|
13
|
+
"description": "Threshold constrained to the [0, 1] interval.",
|
|
14
|
+
"type": "number",
|
|
15
|
+
"minimum": 0.0,
|
|
16
|
+
"maximum": 1.0
|
|
17
|
+
},
|
|
18
|
+
"cfg_soft": {
|
|
19
|
+
"type": "object",
|
|
20
|
+
"description": "Soft grammar preferences applied before canonical automaton rules.",
|
|
21
|
+
"properties": {
|
|
22
|
+
"window": {
|
|
23
|
+
"description": "History window inspected to avoid short-term glyph repetitions.",
|
|
24
|
+
"type": "integer",
|
|
25
|
+
"minimum": 0
|
|
26
|
+
},
|
|
27
|
+
"avoid_repeats": {
|
|
28
|
+
"description": "Glyph codes that should be substituted when repeated inside the window.",
|
|
29
|
+
"type": "array",
|
|
30
|
+
"items": { "$ref": "#/definitions/glyphCode" },
|
|
31
|
+
"uniqueItems": true
|
|
32
|
+
},
|
|
33
|
+
"fallbacks": {
|
|
34
|
+
"description": "Mapping of glyph codes to their explicit substitution when repetition is detected.",
|
|
35
|
+
"type": "object",
|
|
36
|
+
"additionalProperties": { "$ref": "#/definitions/glyphCode" }
|
|
37
|
+
},
|
|
38
|
+
"force_dnfr": {
|
|
39
|
+
"description": "Minimum |ΔNFR| normalised score that bypasses soft filtering.",
|
|
40
|
+
"$ref": "#/definitions/probability"
|
|
41
|
+
},
|
|
42
|
+
"force_accel": {
|
|
43
|
+
"description": "Minimum acceleration normalised score that bypasses soft filtering.",
|
|
44
|
+
"$ref": "#/definitions/probability"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"additionalProperties": true
|
|
48
|
+
},
|
|
49
|
+
"cfg_canon": {
|
|
50
|
+
"type": "object",
|
|
51
|
+
"description": "Canonical grammar thresholds enforced by the automaton.",
|
|
52
|
+
"properties": {
|
|
53
|
+
"enabled": {
|
|
54
|
+
"description": "Toggle canonical grammar enforcement during metric runs.",
|
|
55
|
+
"type": "boolean"
|
|
56
|
+
},
|
|
57
|
+
"zhir_requires_oz_window": {
|
|
58
|
+
"description": "Window requiring a DISSONANCE glyph before a MUTATION.",
|
|
59
|
+
"type": "integer",
|
|
60
|
+
"minimum": 0
|
|
61
|
+
},
|
|
62
|
+
"zhir_dnfr_min": {
|
|
63
|
+
"description": "Minimum normalised |ΔNFR| required to allow MUTATION without recent DISSONANCE.",
|
|
64
|
+
"type": "number",
|
|
65
|
+
"minimum": 0.0
|
|
66
|
+
},
|
|
67
|
+
"thol_min_len": {
|
|
68
|
+
"description": "Minimum number of THOL glyphs before canonical closure is allowed.",
|
|
69
|
+
"type": "integer",
|
|
70
|
+
"minimum": 0
|
|
71
|
+
},
|
|
72
|
+
"thol_max_len": {
|
|
73
|
+
"description": "Maximum number of THOL glyphs tolerated before forcing closure.",
|
|
74
|
+
"type": "integer",
|
|
75
|
+
"minimum": 0
|
|
76
|
+
},
|
|
77
|
+
"thol_close_dnfr": {
|
|
78
|
+
"description": "Upper bound on normalised |ΔNFR| that triggers THOL closure.",
|
|
79
|
+
"$ref": "#/definitions/probability"
|
|
80
|
+
},
|
|
81
|
+
"si_high": {
|
|
82
|
+
"description": "Sense index threshold: Si at or above this resolves THOL closures with silence; lower Si forces contraction.",
|
|
83
|
+
"$ref": "#/definitions/probability"
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
"additionalProperties": true
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
"properties": {
|
|
90
|
+
"cfg_soft": { "$ref": "#/definitions/cfg_soft" },
|
|
91
|
+
"cfg_canon": { "$ref": "#/definitions/cfg_canon" }
|
|
92
|
+
},
|
|
93
|
+
"additionalProperties": false
|
|
94
|
+
}
|
tnfr/selector.py
CHANGED
|
@@ -8,7 +8,7 @@ from __future__ import annotations
|
|
|
8
8
|
|
|
9
9
|
import threading
|
|
10
10
|
from operator import itemgetter
|
|
11
|
-
from typing import Any, Mapping,
|
|
11
|
+
from typing import TYPE_CHECKING, Any, Mapping, cast
|
|
12
12
|
from weakref import WeakKeyDictionary
|
|
13
13
|
|
|
14
14
|
if TYPE_CHECKING: # pragma: no cover
|
|
@@ -16,11 +16,10 @@ if TYPE_CHECKING: # pragma: no cover
|
|
|
16
16
|
|
|
17
17
|
from .constants import DEFAULTS
|
|
18
18
|
from .constants.core import SELECTOR_THRESHOLD_DEFAULTS
|
|
19
|
-
from .
|
|
19
|
+
from .utils import clamp01
|
|
20
20
|
from .metrics.common import compute_dnfr_accel_max
|
|
21
|
-
from .utils import is_non_string_sequence
|
|
22
21
|
from .types import SelectorNorms, SelectorThresholds, SelectorWeights
|
|
23
|
-
|
|
22
|
+
from .utils import is_non_string_sequence
|
|
24
23
|
|
|
25
24
|
HYSTERESIS_GLYPHS: set[str] = {"IL", "OZ", "ZHIR", "THOL", "NAV", "RA"}
|
|
26
25
|
|
tnfr/selector.pyi
CHANGED
|
@@ -5,7 +5,6 @@ from typing import Any, Mapping
|
|
|
5
5
|
__all__: Any
|
|
6
6
|
|
|
7
7
|
def __getattr__(name: str) -> Any: ...
|
|
8
|
-
|
|
9
8
|
def _apply_selector_hysteresis(
|
|
10
9
|
nd: dict[str, Any],
|
|
11
10
|
Si: float,
|
|
@@ -14,6 +13,7 @@ def _apply_selector_hysteresis(
|
|
|
14
13
|
thr: Mapping[str, float],
|
|
15
14
|
margin: float | None,
|
|
16
15
|
) -> str | None: ...
|
|
16
|
+
|
|
17
17
|
_calc_selector_score: Any
|
|
18
18
|
_selector_norms: Any
|
|
19
19
|
_selector_thresholds: Any
|
tnfr/sense.py
CHANGED
|
@@ -1,30 +1,29 @@
|
|
|
1
1
|
"""Sense calculations."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from collections.abc import Iterable, Iterator, Mapping
|
|
4
|
+
|
|
6
5
|
import math
|
|
7
6
|
from collections import Counter
|
|
7
|
+
from collections.abc import Iterable, Iterator, Mapping
|
|
8
8
|
from itertools import tee
|
|
9
|
+
from typing import Any, Callable, TypeVar
|
|
9
10
|
|
|
10
11
|
import networkx as nx
|
|
11
12
|
|
|
12
|
-
from .constants import get_aliases, get_graph_param
|
|
13
13
|
from .alias import get_attr
|
|
14
|
-
from .helpers.numeric import clamp01, kahan_sum_nd
|
|
15
|
-
from .utils import get_numpy
|
|
16
14
|
from .callback_utils import CallbackEvent, callback_manager
|
|
17
|
-
from .glyph_history import (
|
|
18
|
-
ensure_history,
|
|
19
|
-
last_glyph,
|
|
20
|
-
count_glyphs,
|
|
21
|
-
append_metric,
|
|
22
|
-
)
|
|
23
15
|
from .config.constants import (
|
|
24
16
|
ANGLE_MAP,
|
|
25
17
|
GLYPHS_CANONICAL,
|
|
26
18
|
)
|
|
19
|
+
from .constants import get_graph_param
|
|
20
|
+
from .constants.aliases import ALIAS_EPI, ALIAS_SI
|
|
21
|
+
from .glyph_history import append_metric, count_glyphs, ensure_history
|
|
22
|
+
from .glyph_runtime import last_glyph
|
|
23
|
+
from .utils import clamp01, kahan_sum_nd
|
|
27
24
|
from .types import NodeId, SigmaVector, TNFRGraph
|
|
25
|
+
from .utils import get_numpy
|
|
26
|
+
|
|
28
27
|
# -------------------------
|
|
29
28
|
# Canon: circular glyph order and angles
|
|
30
29
|
# -------------------------
|
|
@@ -74,9 +73,6 @@ def glyph_unit(g: str) -> complex:
|
|
|
74
73
|
return _resolve_glyph(g, GLYPH_UNITS)
|
|
75
74
|
|
|
76
75
|
|
|
77
|
-
ALIAS_SI = get_aliases("SI")
|
|
78
|
-
ALIAS_EPI = get_aliases("EPI")
|
|
79
|
-
|
|
80
76
|
MODE_FUNCS: dict[str, Callable[[Mapping[str, Any]], float]] = {
|
|
81
77
|
"Si": lambda nd: clamp01(get_attr(nd, ALIAS_SI, 0.5)),
|
|
82
78
|
"EPI": lambda nd: max(0.0, get_attr(nd, ALIAS_EPI, 0.0)),
|
|
@@ -145,7 +141,9 @@ def _sigma_from_iterable(
|
|
|
145
141
|
number of processed values under the ``"n"`` key.
|
|
146
142
|
"""
|
|
147
143
|
|
|
148
|
-
if isinstance(values, Iterable) and not isinstance(
|
|
144
|
+
if isinstance(values, Iterable) and not isinstance(
|
|
145
|
+
values, (str, bytes, bytearray, Mapping)
|
|
146
|
+
):
|
|
149
147
|
iterator = iter(values)
|
|
150
148
|
else:
|
|
151
149
|
iterator = iter((values,))
|
|
@@ -195,9 +193,7 @@ def _sigma_from_iterable(
|
|
|
195
193
|
}
|
|
196
194
|
|
|
197
195
|
|
|
198
|
-
def _ema_update(
|
|
199
|
-
prev: SigmaVector, current: SigmaVector, alpha: float
|
|
200
|
-
) -> SigmaVector:
|
|
196
|
+
def _ema_update(prev: SigmaVector, current: SigmaVector, alpha: float) -> SigmaVector:
|
|
201
197
|
"""Exponential moving average update for σ vectors."""
|
|
202
198
|
x = (1 - alpha) * prev["x"] + alpha * current["x"]
|
|
203
199
|
y = (1 - alpha) * prev["y"] + alpha * current["y"]
|
|
@@ -231,6 +227,8 @@ def _sigma_from_nodes(
|
|
|
231
227
|
def sigma_vector_node(
|
|
232
228
|
G: TNFRGraph, n: NodeId, weight_mode: str | None = None
|
|
233
229
|
) -> SigmaVector | None:
|
|
230
|
+
"""Return the σ vector for node ``n`` using the configured weighting."""
|
|
231
|
+
|
|
234
232
|
cfg = _sigma_cfg(G)
|
|
235
233
|
nd = G.nodes[n]
|
|
236
234
|
weight_mode = weight_mode or cfg.get("weight", "Si")
|
|
@@ -280,9 +278,7 @@ def sigma_vector_from_graph(
|
|
|
280
278
|
|
|
281
279
|
cfg = _sigma_cfg(G)
|
|
282
280
|
weight_mode = weight_mode or cfg.get("weight", "Si")
|
|
283
|
-
sv, _ = _sigma_from_nodes(
|
|
284
|
-
(nd for _, nd in G.nodes(data=True)), weight_mode
|
|
285
|
-
)
|
|
281
|
+
sv, _ = _sigma_from_nodes((nd for _, nd in G.nodes(data=True)), weight_mode)
|
|
286
282
|
return sv
|
|
287
283
|
|
|
288
284
|
|
|
@@ -292,6 +288,8 @@ def sigma_vector_from_graph(
|
|
|
292
288
|
|
|
293
289
|
|
|
294
290
|
def push_sigma_snapshot(G: TNFRGraph, t: float | None = None) -> None:
|
|
291
|
+
"""Record a global σ snapshot (and optional per-node traces) for ``G``."""
|
|
292
|
+
|
|
295
293
|
cfg = _sigma_cfg(G)
|
|
296
294
|
if not cfg.get("enabled", True):
|
|
297
295
|
return
|
|
@@ -334,6 +332,8 @@ def push_sigma_snapshot(G: TNFRGraph, t: float | None = None) -> None:
|
|
|
334
332
|
|
|
335
333
|
|
|
336
334
|
def register_sigma_callback(G: TNFRGraph) -> None:
|
|
335
|
+
"""Attach :func:`push_sigma_snapshot` to the ``AFTER_STEP`` callback bus."""
|
|
336
|
+
|
|
337
337
|
callback_manager.register_callback(
|
|
338
338
|
G,
|
|
339
339
|
event=CallbackEvent.AFTER_STEP.value,
|
|
@@ -352,9 +352,7 @@ def sigma_rose(G: TNFRGraph, steps: int | None = None) -> dict[str, int]:
|
|
|
352
352
|
steps = int(steps)
|
|
353
353
|
if steps < 0:
|
|
354
354
|
raise ValueError("steps must be non-negative")
|
|
355
|
-
rows = (
|
|
356
|
-
counts if steps >= len(counts) else counts[-steps:]
|
|
357
|
-
) # noqa: E203
|
|
355
|
+
rows = counts if steps >= len(counts) else counts[-steps:] # noqa: E203
|
|
358
356
|
else:
|
|
359
357
|
rows = counts
|
|
360
358
|
counter = Counter()
|
tnfr/sense.pyi
CHANGED
|
@@ -10,21 +10,14 @@ __all__: tuple[str, ...]
|
|
|
10
10
|
GLYPH_UNITS: dict[str, complex]
|
|
11
11
|
|
|
12
12
|
def glyph_angle(g: str) -> float: ...
|
|
13
|
-
|
|
14
13
|
def glyph_unit(g: str) -> complex: ...
|
|
15
|
-
|
|
16
14
|
def push_sigma_snapshot(G: TNFRGraph, t: Optional[float] = None) -> None: ...
|
|
17
|
-
|
|
18
15
|
def register_sigma_callback(G: TNFRGraph) -> None: ...
|
|
19
|
-
|
|
20
16
|
def sigma_rose(G: TNFRGraph, steps: Optional[int] = None) -> dict[str, int]: ...
|
|
21
|
-
|
|
22
17
|
def sigma_vector(dist: Mapping[str, float]) -> SigmaVector: ...
|
|
23
|
-
|
|
24
18
|
def sigma_vector_from_graph(
|
|
25
19
|
G: TNFRGraph, weight_mode: Optional[str] = None
|
|
26
20
|
) -> SigmaVector: ...
|
|
27
|
-
|
|
28
21
|
def sigma_vector_node(
|
|
29
22
|
G: TNFRGraph, n: NodeId, weight_mode: Optional[str] = None
|
|
30
23
|
) -> Optional[SigmaVector]: ...
|