tnfr 4.5.2__py3-none-any.whl → 6.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. tnfr/__init__.py +228 -49
  2. tnfr/__init__.pyi +40 -0
  3. tnfr/_compat.py +11 -0
  4. tnfr/_version.py +7 -0
  5. tnfr/_version.pyi +7 -0
  6. tnfr/alias.py +106 -21
  7. tnfr/alias.pyi +140 -0
  8. tnfr/cache.py +666 -512
  9. tnfr/cache.pyi +232 -0
  10. tnfr/callback_utils.py +2 -9
  11. tnfr/callback_utils.pyi +105 -0
  12. tnfr/cli/__init__.py +21 -7
  13. tnfr/cli/__init__.pyi +47 -0
  14. tnfr/cli/arguments.py +42 -20
  15. tnfr/cli/arguments.pyi +33 -0
  16. tnfr/cli/execution.py +54 -20
  17. tnfr/cli/execution.pyi +80 -0
  18. tnfr/cli/utils.py +0 -2
  19. tnfr/cli/utils.pyi +8 -0
  20. tnfr/config/__init__.py +12 -0
  21. tnfr/config/__init__.pyi +8 -0
  22. tnfr/config/constants.py +104 -0
  23. tnfr/config/constants.pyi +12 -0
  24. tnfr/{config.py → config/init.py} +11 -7
  25. tnfr/config/init.pyi +8 -0
  26. tnfr/config/operator_names.py +106 -0
  27. tnfr/config/operator_names.pyi +28 -0
  28. tnfr/config/presets.py +104 -0
  29. tnfr/config/presets.pyi +7 -0
  30. tnfr/constants/__init__.py +78 -24
  31. tnfr/constants/__init__.pyi +104 -0
  32. tnfr/constants/core.py +1 -2
  33. tnfr/constants/core.pyi +17 -0
  34. tnfr/constants/init.pyi +12 -0
  35. tnfr/constants/metric.py +4 -12
  36. tnfr/constants/metric.pyi +19 -0
  37. tnfr/constants_glyphs.py +9 -91
  38. tnfr/constants_glyphs.pyi +12 -0
  39. tnfr/dynamics/__init__.py +112 -634
  40. tnfr/dynamics/__init__.pyi +83 -0
  41. tnfr/dynamics/adaptation.py +201 -0
  42. tnfr/dynamics/aliases.py +22 -0
  43. tnfr/dynamics/coordination.py +343 -0
  44. tnfr/dynamics/dnfr.py +1936 -354
  45. tnfr/dynamics/dnfr.pyi +33 -0
  46. tnfr/dynamics/integrators.py +369 -75
  47. tnfr/dynamics/integrators.pyi +35 -0
  48. tnfr/dynamics/runtime.py +521 -0
  49. tnfr/dynamics/sampling.py +8 -5
  50. tnfr/dynamics/sampling.pyi +7 -0
  51. tnfr/dynamics/selectors.py +680 -0
  52. tnfr/execution.py +56 -41
  53. tnfr/execution.pyi +65 -0
  54. tnfr/flatten.py +7 -7
  55. tnfr/flatten.pyi +28 -0
  56. tnfr/gamma.py +54 -37
  57. tnfr/gamma.pyi +40 -0
  58. tnfr/glyph_history.py +85 -38
  59. tnfr/glyph_history.pyi +53 -0
  60. tnfr/grammar.py +19 -338
  61. tnfr/grammar.pyi +13 -0
  62. tnfr/helpers/__init__.py +110 -30
  63. tnfr/helpers/__init__.pyi +66 -0
  64. tnfr/helpers/numeric.py +1 -0
  65. tnfr/helpers/numeric.pyi +12 -0
  66. tnfr/immutable.py +55 -19
  67. tnfr/immutable.pyi +37 -0
  68. tnfr/initialization.py +12 -10
  69. tnfr/initialization.pyi +73 -0
  70. tnfr/io.py +99 -34
  71. tnfr/io.pyi +11 -0
  72. tnfr/locking.pyi +7 -0
  73. tnfr/metrics/__init__.pyi +20 -0
  74. tnfr/metrics/coherence.py +934 -294
  75. tnfr/metrics/common.py +1 -3
  76. tnfr/metrics/common.pyi +15 -0
  77. tnfr/metrics/core.py +192 -34
  78. tnfr/metrics/core.pyi +13 -0
  79. tnfr/metrics/diagnosis.py +707 -101
  80. tnfr/metrics/diagnosis.pyi +89 -0
  81. tnfr/metrics/export.py +27 -13
  82. tnfr/metrics/glyph_timing.py +218 -38
  83. tnfr/metrics/reporting.py +22 -18
  84. tnfr/metrics/reporting.pyi +12 -0
  85. tnfr/metrics/sense_index.py +199 -25
  86. tnfr/metrics/sense_index.pyi +9 -0
  87. tnfr/metrics/trig.py +53 -18
  88. tnfr/metrics/trig.pyi +12 -0
  89. tnfr/metrics/trig_cache.py +3 -7
  90. tnfr/metrics/trig_cache.pyi +10 -0
  91. tnfr/node.py +148 -125
  92. tnfr/node.pyi +161 -0
  93. tnfr/observers.py +44 -30
  94. tnfr/observers.pyi +46 -0
  95. tnfr/ontosim.py +14 -13
  96. tnfr/ontosim.pyi +33 -0
  97. tnfr/operators/__init__.py +84 -52
  98. tnfr/operators/__init__.pyi +31 -0
  99. tnfr/operators/definitions.py +181 -0
  100. tnfr/operators/definitions.pyi +92 -0
  101. tnfr/operators/jitter.py +86 -23
  102. tnfr/operators/jitter.pyi +11 -0
  103. tnfr/operators/registry.py +80 -0
  104. tnfr/operators/registry.pyi +15 -0
  105. tnfr/operators/remesh.py +141 -57
  106. tnfr/presets.py +9 -54
  107. tnfr/presets.pyi +7 -0
  108. tnfr/py.typed +0 -0
  109. tnfr/rng.py +259 -73
  110. tnfr/rng.pyi +14 -0
  111. tnfr/selector.py +24 -17
  112. tnfr/selector.pyi +19 -0
  113. tnfr/sense.py +55 -43
  114. tnfr/sense.pyi +30 -0
  115. tnfr/structural.py +44 -267
  116. tnfr/structural.pyi +46 -0
  117. tnfr/telemetry/__init__.py +13 -0
  118. tnfr/telemetry/verbosity.py +37 -0
  119. tnfr/tokens.py +3 -2
  120. tnfr/tokens.pyi +41 -0
  121. tnfr/trace.py +272 -82
  122. tnfr/trace.pyi +68 -0
  123. tnfr/types.py +345 -6
  124. tnfr/types.pyi +145 -0
  125. tnfr/utils/__init__.py +158 -0
  126. tnfr/utils/__init__.pyi +133 -0
  127. tnfr/utils/cache.py +755 -0
  128. tnfr/utils/cache.pyi +156 -0
  129. tnfr/{collections_utils.py → utils/data.py} +57 -90
  130. tnfr/utils/data.pyi +73 -0
  131. tnfr/utils/graph.py +87 -0
  132. tnfr/utils/graph.pyi +10 -0
  133. tnfr/utils/init.py +746 -0
  134. tnfr/utils/init.pyi +85 -0
  135. tnfr/{json_utils.py → utils/io.py} +13 -18
  136. tnfr/utils/io.pyi +10 -0
  137. tnfr/utils/validators.py +130 -0
  138. tnfr/utils/validators.pyi +19 -0
  139. tnfr/validation/__init__.py +25 -0
  140. tnfr/validation/__init__.pyi +17 -0
  141. tnfr/validation/compatibility.py +59 -0
  142. tnfr/validation/compatibility.pyi +8 -0
  143. tnfr/validation/grammar.py +149 -0
  144. tnfr/validation/grammar.pyi +11 -0
  145. tnfr/validation/rules.py +194 -0
  146. tnfr/validation/rules.pyi +18 -0
  147. tnfr/validation/syntax.py +151 -0
  148. tnfr/validation/syntax.pyi +7 -0
  149. tnfr-6.0.0.dist-info/METADATA +135 -0
  150. tnfr-6.0.0.dist-info/RECORD +157 -0
  151. tnfr/graph_utils.py +0 -84
  152. tnfr/import_utils.py +0 -228
  153. tnfr/logging_utils.py +0 -116
  154. tnfr/validators.py +0 -84
  155. tnfr/value_utils.py +0 -59
  156. tnfr-4.5.2.dist-info/METADATA +0 -379
  157. tnfr-4.5.2.dist-info/RECORD +0 -67
  158. {tnfr-4.5.2.dist-info → tnfr-6.0.0.dist-info}/WHEEL +0 -0
  159. {tnfr-4.5.2.dist-info → tnfr-6.0.0.dist-info}/entry_points.txt +0 -0
  160. {tnfr-4.5.2.dist-info → tnfr-6.0.0.dist-info}/licenses/LICENSE.md +0 -0
  161. {tnfr-4.5.2.dist-info → tnfr-6.0.0.dist-info}/top_level.txt +0 -0
