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/helpers/__init__.py CHANGED
@@ -7,19 +7,29 @@ cache invalidation.
7
7
 
8
8
  from __future__ import annotations
9
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
10
+ from collections import Counter
11
+ from typing import TYPE_CHECKING, Any, Callable, Mapping, MutableMapping, Protocol, cast
12
+
13
+ from ..types import TNFRGraph
14
+
15
+ if TYPE_CHECKING: # pragma: no cover - import-time only for typing
16
+ from ..utils import (
17
+ CacheManager,
18
+ EdgeCacheManager,
19
+ cached_node_list,
20
+ cached_nodes_and_A,
21
+ edge_version_cache,
22
+ edge_version_update,
23
+ ensure_node_index_map,
24
+ ensure_node_offset_map,
25
+ get_graph,
26
+ get_graph_mapping,
27
+ increment_edge_version,
28
+ mark_dnfr_prep_dirty,
29
+ node_set_checksum,
30
+ stable_json,
31
+ )
32
+ from ..glyph_history import HistoryDict
23
33
  from .numeric import (
24
34
  angle_diff,
25
35
  clamp,
@@ -27,17 +37,8 @@ from .numeric import (
27
37
  kahan_sum_nd,
28
38
  )
29
39
 
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
40
  __all__ = (
41
+ "CacheManager",
41
42
  "EdgeCacheManager",
42
43
  "angle_diff",
43
44
  "cached_node_list",
@@ -60,12 +61,91 @@ __all__ = (
60
61
  "last_glyph",
61
62
  "push_glyph",
62
63
  "recent_glyph",
64
+ "__getattr__",
63
65
  )
64
66
 
65
- _GLYPH_HISTORY_EXPORTS = (
66
- "count_glyphs",
67
- "ensure_history",
68
- "last_glyph",
69
- "push_glyph",
70
- "recent_glyph",
71
- )
67
+
68
+ _UTIL_EXPORTS = {
69
+ "CacheManager",
70
+ "EdgeCacheManager",
71
+ "cached_node_list",
72
+ "cached_nodes_and_A",
73
+ "edge_version_cache",
74
+ "edge_version_update",
75
+ "ensure_node_index_map",
76
+ "ensure_node_offset_map",
77
+ "get_graph",
78
+ "get_graph_mapping",
79
+ "increment_edge_version",
80
+ "mark_dnfr_prep_dirty",
81
+ "node_set_checksum",
82
+ "stable_json",
83
+ }
84
+
85
+
86
+ def __getattr__(name: str) -> Any: # pragma: no cover - simple delegation
87
+ if name in _UTIL_EXPORTS:
88
+ from .. import utils as _utils
89
+
90
+ value = getattr(_utils, name)
91
+ globals()[name] = value
92
+ return value
93
+ raise AttributeError(name)
94
+
95
+
96
+ def __dir__() -> list[str]: # pragma: no cover - simple reflection
97
+ return sorted(set(__all__))
98
+
99
+
100
+ class _PushGlyphCallable(Protocol):
101
+ def __call__(self, nd: MutableMapping[str, Any], glyph: str, window: int) -> None:
102
+ ...
103
+
104
+
105
+ class _RecentGlyphCallable(Protocol):
106
+ def __call__(self, nd: MutableMapping[str, Any], glyph: str, window: int) -> bool:
107
+ ...
108
+
109
+
110
+ class _EnsureHistoryCallable(Protocol):
111
+ def __call__(self, G: TNFRGraph) -> "HistoryDict | dict[str, Any]":
112
+ ...
113
+
114
+
115
+ class _LastGlyphCallable(Protocol):
116
+ def __call__(self, nd: Mapping[str, Any]) -> str | None:
117
+ ...
118
+
119
+
120
+ class _CountGlyphsCallable(Protocol):
121
+ def __call__(
122
+ self, G: TNFRGraph, window: int | None = ..., *, last_only: bool = ...
123
+ ) -> Counter[str]:
124
+ ...
125
+
126
+
127
+ def _glyph_history_proxy(name: str) -> Callable[..., Any]:
128
+ """Return a wrapper that delegates to :mod:`tnfr.glyph_history` lazily."""
129
+
130
+ target: dict[str, Callable[..., Any] | None] = {"func": None}
131
+
132
+ def _call(*args: Any, **kwargs: Any) -> Any:
133
+ func = target["func"]
134
+ if func is None:
135
+ from .. import glyph_history as _glyph_history
136
+
137
+ func = getattr(_glyph_history, name)
138
+ target["func"] = func
139
+ return func(*args, **kwargs)
140
+
141
+ _call.__name__ = name
142
+ _call.__qualname__ = name
143
+ _call.__doc__ = f"Proxy for :func:`tnfr.glyph_history.{name}`."
144
+ return _call
145
+
146
+
147
+ count_glyphs = cast(_CountGlyphsCallable, _glyph_history_proxy("count_glyphs"))
148
+ ensure_history = cast(_EnsureHistoryCallable, _glyph_history_proxy("ensure_history"))
149
+ last_glyph = cast(_LastGlyphCallable, _glyph_history_proxy("last_glyph"))
150
+ push_glyph = cast(_PushGlyphCallable, _glyph_history_proxy("push_glyph"))
151
+ recent_glyph = cast(_RecentGlyphCallable, _glyph_history_proxy("recent_glyph"))
@@ -0,0 +1,66 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from ..cache import CacheManager as CacheManager
6
+ from ..glyph_history import (
7
+ HistoryDict,
8
+ count_glyphs as count_glyphs,
9
+ ensure_history as ensure_history,
10
+ last_glyph as last_glyph,
11
+ push_glyph as push_glyph,
12
+ recent_glyph as recent_glyph,
13
+ )
14
+ from ..utils.cache import (
15
+ EdgeCacheManager as EdgeCacheManager,
16
+ cached_node_list as cached_node_list,
17
+ cached_nodes_and_A as cached_nodes_and_A,
18
+ edge_version_cache as edge_version_cache,
19
+ edge_version_update as edge_version_update,
20
+ ensure_node_index_map as ensure_node_index_map,
21
+ ensure_node_offset_map as ensure_node_offset_map,
22
+ node_set_checksum as node_set_checksum,
23
+ stable_json as stable_json,
24
+ )
25
+ from ..utils.graph import (
26
+ get_graph as get_graph,
27
+ get_graph_mapping as get_graph_mapping,
28
+ increment_edge_version as increment_edge_version,
29
+ mark_dnfr_prep_dirty as mark_dnfr_prep_dirty,
30
+ )
31
+ from .numeric import (
32
+ angle_diff as angle_diff,
33
+ clamp as clamp,
34
+ clamp01 as clamp01,
35
+ kahan_sum_nd as kahan_sum_nd,
36
+ )
37
+
38
+ __all__ = (
39
+ "CacheManager",
40
+ "EdgeCacheManager",
41
+ "angle_diff",
42
+ "cached_node_list",
43
+ "cached_nodes_and_A",
44
+ "clamp",
45
+ "clamp01",
46
+ "edge_version_cache",
47
+ "edge_version_update",
48
+ "ensure_node_index_map",
49
+ "ensure_node_offset_map",
50
+ "get_graph",
51
+ "get_graph_mapping",
52
+ "increment_edge_version",
53
+ "kahan_sum_nd",
54
+ "mark_dnfr_prep_dirty",
55
+ "node_set_checksum",
56
+ "stable_json",
57
+ "count_glyphs",
58
+ "ensure_history",
59
+ "last_glyph",
60
+ "push_glyph",
61
+ "recent_glyph",
62
+ "__getattr__",
63
+ )
64
+
65
+
66
+ def __getattr__(name: str) -> Any: ...
tnfr/helpers/numeric.py CHANGED
@@ -58,6 +58,7 @@ def similarity_abs(a: float, b: float, lo: float, hi: float) -> float:
58
58
 
59
59
  return 1.0 - _norm01(abs(float(a) - float(b)), 0.0, hi - lo)
60
60
 
61
+
61
62
  def kahan_sum_nd(
62
63
  values: Iterable[Sequence[float]], dims: int
63
64
  ) -> tuple[float, ...]:
@@ -0,0 +1,12 @@
1
+ from typing import Any
2
+
3
+ __all__: Any
4
+
5
+ def __getattr__(name: str) -> Any: ...
6
+
7
+ angle_diff: Any
8
+ clamp: Any
9
+ clamp01: Any
10
+ kahan_sum_nd: Any
11
+ similarity_abs: Any
12
+ within_range: Any
tnfr/immutable.py CHANGED
@@ -9,21 +9,44 @@ from __future__ import annotations
9
9
 
10
10
  from contextlib import contextmanager
11
11
  from dataclasses import asdict, is_dataclass
12
- from functools import lru_cache, singledispatch, wraps, partial
13
- from typing import Any, Callable
12
+ from functools import lru_cache, partial, singledispatch, wraps
13
+ from typing import Any, Callable, Iterable, Iterator, cast
14
14
  from collections.abc import Mapping
15
15
  from types import MappingProxyType
16
16
  import threading
17
17
  import weakref
18
18
 
19
+ from ._compat import TypeAlias
20
+
19
21
  # Types considered immutable without further inspection
20
22
  IMMUTABLE_SIMPLE = frozenset(
21
23
  {int, float, complex, str, bool, bytes, type(None)}
22
24
  )
23
25
 
24
26
 
27
+ FrozenPrimitive: TypeAlias = int | float | complex | str | bool | bytes | None
28
+ """Primitive immutable values handled directly by :func:`_freeze`."""
29
+
30
+ FrozenCollectionItems: TypeAlias = tuple["FrozenSnapshot", ...]
31
+ """Frozen representation for generic iterables."""
32
+
33
+ FrozenMappingItems: TypeAlias = tuple[tuple[Any, "FrozenSnapshot"], ...]
34
+ """Frozen representation for mapping ``items()`` snapshots."""
35
+
36
+ FrozenTaggedCollection: TypeAlias = tuple[str, FrozenCollectionItems]
37
+ """Tagged iterable snapshot identifying the original container type."""
38
+
39
+ FrozenTaggedMapping: TypeAlias = tuple[str, FrozenMappingItems]
40
+ """Tagged mapping snapshot identifying the original mapping flavour."""
41
+
42
+ FrozenSnapshot: TypeAlias = (
43
+ FrozenPrimitive | FrozenCollectionItems | FrozenTaggedCollection | FrozenTaggedMapping
44
+ )
45
+ """Union describing the immutable snapshot returned by :func:`_freeze`."""
46
+
47
+
25
48
  @contextmanager
26
- def _cycle_guard(value: Any, seen: set[int] | None = None):
49
+ def _cycle_guard(value: Any, seen: set[int] | None = None) -> Iterator[set[int]]:
27
50
  """Context manager that detects reference cycles during freezing."""
28
51
  if seen is None:
29
52
  seen = set()
@@ -37,18 +60,20 @@ def _cycle_guard(value: Any, seen: set[int] | None = None):
37
60
  seen.remove(obj_id)
38
61
 
39
62
 
40
- def _check_cycle(func: Callable[[Any, set[int] | None], Any]):
63
+ def _check_cycle(
64
+ func: Callable[[Any, set[int] | None], FrozenSnapshot]
65
+ ) -> Callable[[Any, set[int] | None], FrozenSnapshot]:
41
66
  """Decorator applying :func:`_cycle_guard` to ``func``."""
42
67
 
43
68
  @wraps(func)
44
- def wrapper(value: Any, seen: set[int] | None = None):
45
- with _cycle_guard(value, seen) as seen:
46
- return func(value, seen)
69
+ def wrapper(value: Any, seen: set[int] | None = None) -> FrozenSnapshot:
70
+ with _cycle_guard(value, seen) as guard_seen:
71
+ return func(value, guard_seen)
47
72
 
48
73
  return wrapper
49
74
 
50
75
 
51
- def _freeze_dataclass(value: Any, seen: set[int]):
76
+ def _freeze_dataclass(value: Any, seen: set[int]) -> FrozenTaggedMapping:
52
77
  params = getattr(type(value), "__dataclass_params__", None)
53
78
  frozen = bool(params and params.frozen)
54
79
  data = asdict(value)
@@ -58,9 +83,10 @@ def _freeze_dataclass(value: Any, seen: set[int]):
58
83
 
59
84
  @singledispatch
60
85
  @_check_cycle
61
- def _freeze(value: Any, seen: set[int] | None = None):
86
+ def _freeze(value: Any, seen: set[int] | None = None) -> FrozenSnapshot:
62
87
  """Recursively convert ``value`` into an immutable representation."""
63
88
  if is_dataclass(value) and not isinstance(value, type):
89
+ assert seen is not None
64
90
  return _freeze_dataclass(value, seen)
65
91
  if type(value) in IMMUTABLE_SIMPLE:
66
92
  return value
@@ -69,22 +95,27 @@ def _freeze(value: Any, seen: set[int] | None = None):
69
95
 
70
96
  @_freeze.register(tuple)
71
97
  @_check_cycle
72
- def _freeze_tuple(value: tuple, seen: set[int] | None = None): # noqa: F401
98
+ def _freeze_tuple(value: tuple[Any, ...], seen: set[int] | None = None) -> FrozenCollectionItems: # noqa: F401
99
+ assert seen is not None
73
100
  return tuple(_freeze(v, seen) for v in value)
74
101
 
75
102
 
76
- def _freeze_iterable(container: Any, tag: str, seen: set[int] | None) -> tuple[str, tuple]:
103
+ def _freeze_iterable(
104
+ container: Iterable[Any], tag: str, seen: set[int]
105
+ ) -> FrozenTaggedCollection:
77
106
  return (tag, tuple(_freeze(v, seen) for v in container))
78
107
 
79
108
 
80
109
  def _freeze_iterable_with_tag(
81
- value: Any, seen: set[int] | None = None, *, tag: str
82
- ) -> tuple[str, tuple]:
110
+ value: Iterable[Any], seen: set[int] | None = None, *, tag: str
111
+ ) -> FrozenTaggedCollection:
112
+ assert seen is not None
83
113
  return _freeze_iterable(value, tag, seen)
84
114
 
85
115
 
86
116
  def _register_iterable(cls: type, tag: str) -> None:
87
- _freeze.register(cls)(_check_cycle(partial(_freeze_iterable_with_tag, tag=tag)))
117
+ handler = _check_cycle(partial(_freeze_iterable_with_tag, tag=tag))
118
+ _freeze.register(cls)(cast(Callable[[Any, set[int] | None], FrozenSnapshot], handler))
88
119
 
89
120
 
90
121
  for _cls, _tag in (
@@ -98,17 +129,22 @@ for _cls, _tag in (
98
129
 
99
130
  @_freeze.register(Mapping)
100
131
  @_check_cycle
101
- def _freeze_mapping(value: Mapping, seen: set[int] | None = None): # noqa: F401
132
+ def _freeze_mapping(
133
+ value: Mapping[Any, Any], seen: set[int] | None = None
134
+ ) -> FrozenTaggedMapping: # noqa: F401
135
+ assert seen is not None
102
136
  tag = "dict" if hasattr(value, "__setitem__") else "mapping"
103
137
  return (tag, tuple((k, _freeze(v, seen)) for k, v in value.items()))
104
138
 
105
139
 
106
- def _all_immutable(iterable) -> bool:
140
+ def _all_immutable(iterable: Iterable[Any]) -> bool:
107
141
  return all(_is_immutable_inner(v) for v in iterable)
108
142
 
109
143
 
110
144
  # Dispatch table kept immutable to avoid accidental mutation.
111
- _IMMUTABLE_TAG_DISPATCH: Mapping[str, Callable[[tuple], bool]] = MappingProxyType(
145
+ ImmutableTagHandler: TypeAlias = Callable[[tuple[Any, ...]], bool]
146
+
147
+ _IMMUTABLE_TAG_DISPATCH: Mapping[str, ImmutableTagHandler] = MappingProxyType(
112
148
  {
113
149
  "mapping": lambda v: _all_immutable(v[1]),
114
150
  "frozenset": lambda v: _all_immutable(v[1]),
@@ -127,7 +163,7 @@ def _is_immutable_inner(value: Any) -> bool:
127
163
 
128
164
 
129
165
  @_is_immutable_inner.register(tuple)
130
- def _is_immutable_inner_tuple(value: tuple) -> bool: # noqa: F401
166
+ def _is_immutable_inner_tuple(value: tuple[Any, ...]) -> bool: # noqa: F401
131
167
  if value and isinstance(value[0], str):
132
168
  handler = _IMMUTABLE_TAG_DISPATCH.get(value[0])
133
169
  if handler is not None:
@@ -136,7 +172,7 @@ def _is_immutable_inner_tuple(value: tuple) -> bool: # noqa: F401
136
172
 
137
173
 
138
174
  @_is_immutable_inner.register(frozenset)
139
- def _is_immutable_inner_frozenset(value: frozenset) -> bool: # noqa: F401
175
+ def _is_immutable_inner_frozenset(value: frozenset[Any]) -> bool: # noqa: F401
140
176
  return _all_immutable(value)
141
177
 
142
178
 
tnfr/immutable.pyi ADDED
@@ -0,0 +1,37 @@
1
+ from typing import Any, Callable, Iterator, Mapping
2
+
3
+ from ._compat import TypeAlias
4
+
5
+ FrozenPrimitive: TypeAlias = int | float | complex | str | bool | bytes | None
6
+ FrozenCollectionItems: TypeAlias = tuple["FrozenSnapshot", ...]
7
+ FrozenMappingItems: TypeAlias = tuple[tuple[Any, "FrozenSnapshot"], ...]
8
+ FrozenTaggedCollection: TypeAlias = tuple[str, FrozenCollectionItems]
9
+ FrozenTaggedMapping: TypeAlias = tuple[str, FrozenMappingItems]
10
+ FrozenSnapshot: TypeAlias = (
11
+ FrozenPrimitive | FrozenCollectionItems | FrozenTaggedCollection | FrozenTaggedMapping
12
+ )
13
+ ImmutableTagHandler: TypeAlias = Callable[[tuple[Any, ...]], bool]
14
+
15
+ __all__: tuple[str, ...]
16
+
17
+ def __getattr__(name: str) -> Any: ...
18
+
19
+ def _cycle_guard(value: Any, seen: set[int] | None = ...) -> Iterator[set[int]]: ...
20
+
21
+ def _check_cycle(
22
+ func: Callable[[Any, set[int] | None], FrozenSnapshot],
23
+ ) -> Callable[[Any, set[int] | None], FrozenSnapshot]: ...
24
+
25
+ def _freeze(value: Any, seen: set[int] | None = ...) -> FrozenSnapshot: ...
26
+
27
+ def _freeze_mapping(
28
+ value: Mapping[Any, Any],
29
+ seen: set[int] | None = ...,
30
+ ) -> FrozenTaggedMapping: ...
31
+
32
+ def _is_immutable(value: Any) -> bool: ...
33
+
34
+ def _is_immutable_inner(value: Any) -> bool: ...
35
+
36
+ _IMMUTABLE_CACHE: Any
37
+ _IMMUTABLE_TAG_DISPATCH: Mapping[str, ImmutableTagHandler]
tnfr/initialization.py CHANGED
@@ -2,23 +2,24 @@
2
2
 
3
3
  from __future__ import annotations
4
4
  import random
5
- from typing import TYPE_CHECKING
5
+ from typing import TYPE_CHECKING, cast
6
6
 
7
7
  from dataclasses import dataclass
8
8
 
9
9
  from .constants import VF_KEY, THETA_KEY, get_graph_param
10
10
  from .helpers.numeric import clamp
11
11
  from .rng import make_rng
12
+ from .types import NodeInitAttrMap
12
13
 
13
14
  if TYPE_CHECKING: # pragma: no cover
14
- import networkx as nx # type: ignore[import-untyped]
15
+ import networkx as nx
15
16
 
16
17
  __all__ = ("InitParams", "init_node_attrs")
17
18
 
18
19
 
19
20
  @dataclass
20
21
  class InitParams:
21
- """Parametros de inicialización nodal."""
22
+ """Parameters governing node initialisation."""
22
23
 
23
24
  seed: int | None
24
25
  init_rand_phase: bool
@@ -38,7 +39,7 @@ class InitParams:
38
39
 
39
40
  @classmethod
40
41
  def from_graph(cls, G: "nx.Graph") -> "InitParams":
41
- """Construir ``InitParams`` desde ``G.graph``."""
42
+ """Construct ``InitParams`` from ``G.graph`` configuration."""
42
43
 
43
44
  return cls(
44
45
  seed=get_graph_param(G, "RANDOM_SEED", int),
@@ -62,7 +63,7 @@ class InitParams:
62
63
 
63
64
 
64
65
  def _init_phase(
65
- nd: dict,
66
+ nd: NodeInitAttrMap,
66
67
  rng: random.Random,
67
68
  *,
68
69
  override: bool,
@@ -82,7 +83,7 @@ def _init_phase(
82
83
 
83
84
 
84
85
  def _init_vf(
85
- nd: dict,
86
+ nd: NodeInitAttrMap,
86
87
  rng: random.Random,
87
88
  *,
88
89
  override: bool,
@@ -118,7 +119,7 @@ def _init_vf(
118
119
 
119
120
 
120
121
  def _init_si_epi(
121
- nd: dict,
122
+ nd: NodeInitAttrMap,
122
123
  rng: random.Random,
123
124
  *,
124
125
  override: bool,
@@ -163,9 +164,10 @@ def init_node_attrs(G: "nx.Graph", *, override: bool = True) -> "nx.Graph":
163
164
 
164
165
  rng = make_rng(params.seed, -1, G)
165
166
  for _, nd in G.nodes(data=True):
167
+ node_attrs = cast(NodeInitAttrMap, nd)
166
168
 
167
169
  _init_phase(
168
- nd,
170
+ node_attrs,
169
171
  rng,
170
172
  override=override,
171
173
  random_phase=params.init_rand_phase,
@@ -173,7 +175,7 @@ def init_node_attrs(G: "nx.Graph", *, override: bool = True) -> "nx.Graph":
173
175
  th_max=params.th_max,
174
176
  )
175
177
  _init_vf(
176
- nd,
178
+ node_attrs,
177
179
  rng,
178
180
  override=override,
179
181
  mode=params.vf_mode,
@@ -186,7 +188,7 @@ def init_node_attrs(G: "nx.Graph", *, override: bool = True) -> "nx.Graph":
186
188
  clamp_to_limits=params.clamp_to_limits,
187
189
  )
188
190
  _init_si_epi(
189
- nd,
191
+ node_attrs,
190
192
  rng,
191
193
  override=override,
192
194
  si_min=params.si_min,
@@ -0,0 +1,73 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ import random
5
+
6
+ import networkx as nx
7
+
8
+ from .types import NodeInitAttrMap
9
+
10
+ __all__: tuple[str, str] = ("InitParams", "init_node_attrs")
11
+
12
+
13
+ @dataclass
14
+ class InitParams:
15
+ seed: int | None
16
+ init_rand_phase: bool
17
+ th_min: float
18
+ th_max: float
19
+ vf_mode: str
20
+ vf_min_lim: float
21
+ vf_max_lim: float
22
+ vf_uniform_min: float | None
23
+ vf_uniform_max: float | None
24
+ vf_mean: float
25
+ vf_std: float
26
+ clamp_to_limits: bool
27
+ si_min: float
28
+ si_max: float
29
+ epi_val: float
30
+
31
+ @classmethod
32
+ def from_graph(cls, G: nx.Graph) -> InitParams: ...
33
+
34
+
35
+ def _init_phase(
36
+ nd: NodeInitAttrMap,
37
+ rng: random.Random,
38
+ *,
39
+ override: bool,
40
+ random_phase: bool,
41
+ th_min: float,
42
+ th_max: float,
43
+ ) -> None: ...
44
+
45
+
46
+ def _init_vf(
47
+ nd: NodeInitAttrMap,
48
+ rng: random.Random,
49
+ *,
50
+ override: bool,
51
+ mode: str,
52
+ vf_uniform_min: float,
53
+ vf_uniform_max: float,
54
+ vf_mean: float,
55
+ vf_std: float,
56
+ vf_min_lim: float,
57
+ vf_max_lim: float,
58
+ clamp_to_limits: bool,
59
+ ) -> None: ...
60
+
61
+
62
+ def _init_si_epi(
63
+ nd: NodeInitAttrMap,
64
+ rng: random.Random,
65
+ *,
66
+ override: bool,
67
+ si_min: float,
68
+ si_max: float,
69
+ epi_val: float,
70
+ ) -> None: ...
71
+
72
+
73
+ def init_node_attrs(G: nx.Graph, *, override: bool = True) -> nx.Graph: ...