tnfr 4.5.1__py3-none-any.whl → 4.5.2__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 (78) hide show
  1. tnfr/__init__.py +91 -90
  2. tnfr/alias.py +546 -0
  3. tnfr/cache.py +578 -0
  4. tnfr/callback_utils.py +388 -0
  5. tnfr/cli/__init__.py +75 -0
  6. tnfr/cli/arguments.py +177 -0
  7. tnfr/cli/execution.py +288 -0
  8. tnfr/cli/utils.py +36 -0
  9. tnfr/collections_utils.py +300 -0
  10. tnfr/config.py +19 -28
  11. tnfr/constants/__init__.py +174 -0
  12. tnfr/constants/core.py +159 -0
  13. tnfr/constants/init.py +31 -0
  14. tnfr/constants/metric.py +110 -0
  15. tnfr/constants_glyphs.py +98 -0
  16. tnfr/dynamics/__init__.py +658 -0
  17. tnfr/dynamics/dnfr.py +733 -0
  18. tnfr/dynamics/integrators.py +267 -0
  19. tnfr/dynamics/sampling.py +31 -0
  20. tnfr/execution.py +201 -0
  21. tnfr/flatten.py +283 -0
  22. tnfr/gamma.py +302 -88
  23. tnfr/glyph_history.py +290 -0
  24. tnfr/grammar.py +285 -96
  25. tnfr/graph_utils.py +84 -0
  26. tnfr/helpers/__init__.py +71 -0
  27. tnfr/helpers/numeric.py +87 -0
  28. tnfr/immutable.py +178 -0
  29. tnfr/import_utils.py +228 -0
  30. tnfr/initialization.py +197 -0
  31. tnfr/io.py +246 -0
  32. tnfr/json_utils.py +162 -0
  33. tnfr/locking.py +37 -0
  34. tnfr/logging_utils.py +116 -0
  35. tnfr/metrics/__init__.py +41 -0
  36. tnfr/metrics/coherence.py +829 -0
  37. tnfr/metrics/common.py +151 -0
  38. tnfr/metrics/core.py +101 -0
  39. tnfr/metrics/diagnosis.py +234 -0
  40. tnfr/metrics/export.py +137 -0
  41. tnfr/metrics/glyph_timing.py +189 -0
  42. tnfr/metrics/reporting.py +148 -0
  43. tnfr/metrics/sense_index.py +120 -0
  44. tnfr/metrics/trig.py +181 -0
  45. tnfr/metrics/trig_cache.py +109 -0
  46. tnfr/node.py +214 -159
  47. tnfr/observers.py +126 -136
  48. tnfr/ontosim.py +134 -134
  49. tnfr/operators/__init__.py +420 -0
  50. tnfr/operators/jitter.py +203 -0
  51. tnfr/operators/remesh.py +485 -0
  52. tnfr/presets.py +46 -14
  53. tnfr/rng.py +254 -0
  54. tnfr/selector.py +210 -0
  55. tnfr/sense.py +284 -131
  56. tnfr/structural.py +207 -79
  57. tnfr/tokens.py +60 -0
  58. tnfr/trace.py +329 -94
  59. tnfr/types.py +43 -17
  60. tnfr/validators.py +70 -24
  61. tnfr/value_utils.py +59 -0
  62. tnfr-4.5.2.dist-info/METADATA +379 -0
  63. tnfr-4.5.2.dist-info/RECORD +67 -0
  64. tnfr/cli.py +0 -322
  65. tnfr/constants.py +0 -277
  66. tnfr/dynamics.py +0 -814
  67. tnfr/helpers.py +0 -264
  68. tnfr/main.py +0 -47
  69. tnfr/metrics.py +0 -597
  70. tnfr/operators.py +0 -525
  71. tnfr/program.py +0 -176
  72. tnfr/scenarios.py +0 -34
  73. tnfr-4.5.1.dist-info/METADATA +0 -221
  74. tnfr-4.5.1.dist-info/RECORD +0 -28
  75. {tnfr-4.5.1.dist-info → tnfr-4.5.2.dist-info}/WHEEL +0 -0
  76. {tnfr-4.5.1.dist-info → tnfr-4.5.2.dist-info}/entry_points.txt +0 -0
  77. {tnfr-4.5.1.dist-info → tnfr-4.5.2.dist-info}/licenses/LICENSE.md +0 -0
  78. {tnfr-4.5.1.dist-info → tnfr-4.5.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,174 @@
1
+ """Shared constants."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Callable
6
+ from collections.abc import Mapping
7
+ import copy
8
+ from types import MappingProxyType
9
+
10
+ from .core import CORE_DEFAULTS, REMESH_DEFAULTS
11
+ from .init import INIT_DEFAULTS
12
+ from .metric import (
13
+ METRIC_DEFAULTS,
14
+ SIGMA,
15
+ TRACE,
16
+ METRICS,
17
+ GRAMMAR_CANON,
18
+ COHERENCE,
19
+ DIAGNOSIS,
20
+ )
21
+
22
+ from ..immutable import _is_immutable
23
+
24
+ try: # pragma: no cover - optional dependency
25
+ from ..cache import ensure_node_offset_map
26
+ except ImportError: # noqa: BLE001 - allow any import error
27
+ ensure_node_offset_map = None
28
+
29
+ # Secciones individuales exportadas
30
+ DEFAULT_SECTIONS: Mapping[str, Mapping[str, Any]] = MappingProxyType(
31
+ {
32
+ "core": CORE_DEFAULTS,
33
+ "init": INIT_DEFAULTS,
34
+ "remesh": REMESH_DEFAULTS,
35
+ "metric": METRIC_DEFAULTS,
36
+ }
37
+ )
38
+
39
+ # Diccionario combinado exportado
40
+ # Unimos los diccionarios en orden de menor a mayor prioridad para que los
41
+ # valores de ``METRIC_DEFAULTS`` sobrescriban al resto, como hacía
42
+ # ``ChainMap``.
43
+ DEFAULTS: Mapping[str, Any] = MappingProxyType(
44
+ CORE_DEFAULTS | INIT_DEFAULTS | REMESH_DEFAULTS | METRIC_DEFAULTS
45
+ )
46
+
47
+ # -------------------------
48
+ # Utilidades
49
+ # -------------------------
50
+
51
+
52
+ def inject_defaults(
53
+ G, defaults: Mapping[str, Any] = DEFAULTS, override: bool = False
54
+ ) -> None:
55
+ """Inject ``defaults`` into ``G.graph``.
56
+
57
+ ``defaults`` is usually ``DEFAULTS``, combining all sub-dictionaries.
58
+ If ``override`` is ``True`` existing values are overwritten. Immutable
59
+ values (numbers, strings, tuples, etc.) are assigned directly. Tuples are
60
+ inspected recursively; if any element is mutable, a ``deepcopy`` is made
61
+ to avoid shared state.
62
+ """
63
+ G.graph.setdefault("_tnfr_defaults_attached", False)
64
+ for k, v in defaults.items():
65
+ if override or k not in G.graph:
66
+ G.graph[k] = v if _is_immutable(v) else copy.deepcopy(v)
67
+ G.graph["_tnfr_defaults_attached"] = True
68
+ if ensure_node_offset_map is not None:
69
+ ensure_node_offset_map(G)
70
+
71
+
72
+ def merge_overrides(G, **overrides) -> None:
73
+ """Apply specific changes to ``G.graph``.
74
+
75
+ Non-immutable values are deep-copied to avoid shared state with
76
+ :data:`DEFAULTS`.
77
+ """
78
+ for key, value in overrides.items():
79
+ if key not in DEFAULTS:
80
+ raise KeyError(f"Parámetro desconocido: '{key}'")
81
+ G.graph[key] = value if _is_immutable(value) else copy.deepcopy(value)
82
+
83
+
84
+ def get_param(G, key: str):
85
+ """Retrieve a parameter from ``G.graph`` or fall back to defaults."""
86
+ if key in G.graph:
87
+ return G.graph[key]
88
+ if key not in DEFAULTS:
89
+ raise KeyError(f"Parámetro desconocido: '{key}'")
90
+ return DEFAULTS[key]
91
+
92
+
93
+ def get_graph_param(G, key: str, cast: Callable[[Any], Any] = float):
94
+ """Return ``key`` from ``G.graph`` applying ``cast``.
95
+
96
+ The ``cast`` argument must be a function (e.g. ``float``, ``int``,
97
+ ``bool``). If the stored value is ``None`` it is returned without
98
+ casting.
99
+ """
100
+ val = get_param(G, key)
101
+ return None if val is None else cast(val)
102
+
103
+
104
+ # Claves canónicas con nombres ASCII
105
+ VF_KEY = "νf"
106
+ THETA_KEY = "θ"
107
+
108
+ # Mapa de aliases para atributos nodales
109
+ ALIASES: dict[str, tuple[str, ...]] = {
110
+ "VF": (VF_KEY, "nu_f", "nu-f", "nu", "freq", "frequency"),
111
+ "THETA": (THETA_KEY, "theta", "fase", "phi", "phase"),
112
+ "DNFR": ("ΔNFR", "delta_nfr", "dnfr"),
113
+ "EPI": ("EPI", "psi", "PSI", "value"),
114
+ "EPI_KIND": ("EPI_kind", "epi_kind", "source_glyph"),
115
+ "SI": ("Si", "sense_index", "S_i", "sense", "meaning_index"),
116
+ "DEPI": ("dEPI_dt", "dpsi_dt", "dEPI", "velocity"),
117
+ "D2EPI": ("d2EPI_dt2", "d2psi_dt2", "d2EPI", "accel"),
118
+ "DVF": ("dνf_dt", "dvf_dt", "dnu_dt", "dvf"),
119
+ "D2VF": ("d2νf_dt2", "d2vf_dt2", "d2nu_dt2", "B"),
120
+ "DSI": ("δSi", "delta_Si", "dSi"),
121
+ }
122
+
123
+
124
+ def get_aliases(key: str) -> tuple[str, ...]:
125
+ """Return alias tuple for canonical ``key``."""
126
+
127
+ return ALIASES[key]
128
+
129
+
130
+ VF_PRIMARY = get_aliases("VF")[0]
131
+ THETA_PRIMARY = get_aliases("THETA")[0]
132
+ DNFR_PRIMARY = get_aliases("DNFR")[0]
133
+ EPI_PRIMARY = get_aliases("EPI")[0]
134
+ EPI_KIND_PRIMARY = get_aliases("EPI_KIND")[0]
135
+ SI_PRIMARY = get_aliases("SI")[0]
136
+ dEPI_PRIMARY = get_aliases("DEPI")[0]
137
+ D2EPI_PRIMARY = get_aliases("D2EPI")[0]
138
+ dVF_PRIMARY = get_aliases("DVF")[0]
139
+ D2VF_PRIMARY = get_aliases("D2VF")[0]
140
+ dSI_PRIMARY = get_aliases("DSI")[0]
141
+
142
+ __all__ = (
143
+ "CORE_DEFAULTS",
144
+ "INIT_DEFAULTS",
145
+ "REMESH_DEFAULTS",
146
+ "METRIC_DEFAULTS",
147
+ "SIGMA",
148
+ "TRACE",
149
+ "METRICS",
150
+ "GRAMMAR_CANON",
151
+ "COHERENCE",
152
+ "DIAGNOSIS",
153
+ "DEFAULTS",
154
+ "DEFAULT_SECTIONS",
155
+ "ALIASES",
156
+ "inject_defaults",
157
+ "merge_overrides",
158
+ "get_param",
159
+ "get_graph_param",
160
+ "get_aliases",
161
+ "VF_KEY",
162
+ "THETA_KEY",
163
+ "VF_PRIMARY",
164
+ "THETA_PRIMARY",
165
+ "DNFR_PRIMARY",
166
+ "EPI_PRIMARY",
167
+ "EPI_KIND_PRIMARY",
168
+ "SI_PRIMARY",
169
+ "dEPI_PRIMARY",
170
+ "D2EPI_PRIMARY",
171
+ "dVF_PRIMARY",
172
+ "D2VF_PRIMARY",
173
+ "dSI_PRIMARY",
174
+ )
tnfr/constants/core.py ADDED
@@ -0,0 +1,159 @@
1
+ """Core constants."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, asdict, field
6
+ from typing import Any, Mapping
7
+ from types import MappingProxyType
8
+
9
+
10
+ SELECTOR_THRESHOLD_DEFAULTS: Mapping[str, float] = MappingProxyType(
11
+ {
12
+ "si_hi": 0.66,
13
+ "si_lo": 0.33,
14
+ "dnfr_hi": 0.50,
15
+ "dnfr_lo": 0.10,
16
+ "accel_hi": 0.50,
17
+ "accel_lo": 0.10,
18
+ }
19
+ )
20
+
21
+
22
+ @dataclass(frozen=True, slots=True)
23
+ class CoreDefaults:
24
+ """Default parameters for the core engine.
25
+
26
+ The fields are exported via :data:`CORE_DEFAULTS` and may therefore appear
27
+ unused to static analysis tools such as Vulture.
28
+ """
29
+
30
+ DT: float = 1.0
31
+ INTEGRATOR_METHOD: str = "euler"
32
+ DT_MIN: float = 0.1
33
+ EPI_MIN: float = -1.0
34
+ EPI_MAX: float = 1.0
35
+ VF_MIN: float = 0.0
36
+ VF_MAX: float = 1.0
37
+ THETA_WRAP: bool = True
38
+ DNFR_WEIGHTS: dict[str, float] = field(
39
+ default_factory=lambda: {
40
+ "phase": 0.34,
41
+ "epi": 0.33,
42
+ "vf": 0.33,
43
+ "topo": 0.0,
44
+ }
45
+ )
46
+ SI_WEIGHTS: dict[str, float] = field(
47
+ default_factory=lambda: {"alpha": 0.34, "beta": 0.33, "gamma": 0.33}
48
+ )
49
+ PHASE_K_GLOBAL: float = 0.05
50
+ PHASE_K_LOCAL: float = 0.15
51
+ PHASE_ADAPT: dict[str, Any] = field(
52
+ default_factory=lambda: {
53
+ "enabled": True,
54
+ "R_hi": 0.90,
55
+ "R_lo": 0.60,
56
+ "disr_hi": 0.50,
57
+ "disr_lo": 0.25,
58
+ "kG_min": 0.01,
59
+ "kG_max": 0.20,
60
+ "kL_min": 0.05,
61
+ "kL_max": 0.25,
62
+ "up": 0.10,
63
+ "down": 0.07,
64
+ }
65
+ )
66
+ UM_COMPAT_THRESHOLD: float = 0.75
67
+ UM_CANDIDATE_MODE: str = "sample"
68
+ UM_CANDIDATE_COUNT: int = 0
69
+ GLYPH_HYSTERESIS_WINDOW: int = 7
70
+ AL_MAX_LAG: int = 5
71
+ EN_MAX_LAG: int = 3
72
+ GLYPH_SELECTOR_MARGIN: float = 0.05
73
+ VF_ADAPT_TAU: int = 5
74
+ VF_ADAPT_MU: float = 0.1
75
+ GLYPH_FACTORS: dict[str, float] = field(
76
+ default_factory=lambda: {
77
+ "AL_boost": 0.05,
78
+ "EN_mix": 0.25,
79
+ "IL_dnfr_factor": 0.7,
80
+ "OZ_dnfr_factor": 1.3,
81
+ "UM_theta_push": 0.25,
82
+ "RA_epi_diff": 0.15,
83
+ "SHA_vf_factor": 0.85,
84
+ "VAL_scale": 1.15,
85
+ "NUL_scale": 0.85,
86
+ "THOL_accel": 0.10,
87
+ "ZHIR_theta_shift": 1.57079632679,
88
+ "NAV_jitter": 0.05,
89
+ "NAV_eta": 0.5,
90
+ "REMESH_alpha": 0.5,
91
+ }
92
+ )
93
+ GLYPH_THRESHOLDS: dict[str, float] = field(
94
+ default_factory=lambda: {"hi": 0.66, "lo": 0.33, "dnfr": 1e-3}
95
+ )
96
+ NAV_RANDOM: bool = True
97
+ NAV_STRICT: bool = False
98
+ RANDOM_SEED: int = 0
99
+ JITTER_CACHE_SIZE: int = 256
100
+ OZ_NOISE_MODE: bool = False
101
+ OZ_SIGMA: float = 0.1
102
+ GRAMMAR: dict[str, Any] = field(
103
+ default_factory=lambda: {
104
+ "window": 3,
105
+ "avoid_repeats": ["ZHIR", "OZ", "THOL"],
106
+ "force_dnfr": 0.60,
107
+ "force_accel": 0.60,
108
+ "fallbacks": {"ZHIR": "NAV", "OZ": "ZHIR", "THOL": "NAV"},
109
+ }
110
+ )
111
+ SELECTOR_WEIGHTS: dict[str, float] = field(
112
+ default_factory=lambda: {"w_si": 0.5, "w_dnfr": 0.3, "w_accel": 0.2}
113
+ )
114
+ SELECTOR_THRESHOLDS: dict[str, float] = field(
115
+ default_factory=lambda: dict(SELECTOR_THRESHOLD_DEFAULTS)
116
+ )
117
+ GAMMA: dict[str, Any] = field(
118
+ default_factory=lambda: {"type": "none", "beta": 0.0, "R0": 0.0}
119
+ )
120
+ CALLBACKS_STRICT: bool = False
121
+ VALIDATORS_STRICT: bool = False
122
+ PROGRAM_TRACE_MAXLEN: int = 50
123
+ HISTORY_MAXLEN: int = 0
124
+
125
+
126
+ @dataclass(frozen=True, slots=True)
127
+ class RemeshDefaults:
128
+ """Default parameters for the remeshing subsystem.
129
+
130
+ As with :class:`CoreDefaults`, the fields are exported via
131
+ :data:`REMESH_DEFAULTS` and may look unused to static analysers.
132
+ """
133
+
134
+ EPS_DNFR_STABLE: float = 1e-3
135
+ EPS_DEPI_STABLE: float = 1e-3
136
+ FRACTION_STABLE_REMESH: float = 0.80
137
+ REMESH_COOLDOWN_VENTANA: int = 20
138
+ REMESH_COOLDOWN_TS: float = 0.0
139
+ REMESH_REQUIRE_STABILITY: bool = True
140
+ REMESH_STABILITY_WINDOW: int = 25
141
+ REMESH_MIN_PHASE_SYNC: float = 0.85
142
+ REMESH_MAX_GLYPH_DISR: float = 0.35
143
+ REMESH_MIN_SIGMA_MAG: float = 0.50
144
+ REMESH_MIN_KURAMOTO_R: float = 0.80
145
+ REMESH_MIN_SI_HI_FRAC: float = 0.50
146
+ REMESH_LOG_EVENTS: bool = True
147
+ REMESH_MODE: str = "knn"
148
+ REMESH_COMMUNITY_K: int = 2
149
+ REMESH_TAU_GLOBAL: int = 8
150
+ REMESH_TAU_LOCAL: int = 4
151
+ REMESH_ALPHA: float = 0.5
152
+ REMESH_ALPHA_HARD: bool = False
153
+
154
+
155
+ _core_defaults = asdict(CoreDefaults())
156
+ _remesh_defaults = asdict(RemeshDefaults())
157
+
158
+ CORE_DEFAULTS = MappingProxyType(_core_defaults)
159
+ REMESH_DEFAULTS = MappingProxyType(_remesh_defaults)
tnfr/constants/init.py ADDED
@@ -0,0 +1,31 @@
1
+ """Initialization constants."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, asdict
6
+ import math
7
+
8
+
9
+ @dataclass(frozen=True, slots=True)
10
+ class InitDefaults:
11
+ """Default parameters for node initialisation.
12
+
13
+ The fields are collected into :data:`INIT_DEFAULTS` and may therefore
14
+ appear unused to tools like Vulture.
15
+ """
16
+
17
+ INIT_RANDOM_PHASE: bool = True
18
+ INIT_THETA_MIN: float = -math.pi
19
+ INIT_THETA_MAX: float = math.pi
20
+ INIT_VF_MODE: str = "uniform"
21
+ INIT_VF_MIN: float | None = None
22
+ INIT_VF_MAX: float | None = None
23
+ INIT_VF_MEAN: float = 0.5
24
+ INIT_VF_STD: float = 0.15
25
+ INIT_VF_CLAMP_TO_LIMITS: bool = True
26
+ INIT_SI_MIN: float = 0.4
27
+ INIT_SI_MAX: float = 0.7
28
+ INIT_EPI_VALUE: float = 0.0
29
+
30
+
31
+ INIT_DEFAULTS = asdict(InitDefaults())
@@ -0,0 +1,110 @@
1
+ """Metric constants."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, asdict, field
6
+ from typing import Any
7
+ from types import MappingProxyType
8
+
9
+
10
+ @dataclass(frozen=True, slots=True)
11
+ class MetricDefaults:
12
+ """Default parameters for metric computation.
13
+
14
+ The fields are gathered into :data:`METRIC_DEFAULTS` and exposed through
15
+ read-only views below, so they may appear unused to static analysis tools.
16
+ """
17
+
18
+ PHASE_HISTORY_MAXLEN: int = 50
19
+ STOP_EARLY: dict[str, Any] = field(
20
+ default_factory=lambda: {
21
+ "enabled": False,
22
+ "window": 25,
23
+ "fraction": 0.90,
24
+ }
25
+ )
26
+ SIGMA: dict[str, Any] = field(
27
+ default_factory=lambda: {
28
+ "enabled": True,
29
+ "weight": "Si", # "Si" | "EPI" | "1"
30
+ "smooth": 0.0, # EMA sobre el vector global (0=off)
31
+ "history_key": "sigma_global",
32
+ "per_node": False,
33
+ }
34
+ )
35
+ TRACE: dict[str, Any] = field(
36
+ default_factory=lambda: {
37
+ "enabled": True,
38
+ "capture": [
39
+ "gamma",
40
+ "grammar",
41
+ "selector",
42
+ "dnfr_weights",
43
+ "si_weights",
44
+ "callbacks",
45
+ "thol_open_nodes",
46
+ "sigma",
47
+ "kuramoto",
48
+ "glyph_counts",
49
+ ],
50
+ "history_key": "trace_meta",
51
+ }
52
+ )
53
+ METRICS: dict[str, Any] = field(
54
+ default_factory=lambda: {
55
+ "enabled": True,
56
+ "save_by_node": True,
57
+ "normalize_series": False,
58
+ }
59
+ )
60
+ GRAMMAR_CANON: dict[str, Any] = field(
61
+ default_factory=lambda: {
62
+ "enabled": True,
63
+ "zhir_requires_oz_window": 3,
64
+ "zhir_dnfr_min": 0.05,
65
+ "thol_min_len": 2,
66
+ "thol_max_len": 6,
67
+ "thol_close_dnfr": 0.15,
68
+ "si_high": 0.66,
69
+ }
70
+ )
71
+ COHERENCE: dict[str, Any] = field(
72
+ default_factory=lambda: {
73
+ "enabled": True,
74
+ "scope": "neighbors",
75
+ "weights": {"phase": 0.34, "epi": 0.33, "vf": 0.20, "si": 0.13},
76
+ "self_on_diag": True,
77
+ "store_mode": "sparse",
78
+ "threshold": 0.0,
79
+ "history_key": "W_sparse",
80
+ "Wi_history_key": "W_i",
81
+ "stats_history_key": "W_stats",
82
+ }
83
+ )
84
+ DIAGNOSIS: dict[str, Any] = field(
85
+ default_factory=lambda: {
86
+ "enabled": True,
87
+ "window": 16,
88
+ "history_key": "nodal_diag",
89
+ "stable": {"Rloc_hi": 0.80, "dnfr_lo": 0.20, "persist": 3},
90
+ "dissonance": {"Rloc_lo": 0.40, "dnfr_hi": 0.50, "persist": 3},
91
+ "transition": {"persist": 2},
92
+ "compute_symmetry": True,
93
+ "include_typology": False,
94
+ "advice": {
95
+ "stable": ["Coherence", "Coupling", "Resonance"],
96
+ "transition": ["Transition", "Resonance", "Self-organisation"],
97
+ "dissonant": ["Silence", "Contraction", "Mutation"],
98
+ },
99
+ }
100
+ )
101
+
102
+
103
+ METRIC_DEFAULTS = asdict(MetricDefaults())
104
+
105
+ SIGMA = MappingProxyType(METRIC_DEFAULTS["SIGMA"])
106
+ TRACE = MappingProxyType(METRIC_DEFAULTS["TRACE"])
107
+ METRICS = MappingProxyType(METRIC_DEFAULTS["METRICS"])
108
+ GRAMMAR_CANON = MappingProxyType(METRIC_DEFAULTS["GRAMMAR_CANON"])
109
+ COHERENCE = MappingProxyType(METRIC_DEFAULTS["COHERENCE"])
110
+ DIAGNOSIS = MappingProxyType(METRIC_DEFAULTS["DIAGNOSIS"])
@@ -0,0 +1,98 @@
1
+ """Default glyphs."""
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
+ # Orden canónico y clasificaciones funcionales
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
+ ESTABILIZADORES = (
34
+ Glyph.IL.value,
35
+ Glyph.RA.value,
36
+ Glyph.UM.value,
37
+ Glyph.SHA.value,
38
+ )
39
+
40
+ DISRUPTIVOS = (
41
+ Glyph.OZ.value,
42
+ Glyph.ZHIR.value,
43
+ Glyph.NAV.value,
44
+ Glyph.THOL.value,
45
+ )
46
+
47
+ # Mapa general de agrupaciones glíficas para referencia cruzada.
48
+ GLYPH_GROUPS: Mapping[str, tuple[str, ...]] = MappingProxyType(
49
+ {
50
+ "estabilizadores": ESTABILIZADORES,
51
+ "disruptivos": DISRUPTIVOS,
52
+ # Grupos auxiliares para métricas morfosintácticas
53
+ "ID": (Glyph.OZ.value,),
54
+ "CM": (Glyph.ZHIR.value, Glyph.NAV.value),
55
+ "NE": (Glyph.IL.value, Glyph.THOL.value),
56
+ "PP_num": (Glyph.SHA.value,),
57
+ "PP_den": (Glyph.REMESH.value,),
58
+ }
59
+ )
60
+
61
+ # -------------------------
62
+ # Mapa de ángulos glíficos
63
+ # -------------------------
64
+
65
+ # Ángulos canónicos para todos los glyphs reconocidos. Se calculan a partir
66
+ # del orden canónico y reglas de orientación para las categorías
67
+ # "estabilizadores" y "disruptivos".
68
+
69
+
70
+ def _build_angle_map() -> dict[str, float]:
71
+ """Construir el mapa de ángulos en el plano σ."""
72
+ step = 2 * math.pi / len(GLYPHS_CANONICAL)
73
+ canonical = {g: i * step for i, g in enumerate(GLYPHS_CANONICAL)}
74
+ angles = dict(canonical)
75
+
76
+ # Reglas específicas de orientación
77
+ for idx, g in enumerate(ESTABILIZADORES):
78
+ angles[g] = idx * math.pi / 4
79
+ for idx, g in enumerate(DISRUPTIVOS):
80
+ angles[g] = math.pi + idx * math.pi / 4
81
+
82
+ # Excepciones manuales
83
+ angles[Glyph.VAL.value] = canonical[Glyph.RA.value]
84
+ angles[Glyph.NUL.value] = canonical[Glyph.ZHIR.value]
85
+ angles[Glyph.AL.value] = 0.0
86
+ return angles
87
+
88
+
89
+ ANGLE_MAP: Mapping[str, float] = MappingProxyType(_build_angle_map())
90
+
91
+ __all__ = (
92
+ "GLYPHS_CANONICAL",
93
+ "GLYPHS_CANONICAL_SET",
94
+ "ESTABILIZADORES",
95
+ "DISRUPTIVOS",
96
+ "GLYPH_GROUPS",
97
+ "ANGLE_MAP",
98
+ )