tnfr/cli/execution.py CHANGED
@@ -2,10 +2,11 @@ from __future__ import annotations
2
2
 
3
3
  import argparse
4
4
 
5
+ from copy import deepcopy
5
6
  from pathlib import Path
6
7
  from typing import Any, Optional
7
8
 
8
- import networkx as nx # type: ignore[import-untyped]
9
+ import networkx as nx
9
10
 
10
11
  from ..constants import METRIC_DEFAULTS
11
12
  from ..sense import register_sigma_callback
@@ -17,21 +18,24 @@ from ..metrics import (
17
18
  )
18
19
  from ..metrics.core import _metrics_step
19
20
  from ..trace import register_trace
20
- from ..execution import CANONICAL_PRESET_NAME, play, seq
21
+ from ..execution import CANONICAL_PRESET_NAME, play
21
22
  from ..dynamics import (
22
23
  run,
23
24
  default_glyph_selector,
24
25
  parametric_glyph_selector,
25
26
  validate_canon,
26
27
  )
27
- from ..presets import get_preset
28
+ from ..config.presets import (
29
+ PREFERRED_PRESET_NAMES,
30
+ get_preset,
31
+ legacy_preset_guidance,
32
+ )
28
33
  from ..config import apply_config
29
34
  from ..io import read_structured_file, safe_write, StructuredFileError
