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/import_utils.py DELETED
@@ -1,228 +0,0 @@
1
- """Helpers for optional imports and cached access to heavy modules.
2
-
3
- This module centralises caching for optional dependencies. It exposes
4
- :func:`cached_import`, backed by a small :func:`functools.lru_cache`, alongside a
5
- light-weight registry that tracks failed imports and warnings. Use
6
- :func:`prune_failed_imports` or ``cached_import.cache_clear`` to reset state when
7
- new packages become available at runtime.
8
- """
9
-
10
- from __future__ import annotations
11
-
12
- import importlib
13
- import warnings
14
- from collections import OrderedDict
15
- from dataclasses import dataclass, field
16
- from functools import lru_cache
17
- from typing import Any, Callable, Literal
18
- import threading
19
-
20
- from .logging_utils import get_logger
21
-
22
- __all__ = (
23
- "cached_import",
24
- "get_numpy",
25
- "get_nodonx",
26
- "prune_failed_imports",
27
- "IMPORT_LOG",
28
- )
29
-
30
-
31
- logger = get_logger(__name__)
32
-
33
-
34
- def _emit(message: str, mode: Literal["warn", "log", "both"]) -> None:
35
- """Emit ``message`` via :mod:`warnings`, logger or both."""
36
-
37
- if mode in ("warn", "both"):
38
- warnings.warn(message, RuntimeWarning, stacklevel=2)
39
- if mode in ("log", "both"):
40
- logger.warning(message)
41
-
42
-
43
- EMIT_MAP: dict[str, Callable[[str], None]] = {
44
- "warn": lambda msg: _emit(msg, "warn"),
45
- "log": lambda msg: _emit(msg, "log"),
46
- "both": lambda msg: _emit(msg, "both"),
47
- }
48
-
49
-
50
- def _format_failure_message(module: str, attr: str | None, err: Exception) -> str:
51
- """Return a standardised failure message."""
52
-
53
- return (
54
- f"Failed to import module '{module}': {err}"
55
- if isinstance(err, ImportError)
56
- else f"Module '{module}' has no attribute '{attr}': {err}"
57
- )
58
-
59
-
60
- _FAILED_IMPORT_LIMIT = 128
61
- _DEFAULT_CACHE_SIZE = 128
62
-
63
-
64
- @dataclass(slots=True)
65
- class ImportRegistry:
66
- """Process-wide registry tracking failed imports and emitted warnings."""
67
-
68
- limit: int = _FAILED_IMPORT_LIMIT
69
- failed: OrderedDict[str, None] = field(default_factory=OrderedDict)
70
- warned: set[str] = field(default_factory=set)
71
- lock: threading.Lock = field(default_factory=threading.Lock)
72
-
73
- def _insert(self, key: str) -> None:
74
- self.failed[key] = None
75
- self.failed.move_to_end(key)
76
- while len(self.failed) > self.limit:
77
- self.failed.popitem(last=False)
78
-
79
- def record_failure(self, key: str, *, module: str | None = None) -> None:
80
- """Record ``key`` and, optionally, ``module`` as failed imports."""
81
-
82
- with self.lock:
83
- self._insert(key)
84
- if module and module != key:
85
- self._insert(module)
86
-
87
- def discard(self, key: str) -> None:
88
- """Remove ``key`` from the registry and clear its warning state."""
89
-
90
- with self.lock:
91
- self.failed.pop(key, None)
92
- self.warned.discard(key)
93
-
94
- def mark_warning(self, module: str) -> bool:
95
- """Mark ``module`` as warned and return ``True`` if it was new."""
96
-
97
- with self.lock:
98
- if module in self.warned:
99
- return False
100
- self.warned.add(module)
101
- return True
102
-
103
- def clear(self) -> None:
104
- """Remove all failure records and warning markers."""
105
-
106
- with self.lock:
107
- self.failed.clear()
108
- self.warned.clear()
109
-
110
- def __contains__(self, key: str) -> bool: # pragma: no cover - trivial
111
- with self.lock:
112
- return key in self.failed
113
-
114
-
115
- _IMPORT_STATE = ImportRegistry()
116
- # Public alias to ease direct introspection in tests and diagnostics.
117
- IMPORT_LOG = _IMPORT_STATE
118
-
119
-
120
- @lru_cache(maxsize=_DEFAULT_CACHE_SIZE)
121
- def _import_cached(module_name: str, attr: str | None) -> tuple[bool, Any]:
122
- """Import ``module_name`` (and optional ``attr``) capturing failures."""
123
-
124
- try:
125
- module = importlib.import_module(module_name)
126
- obj = getattr(module, attr) if attr else module
127
- except (ImportError, AttributeError) as exc:
128
- return False, exc
129
- return True, obj
130
-
131
-
132
- def _warn_failure(
133
- module: str,
134
- attr: str | None,
135
- err: Exception,
136
- *,
137
- emit: Literal["warn", "log", "both"] = "warn",
138
- ) -> None:
139
- """Emit a warning about a failed import."""
140
-
141
- msg = _format_failure_message(module, attr, err)
142
- if _IMPORT_STATE.mark_warning(module):
143
- EMIT_MAP[emit](msg)
144
- else:
145
- logger.debug(msg)
146
-
147
-
148
- def cached_import(
149
- module_name: str,
150
- attr: str | None = None,
151
- *,
152
- fallback: Any | None = None,
153
- emit: Literal["warn", "log", "both"] = "warn",
154
- ) -> Any | None:
155
- """Import ``module_name`` (and optional ``attr``) with caching and fallback.
156
-
157
- Parameters
158
- ----------
159
- module_name:
160
- Module to import.
161
- attr:
162
- Optional attribute to fetch from the module.
163
- fallback:
164
- Value returned when the import fails.
165
- emit:
166
- Destination for warnings emitted on failure (``"warn"``/``"log"``/``"both"``).
167
- """
168
-
169
- key = module_name if attr is None else f"{module_name}.{attr}"
170
- success, result = _import_cached(module_name, attr)
171
- if success:
172
- _IMPORT_STATE.discard(key)
173
- if attr is not None:
174
- _IMPORT_STATE.discard(module_name)
175
- return result
176
- exc = result
177
- include_module = isinstance(exc, ImportError)
178
- _warn_failure(module_name, attr, exc, emit=emit)
179
- _IMPORT_STATE.record_failure(key, module=module_name if include_module else None)
180
- return fallback
181
-
182
-
183
- def _clear_default_cache() -> None:
184
- global _NP_MISSING_LOGGED
185
-
186
- _import_cached.cache_clear()
187
- _NP_MISSING_LOGGED = False
188
-
189
-
190
- cached_import.cache_clear = _clear_default_cache # type: ignore[attr-defined]
191
-
192
-
193
- _NP_MISSING_LOGGED = False
194
-
195
-
196
- def get_numpy() -> Any | None:
197
- """Return the cached :mod:`numpy` module when available.
198
-
199
- Import attempts are delegated to :func:`cached_import`, which already caches
200
- successes and failures. A lightweight flag suppresses duplicate debug logs
201
- when :mod:`numpy` is unavailable so callers can repeatedly probe without
202
- spamming the logger.
203
- """
204
-
205
- global _NP_MISSING_LOGGED
206
-
207
- np = cached_import("numpy")
208
- if np is None:
209
- if not _NP_MISSING_LOGGED:
210
- logger.debug("Failed to import numpy; continuing in non-vectorised mode")
211
- _NP_MISSING_LOGGED = True
212
- return None
213
-
214
- if _NP_MISSING_LOGGED:
215
- _NP_MISSING_LOGGED = False
216
- return np
217
-
218
-
219
- def get_nodonx() -> type | None:
220
- """Return :class:`tnfr.node.NodoNX` using import caching."""
221
-
222
- return cached_import("tnfr.node", "NodoNX")
223
-
224
-
225
- def prune_failed_imports() -> None:
226
- """Clear the registry of recorded import failures and warnings."""
227
-
228
- _IMPORT_STATE.clear()
tnfr/json_utils.py DELETED
@@ -1,162 +0,0 @@
1
- """JSON helpers with optional :mod:`orjson` support.
2
-
3
- This module lazily imports :mod:`orjson` on first use of :func:`json_dumps`.
4
- The fast serializer is brought in through
5
- ``tnfr.import_utils.cached_import``; its cache and failure registry can be
6
- reset using ``cached_import.cache_clear()`` and
7
- :func:`tnfr.import_utils.prune_failed_imports`.
8
- """
9
-
10
- from __future__ import annotations
11
-
12
- from dataclasses import dataclass
13
- import json
14
- from typing import Any, Callable
15
-
16
- from .import_utils import cached_import
17
- from .logging_utils import get_logger, warn_once
18
-
19
- _ORJSON_PARAMS_MSG = (
20
- "'ensure_ascii', 'separators', 'cls' and extra kwargs are ignored when using orjson: %s"
21
- )
22
-
23
- logger = get_logger(__name__)
24
-
25
- _warn_ignored_params_once = warn_once(logger, _ORJSON_PARAMS_MSG)
26
-
27
-
28
- def clear_orjson_param_warnings() -> None:
29
- """Reset cached warnings for ignored :mod:`orjson` parameters."""
30
-
31
- _warn_ignored_params_once.clear()
32
-
33
-
34
- def _format_ignored_params(combo: frozenset[str]) -> str:
35
- """Return a stable representation for ignored parameter combinations."""
36
- return "{" + ", ".join(map(repr, sorted(combo))) + "}"
37
-
38
-
39
- @dataclass(frozen=True)
40
- class JsonDumpsParams:
41
- """Container describing the parameters used by :func:`json_dumps`."""
42
-
43
- sort_keys: bool = False
44
- default: Callable[[Any], Any] | None = None
45
- ensure_ascii: bool = True
46
- separators: tuple[str, str] = (",", ":")
47
- cls: type[json.JSONEncoder] | None = None
48
- to_bytes: bool = False
49
-
50
-
51
- DEFAULT_PARAMS = JsonDumpsParams()
52
-
53
-
54
- def _collect_ignored_params(
55
- params: JsonDumpsParams, extra_kwargs: dict[str, Any]
56
- ) -> frozenset[str]:
57
- """Return a stable set of parameters ignored by :mod:`orjson`."""
58
-
59
- ignored: set[str] = set()
60
- if params.ensure_ascii is not True:
61
- ignored.add("ensure_ascii")
62
- if params.separators != (",", ":"):
63
- ignored.add("separators")
64
- if params.cls is not None:
65
- ignored.add("cls")
66
- if extra_kwargs:
67
- ignored.update(extra_kwargs.keys())
68
- return frozenset(ignored)
69
-
70
-
71
- def _json_dumps_orjson(
72
- orjson: Any,
73
- obj: Any,
74
- params: JsonDumpsParams,
75
- **kwargs: Any,
76
- ) -> bytes | str:
77
- """Serialize using :mod:`orjson` and warn about unsupported parameters."""
78
-
79
- ignored = _collect_ignored_params(params, kwargs)
80
- if ignored:
81
- _warn_ignored_params_once(ignored, _format_ignored_params(ignored))
82
-
83
- option = orjson.OPT_SORT_KEYS if params.sort_keys else 0
84
- data = orjson.dumps(obj, option=option, default=params.default)
85
- return data if params.to_bytes else data.decode("utf-8")
86
-
87
-
88
- def _json_dumps_std(
89
- obj: Any,
90
- params: JsonDumpsParams,
91
- **kwargs: Any,
92
- ) -> bytes | str:
93
- """Serialize using the standard library :func:`json.dumps`."""
94
- result = json.dumps(
95
- obj,
96
- sort_keys=params.sort_keys,
97
- ensure_ascii=params.ensure_ascii,
98
- separators=params.separators,
99
- cls=params.cls,
100
- default=params.default,
101
- **kwargs,
102
- )
103
- return result if not params.to_bytes else result.encode("utf-8")
104
-
105
-
106
- def json_dumps(
107
- obj: Any,
108
- *,
109
- sort_keys: bool = False,
110
- default: Callable[[Any], Any] | None = None,
111
- ensure_ascii: bool = True,
112
- separators: tuple[str, str] = (",", ":"),
113
- cls: type[json.JSONEncoder] | None = None,
114
- to_bytes: bool = False,
115
- **kwargs: Any,
116
- ) -> bytes | str:
117
- """Serialize ``obj`` to JSON using ``orjson`` when available.
118
-
119
- Returns a ``str`` by default. Pass ``to_bytes=True`` to obtain a ``bytes``
120
- result. When :mod:`orjson` is used, the ``ensure_ascii``, ``separators``,
121
- ``cls`` and any additional keyword arguments are ignored because they are
122
- not supported by :func:`orjson.dumps`. A warning is emitted whenever such
123
- ignored parameters are detected.
124
- """
125
- if not isinstance(sort_keys, bool):
126
- raise TypeError("sort_keys must be a boolean")
127
- if default is not None and not callable(default):
128
- raise TypeError("default must be callable when provided")
129
- if not isinstance(ensure_ascii, bool):
130
- raise TypeError("ensure_ascii must be a boolean")
131
- if not isinstance(separators, tuple) or len(separators) != 2:
132
- raise TypeError("separators must be a tuple of two strings")
133
- if not all(isinstance(part, str) for part in separators):
134
- raise TypeError("separators must be a tuple of two strings")
135
- if cls is not None:
136
- if not isinstance(cls, type) or not issubclass(cls, json.JSONEncoder):
137
- raise TypeError("cls must be a subclass of json.JSONEncoder")
138
- if not isinstance(to_bytes, bool):
139
- raise TypeError("to_bytes must be a boolean")
140
-
141
- if (
142
- sort_keys is False
143
- and default is None
144
- and ensure_ascii is True
145
- and separators == (",", ":")
146
- and cls is None
147
- and to_bytes is False
148
- ):
149
- params = DEFAULT_PARAMS
150
- else:
151
- params = JsonDumpsParams(
152
- sort_keys=sort_keys,
153
- default=default,
154
- ensure_ascii=ensure_ascii,
155
- separators=separators,
156
- cls=cls,
157
- to_bytes=to_bytes,
158
- )
159
- orjson = cached_import("orjson", emit="log")
160
- if orjson is not None:
161
- return _json_dumps_orjson(orjson, obj, params, **kwargs)
162
- return _json_dumps_std(obj, params, **kwargs)
tnfr/logging_utils.py DELETED
@@ -1,116 +0,0 @@
1
- """Logging utilities for TNFR.
2
-
3
- Centralises creation of module-specific loggers so that all TNFR
4
- modules share a consistent configuration.
5
- """
6
-
7
- from __future__ import annotations
8
-
9
- import logging
10
- import threading
11
- from typing import Any, Hashable, Mapping
12
-
13
- __all__ = ("_configure_root", "get_logger", "WarnOnce", "warn_once")
14
-
15
- _LOGGING_CONFIGURED = False
16
-
17
-
18
- def _configure_root() -> None:
19
- """Ensure the root logger has handlers and a default format."""
20
-
21
- global _LOGGING_CONFIGURED
22
- if _LOGGING_CONFIGURED:
23
- return
24
-
25
- root = logging.getLogger()
26
- if not root.handlers:
27
- kwargs = {"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"}
28
- if root.level == logging.NOTSET:
29
- kwargs["level"] = logging.INFO
30
- logging.basicConfig(**kwargs)
31
-
32
- _LOGGING_CONFIGURED = True
33
-
34
-
35
- def get_logger(name: str) -> logging.Logger:
36
- """Return a module-specific logger."""
37
- _configure_root()
38
- return logging.getLogger(name)
39
-
40
-
41
- class WarnOnce:
42
- """Log a warning only once for each unique key.
43
-
44
- ``WarnOnce`` tracks seen keys in a bounded :class:`set`. When ``maxsize`` is
45
- reached an arbitrary key is evicted to keep memory usage stable; ordered
46
- eviction is intentionally avoided to keep the implementation lightweight.
47
- Instances are callable and accept either a mapping of keys to values or a
48
- single key/value pair. Passing ``maxsize <= 0`` disables caching and logs on
49
- every invocation.
50
- """
51
-
52
- def __init__(self, logger: logging.Logger, msg: str, *, maxsize: int = 1024) -> None:
53
- self._logger = logger
54
- self._msg = msg
55
- self._maxsize = maxsize
56
- self._seen: set[Hashable] = set()
57
- self._lock = threading.Lock()
58
-
59
- def _mark_seen(self, key: Hashable) -> bool:
60
- """Return ``True`` when ``key`` has not been seen before."""
61
-
62
- if self._maxsize <= 0:
63
- # Caching disabled – always log.
64
- return True
65
- if key in self._seen:
66
- return False
67
- if len(self._seen) >= self._maxsize:
68
- # ``set.pop()`` removes an arbitrary element which is acceptable for
69
- # this lightweight cache.
70
- self._seen.pop()
71
- self._seen.add(key)
72
- return True
73
-
74
- def __call__(
75
- self,
76
- data: Mapping[Hashable, Any] | Hashable,
77
- value: Any | None = None,
78
- ) -> None:
79
- """Log new keys found in ``data``.
80
-
81
- ``data`` may be a mapping of keys to payloads or a single key. When
82
- called with a single key ``value`` customises the payload passed to the
83
- logging message; the key itself is used when ``value`` is omitted.
84
- """
85
-
86
- if isinstance(data, Mapping):
87
- new_items: dict[Hashable, Any] = {}
88
- with self._lock:
89
- for key, item_value in data.items():
90
- if self._mark_seen(key):
91
- new_items[key] = item_value
92
- if new_items:
93
- self._logger.warning(self._msg, new_items)
94
- return
95
-
96
- key = data
97
- payload = value if value is not None else data
98
- with self._lock:
99
- should_log = self._mark_seen(key)
100
- if should_log:
101
- self._logger.warning(self._msg, payload)
102
-
103
- def clear(self) -> None:
104
- """Reset tracked keys."""
105
- with self._lock:
106
- self._seen.clear()
107
-
108
-
109
- def warn_once(
110
- logger: logging.Logger,
111
- msg: str,
112
- *,
113
- maxsize: int = 1024,
114
- ) -> WarnOnce:
115
- """Return a :class:`WarnOnce` logger."""
116
- return WarnOnce(logger, msg, maxsize=maxsize)
tnfr/presets.py DELETED
@@ -1,60 +0,0 @@
1
- """Predefined configurations."""
2
-
3
- from __future__ import annotations
4
- from .execution import (
5
- CANONICAL_PRESET_NAME,
6
- CANONICAL_PROGRAM_TOKENS,
7
- block,
8
- seq,
9
- wait,
10
- )
11
- from .types import Glyph
12
-
13
- __all__ = ("get_preset",)
14
-
15
-
16
- _PRESETS = {
17
- "arranque_resonante": seq(
18
- Glyph.AL,
19
- Glyph.EN,
20
- Glyph.IL,
21
- Glyph.RA,
22
- Glyph.VAL,
23
- Glyph.UM,
24
- wait(3),
25
- Glyph.SHA,
26
- ),
27
- "mutacion_contenida": seq(
28
- Glyph.AL,
29
- Glyph.EN,
30
- block(Glyph.OZ, Glyph.ZHIR, Glyph.IL, repeat=2),
31
- Glyph.RA,
32
- Glyph.SHA,
33
- ),
34
- "exploracion_acople": seq(
35
- Glyph.AL,
36
- Glyph.EN,
37
- Glyph.IL,
38
- Glyph.VAL,
39
- Glyph.UM,
40
- block(Glyph.OZ, Glyph.NAV, Glyph.IL, repeat=1),
41
- Glyph.RA,
42
- Glyph.SHA,
43
- ),
44
- CANONICAL_PRESET_NAME: list(CANONICAL_PROGRAM_TOKENS),
45
- # Topologías fractales: expansión/contracción modular
46
- "fractal_expand": seq(
47
- block(Glyph.THOL, Glyph.VAL, Glyph.UM, repeat=2, close=Glyph.NUL),
48
- Glyph.RA,
49
- ),
50
- "fractal_contract": seq(
51
- block(Glyph.THOL, Glyph.NUL, Glyph.UM, repeat=2, close=Glyph.SHA),
52
- Glyph.RA,
53
- ),
54
- }
55
-
56
-
57
- def get_preset(name: str):
58
- if name not in _PRESETS:
59
- raise KeyError(f"Preset no encontrado: {name}")
60
- return _PRESETS[name]
tnfr/validators.py DELETED
@@ -1,84 +0,0 @@
1
- """Validation utilities."""
2
-
3
- from __future__ import annotations
4
-
5
- import numbers
6
- import sys
7
-
8
- from .constants import get_aliases, get_param
9
- from .alias import get_attr
10
- from .sense import sigma_vector_from_graph
11
- from .helpers.numeric import within_range
12
- from .constants_glyphs import GLYPHS_CANONICAL_SET
13
-
14
- ALIAS_EPI = get_aliases("EPI")
15
- ALIAS_VF = get_aliases("VF")
16
-
17
- __all__ = ("validate_window", "run_validators")
18
-
19
-
20
- def validate_window(window: int, *, positive: bool = False) -> int:
21
- """Validate ``window`` as an ``int`` and return it.
22
-
23
- Non-integer values raise :class:`TypeError`. When ``positive`` is ``True``
24
- the value must be strictly greater than zero; otherwise it may be zero.
25
- Negative values always raise :class:`ValueError`.
26
- """
27
-
28
- if isinstance(window, bool) or not isinstance(window, numbers.Integral):
29
- raise TypeError("'window' must be an integer")
30
- if window < 0 or (positive and window == 0):
31
- kind = "positive" if positive else "non-negative"
32
- raise ValueError(f"'window'={window} must be {kind}")
33
- return int(window)
34
-
35
-
36
- def _require_attr(data, alias, node, name):
37
- """Return attribute value or raise if missing."""
38
- val = get_attr(data, alias, None)
39
- if val is None:
40
- raise ValueError(f"Missing {name} attribute in node {node}")
41
- return val
42
-
43
-
44
- def _validate_sigma(G) -> None:
45
- sv = sigma_vector_from_graph(G)
46
- if sv.get("mag", 0.0) > 1.0 + sys.float_info.epsilon:
47
- raise ValueError("σ norm exceeds 1")
48
-
49
-
50
- def _check_epi_vf(epi, vf, epi_min, epi_max, vf_min, vf_max, n):
51
- _check_range(epi, epi_min, epi_max, "EPI", n)
52
- _check_range(vf, vf_min, vf_max, "VF", n)
53
-
54
-
55
- def _out_of_range_msg(name, node, val):
56
- return f"{name} out of range in node {node}: {val}"
57
-
58
-
59
- def _check_range(val, lower, upper, name, node, tol: float = 1e-9):
60
- if not within_range(val, lower, upper, tol):
61
- raise ValueError(_out_of_range_msg(name, node, val))
62
-
63
-
64
- def _check_glyph(g, n):
65
- if g and g not in GLYPHS_CANONICAL_SET:
66
- raise KeyError(f"Invalid glyph {g} in node {n}")
67
-
68
-
69
- def run_validators(G) -> None:
70
- """Run all invariant validators on ``G`` with a single node pass."""
71
- from .glyph_history import last_glyph
72
-
73
- epi_min = float(get_param(G, "EPI_MIN"))
74
- epi_max = float(get_param(G, "EPI_MAX"))
75
- vf_min = float(get_param(G, "VF_MIN"))
76
- vf_max = float(get_param(G, "VF_MAX"))
77
-
78
- for n, data in G.nodes(data=True):
79
- epi = _require_attr(data, ALIAS_EPI, n, "EPI")
80
- vf = _require_attr(data, ALIAS_VF, n, "VF")
81
- _check_epi_vf(epi, vf, epi_min, epi_max, vf_min, vf_max, n)
82
- _check_glyph(last_glyph(data), n)
83
-
84
- _validate_sigma(G)
tnfr/value_utils.py DELETED
@@ -1,59 +0,0 @@
1
- """Conversion helpers with logging for value normalisation.
2
-
3
- Wraps conversion callables to standardise error handling and logging.
4
- """
5
-
6
- from __future__ import annotations
7
-
8
- from typing import Any, Callable, TypeVar
9
- import logging
10
- from .logging_utils import get_logger
11
-
12
- T = TypeVar("T")
13
-
14
- logger = get_logger(__name__)
15
-
16
- __all__ = ("convert_value",)
17
-
18
-
19
- def convert_value(
20
- value: Any,
21
- conv: Callable[[Any], T],
22
- *,
23
- strict: bool = False,
24
- key: str | None = None,
25
- log_level: int | None = None,
26
- ) -> tuple[bool, T | None]:
27
- """Attempt to convert a value and report failures.
28
-
29
- Parameters
30
- ----------
31
- value : Any
32
- Input value to convert.
33
- conv : Callable[[Any], T]
34
- Callable performing the conversion.
35
- strict : bool, optional
36
- Raise exceptions directly instead of logging them. Defaults to ``False``.
37
- key : str, optional
38
- Name associated with the value for logging context.
39
- log_level : int, optional
40
- Logging level used when reporting failures. Defaults to
41
- ``logging.DEBUG``.
42
-
43
- Returns
44
- -------
45
- tuple[bool, T | None]
46
- ``(True, result)`` on success or ``(False, None)`` when conversion
47
- fails.
48
- """
49
- try:
50
- return True, conv(value)
51
- except (ValueError, TypeError) as exc:
52
- if strict:
53
- raise
54
- level = log_level if log_level is not None else logging.DEBUG
55
- if key is not None:
56
- logger.log(level, "Could not convert value for %r: %s", key, exc)
57
- else:
58
- logger.log(level, "Could not convert value: %s", exc)
59
- return False, None