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/node.pyi ADDED
@@ -0,0 +1,178 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Hashable, Iterable, MutableMapping, Sequence
4
+ from typing import Any, Callable, Optional, Protocol, SupportsFloat, TypeVar
5
+
6
+ import numpy as np
7
+
8
+ from .mathematics import (
9
+ CoherenceOperator,
10
+ FrequencyOperator,
11
+ HilbertSpace,
12
+ NFRValidator,
13
+ StateProjector,
14
+ )
15
+ from .types import (
16
+ CouplingWeight,
17
+ DeltaNFR,
18
+ EPIValue,
19
+ NodeId,
20
+ Phase,
21
+ SecondDerivativeEPI,
22
+ SenseIndex,
23
+ StructuralFrequency,
24
+ TNFRGraph,
25
+ )
26
+
27
+ T = TypeVar("T")
28
+
29
+ __all__ = ("NodeNX", "NodeProtocol", "add_edge")
30
+
31
+ class AttrSpec:
32
+ aliases: tuple[str, ...]
33
+ default: Any
34
+ getter: Callable[[MutableMapping[str, Any], tuple[str, ...], Any], Any]
35
+ setter: Callable[..., None]
36
+ to_python: Callable[[Any], Any]
37
+ to_storage: Callable[[Any], Any]
38
+ use_graph_setter: bool
39
+
40
+ def build_property(self) -> property: ...
41
+
42
+ ALIAS_EPI: tuple[str, ...]
43
+ ALIAS_VF: tuple[str, ...]
44
+ ALIAS_THETA: tuple[str, ...]
45
+ ALIAS_SI: tuple[str, ...]
46
+ ALIAS_EPI_KIND: tuple[str, ...]
47
+ ALIAS_DNFR: tuple[str, ...]
48
+ ALIAS_D2EPI: tuple[str, ...]
49
+
50
+ ATTR_SPECS: dict[str, AttrSpec]
51
+
52
+ def _add_edge_common(
53
+ n1: NodeId,
54
+ n2: NodeId,
55
+ weight: CouplingWeight | SupportsFloat | str,
56
+ ) -> Optional[CouplingWeight]: ...
57
+ def add_edge(
58
+ graph: TNFRGraph,
59
+ n1: NodeId,
60
+ n2: NodeId,
61
+ weight: CouplingWeight | SupportsFloat | str,
62
+ overwrite: bool = ...,
63
+ ) -> None: ...
64
+
65
+ class NodeProtocol(Protocol):
66
+ EPI: EPIValue
67
+ vf: StructuralFrequency
68
+ theta: Phase
69
+ Si: SenseIndex
70
+ epi_kind: str
71
+ dnfr: DeltaNFR
72
+ d2EPI: SecondDerivativeEPI
73
+ graph: MutableMapping[str, Any]
74
+
75
+ def neighbors(self) -> Iterable[NodeProtocol | Hashable]: ...
76
+ def _glyph_storage(self) -> MutableMapping[str, object]: ...
77
+ def has_edge(self, other: NodeProtocol) -> bool: ...
78
+ def add_edge(
79
+ self,
80
+ other: NodeProtocol,
81
+ weight: CouplingWeight,
82
+ *,
83
+ overwrite: bool = ...,
84
+ ) -> None: ...
85
+ def offset(self) -> int: ...
86
+ def all_nodes(self) -> Iterable[NodeProtocol]: ...
87
+
88
+ class NodeNX(NodeProtocol):
89
+ G: TNFRGraph
90
+ n: NodeId
91
+ graph: MutableMapping[str, Any]
92
+ state_projector: StateProjector
93
+ hilbert_space: HilbertSpace
94
+ coherence_operator: CoherenceOperator | None
95
+ frequency_operator: FrequencyOperator | None
96
+ coherence_threshold: float | None
97
+ validator: NFRValidator | None
98
+ rng: np.random.Generator | None
99
+
100
+ def __init__(
101
+ self,
102
+ G: TNFRGraph,
103
+ n: NodeId,
104
+ *,
105
+ state_projector: StateProjector | None = ...,
106
+ enable_math_validation: Optional[bool] = ...,
107
+ hilbert_space: HilbertSpace | None = ...,
108
+ coherence_operator: CoherenceOperator | None = ...,
109
+ coherence_dim: int | None = ...,
110
+ coherence_spectrum: Sequence[float] | np.ndarray | None = ...,
111
+ coherence_c_min: float | None = ...,
112
+ frequency_operator: FrequencyOperator | None = ...,
113
+ frequency_matrix: Sequence[Sequence[complex]] | np.ndarray | None = ...,
114
+ coherence_threshold: float | None = ...,
115
+ validator: NFRValidator | None = ...,
116
+ rng: np.random.Generator | None = ...,
117
+ ) -> None: ...
118
+ @classmethod
119
+ def from_graph(cls, G: TNFRGraph, n: NodeId) -> "NodeNX": ...
120
+ def _glyph_storage(self) -> MutableMapping[str, Any]: ...
121
+ @property
122
+ def EPI(self) -> EPIValue: ...
123
+ @EPI.setter
124
+ def EPI(self, value: EPIValue) -> None: ...
125
+ @property
126
+ def vf(self) -> StructuralFrequency: ...
127
+ @vf.setter
128
+ def vf(self, value: StructuralFrequency) -> None: ...
129
+ @property
130
+ def theta(self) -> Phase: ...
131
+ @theta.setter
132
+ def theta(self, value: Phase) -> None: ...
133
+ @property
134
+ def Si(self) -> SenseIndex: ...
135
+ @Si.setter
136
+ def Si(self, value: SenseIndex) -> None: ...
137
+ @property
138
+ def epi_kind(self) -> str: ...
139
+ @epi_kind.setter
140
+ def epi_kind(self, value: str) -> None: ...
141
+ @property
142
+ def dnfr(self) -> DeltaNFR: ...
143
+ @dnfr.setter
144
+ def dnfr(self, value: DeltaNFR) -> None: ...
145
+ @property
146
+ def d2EPI(self) -> SecondDerivativeEPI: ...
147
+ @d2EPI.setter
148
+ def d2EPI(self, value: SecondDerivativeEPI) -> None: ...
149
+ def neighbors(self) -> Iterable[NodeId]: ...
150
+ def has_edge(self, other: NodeProtocol) -> bool: ...
151
+ def add_edge(
152
+ self,
153
+ other: NodeProtocol,
154
+ weight: CouplingWeight,
155
+ *,
156
+ overwrite: bool = ...,
157
+ ) -> None: ...
158
+ def offset(self) -> int: ...
159
+ def all_nodes(self) -> Iterable[NodeProtocol]: ...
160
+ def run_sequence_with_validation(
161
+ self,
162
+ ops: Iterable[Callable[[TNFRGraph, NodeId], None]],
163
+ *,
164
+ projector: StateProjector | None = ...,
165
+ hilbert_space: HilbertSpace | None = ...,
166
+ coherence_operator: CoherenceOperator | None = ...,
167
+ coherence_dim: int | None = ...,
168
+ coherence_spectrum: Sequence[float] | np.ndarray | None = ...,
169
+ coherence_c_min: float | None = ...,
170
+ coherence_threshold: float | None = ...,
171
+ frequency_operator: FrequencyOperator | None = ...,
172
+ frequency_matrix: Sequence[Sequence[complex]] | np.ndarray | None = ...,
173
+ validator: NFRValidator | None = ...,
174
+ enforce_frequency_positivity: bool | None = ...,
175
+ enable_validation: bool | None = ...,
176
+ rng: np.random.Generator | None = ...,
177
+ log_metrics: bool = ...,
178
+ ) -> dict[str, Any]: ...
tnfr/observers.py CHANGED
@@ -1,28 +1,32 @@
1
1
  """Observer management."""
