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.

Files changed (195) hide show
  1. tnfr/__init__.py +275 -51
  2. tnfr/__init__.pyi +33 -0
  3. tnfr/_compat.py +10 -0
  4. tnfr/_generated_version.py +34 -0
  5. tnfr/_version.py +49 -0
  6. tnfr/_version.pyi +7 -0
  7. tnfr/alias.py +117 -31
  8. tnfr/alias.pyi +108 -0
  9. tnfr/cache.py +6 -572
  10. tnfr/cache.pyi +16 -0
  11. tnfr/callback_utils.py +16 -38
  12. tnfr/callback_utils.pyi +79 -0
  13. tnfr/cli/__init__.py +34 -14
  14. tnfr/cli/__init__.pyi +26 -0
  15. tnfr/cli/arguments.py +211 -28
  16. tnfr/cli/arguments.pyi +27 -0
  17. tnfr/cli/execution.py +470 -50
  18. tnfr/cli/execution.pyi +70 -0
  19. tnfr/cli/utils.py +18 -3
  20. tnfr/cli/utils.pyi +8 -0
  21. tnfr/config/__init__.py +13 -0
  22. tnfr/config/__init__.pyi +10 -0
  23. tnfr/{constants_glyphs.py → config/constants.py} +26 -20
  24. tnfr/config/constants.pyi +12 -0
  25. tnfr/config/feature_flags.py +83 -0
  26. tnfr/{config.py → config/init.py} +11 -7
  27. tnfr/config/init.pyi +8 -0
  28. tnfr/config/operator_names.py +93 -0
  29. tnfr/config/operator_names.pyi +28 -0
  30. tnfr/config/presets.py +84 -0
  31. tnfr/config/presets.pyi +7 -0
  32. tnfr/constants/__init__.py +80 -29
  33. tnfr/constants/__init__.pyi +92 -0
  34. tnfr/constants/aliases.py +31 -0
  35. tnfr/constants/core.py +4 -4
  36. tnfr/constants/core.pyi +17 -0
  37. tnfr/constants/init.py +1 -1
  38. tnfr/constants/init.pyi +12 -0
  39. tnfr/constants/metric.py +7 -15
  40. tnfr/constants/metric.pyi +19 -0
  41. tnfr/dynamics/__init__.py +165 -633
  42. tnfr/dynamics/__init__.pyi +82 -0
  43. tnfr/dynamics/adaptation.py +267 -0
  44. tnfr/dynamics/aliases.py +23 -0
  45. tnfr/dynamics/coordination.py +385 -0
  46. tnfr/dynamics/dnfr.py +2283 -400
  47. tnfr/dynamics/dnfr.pyi +24 -0
  48. tnfr/dynamics/integrators.py +406 -98
  49. tnfr/dynamics/integrators.pyi +34 -0
  50. tnfr/dynamics/runtime.py +881 -0
  51. tnfr/dynamics/sampling.py +10 -5
  52. tnfr/dynamics/sampling.pyi +7 -0
  53. tnfr/dynamics/selectors.py +719 -0
  54. tnfr/execution.py +70 -48
  55. tnfr/execution.pyi +45 -0
  56. tnfr/flatten.py +13 -9
  57. tnfr/flatten.pyi +21 -0
  58. tnfr/gamma.py +66 -53
  59. tnfr/gamma.pyi +34 -0
  60. tnfr/glyph_history.py +110 -52
  61. tnfr/glyph_history.pyi +35 -0
  62. tnfr/glyph_runtime.py +16 -0
  63. tnfr/glyph_runtime.pyi +9 -0
  64. tnfr/immutable.py +69 -28
  65. tnfr/immutable.pyi +34 -0
  66. tnfr/initialization.py +16 -16
  67. tnfr/initialization.pyi +65 -0
  68. tnfr/io.py +6 -240
  69. tnfr/io.pyi +16 -0
  70. tnfr/locking.pyi +7 -0
  71. tnfr/mathematics/__init__.py +81 -0
  72. tnfr/mathematics/backend.py +426 -0
  73. tnfr/mathematics/dynamics.py +398 -0
  74. tnfr/mathematics/epi.py +254 -0
  75. tnfr/mathematics/generators.py +222 -0
  76. tnfr/mathematics/metrics.py +119 -0
  77. tnfr/mathematics/operators.py +233 -0
  78. tnfr/mathematics/operators_factory.py +71 -0
  79. tnfr/mathematics/projection.py +78 -0
  80. tnfr/mathematics/runtime.py +173 -0
  81. tnfr/mathematics/spaces.py +247 -0
  82. tnfr/mathematics/transforms.py +292 -0
  83. tnfr/metrics/__init__.py +10 -10
  84. tnfr/metrics/__init__.pyi +20 -0
  85. tnfr/metrics/coherence.py +993 -324
  86. tnfr/metrics/common.py +23 -16
  87. tnfr/metrics/common.pyi +46 -0
  88. tnfr/metrics/core.py +251 -35
  89. tnfr/metrics/core.pyi +13 -0
  90. tnfr/metrics/diagnosis.py +708 -111
  91. tnfr/metrics/diagnosis.pyi +85 -0
  92. tnfr/metrics/export.py +27 -15
  93. tnfr/metrics/glyph_timing.py +232 -42
  94. tnfr/metrics/reporting.py +33 -22
  95. tnfr/metrics/reporting.pyi +12 -0
  96. tnfr/metrics/sense_index.py +987 -43
  97. tnfr/metrics/sense_index.pyi +9 -0
  98. tnfr/metrics/trig.py +214 -23
  99. tnfr/metrics/trig.pyi +13 -0
  100. tnfr/metrics/trig_cache.py +115 -22
  101. tnfr/metrics/trig_cache.pyi +10 -0
  102. tnfr/node.py +542 -136
  103. tnfr/node.pyi +178 -0
  104. tnfr/observers.py +152 -35
  105. tnfr/observers.pyi +31 -0
  106. tnfr/ontosim.py +23 -19
  107. tnfr/ontosim.pyi +28 -0
  108. tnfr/operators/__init__.py +601 -82
  109. tnfr/operators/__init__.pyi +45 -0
  110. tnfr/operators/definitions.py +513 -0
  111. tnfr/operators/definitions.pyi +78 -0
  112. tnfr/operators/grammar.py +760 -0
  113. tnfr/operators/jitter.py +107 -38
  114. tnfr/operators/jitter.pyi +11 -0
  115. tnfr/operators/registry.py +75 -0
  116. tnfr/operators/registry.pyi +13 -0
  117. tnfr/operators/remesh.py +149 -88
  118. tnfr/py.typed +0 -0
  119. tnfr/rng.py +46 -143
  120. tnfr/rng.pyi +14 -0
  121. tnfr/schemas/__init__.py +8 -0
  122. tnfr/schemas/grammar.json +94 -0
  123. tnfr/selector.py +25 -19
  124. tnfr/selector.pyi +19 -0
  125. tnfr/sense.py +72 -62
  126. tnfr/sense.pyi +23 -0
  127. tnfr/structural.py +522 -262
  128. tnfr/structural.pyi +69 -0
  129. tnfr/telemetry/__init__.py +35 -0
  130. tnfr/telemetry/cache_metrics.py +226 -0
  131. tnfr/telemetry/nu_f.py +423 -0
  132. tnfr/telemetry/nu_f.pyi +123 -0
  133. tnfr/telemetry/verbosity.py +37 -0
  134. tnfr/tokens.py +1 -3
  135. tnfr/tokens.pyi +36 -0
  136. tnfr/trace.py +270 -113
  137. tnfr/trace.pyi +40 -0
  138. tnfr/types.py +574 -6
  139. tnfr/types.pyi +331 -0
  140. tnfr/units.py +69 -0
  141. tnfr/units.pyi +16 -0
  142. tnfr/utils/__init__.py +217 -0
  143. tnfr/utils/__init__.pyi +202 -0
  144. tnfr/utils/cache.py +2395 -0
  145. tnfr/utils/cache.pyi +468 -0
  146. tnfr/utils/chunks.py +104 -0
  147. tnfr/utils/chunks.pyi +21 -0
  148. tnfr/{collections_utils.py → utils/data.py} +147 -90
  149. tnfr/utils/data.pyi +64 -0
  150. tnfr/utils/graph.py +85 -0
  151. tnfr/utils/graph.pyi +10 -0
  152. tnfr/utils/init.py +770 -0
  153. tnfr/utils/init.pyi +78 -0
  154. tnfr/utils/io.py +456 -0
  155. tnfr/{helpers → utils}/numeric.py +51 -24
  156. tnfr/utils/numeric.pyi +21 -0
  157. tnfr/validation/__init__.py +113 -0
  158. tnfr/validation/__init__.pyi +77 -0
  159. tnfr/validation/compatibility.py +95 -0
  160. tnfr/validation/compatibility.pyi +6 -0
  161. tnfr/validation/grammar.py +71 -0
  162. tnfr/validation/grammar.pyi +40 -0
  163. tnfr/validation/graph.py +138 -0
  164. tnfr/validation/graph.pyi +17 -0
  165. tnfr/validation/rules.py +281 -0
  166. tnfr/validation/rules.pyi +55 -0
  167. tnfr/validation/runtime.py +263 -0
  168. tnfr/validation/runtime.pyi +31 -0
  169. tnfr/validation/soft_filters.py +170 -0
  170. tnfr/validation/soft_filters.pyi +37 -0
  171. tnfr/validation/spectral.py +159 -0
  172. tnfr/validation/spectral.pyi +46 -0
  173. tnfr/validation/syntax.py +40 -0
  174. tnfr/validation/syntax.pyi +10 -0
  175. tnfr/validation/window.py +39 -0
  176. tnfr/validation/window.pyi +1 -0
  177. tnfr/viz/__init__.py +9 -0
  178. tnfr/viz/matplotlib.py +246 -0
  179. tnfr-7.0.0.dist-info/METADATA +179 -0
  180. tnfr-7.0.0.dist-info/RECORD +185 -0
  181. {tnfr-4.5.2.dist-info → tnfr-7.0.0.dist-info}/licenses/LICENSE.md +1 -1
  182. tnfr/grammar.py +0 -344
  183. tnfr/graph_utils.py +0 -84
  184. tnfr/helpers/__init__.py +0 -71
  185. tnfr/import_utils.py +0 -228
  186. tnfr/json_utils.py +0 -162
  187. tnfr/logging_utils.py +0 -116
  188. tnfr/presets.py +0 -60
  189. tnfr/validators.py +0 -84
  190. tnfr/value_utils.py +0 -59
  191. tnfr-4.5.2.dist-info/METADATA +0 -379
  192. tnfr-4.5.2.dist-info/RECORD +0 -67
  193. {tnfr-4.5.2.dist-info → tnfr-7.0.0.dist-info}/WHEEL +0 -0
  194. {tnfr-4.5.2.dist-info → tnfr-7.0.0.dist-info}/entry_points.txt +0 -0
  195. {tnfr-4.5.2.dist-info → tnfr-7.0.0.dist-info}/top_level.txt +0 -0
