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
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""Definitions for canonical TNFR structural operators.
|
|
2
|
+
|
|
3
|
+
English identifiers are the public API. Spanish wrappers were removed in
|
|
4
|
+
TNFR 2.0, so downstream code must import these classes directly.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any, ClassVar
|
|
10
|
+
|
|
11
|
+
from ..config.operator_names import (
|
|
12
|
+
COHERENCE,
|
|
13
|
+
COUPLING,
|
|
14
|
+
DISSONANCE,
|
|
15
|
+
EMISSION,
|
|
16
|
+
MUTATION,
|
|
17
|
+
RECEPTION,
|
|
18
|
+
RECURSIVITY,
|
|
19
|
+
RESONANCE,
|
|
20
|
+
SELF_ORGANIZATION,
|
|
21
|
+
SILENCE,
|
|
22
|
+
TRANSITION,
|
|
23
|
+
CONTRACTION,
|
|
24
|
+
EXPANSION,
|
|
25
|
+
)
|
|
26
|
+
from ..types import Glyph, TNFRGraph
|
|
27
|
+
from .registry import register_operator
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"Operator",
|
|
31
|
+
"Emission",
|
|
32
|
+
"Reception",
|
|
33
|
+
"Coherence",
|
|
34
|
+
"Dissonance",
|
|
35
|
+
"Coupling",
|
|
36
|
+
"Resonance",
|
|
37
|
+
"Silence",
|
|
38
|
+
"Expansion",
|
|
39
|
+
"Contraction",
|
|
40
|
+
"SelfOrganization",
|
|
41
|
+
"Mutation",
|
|
42
|
+
"Transition",
|
|
43
|
+
"Recursivity",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class Operator:
|
|
48
|
+
"""Base class for TNFR operators.
|
|
49
|
+
|
|
50
|
+
Each operator defines ``name`` (ASCII identifier) and ``glyph``. Calling an
|
|
51
|
+
instance applies the corresponding glyph to the node.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
name: ClassVar[str] = "operator"
|
|
55
|
+
glyph: ClassVar[Glyph | None] = None
|
|
56
|
+
|
|
57
|
+
def __call__(self, G: TNFRGraph, node: Any, **kw: Any) -> None:
|
|
58
|
+
if self.glyph is None:
|
|
59
|
+
raise NotImplementedError("Operator without assigned glyph")
|
|
60
|
+
from ..validation.grammar import ( # local import to avoid cycles
|
|
61
|
+
apply_glyph_with_grammar,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
apply_glyph_with_grammar(G, [node], self.glyph, kw.get("window"))
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@register_operator
|
|
68
|
+
class Emission(Operator):
|
|
69
|
+
"""Emission operator (glyph ``AL``)."""
|
|
70
|
+
|
|
71
|
+
__slots__ = ()
|
|
72
|
+
name: ClassVar[str] = EMISSION
|
|
73
|
+
glyph: ClassVar[Glyph] = Glyph.AL
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@register_operator
|
|
77
|
+
class Reception(Operator):
|
|
78
|
+
"""Reception operator (glyph ``EN``)."""
|
|
79
|
+
|
|
80
|
+
__slots__ = ()
|
|
81
|
+
name: ClassVar[str] = RECEPTION
|
|
82
|
+
glyph: ClassVar[Glyph] = Glyph.EN
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@register_operator
|
|
86
|
+
class Coherence(Operator):
|
|
87
|
+
"""Coherence operator (glyph ``IL``)."""
|
|
88
|
+
|
|
89
|
+
__slots__ = ()
|
|
90
|
+
name: ClassVar[str] = COHERENCE
|
|
91
|
+
glyph: ClassVar[Glyph] = Glyph.IL
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@register_operator
|
|
95
|
+
class Dissonance(Operator):
|
|
96
|
+
"""Dissonance operator (glyph ``OZ``)."""
|
|
97
|
+
|
|
98
|
+
__slots__ = ()
|
|
99
|
+
name: ClassVar[str] = DISSONANCE
|
|
100
|
+
glyph: ClassVar[Glyph] = Glyph.OZ
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@register_operator
|
|
104
|
+
class Coupling(Operator):
|
|
105
|
+
"""Coupling operator (glyph ``UM``)."""
|
|
106
|
+
|
|
107
|
+
__slots__ = ()
|
|
108
|
+
name: ClassVar[str] = COUPLING
|
|
109
|
+
glyph: ClassVar[Glyph] = Glyph.UM
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@register_operator
|
|
113
|
+
class Resonance(Operator):
|
|
114
|
+
"""Resonance operator (glyph ``RA``)."""
|
|
115
|
+
|
|
116
|
+
__slots__ = ()
|
|
117
|
+
name: ClassVar[str] = RESONANCE
|
|
118
|
+
glyph: ClassVar[Glyph] = Glyph.RA
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@register_operator
|
|
122
|
+
class Silence(Operator):
|
|
123
|
+
"""Silence operator (glyph ``SHA``)."""
|
|
124
|
+
|
|
125
|
+
__slots__ = ()
|
|
126
|
+
name: ClassVar[str] = SILENCE
|
|
127
|
+
glyph: ClassVar[Glyph] = Glyph.SHA
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@register_operator
|
|
131
|
+
class Expansion(Operator):
|
|
132
|
+
"""Expansion operator (glyph ``VAL``)."""
|
|
133
|
+
|
|
134
|
+
__slots__ = ()
|
|
135
|
+
name: ClassVar[str] = EXPANSION
|
|
136
|
+
glyph: ClassVar[Glyph] = Glyph.VAL
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@register_operator
|
|
140
|
+
class Contraction(Operator):
|
|
141
|
+
"""Contraction operator (glyph ``NUL``)."""
|
|
142
|
+
|
|
143
|
+
__slots__ = ()
|
|
144
|
+
name: ClassVar[str] = CONTRACTION
|
|
145
|
+
glyph: ClassVar[Glyph] = Glyph.NUL
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@register_operator
|
|
149
|
+
class SelfOrganization(Operator):
|
|
150
|
+
"""Self-organization operator (glyph ``THOL``)."""
|
|
151
|
+
|
|
152
|
+
__slots__ = ()
|
|
153
|
+
name: ClassVar[str] = SELF_ORGANIZATION
|
|
154
|
+
glyph: ClassVar[Glyph] = Glyph.THOL
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@register_operator
|
|
158
|
+
class Mutation(Operator):
|
|
159
|
+
"""Mutation operator (glyph ``ZHIR``)."""
|
|
160
|
+
|
|
161
|
+
__slots__ = ()
|
|
162
|
+
name: ClassVar[str] = MUTATION
|
|
163
|
+
glyph: ClassVar[Glyph] = Glyph.ZHIR
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@register_operator
|
|
167
|
+
class Transition(Operator):
|
|
168
|
+
"""Transition operator (glyph ``NAV``)."""
|
|
169
|
+
|
|
170
|
+
__slots__ = ()
|
|
171
|
+
name: ClassVar[str] = TRANSITION
|
|
172
|
+
glyph: ClassVar[Glyph] = Glyph.NAV
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
@register_operator
|
|
176
|
+
class Recursivity(Operator):
|
|
177
|
+
"""Recursivity operator (glyph ``REMESH``)."""
|
|
178
|
+
|
|
179
|
+
__slots__ = ()
|
|
180
|
+
name: ClassVar[str] = RECURSIVITY
|
|
181
|
+
glyph: ClassVar[Glyph] = Glyph.REMESH
|
|
@@ -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
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
|
|
2
|
+
import threading
|
|
3
|
+
|
|
4
|
+
from typing import Any, TYPE_CHECKING, cast
|
|
3
5
|
|
|
4
6
|
from cachetools import LRUCache
|
|
5
7
|
|
|
6
|
-
from ..cache import ensure_node_offset_map
|
|
7
8
|
from ..rng import (
|
|
8
9
|
ScopedCounterCache,
|
|
9
10
|
make_rng,
|
|
@@ -12,10 +13,12 @@ from ..rng import (
|
|
|
12
13
|
clear_rng_cache as _clear_rng_cache,
|
|
13
14
|
seed_hash,
|
|
14
15
|
)
|
|
15
|
-
from ..
|
|
16
|
+
from ..cache import CacheManager
|
|
17
|
+
from ..utils import ensure_node_offset_map, get_nodenx
|
|
18
|
+
from ..types import NodeId, TNFRGraph
|
|
16
19
|
|
|
17
20
|
if TYPE_CHECKING: # pragma: no cover - type checking only
|
|
18
|
-
from ..node import
|
|
21
|
+
from ..node import NodeProtocol
|
|
19
22
|
|
|
20
23
|
# Guarded by the cache lock to ensure thread-safe access. ``seq`` stores
|
|
21
24
|
# per-scope jitter sequence counters in an LRU cache bounded to avoid
|
|
@@ -26,9 +29,44 @@ _JITTER_MAX_ENTRIES = 1024
|
|
|
26
29
|
class JitterCache:
|
|
27
30
|
"""Container for jitter-related caches."""
|
|
28
31
|
|
|
29
|
-
def __init__(
|
|
30
|
-
self
|
|
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
|
|
32
70
|
|
|
33
71
|
@property
|
|
34
72
|
def seq(self) -> LRUCache[tuple[int, int], int]:
|
|
@@ -37,7 +75,7 @@ class JitterCache:
|
|
|
37
75
|
return self._sequence.cache
|
|
38
76
|
|
|
39
77
|
@property
|
|
40
|
-
def lock(self):
|
|
78
|
+
def lock(self) -> threading.Lock | threading.RLock:
|
|
41
79
|
"""Return the lock protecting the sequence cache."""
|
|
42
80
|
|
|
43
81
|
return self._sequence.lock
|
|
@@ -53,7 +91,13 @@ class JitterCache:
|
|
|
53
91
|
"""Set the maximum number of cached jitter sequences."""
|
|
54
92
|
|
|
55
93
|
self._sequence.configure(max_entries=int(value))
|
|
56
|
-
self.
|
|
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))
|
|
57
101
|
|
|
58
102
|
def setup(
|
|
59
103
|
self, force: bool = False, max_entries: int | None = None
|
|
@@ -61,13 +105,14 @@ class JitterCache:
|
|
|
61
105
|
"""Ensure jitter cache matches the configured size."""
|
|
62
106
|
|
|
63
107
|
self._sequence.configure(force=force, max_entries=max_entries)
|
|
64
|
-
self.
|
|
108
|
+
self._refresh_settings()
|
|
65
109
|
|
|
66
110
|
def clear(self) -> None:
|
|
67
111
|
"""Clear cached RNGs and jitter state."""
|
|
68
112
|
|
|
69
113
|
_clear_rng_cache()
|
|
70
114
|
self._sequence.clear()
|
|
115
|
+
self._manager.clear(self._settings_key)
|
|
71
116
|
|
|
72
117
|
def bump(self, key: tuple[int, int]) -> int:
|
|
73
118
|
"""Return current jitter sequence counter for ``key`` and increment it."""
|
|
@@ -78,8 +123,18 @@ class JitterCache:
|
|
|
78
123
|
class JitterCacheManager:
|
|
79
124
|
"""Manager exposing the jitter cache without global reassignment."""
|
|
80
125
|
|
|
81
|
-
def __init__(
|
|
82
|
-
self
|
|
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)
|
|
83
138
|
|
|
84
139
|
# Convenience passthrough properties
|
|
85
140
|
@property
|
|
@@ -91,17 +146,19 @@ class JitterCacheManager:
|
|
|
91
146
|
return self.cache.settings
|
|
92
147
|
|
|
93
148
|
@property
|
|
94
|
-
def lock(self):
|
|
149
|
+
def lock(self) -> threading.Lock | threading.RLock:
|
|
95
150
|
return self.cache.lock
|
|
96
151
|
|
|
97
152
|
@property
|
|
98
153
|
def max_entries(self) -> int:
|
|
99
154
|
"""Return the maximum number of cached jitter entries."""
|
|
155
|
+
|
|
100
156
|
return self.cache.max_entries
|
|
101
157
|
|
|
102
158
|
@max_entries.setter
|
|
103
159
|
def max_entries(self, value: int) -> None:
|
|
104
160
|
"""Set the maximum number of cached jitter entries."""
|
|
161
|
+
|
|
105
162
|
self.cache.max_entries = value
|
|
106
163
|
|
|
107
164
|
def setup(
|
|
@@ -112,6 +169,7 @@ class JitterCacheManager:
|
|
|
112
169
|
``max_entries`` may be provided to explicitly resize the cache.
|
|
113
170
|
When omitted the existing ``cache.max_entries`` is preserved.
|
|
114
171
|
"""
|
|
172
|
+
|
|
115
173
|
if max_entries is not None:
|
|
116
174
|
self.cache.setup(force=True, max_entries=max_entries)
|
|
117
175
|
else:
|
|
@@ -119,6 +177,7 @@ class JitterCacheManager:
|
|
|
119
177
|
|
|
120
178
|
def clear(self) -> None:
|
|
121
179
|
"""Clear cached RNGs and jitter state."""
|
|
180
|
+
|
|
122
181
|
self.cache.clear()
|
|
123
182
|
|
|
124
183
|
def bump(self, key: tuple[int, int]) -> int:
|
|
@@ -148,27 +207,31 @@ def reset_jitter_manager() -> None:
|
|
|
148
207
|
_JITTER_MANAGER = None
|
|
149
208
|
|
|
150
209
|
|
|
151
|
-
def _node_offset(G, n) -> int:
|
|
210
|
+
def _node_offset(G: TNFRGraph, n: NodeId) -> int:
|
|
152
211
|
"""Deterministic node index used for jitter seeds."""
|
|
153
212
|
mapping = ensure_node_offset_map(G)
|
|
154
213
|
return int(mapping.get(n, 0))
|
|
155
214
|
|
|
156
215
|
|
|
157
|
-
def _resolve_jitter_seed(node:
|
|
158
|
-
|
|
159
|
-
if
|
|
160
|
-
raise ImportError("
|
|
161
|
-
if isinstance(node,
|
|
162
|
-
|
|
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)
|
|
163
224
|
uid = getattr(node, "_noise_uid", None)
|
|
164
225
|
if uid is None:
|
|
165
226
|
uid = id(node)
|
|
166
227
|
setattr(node, "_noise_uid", uid)
|
|
167
|
-
|
|
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)
|
|
168
231
|
|
|
169
232
|
|
|
170
233
|
def random_jitter(
|
|
171
|
-
node:
|
|
234
|
+
node: NodeProtocol,
|
|
172
235
|
amplitude: float,
|
|
173
236
|
) -> float:
|
|
174
237
|
"""Return deterministic noise in ``[-amplitude, amplitude]`` for ``node``.
|
|
@@ -184,7 +247,7 @@ def random_jitter(
|
|
|
184
247
|
seed_root = base_seed(node.G)
|
|
185
248
|
seed_key, scope_id = _resolve_jitter_seed(node)
|
|
186
249
|
|
|
187
|
-
cache_key = (seed_root, scope_id)
|
|
250
|
+
cache_key = (seed_root, scope_id, seed_key)
|
|
188
251
|
seq = 0
|
|
189
252
|
if cache_enabled(node.G):
|
|
190
253
|
manager = get_jitter_manager()
|
|
@@ -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]: ...
|