tnfr 4.5.2__py3-none-any.whl → 8.5.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 (365) hide show
  1. tnfr/__init__.py +334 -50
  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 +214 -37
  8. tnfr/alias.pyi +108 -0
  9. tnfr/backends/__init__.py +354 -0
  10. tnfr/backends/jax_backend.py +173 -0
  11. tnfr/backends/numpy_backend.py +238 -0
  12. tnfr/backends/optimized_numpy.py +420 -0
  13. tnfr/backends/torch_backend.py +408 -0
  14. tnfr/cache.py +149 -556
  15. tnfr/cache.pyi +13 -0
  16. tnfr/cli/__init__.py +51 -16
  17. tnfr/cli/__init__.pyi +26 -0
  18. tnfr/cli/arguments.py +344 -32
  19. tnfr/cli/arguments.pyi +29 -0
  20. tnfr/cli/execution.py +676 -50
  21. tnfr/cli/execution.pyi +70 -0
  22. tnfr/cli/interactive_validator.py +614 -0
  23. tnfr/cli/utils.py +18 -3
  24. tnfr/cli/utils.pyi +7 -0
  25. tnfr/cli/validate.py +236 -0
  26. tnfr/compat/__init__.py +85 -0
  27. tnfr/compat/dataclass.py +136 -0
  28. tnfr/compat/jsonschema_stub.py +61 -0
  29. tnfr/compat/matplotlib_stub.py +73 -0
  30. tnfr/compat/numpy_stub.py +155 -0
  31. tnfr/config/__init__.py +224 -0
  32. tnfr/config/__init__.pyi +10 -0
  33. tnfr/{constants_glyphs.py → config/constants.py} +26 -20
  34. tnfr/config/constants.pyi +12 -0
  35. tnfr/config/defaults.py +54 -0
  36. tnfr/{constants/core.py → config/defaults_core.py} +59 -6
  37. tnfr/config/defaults_init.py +33 -0
  38. tnfr/config/defaults_metric.py +104 -0
  39. tnfr/config/feature_flags.py +81 -0
  40. tnfr/config/feature_flags.pyi +16 -0
  41. tnfr/config/glyph_constants.py +31 -0
  42. tnfr/config/init.py +77 -0
  43. tnfr/config/init.pyi +8 -0
  44. tnfr/config/operator_names.py +254 -0
  45. tnfr/config/operator_names.pyi +36 -0
  46. tnfr/config/physics_derivation.py +354 -0
  47. tnfr/config/presets.py +83 -0
  48. tnfr/config/presets.pyi +7 -0
  49. tnfr/config/security.py +927 -0
  50. tnfr/config/thresholds.py +114 -0
  51. tnfr/config/tnfr_config.py +498 -0
  52. tnfr/constants/__init__.py +51 -133
  53. tnfr/constants/__init__.pyi +92 -0
  54. tnfr/constants/aliases.py +33 -0
  55. tnfr/constants/aliases.pyi +27 -0
  56. tnfr/constants/init.py +3 -1
  57. tnfr/constants/init.pyi +12 -0
  58. tnfr/constants/metric.py +9 -15
  59. tnfr/constants/metric.pyi +19 -0
  60. tnfr/core/__init__.py +33 -0
  61. tnfr/core/container.py +226 -0
  62. tnfr/core/default_implementations.py +329 -0
  63. tnfr/core/interfaces.py +279 -0
  64. tnfr/dynamics/__init__.py +213 -633
  65. tnfr/dynamics/__init__.pyi +83 -0
  66. tnfr/dynamics/adaptation.py +267 -0
  67. tnfr/dynamics/adaptation.pyi +7 -0
  68. tnfr/dynamics/adaptive_sequences.py +189 -0
  69. tnfr/dynamics/adaptive_sequences.pyi +14 -0
  70. tnfr/dynamics/aliases.py +23 -0
  71. tnfr/dynamics/aliases.pyi +19 -0
  72. tnfr/dynamics/bifurcation.py +232 -0
  73. tnfr/dynamics/canonical.py +229 -0
  74. tnfr/dynamics/canonical.pyi +48 -0
  75. tnfr/dynamics/coordination.py +385 -0
  76. tnfr/dynamics/coordination.pyi +25 -0
  77. tnfr/dynamics/dnfr.py +2699 -398
  78. tnfr/dynamics/dnfr.pyi +26 -0
  79. tnfr/dynamics/dynamic_limits.py +225 -0
  80. tnfr/dynamics/feedback.py +252 -0
  81. tnfr/dynamics/feedback.pyi +24 -0
  82. tnfr/dynamics/fused_dnfr.py +454 -0
  83. tnfr/dynamics/homeostasis.py +157 -0
  84. tnfr/dynamics/homeostasis.pyi +14 -0
  85. tnfr/dynamics/integrators.py +496 -102
  86. tnfr/dynamics/integrators.pyi +36 -0
  87. tnfr/dynamics/learning.py +310 -0
  88. tnfr/dynamics/learning.pyi +33 -0
  89. tnfr/dynamics/metabolism.py +254 -0
  90. tnfr/dynamics/nbody.py +796 -0
  91. tnfr/dynamics/nbody_tnfr.py +783 -0
  92. tnfr/dynamics/propagation.py +326 -0
  93. tnfr/dynamics/runtime.py +908 -0
  94. tnfr/dynamics/runtime.pyi +77 -0
  95. tnfr/dynamics/sampling.py +10 -5
  96. tnfr/dynamics/sampling.pyi +7 -0
  97. tnfr/dynamics/selectors.py +711 -0
  98. tnfr/dynamics/selectors.pyi +85 -0
  99. tnfr/dynamics/structural_clip.py +207 -0
  100. tnfr/errors/__init__.py +37 -0
  101. tnfr/errors/contextual.py +492 -0
  102. tnfr/execution.py +77 -55
  103. tnfr/execution.pyi +45 -0
  104. tnfr/extensions/__init__.py +205 -0
  105. tnfr/extensions/__init__.pyi +18 -0
  106. tnfr/extensions/base.py +173 -0
  107. tnfr/extensions/base.pyi +35 -0
  108. tnfr/extensions/business/__init__.py +71 -0
  109. tnfr/extensions/business/__init__.pyi +11 -0
  110. tnfr/extensions/business/cookbook.py +88 -0
  111. tnfr/extensions/business/cookbook.pyi +8 -0
  112. tnfr/extensions/business/health_analyzers.py +202 -0
  113. tnfr/extensions/business/health_analyzers.pyi +9 -0
  114. tnfr/extensions/business/patterns.py +183 -0
  115. tnfr/extensions/business/patterns.pyi +8 -0
  116. tnfr/extensions/medical/__init__.py +73 -0
  117. tnfr/extensions/medical/__init__.pyi +11 -0
  118. tnfr/extensions/medical/cookbook.py +88 -0
  119. tnfr/extensions/medical/cookbook.pyi +8 -0
  120. tnfr/extensions/medical/health_analyzers.py +181 -0
  121. tnfr/extensions/medical/health_analyzers.pyi +9 -0
  122. tnfr/extensions/medical/patterns.py +163 -0
  123. tnfr/extensions/medical/patterns.pyi +8 -0
  124. tnfr/flatten.py +29 -50
  125. tnfr/flatten.pyi +21 -0
  126. tnfr/gamma.py +66 -53
  127. tnfr/gamma.pyi +36 -0
  128. tnfr/glyph_history.py +144 -57
  129. tnfr/glyph_history.pyi +35 -0
  130. tnfr/glyph_runtime.py +19 -0
  131. tnfr/glyph_runtime.pyi +8 -0
  132. tnfr/immutable.py +70 -30
  133. tnfr/immutable.pyi +36 -0
  134. tnfr/initialization.py +22 -16
  135. tnfr/initialization.pyi +65 -0
  136. tnfr/io.py +5 -241
  137. tnfr/io.pyi +13 -0
  138. tnfr/locking.pyi +7 -0
  139. tnfr/mathematics/__init__.py +79 -0
  140. tnfr/mathematics/backend.py +453 -0
  141. tnfr/mathematics/backend.pyi +99 -0
  142. tnfr/mathematics/dynamics.py +408 -0
  143. tnfr/mathematics/dynamics.pyi +90 -0
  144. tnfr/mathematics/epi.py +391 -0
  145. tnfr/mathematics/epi.pyi +65 -0
  146. tnfr/mathematics/generators.py +242 -0
  147. tnfr/mathematics/generators.pyi +29 -0
  148. tnfr/mathematics/metrics.py +119 -0
  149. tnfr/mathematics/metrics.pyi +16 -0
  150. tnfr/mathematics/operators.py +239 -0
  151. tnfr/mathematics/operators.pyi +59 -0
  152. tnfr/mathematics/operators_factory.py +124 -0
  153. tnfr/mathematics/operators_factory.pyi +11 -0
  154. tnfr/mathematics/projection.py +87 -0
  155. tnfr/mathematics/projection.pyi +33 -0
  156. tnfr/mathematics/runtime.py +182 -0
  157. tnfr/mathematics/runtime.pyi +64 -0
  158. tnfr/mathematics/spaces.py +256 -0
  159. tnfr/mathematics/spaces.pyi +83 -0
  160. tnfr/mathematics/transforms.py +305 -0
  161. tnfr/mathematics/transforms.pyi +62 -0
  162. tnfr/metrics/__init__.py +47 -9
  163. tnfr/metrics/__init__.pyi +20 -0
  164. tnfr/metrics/buffer_cache.py +163 -0
  165. tnfr/metrics/buffer_cache.pyi +24 -0
  166. tnfr/metrics/cache_utils.py +214 -0
  167. tnfr/metrics/coherence.py +1510 -330
  168. tnfr/metrics/coherence.pyi +129 -0
  169. tnfr/metrics/common.py +23 -16
  170. tnfr/metrics/common.pyi +35 -0
  171. tnfr/metrics/core.py +251 -36
  172. tnfr/metrics/core.pyi +13 -0
  173. tnfr/metrics/diagnosis.py +709 -110
  174. tnfr/metrics/diagnosis.pyi +86 -0
  175. tnfr/metrics/emergence.py +245 -0
  176. tnfr/metrics/export.py +60 -18
  177. tnfr/metrics/export.pyi +7 -0
  178. tnfr/metrics/glyph_timing.py +233 -43
  179. tnfr/metrics/glyph_timing.pyi +81 -0
  180. tnfr/metrics/learning_metrics.py +280 -0
  181. tnfr/metrics/learning_metrics.pyi +21 -0
  182. tnfr/metrics/phase_coherence.py +351 -0
  183. tnfr/metrics/phase_compatibility.py +349 -0
  184. tnfr/metrics/reporting.py +63 -28
  185. tnfr/metrics/reporting.pyi +25 -0
  186. tnfr/metrics/sense_index.py +1126 -43
  187. tnfr/metrics/sense_index.pyi +9 -0
  188. tnfr/metrics/trig.py +215 -23
  189. tnfr/metrics/trig.pyi +13 -0
  190. tnfr/metrics/trig_cache.py +148 -24
  191. tnfr/metrics/trig_cache.pyi +10 -0
  192. tnfr/multiscale/__init__.py +32 -0
  193. tnfr/multiscale/hierarchical.py +517 -0
  194. tnfr/node.py +646 -140
  195. tnfr/node.pyi +139 -0
  196. tnfr/observers.py +160 -45
  197. tnfr/observers.pyi +31 -0
  198. tnfr/ontosim.py +23 -19
  199. tnfr/ontosim.pyi +28 -0
  200. tnfr/operators/__init__.py +1358 -106
  201. tnfr/operators/__init__.pyi +31 -0
  202. tnfr/operators/algebra.py +277 -0
  203. tnfr/operators/canonical_patterns.py +420 -0
  204. tnfr/operators/cascade.py +267 -0
  205. tnfr/operators/cycle_detection.py +358 -0
  206. tnfr/operators/definitions.py +4108 -0
  207. tnfr/operators/definitions.pyi +78 -0
  208. tnfr/operators/grammar.py +1164 -0
  209. tnfr/operators/grammar.pyi +140 -0
  210. tnfr/operators/hamiltonian.py +710 -0
  211. tnfr/operators/health_analyzer.py +809 -0
  212. tnfr/operators/jitter.py +107 -38
  213. tnfr/operators/jitter.pyi +11 -0
  214. tnfr/operators/lifecycle.py +314 -0
  215. tnfr/operators/metabolism.py +618 -0
  216. tnfr/operators/metrics.py +2138 -0
  217. tnfr/operators/network_analysis/__init__.py +27 -0
  218. tnfr/operators/network_analysis/source_detection.py +186 -0
  219. tnfr/operators/nodal_equation.py +395 -0
  220. tnfr/operators/pattern_detection.py +660 -0
  221. tnfr/operators/patterns.py +669 -0
  222. tnfr/operators/postconditions/__init__.py +38 -0
  223. tnfr/operators/postconditions/mutation.py +236 -0
  224. tnfr/operators/preconditions/__init__.py +1226 -0
  225. tnfr/operators/preconditions/coherence.py +305 -0
  226. tnfr/operators/preconditions/dissonance.py +236 -0
  227. tnfr/operators/preconditions/emission.py +128 -0
  228. tnfr/operators/preconditions/mutation.py +580 -0
  229. tnfr/operators/preconditions/reception.py +125 -0
  230. tnfr/operators/preconditions/resonance.py +364 -0
  231. tnfr/operators/registry.py +74 -0
  232. tnfr/operators/registry.pyi +9 -0
  233. tnfr/operators/remesh.py +1415 -91
  234. tnfr/operators/remesh.pyi +26 -0
  235. tnfr/operators/structural_units.py +268 -0
  236. tnfr/operators/unified_grammar.py +105 -0
  237. tnfr/parallel/__init__.py +54 -0
  238. tnfr/parallel/auto_scaler.py +234 -0
  239. tnfr/parallel/distributed.py +384 -0
  240. tnfr/parallel/engine.py +238 -0
  241. tnfr/parallel/gpu_engine.py +420 -0
  242. tnfr/parallel/monitoring.py +248 -0
  243. tnfr/parallel/partitioner.py +459 -0
  244. tnfr/py.typed +0 -0
  245. tnfr/recipes/__init__.py +22 -0
  246. tnfr/recipes/cookbook.py +743 -0
  247. tnfr/rng.py +75 -151
  248. tnfr/rng.pyi +26 -0
  249. tnfr/schemas/__init__.py +8 -0
  250. tnfr/schemas/grammar.json +94 -0
  251. tnfr/sdk/__init__.py +107 -0
  252. tnfr/sdk/__init__.pyi +19 -0
  253. tnfr/sdk/adaptive_system.py +173 -0
  254. tnfr/sdk/adaptive_system.pyi +21 -0
  255. tnfr/sdk/builders.py +370 -0
  256. tnfr/sdk/builders.pyi +51 -0
  257. tnfr/sdk/fluent.py +1121 -0
  258. tnfr/sdk/fluent.pyi +74 -0
  259. tnfr/sdk/templates.py +342 -0
  260. tnfr/sdk/templates.pyi +41 -0
  261. tnfr/sdk/utils.py +341 -0
  262. tnfr/secure_config.py +46 -0
  263. tnfr/security/__init__.py +70 -0
  264. tnfr/security/database.py +514 -0
  265. tnfr/security/subprocess.py +503 -0
  266. tnfr/security/validation.py +290 -0
  267. tnfr/selector.py +59 -22
  268. tnfr/selector.pyi +19 -0
  269. tnfr/sense.py +92 -67
  270. tnfr/sense.pyi +23 -0
  271. tnfr/services/__init__.py +17 -0
  272. tnfr/services/orchestrator.py +325 -0
  273. tnfr/sparse/__init__.py +39 -0
  274. tnfr/sparse/representations.py +492 -0
  275. tnfr/structural.py +639 -263
  276. tnfr/structural.pyi +83 -0
  277. tnfr/telemetry/__init__.py +35 -0
  278. tnfr/telemetry/cache_metrics.py +226 -0
  279. tnfr/telemetry/cache_metrics.pyi +64 -0
  280. tnfr/telemetry/nu_f.py +422 -0
  281. tnfr/telemetry/nu_f.pyi +108 -0
  282. tnfr/telemetry/verbosity.py +36 -0
  283. tnfr/telemetry/verbosity.pyi +15 -0
  284. tnfr/tokens.py +2 -4
  285. tnfr/tokens.pyi +36 -0
  286. tnfr/tools/__init__.py +20 -0
  287. tnfr/tools/domain_templates.py +478 -0
  288. tnfr/tools/sequence_generator.py +846 -0
  289. tnfr/topology/__init__.py +13 -0
  290. tnfr/topology/asymmetry.py +151 -0
  291. tnfr/trace.py +300 -126
  292. tnfr/trace.pyi +42 -0
  293. tnfr/tutorials/__init__.py +38 -0
  294. tnfr/tutorials/autonomous_evolution.py +285 -0
  295. tnfr/tutorials/interactive.py +1576 -0
  296. tnfr/tutorials/structural_metabolism.py +238 -0
  297. tnfr/types.py +743 -12
  298. tnfr/types.pyi +357 -0
  299. tnfr/units.py +68 -0
  300. tnfr/units.pyi +13 -0
  301. tnfr/utils/__init__.py +282 -0
  302. tnfr/utils/__init__.pyi +215 -0
  303. tnfr/utils/cache.py +4223 -0
  304. tnfr/utils/cache.pyi +470 -0
  305. tnfr/{callback_utils.py → utils/callbacks.py} +26 -39
  306. tnfr/utils/callbacks.pyi +49 -0
  307. tnfr/utils/chunks.py +108 -0
  308. tnfr/utils/chunks.pyi +22 -0
  309. tnfr/utils/data.py +428 -0
  310. tnfr/utils/data.pyi +74 -0
  311. tnfr/utils/graph.py +85 -0
  312. tnfr/utils/graph.pyi +10 -0
  313. tnfr/utils/init.py +821 -0
  314. tnfr/utils/init.pyi +80 -0
  315. tnfr/utils/io.py +559 -0
  316. tnfr/utils/io.pyi +66 -0
  317. tnfr/{helpers → utils}/numeric.py +51 -24
  318. tnfr/utils/numeric.pyi +21 -0
  319. tnfr/validation/__init__.py +257 -0
  320. tnfr/validation/__init__.pyi +85 -0
  321. tnfr/validation/compatibility.py +460 -0
  322. tnfr/validation/compatibility.pyi +6 -0
  323. tnfr/validation/config.py +73 -0
  324. tnfr/validation/graph.py +139 -0
  325. tnfr/validation/graph.pyi +18 -0
  326. tnfr/validation/input_validation.py +755 -0
  327. tnfr/validation/invariants.py +712 -0
  328. tnfr/validation/rules.py +253 -0
  329. tnfr/validation/rules.pyi +44 -0
  330. tnfr/validation/runtime.py +279 -0
  331. tnfr/validation/runtime.pyi +28 -0
  332. tnfr/validation/sequence_validator.py +162 -0
  333. tnfr/validation/soft_filters.py +170 -0
  334. tnfr/validation/soft_filters.pyi +32 -0
  335. tnfr/validation/spectral.py +164 -0
  336. tnfr/validation/spectral.pyi +42 -0
  337. tnfr/validation/validator.py +1266 -0
  338. tnfr/validation/window.py +39 -0
  339. tnfr/validation/window.pyi +1 -0
  340. tnfr/visualization/__init__.py +98 -0
  341. tnfr/visualization/cascade_viz.py +256 -0
  342. tnfr/visualization/hierarchy.py +284 -0
  343. tnfr/visualization/sequence_plotter.py +784 -0
  344. tnfr/viz/__init__.py +60 -0
  345. tnfr/viz/matplotlib.py +278 -0
  346. tnfr/viz/matplotlib.pyi +35 -0
  347. tnfr-8.5.0.dist-info/METADATA +573 -0
  348. tnfr-8.5.0.dist-info/RECORD +353 -0
  349. {tnfr-4.5.2.dist-info → tnfr-8.5.0.dist-info}/entry_points.txt +1 -0
  350. {tnfr-4.5.2.dist-info → tnfr-8.5.0.dist-info}/licenses/LICENSE.md +1 -1
  351. tnfr/collections_utils.py +0 -300
  352. tnfr/config.py +0 -32
  353. tnfr/grammar.py +0 -344
  354. tnfr/graph_utils.py +0 -84
  355. tnfr/helpers/__init__.py +0 -71
  356. tnfr/import_utils.py +0 -228
  357. tnfr/json_utils.py +0 -162
  358. tnfr/logging_utils.py +0 -116
  359. tnfr/presets.py +0 -60
  360. tnfr/validators.py +0 -84
  361. tnfr/value_utils.py +0 -59
  362. tnfr-4.5.2.dist-info/METADATA +0 -379
  363. tnfr-4.5.2.dist-info/RECORD +0 -67
  364. {tnfr-4.5.2.dist-info → tnfr-8.5.0.dist-info}/WHEEL +0 -0
  365. {tnfr-4.5.2.dist-info → tnfr-8.5.0.dist-info}/top_level.txt +0 -0