30
35
  from ..glyph_history import ensure_history
31
- from ..ontosim import preparar_red
32
- from ..logging_utils import get_logger
33
- from ..types import Glyph
34
- from ..json_utils import json_dumps
36
+ from ..ontosim import prepare_network
37
+ from ..types import ProgramTokens
38
+ from ..utils import get_logger, json_dumps
35
39
  from ..flatten import parse_program_tokens
36
40
 
37
41
  from .arguments import _args_to_dict
@@ -43,6 +47,8 @@ logger = get_logger(__name__)
43
47
  # inspect the full glyphogram series when needed.
44
48
  DEFAULT_SUMMARY_SERIES_LIMIT = 10
45
49
 
50
+ _PREFERRED_PRESETS_DISPLAY = ", ".join(PREFERRED_PRESET_NAMES)
51
+
46
52
 
47
53
  def _save_json(path: str, data: Any) -> None:
48
54
  payload = json_dumps(data, ensure_ascii=False, indent=2, default=list)
@@ -133,6 +139,18 @@ def apply_cli_config(G: "nx.Graph", args: argparse.Namespace) -> None:
133
139
  "R0": args.gamma_R0,
134
140
  }
135
141
 
142
+ for attr, key in (
143
+ ("trace_verbosity", "TRACE"),
144
+ ("metrics_verbosity", "METRICS"),
145
+ ):
146
+ cfg = G.graph.get(key)
147
+ if not isinstance(cfg, dict):
148
+ cfg = deepcopy(METRIC_DEFAULTS[key])
149
+ G.graph[key] = cfg
150
+ value = getattr(args, attr, None)
151
+ if value is not None:
152
+ cfg["verbosity"] = value
153
+
136
154
 
137
155
  def register_callbacks_and_observer(G: "nx.Graph") -> None:
138
156
  _attach_callbacks(G)
@@ -144,12 +162,12 @@ def _build_graph_from_args(args: argparse.Namespace) -> "nx.Graph":
144
162
  apply_cli_config(G, args)
145
163
  if getattr(args, "observer", False):
146
164
  G.graph["ATTACH_STD_OBSERVER"] = True
147
- preparar_red(G)
165
+ prepare_network(G)
148
166
  register_callbacks_and_observer(G)
149
167
  return G
150
168
 
151
169
 
