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/operators/__init__.py
CHANGED
|
@@ -2,36 +2,52 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from collections.abc import Callable, Iterator
|
|
6
|
-
from typing import Any, TYPE_CHECKING
|
|
7
|
-
import math
|
|
8
5
|
import heapq
|
|
6
|
+
import math
|
|
7
|
+
from collections.abc import Callable, Iterator
|
|
9
8
|
from itertools import islice
|
|
10
|
-
from statistics import
|
|
9
|
+
from statistics import StatisticsError, fmean
|
|
10
|
+
from typing import TYPE_CHECKING, Any
|
|
11
11
|
|
|
12
|
-
from
|
|
13
|
-
from ..constants import DEFAULTS, get_aliases, get_param
|
|
12
|
+
from tnfr import glyph_history
|
|
14
13
|
|
|
15
|
-
from ..
|
|
14
|
+
from ..alias import get_attr
|
|
15
|
+
from ..constants import DEFAULTS, get_param
|
|
16
|
+
from ..constants.aliases import ALIAS_EPI
|
|
17
|
+
from ..utils import angle_diff
|
|
16
18
|
from ..metrics.trig import neighbor_phase_mean
|
|
17
|
-
from ..utils import get_nodenx
|
|
18
19
|
from ..rng import make_rng
|
|
19
|
-
from tnfr import glyph_history
|
|
20
20
|
from ..types import EPIValue, Glyph, NodeId, TNFRGraph
|
|
21
|
-
|
|
21
|
+
from ..utils import get_nodenx
|
|
22
22
|
from . import definitions as _definitions
|
|
23
|
+
from .grammar import (
|
|
24
|
+
GrammarContext,
|
|
25
|
+
StructuralGrammarError,
|
|
26
|
+
RepeatWindowError,
|
|
27
|
+
MutationPreconditionError,
|
|
28
|
+
TholClosureError,
|
|
29
|
+
TransitionCompatibilityError,
|
|
30
|
+
SequenceSyntaxError,
|
|
31
|
+
SequenceValidationResult,
|
|
32
|
+
_gram_state,
|
|
33
|
+
apply_glyph_with_grammar,
|
|
34
|
+
enforce_canonical_grammar,
|
|
35
|
+
on_applied_glyph,
|
|
36
|
+
parse_sequence,
|
|
37
|
+
validate_sequence,
|
|
38
|
+
)
|
|
23
39
|
from .jitter import (
|
|
24
40
|
JitterCache,
|
|
25
41
|
JitterCacheManager,
|
|
26
42
|
get_jitter_manager,
|
|
27
|
-
reset_jitter_manager,
|
|
28
43
|
random_jitter,
|
|
44
|
+
reset_jitter_manager,
|
|
29
45
|
)
|
|
30
46
|
from .registry import OPERATORS, discover_operators, get_operator_class
|
|
31
47
|
from .remesh import (
|
|
32
48
|
apply_network_remesh,
|
|
33
|
-
apply_topological_remesh,
|
|
34
49
|
apply_remesh_if_globally_stable,
|
|
50
|
+
apply_topological_remesh,
|
|
35
51
|
)
|
|
36
52
|
|
|
37
53
|
_remesh_doc = (
|
|
@@ -49,8 +65,7 @@ else:
|
|
|
49
65
|
discover_operators()
|
|
50
66
|
|
|
51
67
|
_DEFINITION_EXPORTS = {
|
|
52
|
-
name: getattr(_definitions, name)
|
|
53
|
-
for name in getattr(_definitions, "__all__", ())
|
|
68
|
+
name: getattr(_definitions, name) for name in getattr(_definitions, "__all__", ())
|
|
54
69
|
}
|
|
55
70
|
globals().update(_DEFINITION_EXPORTS)
|
|
56
71
|
|
|
@@ -60,14 +75,26 @@ if TYPE_CHECKING: # pragma: no cover - type checking only
|
|
|
60
75
|
GlyphFactors = dict[str, Any]
|
|
61
76
|
GlyphOperation = Callable[["NodeProtocol", GlyphFactors], None]
|
|
62
77
|
|
|
63
|
-
ALIAS_EPI = get_aliases("EPI")
|
|
64
|
-
|
|
65
78
|
__all__ = [
|
|
66
79
|
"JitterCache",
|
|
67
80
|
"JitterCacheManager",
|
|
68
81
|
"get_jitter_manager",
|
|
69
82
|
"reset_jitter_manager",
|
|
70
83
|
"random_jitter",
|
|
84
|
+
"GrammarContext",
|
|
85
|
+
"StructuralGrammarError",
|
|
86
|
+
"RepeatWindowError",
|
|
87
|
+
"MutationPreconditionError",
|
|
88
|
+
"TholClosureError",
|
|
89
|
+
"TransitionCompatibilityError",
|
|
90
|
+
"SequenceValidationResult",
|
|
91
|
+
"SequenceSyntaxError",
|
|
92
|
+
"_gram_state",
|
|
93
|
+
"apply_glyph_with_grammar",
|
|
94
|
+
"parse_sequence",
|
|
95
|
+
"validate_sequence",
|
|
96
|
+
"enforce_canonical_grammar",
|
|
97
|
+
"on_applied_glyph",
|
|
71
98
|
"get_neighbor_epi",
|
|
72
99
|
"get_glyph_factors",
|
|
73
100
|
"GLYPH_OPERATIONS",
|
|
@@ -85,22 +112,111 @@ __all__.extend(_DEFINITION_EXPORTS.keys())
|
|
|
85
112
|
|
|
86
113
|
|
|
87
114
|
def get_glyph_factors(node: NodeProtocol) -> GlyphFactors:
|
|
88
|
-
"""
|
|
115
|
+
"""Fetch glyph tuning factors for a node.
|
|
116
|
+
|
|
117
|
+
The glyph factors expose per-operator coefficients that modulate how an
|
|
118
|
+
operator reorganizes a node's Primary Information Structure (EPI),
|
|
119
|
+
structural frequency (νf), internal reorganization differential (ΔNFR), and
|
|
120
|
+
phase. Missing factors fall back to the canonical defaults stored at the
|
|
121
|
+
graph level.
|
|
122
|
+
|
|
123
|
+
Parameters
|
|
124
|
+
----------
|
|
125
|
+
node : NodeProtocol
|
|
126
|
+
TNFR node providing a ``graph`` mapping where glyph factors may be
|
|
127
|
+
cached under ``"GLYPH_FACTORS"``.
|
|
128
|
+
|
|
129
|
+
Returns
|
|
130
|
+
-------
|
|
131
|
+
GlyphFactors
|
|
132
|
+
Mapping with operator-specific coefficients merged with the canonical
|
|
133
|
+
defaults. Mutating the returned mapping does not affect the graph.
|
|
134
|
+
|
|
135
|
+
Examples
|
|
136
|
+
--------
|
|
137
|
+
>>> class MockNode:
|
|
138
|
+
... def __init__(self):
|
|
139
|
+
... self.graph = {"GLYPH_FACTORS": {"AL_boost": 0.2}}
|
|
140
|
+
>>> node = MockNode()
|
|
141
|
+
>>> factors = get_glyph_factors(node)
|
|
142
|
+
>>> factors["AL_boost"]
|
|
143
|
+
0.2
|
|
144
|
+
>>> factors["EN_mix"] # Fallback to the default reception mix
|
|
145
|
+
0.25
|
|
146
|
+
"""
|
|
89
147
|
return node.graph.get("GLYPH_FACTORS", DEFAULTS["GLYPH_FACTORS"].copy())
|
|
90
148
|
|
|
91
149
|
|
|
92
150
|
def get_factor(gf: GlyphFactors, key: str, default: float) -> float:
|
|
93
|
-
"""Return
|
|
151
|
+
"""Return a glyph factor as ``float`` with a default fallback.
|
|
152
|
+
|
|
153
|
+
Parameters
|
|
154
|
+
----------
|
|
155
|
+
gf : GlyphFactors
|
|
156
|
+
Mapping of glyph names to numeric factors.
|
|
157
|
+
key : str
|
|
158
|
+
Factor identifier to look up.
|
|
159
|
+
default : float
|
|
160
|
+
Value used when ``key`` is absent. This typically corresponds to the
|
|
161
|
+
canonical operator tuning and protects structural invariants.
|
|
162
|
+
|
|
163
|
+
Returns
|
|
164
|
+
-------
|
|
165
|
+
float
|
|
166
|
+
The resolved factor converted to ``float``.
|
|
167
|
+
|
|
168
|
+
Examples
|
|
169
|
+
--------
|
|
170
|
+
>>> get_factor({"AL_boost": 0.3}, "AL_boost", 0.05)
|
|
171
|
+
0.3
|
|
172
|
+
>>> get_factor({}, "IL_dnfr_factor", 0.7)
|
|
173
|
+
0.7
|
|
174
|
+
"""
|
|
94
175
|
return float(gf.get(key, default))
|
|
95
176
|
|
|
96
177
|
|
|
97
178
|
# -------------------------
|
|
98
|
-
# Glyphs (
|
|
179
|
+
# Glyphs (local operators)
|
|
99
180
|
# -------------------------
|
|
100
181
|
|
|
101
182
|
|
|
102
183
|
def get_neighbor_epi(node: NodeProtocol) -> tuple[list[NodeProtocol], EPIValue]:
|
|
103
|
-
"""
|
|
184
|
+
"""Collect neighbour nodes and their mean EPI.
|
|
185
|
+
|
|
186
|
+
The neighbour EPI is used by reception-like glyphs (e.g., EN, RA) to
|
|
187
|
+
harmonise the node's EPI with the surrounding field without mutating νf,
|
|
188
|
+
ΔNFR, or phase. When a neighbour lacks a direct ``EPI`` attribute the
|
|
189
|
+
function resolves it from NetworkX metadata using known aliases.
|
|
190
|
+
|
|
191
|
+
Parameters
|
|
192
|
+
----------
|
|
193
|
+
node : NodeProtocol
|
|
194
|
+
Node whose neighbours participate in the averaging.
|
|
195
|
+
|
|
196
|
+
Returns
|
|
197
|
+
-------
|
|
198
|
+
list of NodeProtocol
|
|
199
|
+
Concrete neighbour objects that expose TNFR attributes.
|
|
200
|
+
EPIValue
|
|
201
|
+
Arithmetic mean of the neighbouring EPIs. Equals the node EPI when no
|
|
202
|
+
valid neighbours are found, allowing glyphs to preserve the node state.
|
|
203
|
+
|
|
204
|
+
Examples
|
|
205
|
+
--------
|
|
206
|
+
>>> class MockNode:
|
|
207
|
+
... def __init__(self, epi, neighbors):
|
|
208
|
+
... self.EPI = epi
|
|
209
|
+
... self._neighbors = neighbors
|
|
210
|
+
... self.graph = {}
|
|
211
|
+
... def neighbors(self):
|
|
212
|
+
... return self._neighbors
|
|
213
|
+
>>> neigh_a = MockNode(1.0, [])
|
|
214
|
+
>>> neigh_b = MockNode(2.0, [])
|
|
215
|
+
>>> node = MockNode(0.5, [neigh_a, neigh_b])
|
|
216
|
+
>>> neighbors, epi_bar = get_neighbor_epi(node)
|
|
217
|
+
>>> len(neighbors), round(epi_bar, 2)
|
|
218
|
+
(2, 1.5)
|
|
219
|
+
"""
|
|
104
220
|
|
|
105
221
|
epi = node.EPI
|
|
106
222
|
neigh = list(node.neighbors())
|
|
@@ -134,8 +250,7 @@ def get_neighbor_epi(node: NodeProtocol) -> tuple[list[NodeProtocol], EPIValue]:
|
|
|
134
250
|
if NodeNX is None:
|
|
135
251
|
raise ImportError("NodeNX is unavailable")
|
|
136
252
|
neigh = [
|
|
137
|
-
v if hasattr(v, "EPI") else NodeNX.from_graph(node.G, v)
|
|
138
|
-
for v in neigh
|
|
253
|
+
v if hasattr(v, "EPI") else NodeNX.from_graph(node.G, v) for v in neigh
|
|
139
254
|
]
|
|
140
255
|
else:
|
|
141
256
|
try:
|
|
@@ -149,7 +264,35 @@ def get_neighbor_epi(node: NodeProtocol) -> tuple[list[NodeProtocol], EPIValue]:
|
|
|
149
264
|
def _determine_dominant(
|
|
150
265
|
neigh: list[NodeProtocol], default_kind: str
|
|
151
266
|
) -> tuple[str, float]:
|
|
152
|
-
"""
|
|
267
|
+
"""Resolve the dominant ``epi_kind`` across neighbours.
|
|
268
|
+
|
|
269
|
+
The dominant kind guides glyphs that synchronise EPI, ensuring that
|
|
270
|
+
reshaping a node's EPI also maintains a coherent semantic label for the
|
|
271
|
+
structural phase space.
|
|
272
|
+
|
|
273
|
+
Parameters
|
|
274
|
+
----------
|
|
275
|
+
neigh : list of NodeProtocol
|
|
276
|
+
Neighbouring nodes providing EPI magnitude and semantic kind.
|
|
277
|
+
default_kind : str
|
|
278
|
+
Fallback label when no neighbour exposes an ``epi_kind``.
|
|
279
|
+
|
|
280
|
+
Returns
|
|
281
|
+
-------
|
|
282
|
+
tuple of (str, float)
|
|
283
|
+
The dominant ``epi_kind`` together with the maximum absolute EPI. The
|
|
284
|
+
amplitude assists downstream logic when choosing between the node's own
|
|
285
|
+
label and the neighbour-driven kind.
|
|
286
|
+
|
|
287
|
+
Examples
|
|
288
|
+
--------
|
|
289
|
+
>>> class Mock:
|
|
290
|
+
... def __init__(self, epi, kind):
|
|
291
|
+
... self.EPI = epi
|
|
292
|
+
... self.epi_kind = kind
|
|
293
|
+
>>> _determine_dominant([Mock(0.2, "seed"), Mock(-1.0, "pulse")], "seed")
|
|
294
|
+
('pulse', 1.0)
|
|
295
|
+
"""
|
|
153
296
|
best_kind: str | None = None
|
|
154
297
|
best_abs = 0.0
|
|
155
298
|
for v in neigh:
|
|
@@ -165,11 +308,46 @@ def _determine_dominant(
|
|
|
165
308
|
def _mix_epi_with_neighbors(
|
|
166
309
|
node: NodeProtocol, mix: float, default_glyph: Glyph | str
|
|
167
310
|
) -> tuple[float, str]:
|
|
168
|
-
"""
|
|
311
|
+
"""Blend node EPI with the neighbour field and update its semantic label.
|
|
312
|
+
|
|
313
|
+
The routine is shared by reception-like glyphs. It interpolates between the
|
|
314
|
+
node EPI and the neighbour mean while selecting a dominant ``epi_kind``.
|
|
315
|
+
ΔNFR, νf, and phase remain untouched; the function focuses on reconciling
|
|
316
|
+
form.
|
|
317
|
+
|
|
318
|
+
Parameters
|
|
319
|
+
----------
|
|
320
|
+
node : NodeProtocol
|
|
321
|
+
Node that exposes ``EPI`` and ``epi_kind`` attributes.
|
|
322
|
+
mix : float
|
|
323
|
+
Interpolation weight for the neighbour mean. ``mix = 0`` preserves the
|
|
324
|
+
current EPI, while ``mix = 1`` adopts the average neighbour field.
|
|
325
|
+
default_glyph : Glyph or str
|
|
326
|
+
Glyph driving the mix. Its value informs the fallback ``epi_kind``.
|
|
327
|
+
|
|
328
|
+
Returns
|
|
329
|
+
-------
|
|
330
|
+
tuple of (float, str)
|
|
331
|
+
The neighbour mean EPI and the resolved ``epi_kind`` after mixing.
|
|
332
|
+
|
|
333
|
+
Examples
|
|
334
|
+
--------
|
|
335
|
+
>>> class MockNode:
|
|
336
|
+
... def __init__(self, epi, kind, neighbors):
|
|
337
|
+
... self.EPI = epi
|
|
338
|
+
... self.epi_kind = kind
|
|
339
|
+
... self.graph = {}
|
|
340
|
+
... self._neighbors = neighbors
|
|
341
|
+
... def neighbors(self):
|
|
342
|
+
... return self._neighbors
|
|
343
|
+
>>> neigh = [MockNode(0.8, "wave", []), MockNode(1.2, "wave", [])]
|
|
344
|
+
>>> node = MockNode(0.0, "seed", neigh)
|
|
345
|
+
>>> _, kind = _mix_epi_with_neighbors(node, 0.5, Glyph.EN)
|
|
346
|
+
>>> round(node.EPI, 2), kind
|
|
347
|
+
(0.5, 'wave')
|
|
348
|
+
"""
|
|
169
349
|
default_kind = (
|
|
170
|
-
default_glyph.value
|
|
171
|
-
if isinstance(default_glyph, Glyph)
|
|
172
|
-
else str(default_glyph)
|
|
350
|
+
default_glyph.value if isinstance(default_glyph, Glyph) else str(default_glyph)
|
|
173
351
|
)
|
|
174
352
|
epi = node.EPI
|
|
175
353
|
neigh, epi_bar = get_neighbor_epi(node)
|
|
@@ -189,21 +367,119 @@ def _mix_epi_with_neighbors(
|
|
|
189
367
|
|
|
190
368
|
|
|
191
369
|
def _op_AL(node: NodeProtocol, gf: GlyphFactors) -> None: # AL — Emission
|
|
370
|
+
"""Amplify the node EPI via the Emission glyph.
|
|
371
|
+
|
|
372
|
+
Emission injects additional coherence into the node by boosting its EPI
|
|
373
|
+
without touching νf, ΔNFR, or phase. The boost amplitude is controlled by
|
|
374
|
+
``AL_boost``.
|
|
375
|
+
|
|
376
|
+
Parameters
|
|
377
|
+
----------
|
|
378
|
+
node : NodeProtocol
|
|
379
|
+
Node whose EPI is increased.
|
|
380
|
+
gf : GlyphFactors
|
|
381
|
+
Factor mapping used to resolve ``AL_boost``.
|
|
382
|
+
|
|
383
|
+
Examples
|
|
384
|
+
--------
|
|
385
|
+
>>> class MockNode:
|
|
386
|
+
... def __init__(self, epi):
|
|
387
|
+
... self.EPI = epi
|
|
388
|
+
>>> node = MockNode(0.8)
|
|
389
|
+
>>> _op_AL(node, {"AL_boost": 0.2})
|
|
390
|
+
>>> node.EPI
|
|
391
|
+
1.0
|
|
392
|
+
"""
|
|
192
393
|
f = get_factor(gf, "AL_boost", 0.05)
|
|
193
394
|
node.EPI = node.EPI + f
|
|
194
395
|
|
|
195
396
|
|
|
196
397
|
def _op_EN(node: NodeProtocol, gf: GlyphFactors) -> None: # EN — Reception
|
|
398
|
+
"""Mix the node EPI with the neighbour field via Reception.
|
|
399
|
+
|
|
400
|
+
Reception reorganizes the node's EPI towards the neighbourhood mean while
|
|
401
|
+
choosing a coherent ``epi_kind``. νf, ΔNFR, and phase remain unchanged.
|
|
402
|
+
|
|
403
|
+
Parameters
|
|
404
|
+
----------
|
|
405
|
+
node : NodeProtocol
|
|
406
|
+
Node whose EPI is being reconciled.
|
|
407
|
+
gf : GlyphFactors
|
|
408
|
+
Source of the ``EN_mix`` blending coefficient.
|
|
409
|
+
|
|
410
|
+
Examples
|
|
411
|
+
--------
|
|
412
|
+
>>> class MockNode:
|
|
413
|
+
... def __init__(self, epi, neighbors):
|
|
414
|
+
... self.EPI = epi
|
|
415
|
+
... self.epi_kind = "seed"
|
|
416
|
+
... self.graph = {}
|
|
417
|
+
... self._neighbors = neighbors
|
|
418
|
+
... def neighbors(self):
|
|
419
|
+
... return self._neighbors
|
|
420
|
+
>>> neigh = [MockNode(1.0, []), MockNode(0.0, [])]
|
|
421
|
+
>>> node = MockNode(0.4, neigh)
|
|
422
|
+
>>> _op_EN(node, {"EN_mix": 0.5})
|
|
423
|
+
>>> round(node.EPI, 2)
|
|
424
|
+
0.7
|
|
425
|
+
"""
|
|
197
426
|
mix = get_factor(gf, "EN_mix", 0.25)
|
|
198
427
|
_mix_epi_with_neighbors(node, mix, Glyph.EN)
|
|
199
428
|
|
|
200
429
|
|
|
201
430
|
def _op_IL(node: NodeProtocol, gf: GlyphFactors) -> None: # IL — Coherence
|
|
431
|
+
"""Dampen ΔNFR magnitudes through the Coherence glyph.
|
|
432
|
+
|
|
433
|
+
Coherence contracts the internal reorganization differential (ΔNFR) while
|
|
434
|
+
leaving EPI, νf, and phase untouched. The contraction preserves the sign of
|
|
435
|
+
ΔNFR, increasing structural stability.
|
|
436
|
+
|
|
437
|
+
Parameters
|
|
438
|
+
----------
|
|
439
|
+
node : NodeProtocol
|
|
440
|
+
Node whose ΔNFR is being scaled.
|
|
441
|
+
gf : GlyphFactors
|
|
442
|
+
Provides ``IL_dnfr_factor`` controlling the contraction strength.
|
|
443
|
+
|
|
444
|
+
Examples
|
|
445
|
+
--------
|
|
446
|
+
>>> class MockNode:
|
|
447
|
+
... def __init__(self, dnfr):
|
|
448
|
+
... self.dnfr = dnfr
|
|
449
|
+
>>> node = MockNode(0.5)
|
|
450
|
+
>>> _op_IL(node, {"IL_dnfr_factor": 0.2})
|
|
451
|
+
>>> node.dnfr
|
|
452
|
+
0.1
|
|
453
|
+
"""
|
|
202
454
|
factor = get_factor(gf, "IL_dnfr_factor", 0.7)
|
|
203
455
|
node.dnfr = factor * getattr(node, "dnfr", 0.0)
|
|
204
456
|
|
|
205
457
|
|
|
206
458
|
def _op_OZ(node: NodeProtocol, gf: GlyphFactors) -> None: # OZ — Dissonance
|
|
459
|
+
"""Excite ΔNFR through the Dissonance glyph.
|
|
460
|
+
|
|
461
|
+
Dissonance amplifies ΔNFR or injects jitter, testing the node's stability.
|
|
462
|
+
EPI, νf, and phase remain unaffected while ΔNFR grows to trigger potential
|
|
463
|
+
bifurcations.
|
|
464
|
+
|
|
465
|
+
Parameters
|
|
466
|
+
----------
|
|
467
|
+
node : NodeProtocol
|
|
468
|
+
Node whose ΔNFR is being stressed.
|
|
469
|
+
gf : GlyphFactors
|
|
470
|
+
Supplies ``OZ_dnfr_factor`` and optional noise parameters.
|
|
471
|
+
|
|
472
|
+
Examples
|
|
473
|
+
--------
|
|
474
|
+
>>> class MockNode:
|
|
475
|
+
... def __init__(self, dnfr):
|
|
476
|
+
... self.dnfr = dnfr
|
|
477
|
+
... self.graph = {}
|
|
478
|
+
>>> node = MockNode(0.2)
|
|
479
|
+
>>> _op_OZ(node, {"OZ_dnfr_factor": 2.0})
|
|
480
|
+
>>> node.dnfr
|
|
481
|
+
0.4
|
|
482
|
+
"""
|
|
207
483
|
factor = get_factor(gf, "OZ_dnfr_factor", 1.3)
|
|
208
484
|
dnfr = getattr(node, "dnfr", 0.0)
|
|
209
485
|
if bool(node.graph.get("OZ_NOISE_MODE", False)):
|
|
@@ -226,9 +502,7 @@ def _um_candidate_iter(node: NodeProtocol) -> Iterator[NodeProtocol]:
|
|
|
226
502
|
else:
|
|
227
503
|
base = node.all_nodes()
|
|
228
504
|
for j in base:
|
|
229
|
-
same = (j is node) or (
|
|
230
|
-
getattr(node, "n", None) == getattr(j, "n", None)
|
|
231
|
-
)
|
|
505
|
+
same = (j is node) or (getattr(node, "n", None) == getattr(j, "n", None))
|
|
232
506
|
if same or node.has_edge(j):
|
|
233
507
|
continue
|
|
234
508
|
yield j
|
|
@@ -265,6 +539,45 @@ def _um_select_candidates(
|
|
|
265
539
|
|
|
266
540
|
|
|
267
541
|
def _op_UM(node: NodeProtocol, gf: GlyphFactors) -> None: # UM — Coupling
|
|
542
|
+
"""Align node phase with neighbours and optionally create links.
|
|
543
|
+
|
|
544
|
+
Coupling shifts the node phase ``theta`` towards the neighbour mean while
|
|
545
|
+
respecting νf and EPI. When functional links are enabled it may add edges
|
|
546
|
+
based on combined phase, EPI, and sense-index similarity.
|
|
547
|
+
|
|
548
|
+
Parameters
|
|
549
|
+
----------
|
|
550
|
+
node : NodeProtocol
|
|
551
|
+
Node whose phase is being synchronised.
|
|
552
|
+
gf : GlyphFactors
|
|
553
|
+
Provides ``UM_theta_push`` and optional selection parameters.
|
|
554
|
+
|
|
555
|
+
Examples
|
|
556
|
+
--------
|
|
557
|
+
>>> import math
|
|
558
|
+
>>> class MockNode:
|
|
559
|
+
... def __init__(self, theta, neighbors):
|
|
560
|
+
... self.theta = theta
|
|
561
|
+
... self.EPI = 1.0
|
|
562
|
+
... self.Si = 0.5
|
|
563
|
+
... self.graph = {}
|
|
564
|
+
... self._neighbors = neighbors
|
|
565
|
+
... def neighbors(self):
|
|
566
|
+
... return self._neighbors
|
|
567
|
+
... def offset(self):
|
|
568
|
+
... return 0
|
|
569
|
+
... def all_nodes(self):
|
|
570
|
+
... return []
|
|
571
|
+
... def has_edge(self, _):
|
|
572
|
+
... return False
|
|
573
|
+
... def add_edge(self, *_):
|
|
574
|
+
... raise AssertionError("not used in example")
|
|
575
|
+
>>> neighbor = MockNode(math.pi / 2, [])
|
|
576
|
+
>>> node = MockNode(0.0, [neighbor])
|
|
577
|
+
>>> _op_UM(node, {"UM_theta_push": 0.5})
|
|
578
|
+
>>> round(node.theta, 2)
|
|
579
|
+
0.79
|
|
580
|
+
"""
|
|
268
581
|
k = get_factor(gf, "UM_theta_push", 0.25)
|
|
269
582
|
th = node.theta
|
|
270
583
|
thL = neighbor_phase_mean(node)
|
|
@@ -292,9 +605,7 @@ def _op_UM(node: NodeProtocol, gf: GlyphFactors) -> None: # UM — Coupling
|
|
|
292
605
|
dphi = abs(angle_diff(th_j, th)) / math.pi
|
|
293
606
|
epi_j = j.EPI
|
|
294
607
|
si_j = j.Si
|
|
295
|
-
epi_sim = 1.0 - abs(epi_i - epi_j) / (
|
|
296
|
-
abs(epi_i) + abs(epi_j) + 1e-9
|
|
297
|
-
)
|
|
608
|
+
epi_sim = 1.0 - abs(epi_i - epi_j) / (abs(epi_i) + abs(epi_j) + 1e-9)
|
|
298
609
|
si_sim = 1.0 - abs(si_i - si_j)
|
|
299
610
|
compat = (1 - dphi) * 0.5 + 0.25 * epi_sim + 0.25 * si_sim
|
|
300
611
|
if compat >= thr:
|
|
@@ -302,11 +613,63 @@ def _op_UM(node: NodeProtocol, gf: GlyphFactors) -> None: # UM — Coupling
|
|
|
302
613
|
|
|
303
614
|
|
|
304
615
|
def _op_RA(node: NodeProtocol, gf: GlyphFactors) -> None: # RA — Resonance
|
|
616
|
+
"""Diffuse EPI to the node through the Resonance glyph.
|
|
617
|
+
|
|
618
|
+
Resonance propagates EPI along existing couplings without affecting νf,
|
|
619
|
+
ΔNFR, or phase. The glyph nudges the node towards the neighbour mean using
|
|
620
|
+
``RA_epi_diff``.
|
|
621
|
+
|
|
622
|
+
Parameters
|
|
623
|
+
----------
|
|
624
|
+
node : NodeProtocol
|
|
625
|
+
Node harmonising with its neighbourhood.
|
|
626
|
+
gf : GlyphFactors
|
|
627
|
+
Provides ``RA_epi_diff`` as the mixing coefficient.
|
|
628
|
+
|
|
629
|
+
Examples
|
|
630
|
+
--------
|
|
631
|
+
>>> class MockNode:
|
|
632
|
+
... def __init__(self, epi, neighbors):
|
|
633
|
+
... self.EPI = epi
|
|
634
|
+
... self.epi_kind = "seed"
|
|
635
|
+
... self.graph = {}
|
|
636
|
+
... self._neighbors = neighbors
|
|
637
|
+
... def neighbors(self):
|
|
638
|
+
... return self._neighbors
|
|
639
|
+
>>> neighbor = MockNode(1.0, [])
|
|
640
|
+
>>> node = MockNode(0.2, [neighbor])
|
|
641
|
+
>>> _op_RA(node, {"RA_epi_diff": 0.25})
|
|
642
|
+
>>> round(node.EPI, 2)
|
|
643
|
+
0.4
|
|
644
|
+
"""
|
|
305
645
|
diff = get_factor(gf, "RA_epi_diff", 0.15)
|
|
306
646
|
_mix_epi_with_neighbors(node, diff, Glyph.RA)
|
|
307
647
|
|
|
308
648
|
|
|
309
649
|
def _op_SHA(node: NodeProtocol, gf: GlyphFactors) -> None: # SHA — Silence
|
|
650
|
+
"""Reduce νf while preserving EPI, ΔNFR, and phase.
|
|
651
|
+
|
|
652
|
+
Silence decelerates a node by scaling νf (structural frequency) towards
|
|
653
|
+
stillness. EPI, ΔNFR, and phase remain unchanged, signalling a temporary
|
|
654
|
+
suspension of structural evolution.
|
|
655
|
+
|
|
656
|
+
Parameters
|
|
657
|
+
----------
|
|
658
|
+
node : NodeProtocol
|
|
659
|
+
Node whose νf is being attenuated.
|
|
660
|
+
gf : GlyphFactors
|
|
661
|
+
Provides ``SHA_vf_factor`` to scale νf.
|
|
662
|
+
|
|
663
|
+
Examples
|
|
664
|
+
--------
|
|
665
|
+
>>> class MockNode:
|
|
666
|
+
... def __init__(self, vf):
|
|
667
|
+
... self.vf = vf
|
|
668
|
+
>>> node = MockNode(1.0)
|
|
669
|
+
>>> _op_SHA(node, {"SHA_vf_factor": 0.5})
|
|
670
|
+
>>> node.vf
|
|
671
|
+
0.5
|
|
672
|
+
"""
|
|
310
673
|
factor = get_factor(gf, "SHA_vf_factor", 0.85)
|
|
311
674
|
node.vf = factor * node.vf
|
|
312
675
|
|
|
@@ -317,6 +680,15 @@ _SCALE_FACTORS = {Glyph.VAL: factor_val, Glyph.NUL: factor_nul}
|
|
|
317
680
|
|
|
318
681
|
|
|
319
682
|
def _op_scale(node: NodeProtocol, factor: float) -> None:
|
|
683
|
+
"""Scale νf with the provided factor.
|
|
684
|
+
|
|
685
|
+
Parameters
|
|
686
|
+
----------
|
|
687
|
+
node : NodeProtocol
|
|
688
|
+
Node whose νf is being updated.
|
|
689
|
+
factor : float
|
|
690
|
+
Multiplicative change applied to νf.
|
|
691
|
+
"""
|
|
320
692
|
node.vf *= factor
|
|
321
693
|
|
|
322
694
|
|
|
@@ -327,26 +699,118 @@ def _make_scale_op(glyph: Glyph) -> GlyphOperation:
|
|
|
327
699
|
factor = get_factor(gf, key, default)
|
|
328
700
|
_op_scale(node, factor)
|
|
329
701
|
|
|
702
|
+
_op.__doc__ = (
|
|
703
|
+
"""{} glyph scales νf to modulate expansion or contraction.
|
|
704
|
+
|
|
705
|
+
VAL (expansion) increases νf, whereas NUL (contraction) decreases it.
|
|
706
|
+
EPI, ΔNFR, and phase remain fixed, isolating the change to temporal
|
|
707
|
+
cadence.
|
|
708
|
+
|
|
709
|
+
Parameters
|
|
710
|
+
----------
|
|
711
|
+
node : NodeProtocol
|
|
712
|
+
Node whose νf is updated.
|
|
713
|
+
gf : GlyphFactors
|
|
714
|
+
Provides the respective scale factor (``VAL_scale`` or
|
|
715
|
+
``NUL_scale``).
|
|
716
|
+
|
|
717
|
+
Examples
|
|
718
|
+
--------
|
|
719
|
+
>>> class MockNode:
|
|
720
|
+
... def __init__(self, vf):
|
|
721
|
+
... self.vf = vf
|
|
722
|
+
>>> node = MockNode(1.0)
|
|
723
|
+
>>> op = _make_scale_op(Glyph.VAL)
|
|
724
|
+
>>> op(node, {{"VAL_scale": 1.5}})
|
|
725
|
+
>>> node.vf
|
|
726
|
+
1.5
|
|
727
|
+
""".format(glyph.name)
|
|
728
|
+
)
|
|
330
729
|
return _op
|
|
331
730
|
|
|
332
731
|
|
|
333
|
-
def _op_THOL(
|
|
334
|
-
|
|
335
|
-
|
|
732
|
+
def _op_THOL(node: NodeProtocol, gf: GlyphFactors) -> None: # THOL — Self-organization
|
|
733
|
+
"""Inject curvature from ``d2EPI`` into ΔNFR to trigger self-organization.
|
|
734
|
+
|
|
735
|
+
The glyph keeps EPI, νf, and phase fixed while increasing ΔNFR according to
|
|
736
|
+
the second derivative of EPI, accelerating structural rearrangement.
|
|
737
|
+
|
|
738
|
+
Parameters
|
|
739
|
+
----------
|
|
740
|
+
node : NodeProtocol
|
|
741
|
+
Node contributing ``d2EPI`` to ΔNFR.
|
|
742
|
+
gf : GlyphFactors
|
|
743
|
+
Source of the ``THOL_accel`` multiplier.
|
|
744
|
+
|
|
745
|
+
Examples
|
|
746
|
+
--------
|
|
747
|
+
>>> class MockNode:
|
|
748
|
+
... def __init__(self, dnfr, curvature):
|
|
749
|
+
... self.dnfr = dnfr
|
|
750
|
+
... self.d2EPI = curvature
|
|
751
|
+
>>> node = MockNode(0.1, 0.5)
|
|
752
|
+
>>> _op_THOL(node, {"THOL_accel": 0.2})
|
|
753
|
+
>>> node.dnfr
|
|
754
|
+
0.2
|
|
755
|
+
"""
|
|
336
756
|
a = get_factor(gf, "THOL_accel", 0.10)
|
|
337
757
|
node.dnfr = node.dnfr + a * getattr(node, "d2EPI", 0.0)
|
|
338
758
|
|
|
339
759
|
|
|
340
|
-
def _op_ZHIR(
|
|
341
|
-
|
|
342
|
-
|
|
760
|
+
def _op_ZHIR(node: NodeProtocol, gf: GlyphFactors) -> None: # ZHIR — Mutation
|
|
761
|
+
"""Shift phase by a fixed offset to enact mutation.
|
|
762
|
+
|
|
763
|
+
Mutation changes the node's phase (θ) while preserving EPI, νf, and ΔNFR.
|
|
764
|
+
The glyph encodes discrete structural transitions between coherent states.
|
|
765
|
+
|
|
766
|
+
Parameters
|
|
767
|
+
----------
|
|
768
|
+
node : NodeProtocol
|
|
769
|
+
Node whose phase is rotated.
|
|
770
|
+
gf : GlyphFactors
|
|
771
|
+
Supplies ``ZHIR_theta_shift`` defining the rotation.
|
|
772
|
+
|
|
773
|
+
Examples
|
|
774
|
+
--------
|
|
775
|
+
>>> import math
|
|
776
|
+
>>> class MockNode:
|
|
777
|
+
... def __init__(self, theta):
|
|
778
|
+
... self.theta = theta
|
|
779
|
+
>>> node = MockNode(0.0)
|
|
780
|
+
>>> _op_ZHIR(node, {"ZHIR_theta_shift": math.pi / 2})
|
|
781
|
+
>>> round(node.theta, 2)
|
|
782
|
+
1.57
|
|
783
|
+
"""
|
|
343
784
|
shift = get_factor(gf, "ZHIR_theta_shift", math.pi / 2)
|
|
344
785
|
node.theta = node.theta + shift
|
|
345
786
|
|
|
346
787
|
|
|
347
|
-
def _op_NAV(
|
|
348
|
-
|
|
349
|
-
|
|
788
|
+
def _op_NAV(node: NodeProtocol, gf: GlyphFactors) -> None: # NAV — Transition
|
|
789
|
+
"""Rebalance ΔNFR towards νf while permitting jitter.
|
|
790
|
+
|
|
791
|
+
Transition pulls ΔNFR towards a νf-aligned target, optionally adding jitter
|
|
792
|
+
to explore nearby states. EPI and phase remain untouched; νf may be used as
|
|
793
|
+
a reference but is not directly changed.
|
|
794
|
+
|
|
795
|
+
Parameters
|
|
796
|
+
----------
|
|
797
|
+
node : NodeProtocol
|
|
798
|
+
Node whose ΔNFR is redirected.
|
|
799
|
+
gf : GlyphFactors
|
|
800
|
+
Supplies ``NAV_eta`` and ``NAV_jitter`` tuning parameters.
|
|
801
|
+
|
|
802
|
+
Examples
|
|
803
|
+
--------
|
|
804
|
+
>>> class MockNode:
|
|
805
|
+
... def __init__(self, dnfr, vf):
|
|
806
|
+
... self.dnfr = dnfr
|
|
807
|
+
... self.vf = vf
|
|
808
|
+
... self.graph = {"NAV_RANDOM": False}
|
|
809
|
+
>>> node = MockNode(-0.6, 0.4)
|
|
810
|
+
>>> _op_NAV(node, {"NAV_eta": 0.5, "NAV_jitter": 0.0})
|
|
811
|
+
>>> round(node.dnfr, 2)
|
|
812
|
+
-0.1
|
|
813
|
+
"""
|
|
350
814
|
dnfr = node.dnfr
|
|
351
815
|
vf = node.vf
|
|
352
816
|
eta = get_factor(gf, "NAV_eta", 0.5)
|
|
@@ -368,6 +832,29 @@ def _op_NAV(
|
|
|
368
832
|
def _op_REMESH(
|
|
369
833
|
node: NodeProtocol, gf: GlyphFactors | None = None
|
|
370
834
|
) -> None: # REMESH — advisory
|
|
835
|
+
"""Record an advisory requesting network-scale remeshing.
|
|
836
|
+
|
|
837
|
+
REMESH does not change node-level EPI, νf, ΔNFR, or phase. Instead it
|
|
838
|
+
annotates the glyph history so orchestrators can trigger global remesh
|
|
839
|
+
procedures once the stability conditions are met.
|
|
840
|
+
|
|
841
|
+
Parameters
|
|
842
|
+
----------
|
|
843
|
+
node : NodeProtocol
|
|
844
|
+
Node whose history records the advisory.
|
|
845
|
+
gf : GlyphFactors, optional
|
|
846
|
+
Unused but accepted for API symmetry.
|
|
847
|
+
|
|
848
|
+
Examples
|
|
849
|
+
--------
|
|
850
|
+
>>> class MockNode:
|
|
851
|
+
... def __init__(self):
|
|
852
|
+
... self.graph = {}
|
|
853
|
+
>>> node = MockNode()
|
|
854
|
+
>>> _op_REMESH(node)
|
|
855
|
+
>>> "_remesh_warn_step" in node.graph
|
|
856
|
+
True
|
|
857
|
+
"""
|
|
371
858
|
step_idx = glyph_history.current_step_idx(node)
|
|
372
859
|
last_warn = node.graph.get("_remesh_warn_step", None)
|
|
373
860
|
if last_warn != step_idx:
|