tnfr/glyph_history.pyi ADDED
@@ -0,0 +1,35 @@
1
+ from __future__ import annotations
2
+
3
+ from collections import Counter
4
+ from collections.abc import Mapping, MutableMapping
5
+ from typing import Any
6
+
7
+ from .types import TNFRGraph
8
+
9
+ __all__: tuple[str, ...]
10
+
11
+ class HistoryDict(dict[str, Any]):
12
+ _maxlen: int
13
+ _counts: Counter[str]
14
+
15
+ def __init__(
16
+ self, data: Mapping[str, Any] | None = ..., *, maxlen: int = ...
17
+ ) -> None: ...
18
+ def get_increment(self, key: str, default: Any = ...) -> Any: ...
19
+ def __getitem__(self, key: str) -> Any: ...
20
+ def get(self, key: str, default: Any | None = ...) -> Any: ...
21
+ def __setitem__(self, key: str, value: Any) -> None: ...
22
+ def setdefault(self, key: str, default: Any | None = ...) -> Any: ...
23
+ def pop_least_used(self) -> Any: ...
24
+ def pop_least_used_batch(self, k: int) -> None: ...
25
+
26
+ def push_glyph(nd: MutableMapping[str, Any], glyph: str, window: int) -> None: ...
27
+ def recent_glyph(nd: MutableMapping[str, Any], glyph: str, window: int) -> bool: ...
28
+ def ensure_history(G: TNFRGraph) -> HistoryDict | dict[str, Any]: ...
29
+ def current_step_idx(G: TNFRGraph | Mapping[str, Any]) -> int: ...
30
+ def append_metric(
31
+ hist: MutableMapping[str, list[Any]], key: str, value: Any
32
+ ) -> None: ...
33
+ def count_glyphs(
34
+ G: TNFRGraph, window: int | None = ..., *, last_only: bool = ...
35
+ ) -> Counter[str]: ...
tnfr/glyph_runtime.py ADDED
@@ -0,0 +1,19 @@
1
+ """Runtime helpers for structural operator glyphs decoupled from validation internals.
2
+
3
+ This module provides utilities for working with glyphs (structural symbols like
4
+ AL, EN, IL, etc.) that represent the application of structural operators to nodes.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from collections.abc import Mapping
10
+ from typing import Any
11
+
12
+ __all__ = ("last_glyph",)
13
+
14
+
15
+ def last_glyph(nd: Mapping[str, Any]) -> str | None:
16
+ """Return the most recent glyph for node or ``None``."""
17
+
18
+ hist = nd.get("glyph_history")
19
+ return hist[-1] if hist else None
tnfr/glyph_runtime.pyi ADDED
@@ -0,0 +1,8 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Mapping
4
+ from typing import Any
5
+
6
+ __all__: tuple[str, ...]
7
+
8
+ def last_glyph(nd: Mapping[str, Any]) -> str | None: ...
tnfr/immutable.py CHANGED
@@ -7,23 +7,46 @@ encountered.
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
+ import threading
11
+ import weakref
12
+ from collections.abc import Mapping
10
13
  from contextlib import contextmanager
11
14
  from dataclasses import asdict, is_dataclass
12
- from functools import lru_cache, singledispatch, wraps, partial
13
- from typing import Any, Callable
14
- from collections.abc import Mapping
15
+ from functools import lru_cache, partial, singledispatch, wraps
15
16
  from types import MappingProxyType
16
- import threading
17
- import weakref
17
+ from typing import Any, Callable, Iterable, Iterator, cast
18
+
19
+ from ._compat import TypeAlias
18
20
 
19
21
  # Types considered immutable without further inspection
20
- IMMUTABLE_SIMPLE = frozenset(
21
- {int, float, complex, str, bool, bytes, type(None)}
22
+ IMMUTABLE_SIMPLE = frozenset({int, float, complex, str, bool, bytes, type(None)})
23
+
24
+ FrozenPrimitive: TypeAlias = int | float | complex | str | bool | bytes | None
25
+ """Primitive immutable values handled directly by :func:`_freeze`."""
26
+
27
+ FrozenCollectionItems: TypeAlias = tuple["FrozenSnapshot", ...]
28
+ """Frozen representation for generic iterables."""
29
+
30
+ FrozenMappingItems: TypeAlias = tuple[tuple[Any, "FrozenSnapshot"], ...]
31
+ """Frozen representation for mapping ``items()`` snapshots."""
32
+
33
+ FrozenTaggedCollection: TypeAlias = tuple[str, FrozenCollectionItems]
34
+ """Tagged iterable snapshot identifying the original container type."""
35
+
36
+ FrozenTaggedMapping: TypeAlias = tuple[str, FrozenMappingItems]
37
+ """Tagged mapping snapshot identifying the original mapping flavour."""
38
+
39
+ FrozenSnapshot: TypeAlias = (
40
+ FrozenPrimitive
41
+ | FrozenCollectionItems
42
+ | FrozenTaggedCollection
43
+ | FrozenTaggedMapping
22
44
  )
45
+ """Union describing the immutable snapshot returned by :func:`_freeze`."""
23
46
 
24
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]):
41
- """Decorator applying :func:`_cycle_guard` to ``func``."""
63
+ def _check_cycle(
64
+ func: Callable[[Any, set[int] | None], FrozenSnapshot],
65
+ ) -> Callable[[Any, set[int] | None], FrozenSnapshot]:
66
+ """Apply :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,31 @@ 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(
99
+ value: tuple[Any, ...], seen: set[int] | None = None
100
+ ) -> FrozenCollectionItems: # noqa: F401
101
+ assert seen is not None
73
102
  return tuple(_freeze(v, seen) for v in value)