2
2
 
3
3
  from __future__ import annotations
4
- from functools import partial
4
+
5
5
  import statistics
6
+ from collections.abc import Mapping
7
+ from functools import partial
6
8
  from statistics import StatisticsError, pvariance
7
9
 
8
- from .constants import get_aliases
9
- from .alias import get_attr
10
- from .helpers.numeric import angle_diff
10
+ from .alias import get_theta_attr
11
11
  from .callback_utils import CallbackEvent, callback_manager
12
+ from .config.constants import GLYPH_GROUPS
13
+ from .gamma import kuramoto_R_psi
12
14
  from .glyph_history import (
13
- ensure_history,
14
- count_glyphs,
15
15
  append_metric,
16
+ count_glyphs,
17
+ ensure_history,
16
18
  )
17
- from .collections_utils import normalize_counter, mix_groups
18
- from .constants_glyphs import GLYPH_GROUPS
19
- from .gamma import kuramoto_R_psi
20
- from .logging_utils import get_logger
21
- from .import_utils import get_numpy
19
+ from .utils import angle_diff
22
20
  from .metrics.common import compute_coherence
23
- from .validators import validate_window
24
-
25
- ALIAS_THETA = get_aliases("THETA")
21
+ from .types import Glyph, GlyphLoadDistribution, TNFRGraph
22
+ from .utils import (
23
+ get_logger,
24
+ get_numpy,
25
+ mix_groups,
26
+ normalize_counter,
27
+ )
28
+ from .validation import validate_window
29
+ from .telemetry import ensure_nu_f_telemetry, record_nu_f_window
26
30
 
