tnfr 4.5.2__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 +275 -51
- tnfr/__init__.pyi +33 -0
- tnfr/_compat.py +10 -0
- tnfr/_generated_version.py +34 -0
- tnfr/_version.py +49 -0
- tnfr/_version.pyi +7 -0
- tnfr/alias.py +117 -31
- tnfr/alias.pyi +108 -0
- tnfr/cache.py +6 -572
- tnfr/cache.pyi +16 -0
- tnfr/callback_utils.py +16 -38
- tnfr/callback_utils.pyi +79 -0
- tnfr/cli/__init__.py +34 -14
- tnfr/cli/__init__.pyi +26 -0
- tnfr/cli/arguments.py +211 -28
- tnfr/cli/arguments.pyi +27 -0
- tnfr/cli/execution.py +470 -50
- tnfr/cli/execution.pyi +70 -0
- tnfr/cli/utils.py +18 -3
- tnfr/cli/utils.pyi +8 -0
- tnfr/config/__init__.py +13 -0
- tnfr/config/__init__.pyi +10 -0
- tnfr/{constants_glyphs.py → config/constants.py} +26 -20
- tnfr/config/constants.pyi +12 -0
- tnfr/config/feature_flags.py +83 -0
- tnfr/{config.py → config/init.py} +11 -7
- tnfr/config/init.pyi +8 -0
- tnfr/config/operator_names.py +93 -0
- tnfr/config/operator_names.pyi +28 -0
- tnfr/config/presets.py +84 -0
- tnfr/config/presets.pyi +7 -0
- tnfr/constants/__init__.py +80 -29
- tnfr/constants/__init__.pyi +92 -0
- tnfr/constants/aliases.py +31 -0
- tnfr/constants/core.py +4 -4
- tnfr/constants/core.pyi +17 -0
- tnfr/constants/init.py +1 -1
- tnfr/constants/init.pyi +12 -0
- tnfr/constants/metric.py +7 -15
- tnfr/constants/metric.pyi +19 -0
- tnfr/dynamics/__init__.py +165 -633
- tnfr/dynamics/__init__.pyi +82 -0
- tnfr/dynamics/adaptation.py +267 -0
- tnfr/dynamics/aliases.py +23 -0
- tnfr/dynamics/coordination.py +385 -0
- tnfr/dynamics/dnfr.py +2283 -400
- tnfr/dynamics/dnfr.pyi +24 -0
- tnfr/dynamics/integrators.py +406 -98
- tnfr/dynamics/integrators.pyi +34 -0
- tnfr/dynamics/runtime.py +881 -0
- tnfr/dynamics/sampling.py +10 -5
- tnfr/dynamics/sampling.pyi +7 -0
- tnfr/dynamics/selectors.py +719 -0
- tnfr/execution.py +70 -48
- tnfr/execution.pyi +45 -0
- tnfr/flatten.py +13 -9
- tnfr/flatten.pyi +21 -0
- tnfr/gamma.py +66 -53
- tnfr/gamma.pyi +34 -0
- tnfr/glyph_history.py +110 -52
- tnfr/glyph_history.pyi +35 -0
- tnfr/glyph_runtime.py +16 -0
- tnfr/glyph_runtime.pyi +9 -0
- tnfr/immutable.py +69 -28
- tnfr/immutable.pyi +34 -0
- tnfr/initialization.py +16 -16
- tnfr/initialization.pyi +65 -0
- tnfr/io.py +6 -240
- tnfr/io.pyi +16 -0
- tnfr/locking.pyi +7 -0
- 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/__init__.pyi +20 -0
- tnfr/metrics/coherence.py +993 -324
- tnfr/metrics/common.py +23 -16
- tnfr/metrics/common.pyi +46 -0
- tnfr/metrics/core.py +251 -35
- tnfr/metrics/core.pyi +13 -0
- tnfr/metrics/diagnosis.py +708 -111
- tnfr/metrics/diagnosis.pyi +85 -0
- tnfr/metrics/export.py +27 -15
- tnfr/metrics/glyph_timing.py +232 -42
- tnfr/metrics/reporting.py +33 -22
- tnfr/metrics/reporting.pyi +12 -0
- tnfr/metrics/sense_index.py +987 -43
- tnfr/metrics/sense_index.pyi +9 -0
- tnfr/metrics/trig.py +214 -23
- tnfr/metrics/trig.pyi +13 -0
- tnfr/metrics/trig_cache.py +115 -22
- tnfr/metrics/trig_cache.pyi +10 -0
- tnfr/node.py +542 -136
- tnfr/node.pyi +178 -0
- tnfr/observers.py +152 -35
- tnfr/observers.pyi +31 -0
- tnfr/ontosim.py +23 -19
- tnfr/ontosim.pyi +28 -0
- tnfr/operators/__init__.py +601 -82
- tnfr/operators/__init__.pyi +45 -0
- tnfr/operators/definitions.py +513 -0
- tnfr/operators/definitions.pyi +78 -0
- tnfr/operators/grammar.py +760 -0
- tnfr/operators/jitter.py +107 -38
- tnfr/operators/jitter.pyi +11 -0
- tnfr/operators/registry.py +75 -0
- tnfr/operators/registry.pyi +13 -0
- tnfr/operators/remesh.py +149 -88
- tnfr/py.typed +0 -0
- tnfr/rng.py +46 -143
- tnfr/rng.pyi +14 -0
- tnfr/schemas/__init__.py +8 -0
- tnfr/schemas/grammar.json +94 -0
- tnfr/selector.py +25 -19
- tnfr/selector.pyi +19 -0
- tnfr/sense.py +72 -62
- tnfr/sense.pyi +23 -0
- tnfr/structural.py +522 -262
- tnfr/structural.pyi +69 -0
- tnfr/telemetry/__init__.py +35 -0
- tnfr/telemetry/cache_metrics.py +226 -0
- tnfr/telemetry/nu_f.py +423 -0
- tnfr/telemetry/nu_f.pyi +123 -0
- tnfr/telemetry/verbosity.py +37 -0
- tnfr/tokens.py +1 -3
- tnfr/tokens.pyi +36 -0
- tnfr/trace.py +270 -113
- tnfr/trace.pyi +40 -0
- tnfr/types.py +574 -6
- tnfr/types.pyi +331 -0
- tnfr/units.py +69 -0
- tnfr/units.pyi +16 -0
- tnfr/utils/__init__.py +217 -0
- tnfr/utils/__init__.pyi +202 -0
- tnfr/utils/cache.py +2395 -0
- tnfr/utils/cache.pyi +468 -0
- tnfr/utils/chunks.py +104 -0
- tnfr/utils/chunks.pyi +21 -0
- tnfr/{collections_utils.py → utils/data.py} +147 -90
- tnfr/utils/data.pyi +64 -0
- tnfr/utils/graph.py +85 -0
- tnfr/utils/graph.pyi +10 -0
- tnfr/utils/init.py +770 -0
- tnfr/utils/init.pyi +78 -0
- tnfr/utils/io.py +456 -0
- tnfr/{helpers → utils}/numeric.py +51 -24
- tnfr/utils/numeric.pyi +21 -0
- tnfr/validation/__init__.py +113 -0
- tnfr/validation/__init__.pyi +77 -0
- tnfr/validation/compatibility.py +95 -0
- tnfr/validation/compatibility.pyi +6 -0
- tnfr/validation/grammar.py +71 -0
- tnfr/validation/grammar.pyi +40 -0
- tnfr/validation/graph.py +138 -0
- tnfr/validation/graph.pyi +17 -0
- tnfr/validation/rules.py +281 -0
- tnfr/validation/rules.pyi +55 -0
- 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 +40 -0
- tnfr/validation/syntax.pyi +10 -0
- 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-7.0.0.dist-info/METADATA +179 -0
- tnfr-7.0.0.dist-info/RECORD +185 -0
- {tnfr-4.5.2.dist-info → tnfr-7.0.0.dist-info}/licenses/LICENSE.md +1 -1
- tnfr/grammar.py +0 -344
- tnfr/graph_utils.py +0 -84
- tnfr/helpers/__init__.py +0 -71
- tnfr/import_utils.py +0 -228
- tnfr/json_utils.py +0 -162
- tnfr/logging_utils.py +0 -116
- tnfr/presets.py +0 -60
- 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-7.0.0.dist-info}/WHEEL +0 -0
- {tnfr-4.5.2.dist-info → tnfr-7.0.0.dist-info}/entry_points.txt +0 -0
- {tnfr-4.5.2.dist-info → tnfr-7.0.0.dist-info}/top_level.txt +0 -0
tnfr/operators/__init__.py
CHANGED
|
@@ -1,39 +1,79 @@
|
|
|
1
1
|
"""Network operators."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import math
|
|
4
|
+
|
|
6
5
|
import heapq
|
|
6
|
+
import math
|
|
7
|
+
from collections.abc import Callable, Iterator
|
|
7
8
|
from itertools import islice
|
|
8
|
-
from statistics import
|
|
9
|
+
from statistics import StatisticsError, fmean
|
|
10
|
+
from typing import TYPE_CHECKING, Any
|
|
9
11
|
|
|
10
|
-
from
|
|
11
|
-
from ..constants import DEFAULTS, get_aliases, get_param
|
|
12
|
+
from tnfr import glyph_history
|
|
12
13
|
|
|
13
|
-
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
|
|
14
18
|
from ..metrics.trig import neighbor_phase_mean
|
|
15
|
-
from ..import_utils import get_nodonx
|
|
16
19
|
from ..rng import make_rng
|
|
17
|
-
from
|
|
18
|
-
from ..
|
|
19
|
-
|
|
20
|
+
from ..types import EPIValue, Glyph, NodeId, TNFRGraph
|
|
21
|
+
from ..utils import get_nodenx
|
|
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
|
+
)
|
|
20
39
|
from .jitter import (
|
|
21
40
|
JitterCache,
|
|
22
41
|
JitterCacheManager,
|
|
23
42
|
get_jitter_manager,
|
|
24
|
-
reset_jitter_manager,
|
|
25
43
|
random_jitter,
|
|
44
|
+
reset_jitter_manager,
|
|
26
45
|
)
|
|
46
|
+
from .registry import OPERATORS, discover_operators, get_operator_class
|
|
27
47
|
from .remesh import (
|
|
28
48
|
apply_network_remesh,
|
|
29
|
-
apply_topological_remesh,
|
|
30
49
|
apply_remesh_if_globally_stable,
|
|
50
|
+
apply_topological_remesh,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
_remesh_doc = (
|
|
54
|
+
"Trigger a remesh once the stability window is satisfied.\n\n"
|
|
55
|
+
"Parameters\n----------\n"
|
|
56
|
+
"stable_step_window : int | None\n"
|
|
57
|
+
" Number of consecutive stable steps required before remeshing.\n"
|
|
58
|
+
" Only the English keyword 'stable_step_window' is supported."
|
|
31
59
|
)
|
|
60
|
+
if apply_remesh_if_globally_stable.__doc__:
|
|
61
|
+
apply_remesh_if_globally_stable.__doc__ += "\n\n" + _remesh_doc
|
|
62
|
+
else:
|
|
63
|
+
apply_remesh_if_globally_stable.__doc__ = _remesh_doc
|
|
64
|
+
|
|
65
|
+
discover_operators()
|
|
66
|
+
|
|
67
|
+
_DEFINITION_EXPORTS = {
|
|
68
|
+
name: getattr(_definitions, name) for name in getattr(_definitions, "__all__", ())
|
|
69
|
+
}
|
|
70
|
+
globals().update(_DEFINITION_EXPORTS)
|
|
32
71
|
|
|
33
72
|
if TYPE_CHECKING: # pragma: no cover - type checking only
|
|
34
|
-
from ..node import
|
|
73
|
+
from ..node import NodeProtocol
|
|
35
74
|
|
|
36
|
-
|
|
75
|
+
GlyphFactors = dict[str, Any]
|
|
76
|
+
GlyphOperation = Callable[["NodeProtocol", GlyphFactors], None]
|
|
37
77
|
|
|
38
78
|
__all__ = [
|
|
39
79
|
"JitterCache",
|
|
@@ -41,6 +81,20 @@ __all__ = [
|
|
|
41
81
|
"get_jitter_manager",
|
|
42
82
|
"reset_jitter_manager",
|
|
43
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",
|
|
44
98
|
"get_neighbor_epi",
|
|
45
99
|
"get_glyph_factors",
|
|
46
100
|
"GLYPH_OPERATIONS",
|
|
@@ -49,26 +103,120 @@ __all__ = [
|
|
|
49
103
|
"apply_network_remesh",
|
|
50
104
|
"apply_topological_remesh",
|
|
51
105
|
"apply_remesh_if_globally_stable",
|
|
106
|
+
"OPERATORS",
|
|
107
|
+
"discover_operators",
|
|
108
|
+
"get_operator_class",
|
|
52
109
|
]
|
|
53
110
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
111
|
+
__all__.extend(_DEFINITION_EXPORTS.keys())
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def get_glyph_factors(node: NodeProtocol) -> GlyphFactors:
|
|
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
|
+
"""
|
|
57
147
|
return node.graph.get("GLYPH_FACTORS", DEFAULTS["GLYPH_FACTORS"].copy())
|
|
58
148
|
|
|
59
149
|
|
|
60
|
-
def get_factor(gf:
|
|
61
|
-
"""Return
|
|
150
|
+
def get_factor(gf: GlyphFactors, key: str, default: float) -> float:
|
|
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
|
+
"""
|
|
62
175
|
return float(gf.get(key, default))
|
|
63
176
|
|
|
64
177
|
|
|
65
178
|
# -------------------------
|
|
66
|
-
# Glyphs (
|
|
179
|
+
# Glyphs (local operators)
|
|
67
180
|
# -------------------------
|
|
68
181
|
|
|
69
182
|
|
|
70
|
-
def get_neighbor_epi(node:
|
|
71
|
-
"""
|
|
183
|
+
def get_neighbor_epi(node: NodeProtocol) -> tuple[list[NodeProtocol], EPIValue]:
|
|
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
|
+
"""
|
|
72
220
|
|
|
73
221
|
epi = node.EPI
|
|
74
222
|
neigh = list(node.neighbors())
|
|
@@ -98,12 +246,11 @@ def get_neighbor_epi(node: NodoProtocol) -> tuple[list[NodoProtocol], float]:
|
|
|
98
246
|
return [], epi
|
|
99
247
|
epi_bar = total / count if count else float(epi)
|
|
100
248
|
if needs_conversion:
|
|
101
|
-
|
|
102
|
-
if
|
|
103
|
-
raise ImportError("
|
|
249
|
+
NodeNX = get_nodenx()
|
|
250
|
+
if NodeNX is None:
|
|
251
|
+
raise ImportError("NodeNX is unavailable")
|
|
104
252
|
neigh = [
|
|
105
|
-
v if hasattr(v, "EPI") else
|
|
106
|
-
for v in neigh
|
|
253
|
+
v if hasattr(v, "EPI") else NodeNX.from_graph(node.G, v) for v in neigh
|
|
107
254
|
]
|
|
108
255
|
else:
|
|
109
256
|
try:
|
|
@@ -115,9 +262,37 @@ def get_neighbor_epi(node: NodoProtocol) -> tuple[list[NodoProtocol], float]:
|
|
|
115
262
|
|
|
116
263
|
|
|
117
264
|
def _determine_dominant(
|
|
118
|
-
neigh: list[
|
|
265
|
+
neigh: list[NodeProtocol], default_kind: str
|
|
119
266
|
) -> tuple[str, float]:
|
|
120
|
-
"""
|
|
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
|
+
"""
|
|
121
296
|
best_kind: str | None = None
|
|
122
297
|
best_abs = 0.0
|
|
123
298
|
for v in neigh:
|
|
@@ -131,13 +306,48 @@ def _determine_dominant(
|
|
|
131
306
|
|
|
132
307
|
|
|
133
308
|
def _mix_epi_with_neighbors(
|
|
134
|
-
node:
|
|
309
|
+
node: NodeProtocol, mix: float, default_glyph: Glyph | str
|
|
135
310
|
) -> tuple[float, str]:
|
|
136
|
-
"""
|
|
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
|
+
"""
|
|
137
349
|
default_kind = (
|
|
138
|
-
default_glyph.value
|
|
139
|
-
if isinstance(default_glyph, Glyph)
|
|
140
|
-
else str(default_glyph)
|
|
350
|
+
default_glyph.value if isinstance(default_glyph, Glyph) else str(default_glyph)
|
|
141
351
|
)
|
|
142
352
|
epi = node.EPI
|
|
143
353
|
neigh, epi_bar = get_neighbor_epi(node)
|
|
@@ -156,22 +366,120 @@ def _mix_epi_with_neighbors(
|
|
|
156
366
|
return epi_bar, final
|
|
157
367
|
|
|
158
368
|
|
|
159
|
-
def _op_AL(node:
|
|
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
|
+
"""
|
|
160
393
|
f = get_factor(gf, "AL_boost", 0.05)
|
|
161
394
|
node.EPI = node.EPI + f
|
|
162
395
|
|
|
163
396
|
|
|
164
|
-
def _op_EN(node:
|
|
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
|
+
"""
|
|
165
426
|
mix = get_factor(gf, "EN_mix", 0.25)
|
|
166
427
|
_mix_epi_with_neighbors(node, mix, Glyph.EN)
|
|
167
428
|
|
|
168
429
|
|
|
169
|
-
def _op_IL(node:
|
|
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
|
+
"""
|
|
170
454
|
factor = get_factor(gf, "IL_dnfr_factor", 0.7)
|
|
171
455
|
node.dnfr = factor * getattr(node, "dnfr", 0.0)
|
|
172
456
|
|
|
173
457
|
|
|
174
|
-
def _op_OZ(node:
|
|
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
|
+
"""
|
|
175
483
|
factor = get_factor(gf, "OZ_dnfr_factor", 1.3)
|
|
176
484
|
dnfr = getattr(node, "dnfr", 0.0)
|
|
177
485
|
if bool(node.graph.get("OZ_NOISE_MODE", False)):
|
|
@@ -184,31 +492,29 @@ def _op_OZ(node: NodoProtocol, gf: dict[str, Any]) -> None: # OZ — Disonancia
|
|
|
184
492
|
node.dnfr = factor * dnfr if abs(dnfr) > 1e-9 else 0.1
|
|
185
493
|
|
|
186
494
|
|
|
187
|
-
def _um_candidate_iter(node:
|
|
495
|
+
def _um_candidate_iter(node: NodeProtocol) -> Iterator[NodeProtocol]:
|
|
188
496
|
sample_ids = node.graph.get("_node_sample")
|
|
189
497
|
if sample_ids is not None and hasattr(node, "G"):
|
|
190
|
-
|
|
191
|
-
if
|
|
192
|
-
raise ImportError("
|
|
193
|
-
base = (
|
|
498
|
+
NodeNX = get_nodenx()
|
|
499
|
+
if NodeNX is None:
|
|
500
|
+
raise ImportError("NodeNX is unavailable")
|
|
501
|
+
base = (NodeNX.from_graph(node.G, j) for j in sample_ids)
|
|
194
502
|
else:
|
|
195
503
|
base = node.all_nodes()
|
|
196
504
|
for j in base:
|
|
197
|
-
same = (j is node) or (
|
|
198
|
-
getattr(node, "n", None) == getattr(j, "n", None)
|
|
199
|
-
)
|
|
505
|
+
same = (j is node) or (getattr(node, "n", None) == getattr(j, "n", None))
|
|
200
506
|
if same or node.has_edge(j):
|
|
201
507
|
continue
|
|
202
508
|
yield j
|
|
203
509
|
|
|
204
510
|
|
|
205
511
|
def _um_select_candidates(
|
|
206
|
-
node:
|
|
207
|
-
candidates,
|
|
512
|
+
node: NodeProtocol,
|
|
513
|
+
candidates: Iterator[NodeProtocol],
|
|
208
514
|
limit: int,
|
|
209
515
|
mode: str,
|
|
210
516
|
th: float,
|
|
211
|
-
):
|
|
517
|
+
) -> list[NodeProtocol]:
|
|
212
518
|
"""Select a subset of ``candidates`` for UM coupling."""
|
|
213
519
|
rng = make_rng(int(node.graph.get("RANDOM_SEED", 0)), node.offset(), node.G)
|
|
214
520
|
|
|
@@ -232,7 +538,46 @@ def _um_select_candidates(
|
|
|
232
538
|
return reservoir
|
|
233
539
|
|
|
234
540
|
|
|
235
|
-
def _op_UM(node:
|
|
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
|
+
"""
|
|
236
581
|
k = get_factor(gf, "UM_theta_push", 0.25)
|
|
237
582
|
th = node.theta
|
|
238
583
|
thL = neighbor_phase_mean(node)
|
|
@@ -260,21 +605,71 @@ def _op_UM(node: NodoProtocol, gf: dict[str, Any]) -> None: # UM — Coupling
|
|
|
260
605
|
dphi = abs(angle_diff(th_j, th)) / math.pi
|
|
261
606
|
epi_j = j.EPI
|
|
262
607
|
si_j = j.Si
|
|
263
|
-
epi_sim = 1.0 - abs(epi_i - epi_j) / (
|
|
264
|
-
abs(epi_i) + abs(epi_j) + 1e-9
|
|
265
|
-
)
|
|
608
|
+
epi_sim = 1.0 - abs(epi_i - epi_j) / (abs(epi_i) + abs(epi_j) + 1e-9)
|
|
266
609
|
si_sim = 1.0 - abs(si_i - si_j)
|
|
267
610
|
compat = (1 - dphi) * 0.5 + 0.25 * epi_sim + 0.25 * si_sim
|
|
268
611
|
if compat >= thr:
|
|
269
612
|
node.add_edge(j, compat)
|
|
270
613
|
|
|
271
614
|
|
|
272
|
-
def _op_RA(node:
|
|
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
|
+
"""
|
|
273
645
|
diff = get_factor(gf, "RA_epi_diff", 0.15)
|
|
274
646
|
_mix_epi_with_neighbors(node, diff, Glyph.RA)
|
|
275
647
|
|
|
276
648
|
|
|
277
|
-
def _op_SHA(node:
|
|
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
|
+
"""
|
|
278
673
|
factor = get_factor(gf, "SHA_vf_factor", 0.85)
|
|
279
674
|
node.vf = factor * node.vf
|
|
280
675
|
|
|
@@ -284,37 +679,138 @@ factor_nul = 0.85
|
|
|
284
679
|
_SCALE_FACTORS = {Glyph.VAL: factor_val, Glyph.NUL: factor_nul}
|
|
285
680
|
|
|
286
681
|
|
|
287
|
-
def _op_scale(node:
|
|
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
|
+
"""
|
|
288
692
|
node.vf *= factor
|
|
289
693
|
|
|
290
694
|
|
|
291
|
-
def _make_scale_op(glyph: Glyph):
|
|
292
|
-
def _op(node:
|
|
695
|
+
def _make_scale_op(glyph: Glyph) -> GlyphOperation:
|
|
696
|
+
def _op(node: NodeProtocol, gf: GlyphFactors) -> None:
|
|
293
697
|
key = "VAL_scale" if glyph is Glyph.VAL else "NUL_scale"
|
|
294
698
|
default = _SCALE_FACTORS[glyph]
|
|
295
699
|
factor = get_factor(gf, key, default)
|
|
296
700
|
_op_scale(node, factor)
|
|
297
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
|
+
)
|
|
298
729
|
return _op
|
|
299
730
|
|
|
300
731
|
|
|
301
|
-
def _op_THOL(
|
|
302
|
-
|
|
303
|
-
|
|
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
|
+
"""
|
|
304
756
|
a = get_factor(gf, "THOL_accel", 0.10)
|
|
305
757
|
node.dnfr = node.dnfr + a * getattr(node, "d2EPI", 0.0)
|
|
306
758
|
|
|
307
759
|
|
|
308
|
-
def _op_ZHIR(
|
|
309
|
-
|
|
310
|
-
|
|
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
|
+
"""
|
|
311
784
|
shift = get_factor(gf, "ZHIR_theta_shift", math.pi / 2)
|
|
312
785
|
node.theta = node.theta + shift
|
|
313
786
|
|
|
314
787
|
|
|
315
|
-
def _op_NAV(
|
|
316
|
-
|
|
317
|
-
|
|
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
|
+
"""
|
|
318
814
|
dnfr = node.dnfr
|
|
319
815
|
vf = node.vf
|
|
320
816
|
eta = get_factor(gf, "NAV_eta", 0.5)
|
|
@@ -334,14 +830,37 @@ def _op_NAV(
|
|
|
334
830
|
|
|
335
831
|
|
|
336
832
|
def _op_REMESH(
|
|
337
|
-
node:
|
|
338
|
-
) -> None: # REMESH —
|
|
833
|
+
node: NodeProtocol, gf: GlyphFactors | None = None
|
|
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
|
+
"""
|
|
339
858
|
step_idx = glyph_history.current_step_idx(node)
|
|
340
859
|
last_warn = node.graph.get("_remesh_warn_step", None)
|
|
341
860
|
if last_warn != step_idx:
|
|
342
861
|
msg = (
|
|
343
|
-
"REMESH
|
|
344
|
-
"stable(G)
|
|
862
|
+
"REMESH operates at network scale. Use apply_remesh_if_globally_"
|
|
863
|
+
"stable(G) or apply_network_remesh(G)."
|
|
345
864
|
)
|
|
346
865
|
hist = glyph_history.ensure_history(node)
|
|
347
866
|
glyph_history.append_metric(
|
|
@@ -357,7 +876,7 @@ def _op_REMESH(
|
|
|
357
876
|
# Dispatcher
|
|
358
877
|
# -------------------------
|
|
359
878
|
|
|
360
|
-
GLYPH_OPERATIONS: dict[Glyph,
|
|
879
|
+
GLYPH_OPERATIONS: dict[Glyph, GlyphOperation] = {
|
|
361
880
|
Glyph.AL: _op_AL,
|
|
362
881
|
Glyph.EN: _op_EN,
|
|
363
882
|
Glyph.IL: _op_IL,
|
|
@@ -375,9 +894,9 @@ GLYPH_OPERATIONS: dict[Glyph, Callable[["NodoProtocol", dict[str, Any]], None]]
|
|
|
375
894
|
|
|
376
895
|
|
|
377
896
|
def apply_glyph_obj(
|
|
378
|
-
node:
|
|
897
|
+
node: NodeProtocol, glyph: Glyph | str, *, window: int | None = None
|
|
379
898
|
) -> None:
|
|
380
|
-
"""Apply ``glyph`` to an object satisfying :class:`
|
|
899
|
+
"""Apply ``glyph`` to an object satisfying :class:`NodeProtocol`."""
|
|
381
900
|
|
|
382
901
|
try:
|
|
383
902
|
g = glyph if isinstance(glyph, Glyph) else Glyph(str(glyph))
|
|
@@ -392,15 +911,15 @@ def apply_glyph_obj(
|
|
|
392
911
|
{
|
|
393
912
|
"step": step_idx,
|
|
394
913
|
"node": getattr(node, "n", None),
|
|
395
|
-
"msg": f"glyph
|
|
914
|
+
"msg": f"unknown glyph: {glyph}",
|
|
396
915
|
},
|
|
397
916
|
),
|
|
398
917
|
)
|
|
399
|
-
raise ValueError(f"glyph
|
|
918
|
+
raise ValueError(f"unknown glyph: {glyph}")
|
|
400
919
|
|
|
401
920
|
op = GLYPH_OPERATIONS.get(g)
|
|
402
921
|
if op is None:
|
|
403
|
-
raise ValueError(f"glyph
|
|
922
|
+
raise ValueError(f"glyph has no registered operator: {g}")
|
|
404
923
|
if window is None:
|
|
405
924
|
window = int(get_param(node, "GLYPH_HYSTERESIS_WINDOW"))
|
|
406
925
|
gf = get_glyph_factors(node)
|
|
@@ -410,11 +929,11 @@ def apply_glyph_obj(
|
|
|
410
929
|
|
|
411
930
|
|
|
412
931
|
def apply_glyph(
|
|
413
|
-
G, n, glyph: Glyph | str, *, window: int | None = None
|
|
932
|
+
G: TNFRGraph, n: NodeId, glyph: Glyph | str, *, window: int | None = None
|
|
414
933
|
) -> None:
|
|
415
934
|
"""Adapter to operate on ``networkx`` graphs."""
|
|
416
|
-
|
|
417
|
-
if
|
|
418
|
-
raise ImportError("
|
|
419
|
-
node =
|
|
935
|
+
NodeNX = get_nodenx()
|
|
936
|
+
if NodeNX is None:
|
|
937
|
+
raise ImportError("NodeNX is unavailable")
|
|
938
|
+
node = NodeNX(G, n)
|
|
420
939
|
apply_glyph_obj(node, glyph, window=window)
|