74
103
 
75
104
 
76
- def _freeze_iterable(container: Any, tag: str, seen: set[int] | None) -> tuple[str, tuple]:
105
+ def _freeze_iterable(
106
+ container: Iterable[Any], tag: str, seen: set[int]
107
+ ) -> FrozenTaggedCollection:
77
108
  return (tag, tuple(_freeze(v, seen) for v in container))
78
109
 
79
110
 
80
111
  def _freeze_iterable_with_tag(
81
- value: Any, seen: set[int] | None = None, *, tag: str
82
- ) -> tuple[str, tuple]:
112
+ value: Iterable[Any], seen: set[int] | None = None, *, tag: str
113
+ ) -> FrozenTaggedCollection:
114
+ assert seen is not None
83
115
  return _freeze_iterable(value, tag, seen)
84
116
 
85
117
 
86
118
  def _register_iterable(cls: type, tag: str) -> None:
87
- _freeze.register(cls)(_check_cycle(partial(_freeze_iterable_with_tag, tag=tag)))
119
+ handler = _check_cycle(partial(_freeze_iterable_with_tag, tag=tag))
120
+ _freeze.register(cls)(
121
+ cast(Callable[[Any, set[int] | None], FrozenSnapshot], handler)
122
+ )
88
123
 
89
124
 