152
- def _load_sequence(path: Path) -> list[Any]:
170
+ def _load_sequence(path: Path) -> ProgramTokens:
153
171
  try:
154
172
  data = read_structured_file(path)
155
173
  except (StructuredFileError, OSError) as exc:
@@ -163,15 +181,29 @@ def _load_sequence(path: Path) -> list[Any]:
163
181
 
164
182
 
165
183
  def resolve_program(
166
- args: argparse.Namespace, default: Optional[Any] = None
167
- ) -> Optional[Any]:
184
+ args: argparse.Namespace, default: Optional[ProgramTokens] = None
185
+ ) -> Optional[ProgramTokens]:
168
186
  if getattr(args, "preset", None):
169
187
  try:
170
188
  return get_preset(args.preset)
171
189
  except KeyError as exc:
190
+ guidance = legacy_preset_guidance(args.preset)
191
+ if guidance is not None:
192
+ details = guidance
193
+ else:
194
+ details = (
195
+ exc.args[0]
196
+ if exc.args
197
+ else "Legacy preset identifier rejected."
198
+ )
172
199
  logger.error(
173
- "Preset desconocido '%s'. Usa --sequence-file para cargar secuencias personalizadas",
200
+ (
201
+ "Unknown preset '%s'. Available presets: %s. %s "
202
+ "Use --sequence-file to execute custom sequences."
203
+ ),
174
204
  args.preset,
205
+ _PREFERRED_PRESETS_DISPLAY,
206
+ details,
175
207
  )
176
208
  raise SystemExit(1) from exc
177
209
  if getattr(args, "sequence_file", None):