tnfr/grammar.py DELETED
@@ -1,344 +0,0 @@
1
- """Grammar rules."""
2
-
3
- from __future__ import annotations
4
- from dataclasses import dataclass
5
- from typing import Any, Iterable, Optional, Callable
6
-
7
- from .constants import DEFAULTS, get_aliases, get_param
8
- from .alias import get_attr
9
- from .helpers.numeric import clamp01
10
- from .glyph_history import recent_glyph
11
- from .types import Glyph
12
- from .operators import apply_glyph # avoid repeated import inside functions
13
- from .metrics.common import normalize_dnfr
14
-
15
- ALIAS_SI = get_aliases("SI")
16
- ALIAS_D2EPI = get_aliases("D2EPI")
17
-
18
-
19
- @dataclass
20
- class GrammarContext:
21
- """Shared context for grammar helpers.
22
-
23
- Collects graph-level settings to reduce positional parameters across
24
- helper functions.
25
- """
26
-
27
- G: Any
28
- cfg_soft: dict[str, Any]
29
- cfg_canon: dict[str, Any]
30
- norms: dict[str, Any]
31
-
32
- @classmethod
33
- def from_graph(cls, G: Any) -> "GrammarContext":
34
- """Create a :class:`GrammarContext` for ``G``."""
35
- return cls(
36
- G=G,
37
- cfg_soft=G.graph.get("GRAMMAR", DEFAULTS.get("GRAMMAR", {})),
38
- cfg_canon=G.graph.get(
39
- "GRAMMAR_CANON", DEFAULTS.get("GRAMMAR_CANON", {})
40
- ),
41
- norms=G.graph.get("_sel_norms") or {},
42
- )
43
-
44
-
45
- __all__ = (
46
- "CANON_COMPAT",
47
- "CANON_FALLBACK",
48
- "enforce_canonical_grammar",
49
- "on_applied_glyph",
50
- "apply_glyph_with_grammar",
51
- "GrammarContext",
52
- )
53
-
54
- # -------------------------
55
- # Per-node grammar state
56
- # -------------------------
57
-
58
-
59
- def _gram_state(nd: dict[str, Any]) -> dict[str, Any]:
60
- """Create or return the node grammar state.
61
-
62
- Fields:
63
- - thol_open (bool)
64
- - thol_len (int)
65
- """
66
- return nd.setdefault("_GRAM", {"thol_open": False, "thol_len": 0})
67
-
68
-
69
- # -------------------------
70
- # Canonical compatibilities (allowed next glyphs)
71
- # -------------------------
72
- CANON_COMPAT: dict[Glyph, set[Glyph]] = {
73
- # Inicio / apertura
74
- Glyph.AL: {Glyph.EN, Glyph.RA, Glyph.NAV, Glyph.VAL, Glyph.UM},
75
- Glyph.EN: {Glyph.IL, Glyph.UM, Glyph.RA, Glyph.NAV},
76
- # Estabilización / difusión / acople
77
- Glyph.IL: {Glyph.RA, Glyph.VAL, Glyph.UM, Glyph.SHA},
78
- Glyph.UM: {Glyph.RA, Glyph.IL, Glyph.VAL, Glyph.NAV},
79
- Glyph.RA: {Glyph.IL, Glyph.VAL, Glyph.UM, Glyph.NAV},
80
- Glyph.VAL: {Glyph.UM, Glyph.RA, Glyph.IL, Glyph.NAV},
81
- # Disonancia → transición → mutación
82
- Glyph.OZ: {Glyph.ZHIR, Glyph.NAV},
83
- Glyph.ZHIR: {Glyph.IL, Glyph.NAV},
84
- Glyph.NAV: {Glyph.OZ, Glyph.ZHIR, Glyph.RA, Glyph.IL, Glyph.UM},
85
- # Cierres / latencias
86
- Glyph.SHA: {Glyph.AL, Glyph.EN},
87
- Glyph.NUL: {Glyph.AL, Glyph.IL},
88
- # Bloques autoorganizativos
89
- Glyph.THOL: {
90
- Glyph.OZ,
91
- Glyph.ZHIR,
92
- Glyph.NAV,
93
- Glyph.RA,
94
- Glyph.IL,
95
- Glyph.UM,
96
- Glyph.SHA,
97
- Glyph.NUL,
98
- },
99
- }
100
-
101
- # Canonical fallbacks when a transition is not allowed
102
- CANON_FALLBACK: dict[Glyph, Glyph] = {
103
- Glyph.AL: Glyph.EN,
104
- Glyph.EN: Glyph.IL,
105
- Glyph.IL: Glyph.RA,
106
- Glyph.NAV: Glyph.RA,
107
- Glyph.NUL: Glyph.AL,
108
- Glyph.OZ: Glyph.ZHIR,
109
- Glyph.RA: Glyph.IL,
110
- Glyph.SHA: Glyph.AL,
111
- Glyph.THOL: Glyph.NAV,
112
- Glyph.UM: Glyph.RA,
113
- Glyph.VAL: Glyph.RA,
114
- Glyph.ZHIR: Glyph.IL,
115
- }
116
-
117
-
118
- def _coerce_glyph(val: Any) -> Glyph | Any:
119
- """Return ``val`` as ``Glyph`` when possible."""
120
- try:
121
- return Glyph(val)
122
- except (ValueError, TypeError):
123
- return val
124
-
125
-
126
- def _glyph_fallback(cand_key: str, fallbacks: dict[str, Any]) -> Glyph | str:
127
- """Determine fallback glyph for ``cand_key`` and return converted value."""
128
- glyph_key = _coerce_glyph(cand_key)
129
- canon_fb = (
130
- CANON_FALLBACK.get(glyph_key, cand_key)
131
- if isinstance(glyph_key, Glyph)
132
- else cand_key
133
- )
134
- fb = fallbacks.get(cand_key, canon_fb)
135
- return _coerce_glyph(fb)
136
-
137
-
138
- # -------------------------
139
- # THOL closures and ZHIR preconditions
140
- # -------------------------
141
-
142
-
143
- def get_norm(ctx: GrammarContext, key: str) -> float:
144
- """Retrieve a global normalisation value from ``ctx.norms``."""
145
- return float(ctx.norms.get(key, 1.0)) or 1.0
146
-
147
-
148
- def _norm_attr(ctx: GrammarContext, nd, attr_alias: str, norm_key: str) -> float:
149
- """Normalise ``attr_alias`` using the global maximum ``norm_key``."""
150
-
151
- max_val = get_norm(ctx, norm_key)
152
- return clamp01(abs(get_attr(nd, attr_alias, 0.0)) / max_val)
153
-
154
-
155
- def _si(nd) -> float:
156
- return clamp01(get_attr(nd, ALIAS_SI, 0.5))
157
-
158
-
159
- def _accel_norm(ctx: GrammarContext, nd) -> float:
160
- """Normalise acceleration using the global maximum."""
161
- return _norm_attr(ctx, nd, ALIAS_D2EPI, "accel_max")
162
-
163
-
164
- def _check_repeats(ctx: GrammarContext, n, cand: Glyph | str) -> Glyph | str:
165
- """Avoid recent repetitions according to ``ctx.cfg_soft``."""
166
- nd = ctx.G.nodes[n]
167
- cfg = ctx.cfg_soft
168
- gwin = int(cfg.get("window", 0))
169
- avoid = set(cfg.get("avoid_repeats", []))
170
- fallbacks = cfg.get("fallbacks", {})
171
- cand_key = cand.value if isinstance(cand, Glyph) else str(cand)
172
- if gwin > 0 and cand_key in avoid and recent_glyph(nd, cand_key, gwin):
173
- return _glyph_fallback(cand_key, fallbacks)
174
- return cand
175
-
176
-
177
- def _maybe_force(
178
- ctx: GrammarContext,
179
- n,
180
- cand: Glyph | str,
181
- original: Glyph | str,
182
- accessor: Callable[[GrammarContext, dict[str, Any]], float],
183
- key: str,
184
- ) -> Glyph | str:
185
- """Restore ``original`` if ``accessor`` exceeds ``key`` threshold."""
186
- if cand == original:
187
- return cand
188
- force_th = float(ctx.cfg_soft.get(key, 0.60))
189
- if accessor(ctx, ctx.G.nodes[n]) >= force_th:
190
- return original
191
- return cand
192
-
193
-
194
- def _check_oz_to_zhir(ctx: GrammarContext, n, cand: Glyph | str) -> Glyph | str:
195
- nd = ctx.G.nodes[n]
196
- cand_glyph = _coerce_glyph(cand)
197
- if cand_glyph == Glyph.ZHIR:
198
- cfg = ctx.cfg_canon
199
- win = int(cfg.get("zhir_requires_oz_window", 3))
200
- dn_min = float(cfg.get("zhir_dnfr_min", 0.05))
201
- dnfr_max = get_norm(ctx, "dnfr_max")
202
- if (
203
- not recent_glyph(nd, Glyph.OZ, win)
204
- and normalize_dnfr(nd, dnfr_max) < dn_min
205
- ):
206
- return Glyph.OZ
207
- return cand
208
-
209
-
210
- def _check_thol_closure(
211
- ctx: GrammarContext, n, cand: Glyph | str, st: dict[str, Any]
212
- ) -> Glyph | str:
213
- nd = ctx.G.nodes[n]
214
- if st.get("thol_open", False):
215
- st["thol_len"] = int(st.get("thol_len", 0)) + 1
216
- cfg = ctx.cfg_canon
217
- minlen = int(cfg.get("thol_min_len", 2))
218
- maxlen = int(cfg.get("thol_max_len", 6))
219
- close_dn = float(cfg.get("thol_close_dnfr", 0.15))
220
- dnfr_max = get_norm(ctx, "dnfr_max")
221
- if st["thol_len"] >= maxlen or (
222
- st["thol_len"] >= minlen
223
- and normalize_dnfr(nd, dnfr_max) <= close_dn
224
- ):
225
- return (
226
- Glyph.NUL
227
- if _si(nd) >= float(cfg.get("si_high", 0.66))
228
- else Glyph.SHA
229
- )
230
- return cand
231
-
232
-
233
- def _check_compatibility(ctx: GrammarContext, n, cand: Glyph | str) -> Glyph | str:
234
- nd = ctx.G.nodes[n]
235
- hist = nd.get("glyph_history")
236
- prev = hist[-1] if hist else None
237
- prev_glyph = _coerce_glyph(prev)
238
- cand_glyph = _coerce_glyph(cand)
239
- if isinstance(prev_glyph, Glyph):
240
- allowed = CANON_COMPAT.get(prev_glyph)
241
- if allowed is None:
242
- return cand
243
- if isinstance(cand_glyph, Glyph):
244
- if cand_glyph not in allowed:
245
- return CANON_FALLBACK.get(prev_glyph, cand_glyph)
246
- else:
247
- return CANON_FALLBACK.get(prev_glyph, cand)
248
- return cand
249
-
250
-
251
- # -------------------------
252
- # Core: enforce grammar on a candidate
253
- # -------------------------
254
-
255
-
256
- def enforce_canonical_grammar(
257
- G, n, cand: Glyph | str, ctx: Optional[GrammarContext] = None
258
- ) -> Glyph | str:
259
- """Validate and adjust a candidate glyph according to canonical grammar.
260
-
261
- Key rules:
262
- - Repeat window with forces based on |ΔNFR| and acceleration.
263
- - Transition compatibilities (TNFR path).
264
- - OZ→ZHIR: mutation requires recent dissonance or high |ΔNFR|.
265
- - THOL[...]: forces closure with SHA or NUL when the field stabilises
266
- or block length is reached; maintains per-node state.
267
-
268
- Returns the effective glyph to apply.
269
- """
270
- if ctx is None:
271
- ctx = GrammarContext.from_graph(G)
272
-
273
- nd = ctx.G.nodes[n]
274
- st = _gram_state(nd)
275
-
276
- raw_cand = cand
277
- cand = _coerce_glyph(cand)
278
- input_was_str = isinstance(raw_cand, str)
279
-
280
- # 0) If glyphs outside the alphabet arrive, leave untouched
281
- if not isinstance(cand, Glyph) or cand not in CANON_COMPAT:
282
- return raw_cand if input_was_str else cand
283
-
284
- original = cand
285
- cand = _check_repeats(ctx, n, cand)
286
-
287
- dnfr_accessor = lambda ctx, nd: normalize_dnfr(nd, get_norm(ctx, "dnfr_max"))
288
- cand = _maybe_force(ctx, n, cand, original, dnfr_accessor, "force_dnfr")
289
- cand = _maybe_force(ctx, n, cand, original, _accel_norm, "force_accel")
290
- cand = _check_oz_to_zhir(ctx, n, cand)
291
- cand = _check_thol_closure(ctx, n, cand, st)
292
- cand = _check_compatibility(ctx, n, cand)
293
-
294
- coerced_final = _coerce_glyph(cand)
295
- if input_was_str:
296
- if isinstance(coerced_final, Glyph):
297
- return coerced_final.value
298
- return str(cand)
299
- return coerced_final if isinstance(coerced_final, Glyph) else cand
300
-
301
-
302
- # -------------------------
303
- # Post-selection: update grammar state
304
- # -------------------------
305
-
306
-
307
- def on_applied_glyph(G, n, applied: str) -> None:
308
- nd = G.nodes[n]
309
- st = _gram_state(nd)
310
- if applied == Glyph.THOL:
311
- st["thol_open"] = True
312
- st["thol_len"] = 0
313
- elif applied in (Glyph.SHA, Glyph.NUL):
314
- st["thol_open"] = False
315
- st["thol_len"] = 0
316
-
317
-
318
- # -------------------------
319
- # Direct application with canonical grammar
320
- # -------------------------
321
-
322
-
323
- def apply_glyph_with_grammar(
324
- G,
325
- nodes: Optional[Iterable[Any]],
326
- glyph: Glyph | str,
327
- window: Optional[int] = None,
328
- ) -> None:
329
- """Apply ``glyph`` to ``nodes`` enforcing the canonical grammar.
330
-
331
- ``nodes`` may be a ``NodeView`` or any iterable. The iterable is consumed
332
- directly to avoid unnecessary materialisation; callers must materialise if
333
- they need indexing.
334
- """
335
- if window is None:
336
- window = get_param(G, "GLYPH_HYSTERESIS_WINDOW")
337
-
338
- g_str = glyph.value if isinstance(glyph, Glyph) else str(glyph)
339
- iter_nodes = G.nodes() if nodes is None else nodes
340
- ctx = GrammarContext.from_graph(G)
341
- for n in iter_nodes:
342
- g_eff = enforce_canonical_grammar(G, n, g_str, ctx)
343
- apply_glyph(G, n, g_eff, window=window)
344
- on_applied_glyph(G, n, g_eff)
tnfr/graph_utils.py DELETED
@@ -1,84 +0,0 @@
1
- """Utilities for graph-level bookkeeping.
2
-
3
- This module centralises helpers that operate on the metadata stored inside
4
- graph objects. Besides flagging ΔNFR preparation caches it also exposes
5
- lightweight adapters to obtain the canonical ``graph`` mapping and to read
6
- validated configuration dictionaries.
7
- """
8
-
9
- from __future__ import annotations
10
-
11
- import warnings
12
- from types import MappingProxyType
13
- from typing import Any, Mapping
14
-
15
- __all__ = (
16
- "get_graph",
17
- "get_graph_mapping",
18
- "mark_dnfr_prep_dirty",
19
- "supports_add_edge",
20
- )
21
-
22
-
23
- def get_graph(obj: Any) -> Any:
24
- """Return ``obj.graph`` when present or ``obj`` otherwise."""
25
- return getattr(obj, "graph", obj)
26
-
27
-
28
- def get_graph_mapping(
29
- G: Any, key: str, warn_msg: str
30
- ) -> Mapping[str, Any] | None:
31
- """Return an immutable view of ``G``'s stored mapping for ``key``.
32
-
33
- The helper normalises access to ``G.graph[key]`` by returning
34
- ``None`` when the key is missing or holds a non-mapping value. When a
35
- mapping is found it is wrapped in :class:`types.MappingProxyType` to guard
36
- against accidental mutation. ``warn_msg`` is emitted via
37
- :func:`warnings.warn` when the stored value is not a mapping.
38
- """
39
- graph = get_graph(G)
40
- getter = getattr(graph, "get", None)
41
- if getter is None:
42
- return None
43
-
44
- data = getter(key)
45
- if data is None:
46
- return None
47
- if not isinstance(data, Mapping):
48
- warnings.warn(warn_msg, UserWarning, stacklevel=2)
49
- return None
50
- return MappingProxyType(data)
51
-
52
-
53
- def mark_dnfr_prep_dirty(G: Any) -> None:
54
- """Flag ΔNFR preparation data as stale.
55
-
56
- Parameters
57
- ----------
58
- G : Any
59
- Graph-like object whose ``graph`` attribute will receive the
60
- ``"_dnfr_prep_dirty"`` flag.
61
-
62
- Returns
63
- -------
64
- None
65
- This function mutates ``G`` in place.
66
- """
67
- graph = get_graph(G)
68
- graph["_dnfr_prep_dirty"] = True
69
-
70
-
71
- def supports_add_edge(graph: Any) -> bool:
72
- """Return ``True`` if ``graph`` exposes an ``add_edge`` method.
73
-
74
- Parameters
75
- ----------
76
- graph : Any
77
- Object representing a graph.
78
-
79
- Returns
80
- -------
81
- bool
82
- ``True`` when ``graph`` implements ``add_edge``; ``False`` otherwise.
83
- """
84
- return hasattr(graph, "add_edge")
tnfr/helpers/__init__.py DELETED
@@ -1,71 +0,0 @@
1
- """Curated high-level helpers exposed by :mod:`tnfr.helpers`.
2
-
3
- The module is intentionally small and surfaces utilities that are stable for
4
- external use, covering data preparation, glyph history management, and graph
5
- cache invalidation.
6
- """
7
-
8
- from __future__ import annotations
9
-
10
- from ..cache import (
11
- EdgeCacheManager,
12
- cached_node_list,
13
- cached_nodes_and_A,
14
- edge_version_cache,
15
- edge_version_update,
16
- ensure_node_index_map,
17
- ensure_node_offset_map,
18
- increment_edge_version,
19
- node_set_checksum,
20
- stable_json,
21
- )
22
- from ..graph_utils import get_graph, get_graph_mapping, mark_dnfr_prep_dirty
23
- from .numeric import (
24
- angle_diff,
25
- clamp,
26
- clamp01,
27
- kahan_sum_nd,
28
- )
29
-
30
-
31
- def __getattr__(name: str):
32
- if name in _GLYPH_HISTORY_EXPORTS:
33
- from .. import glyph_history as _glyph_history
34
-
35
- value = getattr(_glyph_history, name)
36
- globals()[name] = value
37
- return value
38
- raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
39
-
40
- __all__ = (
41
- "EdgeCacheManager",
42
- "angle_diff",
43
- "cached_node_list",
44
- "cached_nodes_and_A",
45
- "clamp",
46
- "clamp01",
47
- "edge_version_cache",
48
- "edge_version_update",
49
- "ensure_node_index_map",
50
- "ensure_node_offset_map",
51
- "get_graph",
52
- "get_graph_mapping",
53
- "increment_edge_version",
54
- "kahan_sum_nd",
55
- "mark_dnfr_prep_dirty",
56
- "node_set_checksum",
57
- "stable_json",
58
- "count_glyphs",
59
- "ensure_history",
60
- "last_glyph",
61
- "push_glyph",
62
- "recent_glyph",
63
- )
64
-
65
- _GLYPH_HISTORY_EXPORTS = (
66
- "count_glyphs",
67
- "ensure_history",
68
- "last_glyph",
69
- "push_glyph",
70
- "recent_glyph",
71
- )