90
125
  for _cls, _tag in (
@@ -98,17 +133,22 @@ for _cls, _tag in (
98
133
 
99
134
  @_freeze.register(Mapping)
100
135
  @_check_cycle
101
- def _freeze_mapping(value: Mapping, seen: set[int] | None = None): # noqa: F401
136
+ def _freeze_mapping(
137
+ value: Mapping[Any, Any], seen: set[int] | None = None
138
+ ) -> FrozenTaggedMapping: # noqa: F401
139
+ assert seen is not None
102
140
  tag = "dict" if hasattr(value, "__setitem__") else "mapping"
103
141
  return (tag, tuple((k, _freeze(v, seen)) for k, v in value.items()))
104
142
 
105
143
 
106
- def _all_immutable(iterable) -> bool:
144
+ def _all_immutable(iterable: Iterable[Any]) -> bool:
107
145
  return all(_is_immutable_inner(v) for v in iterable)
108
146
 
109
147
 
110
148
  # Dispatch table kept immutable to avoid accidental mutation.
111
- _IMMUTABLE_TAG_DISPATCH: Mapping[str, Callable[[tuple], bool]] = MappingProxyType(
149
+ ImmutableTagHandler: TypeAlias = Callable[[tuple[Any, ...]], bool]
150
+
151
+ _IMMUTABLE_TAG_DISPATCH: Mapping[str, ImmutableTagHandler] = MappingProxyType(
112
152
  {
113
153
  "mapping": lambda v: _all_immutable(v[1]),
114
154
  "frozenset": lambda v: _all_immutable(v[1]),
@@ -123,11 +163,13 @@ _IMMUTABLE_TAG_DISPATCH: Mapping[str, Callable[[tuple], bool]] = MappingProxyTyp
123
163
  @lru_cache(maxsize=1024)
124
164
  @singledispatch
125
165
  def _is_immutable_inner(value: Any) -> bool:
166
+ """Return ``True`` when ``value`` belongs to the canonical immutable set."""
167
+
126
168
  return type(value) in IMMUTABLE_SIMPLE
127
169
 
128
170
 
129
171
  @_is_immutable_inner.register(tuple)
130
- def _is_immutable_inner_tuple(value: tuple) -> bool: # noqa: F401
172
+ def _is_immutable_inner_tuple(value: tuple[Any, ...]) -> bool: # noqa: F401
131
173
  if value and isinstance(value[0], str):
132
174
  handler = _IMMUTABLE_TAG_DISPATCH.get(value[0])
133
175
  if handler is not None:
@@ -136,13 +178,11 @@ def _is_immutable_inner_tuple(value: tuple) -> bool: # noqa: F401
136
178
 
137
179
 
138
180
  @_is_immutable_inner.register(frozenset)
139
- def _is_immutable_inner_frozenset(value: frozenset) -> bool: # noqa: F401
181
+ def _is_immutable_inner_frozenset(value: frozenset[Any]) -> bool: # noqa: F401
140
182
  return _all_immutable(value)
141
183
 
142
184
 
143
- _IMMUTABLE_CACHE: weakref.WeakKeyDictionary[Any, bool] = (
144
- weakref.WeakKeyDictionary()
145
- )
185
+ _IMMUTABLE_CACHE: weakref.WeakKeyDictionary[Any, bool] = weakref.WeakKeyDictionary()
146
186
  _IMMUTABLE_CACHE_LOCK = threading.Lock()
147
187
 
148
188
 
@@ -152,7 +192,7 @@ def _is_immutable(value: Any) -> bool:
152
192
  try:
153
193
  return _IMMUTABLE_CACHE[value]
154
194
  except (KeyError, TypeError):
155
- pass
195
+ pass # Not in cache or value is unhashable
156
196
 
157
197
  try:
158
198
  frozen = _freeze(value)
@@ -165,7 +205,7 @@ def _is_immutable(value: Any) -> bool:
165
205
  try:
166
206
  _IMMUTABLE_CACHE[value] = result
167
207
  except TypeError:
168
- pass
208
+ pass # Value is unhashable, cannot cache
169
209
 
170
210
  return result
171
211
 
tnfr/immutable.pyi ADDED
@@ -0,0 +1,36 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Callable, Iterator, Mapping, Union
4
+
5
+ from ._compat import TypeAlias
6
+
7
+ FrozenPrimitive: TypeAlias = Union[int, float, complex, str, bool, bytes, None]
8
+ FrozenCollectionItems: TypeAlias = tuple["FrozenSnapshot", ...]
9
+ FrozenMappingItems: TypeAlias = tuple[tuple[Any, "FrozenSnapshot"], ...]
10
+ FrozenTaggedCollection: TypeAlias = tuple[str, FrozenCollectionItems]
11
+ FrozenTaggedMapping: TypeAlias = tuple[str, FrozenMappingItems]
12
+ FrozenSnapshot: TypeAlias = Union[
13
+ FrozenPrimitive,
14
+ FrozenCollectionItems,
15
+ FrozenTaggedCollection,
16
+ FrozenTaggedMapping,
17
+ ]
18
+ ImmutableTagHandler: TypeAlias = Callable[[tuple[Any, ...]], bool]
19
+
20
+ __all__: tuple[str, ...]
21
+
22
+ def __getattr__(name: str) -> Any: ...
23
+ def _cycle_guard(value: Any, seen: set[int] | None = ...) -> Iterator[set[int]]: ...
24
+ def _check_cycle(
25
+ func: Callable[[Any, set[int] | None], FrozenSnapshot],
26
+ ) -> Callable[[Any, set[int] | None], FrozenSnapshot]: ...
27
+ def _freeze(value: Any, seen: set[int] | None = ...) -> FrozenSnapshot: ...
28
+ def _freeze_mapping(
29
+ value: Mapping[Any, Any],
30
+ seen: set[int] | None = ...,
31
+ ) -> FrozenTaggedMapping: ...
32
+ def _is_immutable(value: Any) -> bool: ...
33
+ def _is_immutable_inner(value: Any) -> bool: ...
34
+
35
+ _IMMUTABLE_CACHE: Any
36
+ _IMMUTABLE_TAG_DISPATCH: Mapping[str, ImmutableTagHandler]
tnfr/initialization.py CHANGED
@@ -1,24 +1,25 @@
1
1
  """Node initialization."""
2
2
 
3
3
  from __future__ import annotations
4
- import random
5
- from typing import TYPE_CHECKING
6
4
 
5
+ import random
7
6
  from dataclasses import dataclass
7
+ from typing import TYPE_CHECKING, cast
8
8
 
9
- from .constants import VF_KEY, THETA_KEY, get_graph_param
10
- from .helpers.numeric import clamp
9
+ from .constants import THETA_KEY, VF_KEY, get_graph_param
10
+ from .utils 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),
@@ -52,9 +53,7 @@ class InitParams:
52
53
  vf_uniform_max=get_graph_param(G, "INIT_VF_MAX"),
53
54
  vf_mean=get_graph_param(G, "INIT_VF_MEAN"),
54
55
  vf_std=get_graph_param(G, "INIT_VF_STD"),
55
- clamp_to_limits=get_graph_param(
56
- G, "INIT_VF_CLAMP_TO_LIMITS", bool
57
- ),
56
+ clamp_to_limits=get_graph_param(G, "INIT_VF_CLAMP_TO_LIMITS", bool),
58
57
  si_min=get_graph_param(G, "INIT_SI_MIN"),
59
58
  si_max=get_graph_param(G, "INIT_SI_MAX"),
60
59
  epi_val=get_graph_param(G, "INIT_EPI_VALUE"),
@@ -62,7 +61,7 @@ class InitParams:
62
61
 
63
62
 
64
63
  def _init_phase(
65
- nd: dict,
64
+ nd: NodeInitAttrMap,
66
65
  rng: random.Random,
67
66
  *,
68
67
  override: bool,
@@ -82,7 +81,7 @@ def _init_phase(
82
81
 
83
82
 
84
83
  def _init_vf(
85
- nd: dict,
84
+ nd: NodeInitAttrMap,
86
85
  rng: random.Random,
87
86
  *,
88
87
  override: bool,
@@ -118,7 +117,7 @@ def _init_vf(
118
117
 
119
118
 
120
119
  def _init_si_epi(
121
- nd: dict,
120
+ nd: NodeInitAttrMap,
122
121
  rng: random.Random,
123
122
  *,
124
123
  override: bool,
@@ -145,6 +144,8 @@ def init_node_attrs(G: "nx.Graph", *, override: bool = True) -> "nx.Graph":
145
144
  Ranges for ``Si`` are added via ``INIT_SI_MIN`` and ``INIT_SI_MAX``, and
146
145
  for ``EPI`` via ``INIT_EPI_VALUE``. If ``INIT_VF_MIN`` is greater than
147
146
  ``INIT_VF_MAX``, values are swapped and clamped to ``VF_MIN``/``VF_MAX``.
147
+ When clamping results in an invalid range (min > max), both bounds
148
+ collapse to ``VF_MIN``, ensuring ``VF_MIN``/``VF_MAX`` are hard limits.
148
149
  """
149
150
  params = InitParams.from_graph(G)
150
151
 
@@ -160,12 +161,17 @@ def init_node_attrs(G: "nx.Graph", *, override: bool = True) -> "nx.Graph":
160
161
  vf_uniform_min, vf_uniform_max = vf_uniform_max, vf_uniform_min
161
162
  params.vf_uniform_min = max(vf_uniform_min, vf_min_lim)
162
163
  params.vf_uniform_max = min(vf_uniform_max, vf_max_lim)
164
+ # After clamping to VF_MIN/VF_MAX, ensure min <= max
165
+ if params.vf_uniform_min > params.vf_uniform_max:
166
+ # Collapse to VF_MIN when the requested range is entirely below the limit
167
+ params.vf_uniform_min = params.vf_uniform_max = vf_min_lim
163
168
 
164
169
  rng = make_rng(params.seed, -1, G)
165
170
  for _, nd in G.nodes(data=True):
171
+ node_attrs = cast(NodeInitAttrMap, nd)
166
172
 
167
173
  _init_phase(
168
- nd,
174
+ node_attrs,
169
175
  rng,
170
176
  override=override,
171
177
  random_phase=params.init_rand_phase,
@@ -173,7 +179,7 @@ def init_node_attrs(G: "nx.Graph", *, override: bool = True) -> "nx.Graph":
173
179
  th_max=params.th_max,
174
180
  )
175
181
  _init_vf(
176
- nd,
182
+ node_attrs,
177
183
  rng,
178
184
  override=override,
179
185
  mode=params.vf_mode,
@@ -186,7 +192,7 @@ def init_node_attrs(G: "nx.Graph", *, override: bool = True) -> "nx.Graph":
186
192
  clamp_to_limits=params.clamp_to_limits,
187
193
  )
188
194
  _init_si_epi(
189
- nd,
195
+ node_attrs,
190
196
  rng,
191
197
  override=override,
192
198
  si_min=params.si_min,
@@ -0,0 +1,65 @@
1
+ from __future__ import annotations
2
+
3
+ import random
4
+ from dataclasses import dataclass
5
+
6
+ import networkx as nx
7
+
8
+ from .types import NodeInitAttrMap
9
+
10
+ __all__: tuple[str, str] = ("InitParams", "init_node_attrs")
11
+
12
+ @dataclass
13
+ class InitParams:
14
+ seed: int | None
15
+ init_rand_phase: bool
16
+ th_min: float
17
+ th_max: float
18
+ vf_mode: str
19
+ vf_min_lim: float
20
+ vf_max_lim: float
21
+ vf_uniform_min: float | None
22
+ vf_uniform_max: float | None
23
+ vf_mean: float
24
+ vf_std: float
25
+ clamp_to_limits: bool
26
+ si_min: float
27
+ si_max: float
28
+ epi_val: float
29
+
30
+ @classmethod
31
+ def from_graph(cls, G: nx.Graph) -> InitParams: ...
32
+
33
+ def _init_phase(
34
+ nd: NodeInitAttrMap,
35
+ rng: random.Random,
36
+ *,
37
+ override: bool,
38
+ random_phase: bool,
39
+ th_min: float,
40
+ th_max: float,
41
+ ) -> None: ...
42
+ def _init_vf(
43
+ nd: NodeInitAttrMap,
44
+ rng: random.Random,
45
+ *,
46
+ override: bool,
47
+ mode: str,
48
+ vf_uniform_min: float,
49
+ vf_uniform_max: float,
50
+ vf_mean: float,
51
+ vf_std: float,
52
+ vf_min_lim: float,
53
+ vf_max_lim: float,
54
+ clamp_to_limits: bool,
55
+ ) -> None: ...
56
+ def _init_si_epi(
57
+ nd: NodeInitAttrMap,
58
+ rng: random.Random,
59
+ *,
60
+ override: bool,
61
+ si_min: float,
62
+ si_max: float,
63
+ epi_val: float,
64
+ ) -> None: ...
65
+ def init_node_attrs(G: nx.Graph, *, override: bool = True) -> nx.Graph: ...