@@ -180,7 +212,9 @@ def resolve_program(
180
212
 
181
213
 
182
214
  def run_program(
183
- G: Optional["nx.Graph"], program: Optional[Any], args: argparse.Namespace
215
+ G: Optional["nx.Graph"],
216
+ program: Optional[ProgramTokens],
217
+ args: argparse.Namespace,
184
218
  ) -> "nx.Graph":
185
219
  if G is None:
186
220
  G = _build_graph_from_args(args)
@@ -208,7 +242,7 @@ def run_program(
208
242
  def _run_cli_program(
209
243
  args: argparse.Namespace,
210
244
  *,
211
- default_program: Optional[Any] = None,
245
+ default_program: Optional[ProgramTokens] = None,
212
246
  graph: Optional["nx.Graph"] = None,
213
247
  ) -> tuple[int, Optional["nx.Graph"]]:
214
248
  try:
@@ -229,23 +263,23 @@ def _log_run_summaries(G: "nx.Graph", args: argparse.Namespace) -> None:
229
263
  if cfg_coh.get("enabled", True):
230
264
  Wstats = hist.get(cfg_coh.get("stats_history_key", "W_stats"), [])
231
265
  if Wstats:
232
- logger.info("[COHERENCE] último paso: %s", Wstats[-1])
266
+ logger.info("[COHERENCE] last step: %s", Wstats[-1])
233
267
 
234
268
  if cfg_diag.get("enabled", True):
235
269
  last_diag = hist.get(cfg_diag.get("history_key", "nodal_diag"), [])
236
270
  if last_diag:
237
271
  sample = list(last_diag[-1].values())[:3]
238
- logger.info("[DIAGNOSIS] ejemplo: %s", sample)
272
+ logger.info("[DIAGNOSIS] sample: %s", sample)
239
273
 
240
274
  if args.summary:
241
275
  summary_limit = getattr(args, "summary_limit", DEFAULT_SUMMARY_SERIES_LIMIT)
242
276
  summary, has_latency_values = build_metrics_summary(
243
277
  G, series_limit=summary_limit
244
278
  )
245
- logger.info("Tg global: %s", summary["Tg_global"])
246
- logger.info("Top operadores por Tg: %s", glyph_top(G, k=5))
279
+ logger.info("Global Tg: %s", summary["Tg_global"])
280
+ logger.info("Top operators by Tg: %s", glyph_top(G, k=5))
247
281
  if has_latency_values:
248
- logger.info("Latencia media: %s", summary["latency_mean"])
282
+ logger.info("Average latency: %s", summary["latency_mean"])
249
283
 
250
284
 
251
285
  def cmd_run(args: argparse.Namespace) -> int:
@@ -261,7 +295,7 @@ def cmd_run(args: argparse.Namespace) -> int:
261
295
  def cmd_sequence(args: argparse.Namespace) -> int:
262
296
  if args.preset and args.sequence_file:
263
297
  logger.error(
264
- "No se puede usar --preset y --sequence-file al mismo tiempo"
298
+ "Cannot use --preset and --sequence-file at the same time"
265
299
  )
266
300
  return 1
267
301
  code, _ = _run_cli_program(
tnfr/cli/execution.pyi ADDED
@@ -0,0 +1,80 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ from pathlib import Path
5
+ from typing import Any, Optional
6
+
7
+ import networkx as nx
8
+
9
+ from ..constants import METRIC_DEFAULTS
10
+ from ..dynamics import (
11
+ run,
12
+ default_glyph_selector,
13
+ parametric_glyph_selector,
14
+ validate_canon,
15
+ )
16
+ from ..execution import CANONICAL_PRESET_NAME, play, seq
17
+ from ..flatten import parse_program_tokens
18
+ from ..glyph_history import ensure_history
19
+ from ..io import read_structured_file, safe_write, StructuredFileError
20
+ from ..metrics import (
21
+ register_metrics_callbacks,
22
+ glyph_top,
23
+ export_metrics,
24
+ build_metrics_summary,
25
+ )
26
+ from ..metrics.core import _metrics_step
27
+ from ..ontosim import prepare_network
28
+ from ..sense import register_sigma_callback
29
+ from ..trace import register_trace
30
+ from ..types import Glyph, ProgramTokens
31
+ from ..utils import get_logger, json_dumps
32
+ from ..config import apply_config
33
+ from ..config.presets import get_preset
34
+
35
+ from .arguments import _args_to_dict
36
+
37
+ DEFAULT_SUMMARY_SERIES_LIMIT: int
38
+ logger: Any
39
+
40
+
41
+ def _save_json(path: str, data: Any) -> None: ...
42
+
43
+ def _attach_callbacks(G: nx.Graph) -> None: ...
44
+
45
+ def _persist_history(G: nx.Graph, args: argparse.Namespace) -> None: ...
46
+
47
+ def build_basic_graph(args: argparse.Namespace) -> nx.Graph: ...
48
+
49
+ def apply_cli_config(G: nx.Graph, args: argparse.Namespace) -> None: ...
50
+
51
+ def register_callbacks_and_observer(G: nx.Graph) -> None: ...
52
+
53
+ def _build_graph_from_args(args: argparse.Namespace) -> nx.Graph: ...
54
+
55
+ def _load_sequence(path: Path) -> ProgramTokens: ...
56
+
57
+ def resolve_program(
58
+ args: argparse.Namespace, default: Optional[ProgramTokens] = ...
59
+ ) -> Optional[ProgramTokens]: ...
60
+
61
+ def run_program(
62
+ G: Optional[nx.Graph],
63
+ program: Optional[ProgramTokens],
64
+ args: argparse.Namespace,
65
+ ) -> nx.Graph: ...
66
+
67
+ def _run_cli_program(
68
+ args: argparse.Namespace,
69
+ *,
70
+ default_program: Optional[ProgramTokens] = ...,
71
+ graph: Optional[nx.Graph] = ...,
72
+ ) -> tuple[int, Optional[nx.Graph]]: ...
73
+
74
+ def _log_run_summaries(G: nx.Graph, args: argparse.Namespace) -> None: ...
75
+
76
+ def cmd_run(args: argparse.Namespace) -> int: ...
77
+
78
+ def cmd_sequence(args: argparse.Namespace) -> int: ...
79
+
80
+ def cmd_metrics(args: argparse.Namespace) -> int: ...
tnfr/cli/utils.py CHANGED
@@ -32,5 +32,3 @@ def spec(opt: str, /, **kwargs: Any) -> tuple[str, dict[str, Any]]:
32
32
  )
33
33
  kwargs.setdefault("default", None)
34
34
  return opt, kwargs
35
-
36
-
tnfr/cli/utils.pyi ADDED
@@ -0,0 +1,8 @@
1
+ from typing import Any
2
+
3
+ __all__: Any
4
+
5
+ def __getattr__(name: str) -> Any: ...
6
+
7
+ annotations: Any
8
+ spec: Any
@@ -0,0 +1,12 @@
1
+ """Configuration package for TNFR.
2
+
3
+ This package groups helpers and canonical defaults that orchestrate how
4
+ configuration payloads interact with the engine. The public API mirrors the
5
+ previous module level functions so downstream importers remain stable.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from .init import apply_config, load_config
11
+
12
+ __all__ = ("load_config", "apply_config")
@@ -0,0 +1,8 @@
1
+ from typing import Any
2
+
3
+ __all__: Any
4
+
5
+ def __getattr__(name: str) -> Any: ...
6
+
7
+ apply_config: Any
8
+ load_config: Any
@@ -0,0 +1,104 @@
1
+ """Canonical glyph constants tied to configuration presets."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ from types import MappingProxyType
7
+ from typing import Mapping
8
+
9
+ from ..types import Glyph
10
+
11
+ # -------------------------
12
+ # Canonical order and functional classifications
13
+ # -------------------------
14
+
15
+ GLYPHS_CANONICAL: tuple[str, ...] = (
16
+ Glyph.AL.value, # 0
17
+ Glyph.EN.value, # 1
18
+ Glyph.IL.value, # 2
19
+ Glyph.OZ.value, # 3
20
+ Glyph.UM.value, # 4
21
+ Glyph.RA.value, # 5
22
+ Glyph.SHA.value, # 6
23
+ Glyph.VAL.value, # 7
24
+ Glyph.NUL.value, # 8
25
+ Glyph.THOL.value, # 9
26
+ Glyph.ZHIR.value, # 10
27
+ Glyph.NAV.value, # 11
28
+ Glyph.REMESH.value, # 12
29
+ )
30
+
31
+ GLYPHS_CANONICAL_SET: frozenset[str] = frozenset(GLYPHS_CANONICAL)
32
+
33
+ STABILIZERS: tuple[str, ...] = (
34
+ Glyph.IL.value,
35
+ Glyph.RA.value,
36
+ Glyph.UM.value,
37
+ Glyph.SHA.value,
38
+ )
39
+
40
+ DISRUPTORS: tuple[str, ...] = (
41
+ Glyph.OZ.value,
42
+ Glyph.ZHIR.value,
43
+ Glyph.NAV.value,
44
+ Glyph.THOL.value,
45
+ )
46
+
47
+ # General map of glyph groupings for cross-reference.
48
+ #
49
+ # Spanish keys (``estabilizadores`` / ``disruptivos``) were removed in TNFR 7.0
50
+ # to keep the public surface English-only. Code that still referenced those
51
+ # identifiers must switch to the canonical ``stabilizers`` / ``disruptors``
52
+ # entries or maintain a private compatibility layer.
53
+ GLYPH_GROUPS: Mapping[str, tuple[str, ...]] = MappingProxyType(
54
+ {
55
+ "stabilizers": STABILIZERS,
56
+ "disruptors": DISRUPTORS,
57
+ # Auxiliary groups for morphosyntactic metrics
58
+ "ID": (Glyph.OZ.value,),
59
+ "CM": (Glyph.ZHIR.value, Glyph.NAV.value),
60
+ "NE": (Glyph.IL.value, Glyph.THOL.value),
61
+ "PP_num": (Glyph.SHA.value,),
62
+ "PP_den": (Glyph.REMESH.value,),
63
+ }
64
+ )
65
+
66
+ # -------------------------
67
+ # Glyph angle map
68
+ # -------------------------
69
+
70
+ # Canonical angles for all recognised glyphs. They are computed from the
71
+ # canonical order and orientation rules for the "stabilizers" and
72
+ # "disruptors" categories.
73
+
74
+
75
+ def _build_angle_map() -> dict[str, float]:
76
+ """Build the angle map in the σ-plane."""
77
+
78
+ step = 2 * math.pi / len(GLYPHS_CANONICAL)
79
+ canonical = {g: i * step for i, g in enumerate(GLYPHS_CANONICAL)}
80
+ angles = dict(canonical)
81
+
82
+ # Orientation rules
83
+ for idx, g in enumerate(STABILIZERS):
84
+ angles[g] = idx * math.pi / 4
85
+ for idx, g in enumerate(DISRUPTORS):
86
+ angles[g] = math.pi + idx * math.pi / 4
87
+
88
+ # Manual exceptions
89
+ angles[Glyph.VAL.value] = canonical[Glyph.RA.value]
90
+ angles[Glyph.NUL.value] = canonical[Glyph.ZHIR.value]
91
+ angles[Glyph.AL.value] = 0.0
92
+ return angles
93
+
94
+
95
+ ANGLE_MAP: Mapping[str, float] = MappingProxyType(_build_angle_map())
96
+
97
+ __all__ = (
98
+ "GLYPHS_CANONICAL",
99
+ "GLYPHS_CANONICAL_SET",
100
+ "STABILIZERS",
101
+ "DISRUPTORS",
102
+ "GLYPH_GROUPS",
103
+ "ANGLE_MAP",
104
+ )
@@ -0,0 +1,12 @@
1
+ from typing import Any
2
+
3
+ __all__: Any
4
+
5
+ def __getattr__(name: str) -> Any: ...
6
+
7
+ ANGLE_MAP: Any
8
+ DISRUPTORS: Any
9
+ STABILIZERS: Any
10
+ GLYPHS_CANONICAL: Any
11
+ GLYPHS_CANONICAL_SET: Any
12
+ GLYPH_GROUPS: Any
@@ -1,21 +1,23 @@
1
- """Configuration utilities."""
1
+ """Core configuration helpers."""
2
2
 
3
3
  from __future__ import annotations
4
- from typing import Any, TYPE_CHECKING
4
+
5
5
  from collections.abc import Mapping
6
6
  from pathlib import Path
7
- from .io import read_structured_file
7
+ from typing import TYPE_CHECKING, Any
8
8
 
9
- from .constants import inject_defaults
9
+ from ..constants import inject_defaults
10
+ from ..io import read_structured_file
10
11
 
11
12
  if TYPE_CHECKING: # pragma: no cover - only for type checkers
12
- import networkx as nx # type: ignore[import-untyped]
13
+ import networkx as nx
13
14
 
14
15
  __all__ = ("load_config", "apply_config")
15
16
 
16
17
 
17
18
  def load_config(path: str | Path) -> Mapping[str, Any]:
18
19
  """Read a JSON/YAML file and return a mapping with parameters."""
20
+
19
21
  path_obj = path if isinstance(path, Path) else Path(path)
20
22
  data = read_structured_file(path_obj)
21
23
  if not isinstance(data, Mapping):
@@ -23,10 +25,12 @@ def load_config(path: str | Path) -> Mapping[str, Any]:
23
25
  return data
24
26
 
25
27
 
26
- def apply_config(G: nx.Graph, path: str | Path) -> None:
28
+ def apply_config(G: "nx.Graph", path: str | Path) -> None:
27
29
  """Inject parameters from ``path`` into ``G.graph``.
28
30
 
29
- Reuses :func:`inject_defaults` to keep canonical default semantics.
31
+ Reuses :func:`tnfr.constants.inject_defaults` to keep canonical default
32
+ semantics.
30
33
  """
34
+
31
35
  cfg = load_config(path)
32
36
  inject_defaults(G, cfg, override=True)
tnfr/config/init.pyi ADDED
@@ -0,0 +1,8 @@
1
+ from typing import Any
2
+
3
+ __all__: Any
4
+
5
+ def __getattr__(name: str) -> Any: ...
6
+
7
+ apply_config: Any
8
+ load_config: Any
@@ -0,0 +1,106 @@
1
+ """Canonical operator name constants and reusable sets."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+
8
+ # Canonical operator identifiers (English tokens)
9
+ EMISSION = "emission"
10
+ RECEPTION = "reception"
11
+ COHERENCE = "coherence"
12
+ DISSONANCE = "dissonance"
13
+ COUPLING = "coupling"
14
+ RESONANCE = "resonance"
15
+ SILENCE = "silence"
16
+ EXPANSION = "expansion"
17
+ CONTRACTION = "contraction"
18
+ SELF_ORGANIZATION = "self_organization"
19
+ MUTATION = "mutation"
20
+ TRANSITION = "transition"
21
+ RECURSIVITY = "recursivity"
22
+
23
+
24
+ # Canonical collections -------------------------------------------------------
25
+
26
+ CANONICAL_OPERATOR_NAMES = frozenset(
27
+ {
28
+ EMISSION,
29
+ RECEPTION,
30
+ COHERENCE,
31
+ DISSONANCE,
32
+ COUPLING,
33
+ RESONANCE,
34
+ SILENCE,
35
+ EXPANSION,
36
+ CONTRACTION,
37
+ SELF_ORGANIZATION,
38
+ MUTATION,
39
+ TRANSITION,
40
+ RECURSIVITY,
41
+ }
42
+ )
43
+
44
+ ALL_OPERATOR_NAMES = CANONICAL_OPERATOR_NAMES
45
+ ENGLISH_OPERATOR_NAMES = CANONICAL_OPERATOR_NAMES
46
+
47
+ VALID_START_OPERATORS = frozenset({EMISSION, RECURSIVITY})
48
+ INTERMEDIATE_OPERATORS = frozenset({DISSONANCE, COUPLING, RESONANCE})
49
+ VALID_END_OPERATORS = frozenset({SILENCE, TRANSITION, RECURSIVITY})
50
+ SELF_ORGANIZATION_CLOSURES = frozenset({SILENCE, CONTRACTION})
51
+
52
+
53
+ _LEGACY_COLLECTION_ALIASES: dict[str, str] = {
54
+ "INICIO_VALIDOS": "VALID_START_OPERATORS",
55
+ "TRAMO_INTERMEDIO": "INTERMEDIATE_OPERATORS",
56
+ "CIERRE_VALIDO": "VALID_END_OPERATORS",
57
+ "AUTOORGANIZACION_CIERRES": "SELF_ORGANIZATION_CLOSURES",
58
+ }
59
+
60
+ def canonical_operator_name(name: str) -> str:
61
+ """Return the canonical operator token for ``name``."""
62
+
63
+ return name
64
+
65
+
66
+ def operator_display_name(name: str) -> str:
67
+ """Return the display label for ``name`` (currently the canonical token)."""
68
+
69
+ return canonical_operator_name(name)
70
+
71
+
72
+ __all__ = [
73
+ "EMISSION",
74
+ "RECEPTION",
75
+ "COHERENCE",
76
+ "DISSONANCE",
77
+ "COUPLING",
78
+ "RESONANCE",
79
+ "SILENCE",
80
+ "EXPANSION",
81
+ "CONTRACTION",
82
+ "SELF_ORGANIZATION",
83
+ "MUTATION",
84
+ "TRANSITION",
85
+ "RECURSIVITY",
86
+ "CANONICAL_OPERATOR_NAMES",
87
+ "ENGLISH_OPERATOR_NAMES",
88
+ "ALL_OPERATOR_NAMES",
89
+ "VALID_START_OPERATORS",
90
+ "INTERMEDIATE_OPERATORS",
91
+ "VALID_END_OPERATORS",
92
+ "SELF_ORGANIZATION_CLOSURES",
93
+ "canonical_operator_name",
94
+ "operator_display_name",
95
+ ]
96
+
97
+
98
+ def __getattr__(name: str) -> Any:
99
+ """Provide guidance for legacy operator collection aliases."""
100
+
101
+ canonical = _LEGACY_COLLECTION_ALIASES.get(name)
102
+ if canonical is not None:
103
+ raise AttributeError(
104
+ f"module '{__name__}' has no attribute '{name}'; use '{canonical}' instead."
105
+ )
106
+ raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
@@ -0,0 +1,28 @@
1
+ from typing import Any
2
+
3
+ __all__: Any
4
+
5
+ def __getattr__(name: str) -> Any: ...
6
+
7
+ ALL_OPERATOR_NAMES: Any
8
+ CANONICAL_OPERATOR_NAMES: Any
9
+ COHERENCE: Any
10
+ CONTRACTION: Any
11
+ COUPLING: Any
12
+ DISSONANCE: Any
13
+ EMISSION: Any
14
+ ENGLISH_OPERATOR_NAMES: Any
15
+ EXPANSION: Any
16
+ INTERMEDIATE_OPERATORS: Any
17
+ MUTATION: Any
18
+ RECEPTION: Any
19
+ RECURSIVITY: Any
20
+ RESONANCE: Any
21
+ SELF_ORGANIZATION: Any
22
+ SELF_ORGANIZATION_CLOSURES: Any
23
+ SILENCE: Any
24
+ TRANSITION: Any
25
+ VALID_END_OPERATORS: Any
26
+ VALID_START_OPERATORS: Any
27
+ canonical_operator_name: Any
28
+ operator_display_name: Any