27
31
  __all__ = (
28
32
  "attach_standard_observer",
@@ -42,11 +46,10 @@ DEFAULT_GLYPH_LOAD_SPAN = 50
42
46
  DEFAULT_WBAR_SPAN = 25
43
47
 
44
48
 
45
-
46
49
  # -------------------------
47
- # Observador estándar Γ(R)
50
+ # Standard Γ(R) observer
48
51
  # -------------------------
49
- def _std_log(kind: str, G, ctx: dict):
52
+ def _std_log(kind: str, G: TNFRGraph, ctx: Mapping[str, object]) -> None:
50
53
  """Store compact events in ``history['events']``."""
51
54
  h = ensure_history(G)
52
55
  append_metric(h, "events", (kind, dict(ctx)))
@@ -56,25 +59,124 @@ _STD_CALLBACKS = {
56
59
  CallbackEvent.BEFORE_STEP.value: partial(_std_log, "before"),
57
60
  CallbackEvent.AFTER_STEP.value: partial(_std_log, "after"),
58
61
  CallbackEvent.ON_REMESH.value: partial(_std_log, "remesh"),
62
+ CallbackEvent.CACHE_METRICS.value: partial(_std_log, "cache"),
59
63
  }
60
64
 
61
65
 
62
- def attach_standard_observer(G):
66
+ _REORG_STATE_KEY = "_std_observer_reorg"
67
+
68
+
69
+ def _resolve_reorg_state(G: TNFRGraph) -> dict[str, object]:
70
+ state = G.graph.get(_REORG_STATE_KEY)
71
+ if not isinstance(state, dict):
72
+ state = {}
73
+ G.graph[_REORG_STATE_KEY] = state
74
+ return state
75
+
76
+
77
+ def _before_step_reorg(G: TNFRGraph, ctx: Mapping[str, object] | None) -> None:
78
+ """Capture structural time metadata before the step starts."""
79
+
80
+ ensure_nu_f_telemetry(G, confidence_level=None)
81
+ state = _resolve_reorg_state(G)
82
+ step_idx = ctx.get("step") if ctx else None
83
+ try:
84
+ state["step"] = int(step_idx) if step_idx is not None else None
85
+ except (TypeError, ValueError):
86
+ state["step"] = None
87
+ start_t = float(G.graph.get("_t", 0.0))
88
+ state["start_t"] = start_t
89
+ dt_raw = ctx.get("dt") if ctx else None
90
+ try:
91
+ state["dt"] = float(dt_raw) if dt_raw is not None else None
92
+ except (TypeError, ValueError):
93
+ state["dt"] = None
94
+
95
+
96
+ def _after_step_reorg(G: TNFRGraph, ctx: Mapping[str, object] | None) -> None:
97
+ """Record the reorganisation window for νf telemetry."""
98
+
99
+ state = _resolve_reorg_state(G)
100
+ pending_step = state.get("step")
101
+ ctx_step = ctx.get("step") if ctx else None
102
+ if pending_step is not None and ctx_step is not None and pending_step != ctx_step:
103
+ # Ignore mismatched callbacks to avoid double counting.
104
+ return
105
+
106
+ try:
107
+ start_t = float(state.get("start_t", float(G.graph.get("_t", 0.0))))
108
+ except (TypeError, ValueError):
109
+ start_t = float(G.graph.get("_t", 0.0))
110
+ end_t = float(G.graph.get("_t", start_t))
111
+ dt_raw = state.get("dt")
112
+ try:
113
+ duration = float(dt_raw) if dt_raw is not None else end_t - start_t
114
+ except (TypeError, ValueError):
115
+ duration = end_t - start_t
116
+ if duration <= 0.0:
117
+ duration = end_t - start_t
118
+ if duration <= 0.0:
119
+ return
120
+
121
+ stable_frac = ctx.get("stable_frac") if ctx else None
122
+ if stable_frac is None:
123
+ hist = ensure_history(G)
124
+ series = hist.get("stable_frac", [])
125
+ stable_frac = series[-1] if series else None
126
+ try:
127
+ stable_frac_f = float(stable_frac) if stable_frac is not None else None
128
+ except (TypeError, ValueError):
129
+ stable_frac_f = None
130
+ total_nodes = G.number_of_nodes()
131
+ if stable_frac_f is None:
132
+ reorganisations = total_nodes
133
+ else:
134
+ frac = min(max(stable_frac_f, 0.0), 1.0)
135
+ stable_nodes = int(round(frac * total_nodes))
136
+ reorganisations = max(total_nodes - stable_nodes, 0)
137
+
138
+ record_nu_f_window(
139
+ G,
140
+ reorganisations,
141
+ duration,
142
+ start=start_t,
143
+ end=end_t,
144
+ )
145
+ state["last_duration"] = duration
146
+ state["last_reorganisations"] = reorganisations
147
+ state["last_end_t"] = end_t
148
+ state["step"] = None
149
+
150
+
151
+ def attach_standard_observer(G: TNFRGraph) -> TNFRGraph:
63
152
  """Register standard callbacks: before_step, after_step, on_remesh."""
64
153
  if G.graph.get("_STD_OBSERVER"):
65
154
  return G
66
155
  for event, fn in _STD_CALLBACKS.items():
67
156
  callback_manager.register_callback(G, event, fn)
157
+ callback_manager.register_callback(
158
+ G,
159
+ CallbackEvent.BEFORE_STEP.value,
160
+ _before_step_reorg,
161
+ name="std_reorg_before",
162
+ )
163
+ callback_manager.register_callback(
164
+ G,
165
+ CallbackEvent.AFTER_STEP.value,
166
+ _after_step_reorg,
167
+ name="std_reorg_after",
168
+ )
169
+ ensure_nu_f_telemetry(G, confidence_level=None)
68
170
  G.graph["_STD_OBSERVER"] = "attached"
69
171
  return G
70
172
 
71
173
 
72
- def _ensure_nodes(G) -> bool:
174
+ def _ensure_nodes(G: TNFRGraph) -> bool:
73
175
  """Return ``True`` when the graph has nodes."""
74
176
  return bool(G.number_of_nodes())
75
177
 
76
178
 
77
- def kuramoto_metrics(G) -> tuple[float, float]:
179
+ def kuramoto_metrics(G: TNFRGraph) -> tuple[float, float]:
78
180
  """Return Kuramoto order ``R`` and mean phase ``ψ``.
79
181
 
80
182
  Delegates to :func:`kuramoto_R_psi` and performs the computation exactly
@@ -83,7 +185,13 @@ def kuramoto_metrics(G) -> tuple[float, float]:
83
185
  return kuramoto_R_psi(G)
84
186
 
85
187
 
86
- def phase_sync(G, R: float | None = None, psi: float | None = None) -> float:
188
+ def phase_sync(
189
+ G: TNFRGraph,
190
+ R: float | None = None,
191
+ psi: float | None = None,
192
+ ) -> float:
193
+ """Return a [0, 1] synchrony index derived from phase dispersion."""
194
+
87
195
  if not _ensure_nodes(G):
88
196
  return 1.0
89
197
  if R is None or psi is None:
@@ -92,10 +200,12 @@ def phase_sync(G, R: float | None = None, psi: float | None = None) -> float:
92
200
  R = R_calc
93
201
  if psi is None:
94
202
  psi = psi_calc
95
- diffs = (
96
- angle_diff(get_attr(data, ALIAS_THETA, 0.0), psi)
97
- for _, data in G.nodes(data=True)
98
- )
203
+
204
+ def _theta(nd: Mapping[str, object]) -> float:
205
+ value = get_theta_attr(nd, 0.0)
206
+ return float(value) if value is not None else 0.0
207
+
208
+ diffs = (angle_diff(_theta(data), psi) for _, data in G.nodes(data=True))
99
209
  # Try NumPy for a vectorised population variance
100
210
  np = get_numpy()
101
211
  if np is not None:
@@ -110,7 +220,7 @@ def phase_sync(G, R: float | None = None, psi: float | None = None) -> float:
110
220
 
111
221
 
112
222
  def kuramoto_order(
113
- G, R: float | None = None, psi: float | None = None
223
+ G: TNFRGraph, R: float | None = None, psi: float | None = None
114
224
  ) -> float:
115
225
  """R in [0,1], 1 means perfectly aligned phases."""
116
226
  if not _ensure_nodes(G):
@@ -120,7 +230,7 @@ def kuramoto_order(
120
230
  return float(R)
121
231
 
122
232
 
123
- def glyph_load(G, window: int | None = None) -> dict:
233
+ def glyph_load(G: TNFRGraph, window: int | None = None) -> GlyphLoadDistribution:
124
234
  """Return distribution of glyphs applied in the network.
125
235
 
126
236
  - ``window``: if provided, count only the last ``window`` events per node;
@@ -128,21 +238,28 @@ def glyph_load(G, window: int | None = None) -> dict:
128
238
  Returns a dict with proportions per glyph and useful aggregates.
129
239
  """
130
240
  if window == 0:
131
- return {"_count": 0}
241
+ return {"_count": 0.0}
132
242
  if window is None:
133
243
  window_int = DEFAULT_GLYPH_LOAD_SPAN
134
244
  else:
135
245
  window_int = validate_window(window, positive=True)
136
246
  total = count_glyphs(G, window=window_int, last_only=(window_int == 1))
137
- dist, count = normalize_counter(total)
247
+ dist_raw, count = normalize_counter(total)
138
248
  if count == 0:
139
- return {"_count": 0}
140
- dist = mix_groups(dist, GLYPH_GROUPS)
141
- dist["_count"] = count
142
- return dist
249
+ return {"_count": 0.0}
250
+ dist = mix_groups(dist_raw, GLYPH_GROUPS)
251
+ glyph_dist: GlyphLoadDistribution = {}
252
+ for key, value in dist.items():
253
+ try:
254
+ glyph_key: Glyph | str = Glyph(key)
255
+ except ValueError:
256
+ glyph_key = key
257
+ glyph_dist[glyph_key] = value
258
+ glyph_dist["_count"] = float(count)
259
+ return glyph_dist
143
260
 
144
261
 
145
- def wbar(G, window: int | None = None) -> float:
262
+ def wbar(G: TNFRGraph, window: int | None = None) -> float:
146
263
  """Return W̄ = mean of ``C(t)`` over a recent window.
147
264
 
148
265
  Uses :func:`ensure_history` to obtain ``G.graph['history']`` and falls back
@@ -151,7 +268,7 @@ def wbar(G, window: int | None = None) -> float:
151
268
  hist = ensure_history(G)
152
269
  cs = list(hist.get("C_steps", []))
153
270
  if not cs:
154
- # fallback: coherencia instantánea
271
+ # fallback: instantaneous coherence
155
272
  return compute_coherence(G)
156
273
  w_param = DEFAULT_WBAR_SPAN if window is None else window
157
274
  w = validate_window(w_param, positive=True)
tnfr/observers.pyi ADDED
@@ -0,0 +1,31 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Mapping
4
+ from typing import Final
5
+
6
+ from .types import GlyphLoadDistribution, TNFRGraph
7
+
8
+ __all__: tuple[str, ...]
9
+
10
+ DEFAULT_GLYPH_LOAD_SPAN: Final[int]
11
+ DEFAULT_WBAR_SPAN: Final[int]
12
+
13
+ def _std_log(kind: str, G: TNFRGraph, ctx: Mapping[str, object]) -> None: ...
14
+ def attach_standard_observer(G: TNFRGraph) -> TNFRGraph: ...
15
+ def _ensure_nodes(G: TNFRGraph) -> bool: ...
16
+ def kuramoto_metrics(G: TNFRGraph) -> tuple[float, float]: ...
17
+ def phase_sync(
18
+ G: TNFRGraph,
19
+ R: float | None = ...,
20
+ psi: float | None = ...,
21
+ ) -> float: ...
22
+ def kuramoto_order(
23
+ G: TNFRGraph,
24
+ R: float | None = ...,
25
+ psi: float | None = ...,
26
+ ) -> float: ...
27
+ def glyph_load(
28
+ G: TNFRGraph,
29
+ window: int | None = ...,
30
+ ) -> GlyphLoadDistribution: ...
31
+ def wbar(G: TNFRGraph, window: int | None = ...) -> float: ...
tnfr/ontosim.py CHANGED
@@ -1,25 +1,27 @@
1
1
  """Orchestrate the canonical simulation."""
2
2
 
3
3
  from __future__ import annotations
4
+
4
5
  from collections import deque
5
6
  from typing import TYPE_CHECKING
6
7
 
7
8
  from .callback_utils import CallbackEvent
8
- from .constants import METRIC_DEFAULTS, inject_defaults, get_param
9
- from .dynamics import step as _step, run as _run
9
+ from .constants import METRIC_DEFAULTS, get_param, inject_defaults
10
10
  from .dynamics import default_compute_delta_nfr
11
- from .initialization import init_node_attrs
11
+ from .dynamics import run as _run
12
+ from .dynamics import step as _step
12
13
  from .glyph_history import append_metric
13
- from .import_utils import cached_import
14
+ from .initialization import init_node_attrs
15
+ from .utils import cached_import
14
16
 
15
17
  if TYPE_CHECKING: # pragma: no cover
16
- import networkx as nx # type: ignore[import-untyped]
18
+ import networkx as nx
17
19
 
18
- # API de alto nivel
19
- __all__ = ("preparar_red", "step", "run")
20
+ # High-level API exports
21
+ __all__ = ("prepare_network", "step", "run")
20
22
 
21
23
 
22
- def preparar_red(
24
+ def prepare_network(
23
25
  G: "nx.Graph",
24
26
  *,
25
27
  init_attrs: bool = True,
@@ -43,11 +45,9 @@ def preparar_red(
43
45
  from .constants import merge_overrides
44
46
 
45
47
  merge_overrides(G, **overrides)
46
- # Inicializaciones blandas
48
+ # Initialize history buffers
47
49
  ph_len = int(
48
- G.graph.get(
49
- "PHASE_HISTORY_MAXLEN", METRIC_DEFAULTS["PHASE_HISTORY_MAXLEN"]
50
- )
50
+ G.graph.get("PHASE_HISTORY_MAXLEN", METRIC_DEFAULTS["PHASE_HISTORY_MAXLEN"])
51
51
  )
52
52
  hist_keys = [
53
53
  "C_steps",
@@ -59,7 +59,7 @@ def preparar_red(
59
59
  "sense_sigma_mag",
60
60
  "sense_sigma_angle",
61
61
  "iota",
62
- "glyph_load_estab",
62
+ "glyph_load_stabilizers",
63
63
  "glyph_load_disr",
64
64
  "Si_mean",
65
65
  "Si_hi_frac",
@@ -76,12 +76,12 @@ def preparar_red(
76
76
  "phase_disr": deque(maxlen=ph_len),
77
77
  }
78
78
  )
79
- G.graph.setdefault("history", history)
80
- # Memoria global de REMESH
79
+ history_ref = G.graph.setdefault("history", history)
80
+ # Global REMESH memory
81
81
  tau = int(get_param(G, "REMESH_TAU_GLOBAL"))
82
82
  maxlen = max(2 * tau + 5, 64)
83
83
  G.graph.setdefault("_epi_hist", deque(maxlen=maxlen))
84
- # Auto-attach del observador estándar si se pide
84
+ # Auto-attach the standard observer when requested
85
85
  if G.graph.get("ATTACH_STD_OBSERVER", False):
86
86
  attach_standard_observer = cached_import(
87
87
  "tnfr.observers",
@@ -95,7 +95,7 @@ def preparar_red(
95
95
  "_callback_errors",
96
96
  {"event": "attach_std_observer", "error": "ImportError"},
97
97
  )
98
- # Hook explícito para ΔNFR (se puede sustituir luego con
98
+ # Explicit hook for ΔNFR (can later be replaced with
99
99
  # dynamics.set_delta_nfr_hook)
100
100
  G.graph.setdefault("compute_delta_nfr", default_compute_delta_nfr)
101
101
  G.graph.setdefault("_dnfr_hook_name", "default_compute_delta_nfr")
@@ -110,8 +110,8 @@ def preparar_red(
110
110
  )
111
111
  G.graph.setdefault(
112
112
  "_CALLBACKS_DOC",
113
- "Interfaz Γ(R): registrar pares (name, func) con firma (G, ctx) "
114
- "en callbacks['before_step'|'after_step'|'on_remesh']",
113
+ "Γ(R) interface: register (name, func) pairs with signature (G, ctx) "
114
+ "in callbacks['before_step'|'after_step'|'on_remesh']",
115
115
  )
116
116
 
117
117
  if init_attrs:
@@ -126,6 +126,8 @@ def step(
126
126
  use_Si: bool = True,
127
127
  apply_glyphs: bool = True,
128
128
  ) -> None:
129
+ """Advance the ontosim runtime by a single step."""
130
+
129
131
  _step(G, dt=dt, use_Si=use_Si, apply_glyphs=apply_glyphs)
130
132
 
131
133
 
@@ -137,4 +139,6 @@ def run(
137
139
  use_Si: bool = True,
138
140
  apply_glyphs: bool = True,
139
141
  ) -> None:
142
+ """Advance the ontosim runtime ``steps`` times with optional overrides."""
143
+
140
144
  _run(G, steps=steps, dt=dt, use_Si=use_Si, apply_glyphs=apply_glyphs)
tnfr/ontosim.pyi ADDED
@@ -0,0 +1,28 @@
1
+ from __future__ import annotations
2
+
3
+ from .types import TNFRConfigValue, TNFRGraph
4
+
5
+ __all__: tuple[str, ...]
6
+
7
+ def prepare_network(
8
+ G: TNFRGraph,
9
+ *,
10
+ init_attrs: bool = True,
11
+ override_defaults: bool = False,
12
+ **overrides: TNFRConfigValue,
13
+ ) -> TNFRGraph: ...
14
+ def step(
15
+ G: TNFRGraph,
16
+ *,
17
+ dt: float | None = None,
18
+ use_Si: bool = True,
19
+ apply_glyphs: bool = True,
20
+ ) -> None: ...
21
+ def run(
22
+ G: TNFRGraph,
23
+ steps: int,
24
+ *,
25
+ dt: float | None = None,
26
+ use_Si: bool = True,
27
+ apply_glyphs: bool = True,
28
+ ) -> None: ...