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/cache.py CHANGED
@@ -1,578 +1,171 @@
1
- """Core caching utilities shared across TNFR helpers.
1
+ """Unified cache interface for TNFR.
2
2
 
3
- This module consolidates structural cache helpers that previously lived in
4
- ``tnfr.helpers.cache_utils`` and ``tnfr.helpers.edge_cache``. The functions
5
- exposed here are responsible for maintaining deterministic node digests,
6
- scoped graph caches guarded by locks, and version counters that keep edge
7
- artifacts in sync with ΔNFR driven updates.
8
- """
9
-
10
- from __future__ import annotations
11
-
12
- import hashlib
13
- import threading
14
- from collections import defaultdict
15
- from collections.abc import Callable, Hashable, Iterable
16
- from contextlib import contextmanager
17
- from functools import lru_cache
18
- from dataclasses import dataclass
19
- from typing import Any, TypeVar
20
-
21
- from cachetools import LRUCache
22
- import networkx as nx # type: ignore[import-untyped]
23
-
24
- from .graph_utils import get_graph, mark_dnfr_prep_dirty
25
- from .import_utils import get_numpy
26
- from .json_utils import json_dumps
27
- from .logging_utils import get_logger
28
-
29
- T = TypeVar("T")
30
-
31
- __all__ = (
32
- "EdgeCacheManager",
33
- "LockAwareLRUCache",
34
- "NODE_SET_CHECKSUM_KEY",
35
- "cached_node_list",
36
- "cached_nodes_and_A",
37
- "clear_node_repr_cache",
38
- "edge_version_cache",
39
- "edge_version_update",
40
- "ensure_node_index_map",
41
- "ensure_node_offset_map",
42
- "get_graph_version",
43
- "increment_edge_version",
44
- "increment_graph_version",
45
- "node_set_checksum",
46
- "stable_json",
47
- )
48
-
49
- # Key used to store the node set checksum in a graph's ``graph`` attribute.
50
- NODE_SET_CHECKSUM_KEY = "_node_set_checksum_cache"
51
-
52
- logger = get_logger(__name__)
53
-
54
- # Keys of cache entries dependent on the edge version. Any change to the edge
55
- # set requires these to be dropped to avoid stale data.
56
- EDGE_VERSION_CACHE_KEYS = ("_trig_version",)
57
-
58
-
59
- class LockAwareLRUCache(LRUCache[Hashable, Any]):
60
- """``LRUCache`` that drops per-key locks when evicting items."""
61
-
62
- def __init__(self, maxsize: int, locks: dict[Hashable, threading.RLock]):
63
- super().__init__(maxsize)
64
- self._locks: dict[Hashable, threading.RLock] = locks
65
-
66
- def popitem(self) -> tuple[Hashable, Any]: # type: ignore[override]
67
- key, value = super().popitem()
68
- self._locks.pop(key, None)
69
- return key, value
70
-
71
-
72
- def _ensure_graph_entry(
73
- graph: Any,
74
- key: str,
75
- factory: Callable[[], T],
76
- validator: Callable[[Any], bool],
77
- ) -> T:
78
- """Return a validated entry from ``graph`` or create one when missing."""
79
-
80
- value = graph.get(key)
81
- if not validator(value):
82
- value = factory()
83
- graph[key] = value
84
- return value
85
-
86
-
87
- def _ensure_lock_mapping(
88
- graph: Any,
89
- key: str,
90
- *,
91
- lock_factory: Callable[[], threading.RLock] = threading.RLock,
92
- ) -> defaultdict[Hashable, threading.RLock]:
93
- """Ensure ``graph`` holds a ``defaultdict`` of locks under ``key``."""
94
-
95
- return _ensure_graph_entry(
96
- graph,
97
- key,
98
- factory=lambda: defaultdict(lock_factory),
99
- validator=lambda value: isinstance(value, defaultdict)
100
- and value.default_factory is lock_factory,
101
- )
102
-
103
-
104
- def _prune_locks(
105
- cache: dict[Hashable, Any] | LRUCache[Hashable, Any] | None,
106
- locks: dict[Hashable, threading.RLock]
107
- | defaultdict[Hashable, threading.RLock]
108
- | None,
109
- ) -> None:
110
- """Drop locks with no corresponding cache entry."""
111
-
112
- if not isinstance(locks, dict):
113
- return
114
- cache_keys = cache.keys() if isinstance(cache, dict) else ()
115
- for key in list(locks.keys()):
116
- if key not in cache_keys:
117
- locks.pop(key, None)
118
-
119
-
120
- def get_graph_version(graph: Any, key: str, default: int = 0) -> int:
121
- """Return integer version stored in ``graph`` under ``key``."""
122
-
123
- return int(graph.get(key, default))
124
-
125
-
126
- def increment_graph_version(graph: Any, key: str) -> int:
127
- """Increment and store a version counter in ``graph`` under ``key``."""
128
-
129
- version = get_graph_version(graph, key) + 1
130
- graph[key] = version
131
- return version
132
-
133
-
134
- def stable_json(obj: Any) -> str:
135
- """Return a JSON string with deterministic ordering for ``obj``."""
136
-
137
- return json_dumps(
138
- obj,
139
- sort_keys=True,
140
- ensure_ascii=False,
141
- to_bytes=False,
142
- )
143
-
144
-
145
- @lru_cache(maxsize=1024)
146
- def _node_repr_digest(obj: Any) -> tuple[str, bytes]:
147
- """Return cached stable representation and digest for ``obj``."""
148
-
149
- try:
150
- repr_ = stable_json(obj)
151
- except TypeError:
152
- repr_ = repr(obj)
153
- digest = hashlib.blake2b(repr_.encode("utf-8"), digest_size=16).digest()
154
- return repr_, digest
155
-
156
-
157
- def clear_node_repr_cache() -> None:
158
- """Clear cached node representations used for checksums."""
159
-
160
- _node_repr_digest.cache_clear()
161
-
162
-
163
- def _node_repr(n: Any) -> str:
164
- """Stable representation for node hashing and sorting."""
165
-
166
- return _node_repr_digest(n)[0]
167
-
168
-
169
- def _iter_node_digests(
170
- nodes: Iterable[Any], *, presorted: bool
171
- ) -> Iterable[bytes]:
172
- """Yield node digests in a deterministic order."""
173
-
174
- if presorted:
175
- for node in nodes:
176
- yield _node_repr_digest(node)[1]
177
- else:
178
- for _, digest in sorted(
179
- (_node_repr_digest(n) for n in nodes), key=lambda x: x[0]
180
- ):
181
- yield digest
182
-
183
-
184
- def _node_set_checksum_no_nodes(
185
- G: nx.Graph,
186
- graph: Any,
187
- *,
188
- presorted: bool,
189
- store: bool,
190
- ) -> str:
191
- """Checksum helper when no explicit node set is provided."""
192
-
193
- nodes_view = G.nodes()
194
- current_nodes = frozenset(nodes_view)
195
- cached = graph.get(NODE_SET_CHECKSUM_KEY)
196
- if cached and len(cached) == 3 and cached[2] == current_nodes:
197
- return cached[1]
198
-
199
- hasher = hashlib.blake2b(digest_size=16)
200
- for digest in _iter_node_digests(nodes_view, presorted=presorted):
201
- hasher.update(digest)
202
-
203
- checksum = hasher.hexdigest()
204
- if store:
205
- token = checksum[:16]
206
- if cached and cached[0] == token:
207
- return cached[1]
208
- graph[NODE_SET_CHECKSUM_KEY] = (token, checksum, current_nodes)
209
- else:
210
- graph.pop(NODE_SET_CHECKSUM_KEY, None)
211
- return checksum
212
-
213
-
214
- def node_set_checksum(
215
- G: nx.Graph,
216
- nodes: Iterable[Any] | None = None,
217
- *,
218
- presorted: bool = False,
219
- store: bool = True,
220
- ) -> str:
221
- """Return a BLAKE2b checksum of ``G``'s node set."""
222
-
223
- graph = get_graph(G)
224
- if nodes is None:
225
- return _node_set_checksum_no_nodes(
226
- G, graph, presorted=presorted, store=store
227
- )
3
+ This module provides a consolidated entry point for all TNFR caching needs,
4
+ integrating both the core CacheManager infrastructure and the advanced
5
+ hierarchical cache with dependency tracking.
228
6
 
229
- hasher = hashlib.blake2b(digest_size=16)
230
- for digest in _iter_node_digests(nodes, presorted=presorted):
231
- hasher.update(digest)
7
+ Quick Start
8
+ -----------
9
+ For basic caching with metrics and persistence layers::
232
10
 
233
- checksum = hasher.hexdigest()
234
- if store:
235
- token = checksum[:16]
236
- cached = graph.get(NODE_SET_CHECKSUM_KEY)
237
- if cached and cached[0] == token:
238
- return cached[1]
239
- graph[NODE_SET_CHECKSUM_KEY] = (token, checksum)
240
- else:
241
- graph.pop(NODE_SET_CHECKSUM_KEY, None)
242
- return checksum
11
+ from tnfr.cache import CacheManager, build_cache_manager
243
12
 
13
+ # Create a cache manager with default settings
14
+ manager = build_cache_manager()
244
15
 
245
- @dataclass(slots=True)
246
- class NodeCache:
247
- """Container for cached node data."""
16
+ # Register a named cache
17
+ manager.register("my_cache", lambda: {})
248
18
 
249
- checksum: str
250
- nodes: tuple[Any, ...]
251
- sorted_nodes: tuple[Any, ...] | None = None
252
- idx: dict[Any, int] | None = None
253
- offset: dict[Any, int] | None = None
19
+ # Get and use the cache
20
+ cache = manager.get("my_cache")
254
21
 
255
- @property
256
- def n(self) -> int:
257
- return len(self.nodes)
22
+ For advanced hierarchical caching with dependency tracking::
258
23
 
24
+ from tnfr.cache import TNFRHierarchicalCache, CacheLevel
259
25
 
260
- def _update_node_cache(
261
- graph: Any,
262
- nodes: tuple[Any, ...],
263
- key: str,
264
- *,
265
- checksum: str,
266
- sorted_nodes: tuple[Any, ...] | None = None,
267
- ) -> None:
268
- """Store ``nodes`` and ``checksum`` in ``graph`` under ``key``."""
26
+ # Create hierarchical cache
27
+ cache = TNFRHierarchicalCache(max_memory_mb=256)
269
28
 
270
- graph[f"{key}_cache"] = NodeCache(
271
- checksum=checksum, nodes=nodes, sorted_nodes=sorted_nodes
29
+ # Store with dependencies
30
+ cache.set(
31
+ "metric_key",
32
+ value=0.95,
33
+ level=CacheLevel.DERIVED_METRICS,
34
+ dependencies={'graph_topology', 'node_properties'}
272
35
  )
273
- graph[f"{key}_checksum"] = checksum
274
36
 
37
+ # Selective invalidation
38
+ cache.invalidate_by_dependency('graph_topology')
275
39
 
276
- def _refresh_node_list_cache(
277
- G: nx.Graph,
278
- graph: Any,
279
- *,
280
- sort_nodes: bool,
281
- current_n: int,
282
- ) -> tuple[Any, ...]:
283
- """Refresh the cached node list and return the nodes."""
284
-
285
- nodes = tuple(G.nodes())
286
- checksum = node_set_checksum(G, nodes, store=True)
287
- sorted_nodes = tuple(sorted(nodes, key=_node_repr)) if sort_nodes else None
288
- _update_node_cache(
289
- graph,
290
- nodes,
291
- "_node_list",
292
- checksum=checksum,
293
- sorted_nodes=sorted_nodes,
294
- )
295
- graph["_node_list_len"] = current_n
296
- return nodes
40
+ For graph-specific caching::
297
41
 
42
+ from tnfr.cache import configure_graph_cache_limits
43
+ import networkx as nx
298
44
 
299
- def _reuse_node_list_cache(
300
- graph: Any,
301
- cache: NodeCache,
302
- nodes: tuple[Any, ...],
303
- sorted_nodes: tuple[Any, ...] | None,
304
- *,
305
- sort_nodes: bool,
306
- new_checksum: str | None,
307
- ) -> None:
308
- """Reuse existing node cache and record its checksum if missing."""
309
-
310
- checksum = cache.checksum if new_checksum is None else new_checksum
311
- if sort_nodes and sorted_nodes is None:
312
- sorted_nodes = tuple(sorted(nodes, key=_node_repr))
313
- _update_node_cache(
314
- graph,
315
- nodes,
316
- "_node_list",
317
- checksum=checksum,
318
- sorted_nodes=sorted_nodes,
45
+ G = nx.Graph()
46
+ configure_graph_cache_limits(
47
+ G,
48
+ default_capacity=256,
49
+ overrides={"dnfr_prep": 512}
319
50
  )
320
51
 
52
+ See Also
53
+ --------
54
+ tnfr.utils.cache : Core cache infrastructure and CacheManager
55
+ tnfr.caching : Advanced hierarchical cache with dependency tracking
56
+ tnfr.metrics.cache_utils : Hot-path cache configuration helpers
57
+ """
321
58
 
322
- def _cache_node_list(G: nx.Graph) -> tuple[Any, ...]:
323
- """Cache and return the tuple of nodes for ``G``."""
324
-
325
- graph = get_graph(G)
326
- cache: NodeCache | None = graph.get("_node_list_cache")
327
- nodes = cache.nodes if cache else None
328
- sorted_nodes = cache.sorted_nodes if cache else None
329
- stored_len = graph.get("_node_list_len")
330
- current_n = G.number_of_nodes()
331
- dirty = bool(graph.pop("_node_list_dirty", False))
332
-
333
- invalid = nodes is None or stored_len != current_n or dirty
334
- new_checksum: str | None = None
335
-
336
- if not invalid and cache:
337
- new_checksum = node_set_checksum(G)
338
- invalid = cache.checksum != new_checksum
339
-
340
- sort_nodes = bool(graph.get("SORT_NODES", False))
341
-
342
- if invalid:
343
- nodes = _refresh_node_list_cache(
344
- G, graph, sort_nodes=sort_nodes, current_n=current_n
345
- )
346
- elif cache and "_node_list_checksum" not in graph:
347
- _reuse_node_list_cache(
348
- graph,
349
- cache,
350
- nodes,
351
- sorted_nodes,
352
- sort_nodes=sort_nodes,
353
- new_checksum=new_checksum,
354
- )
355
- else:
356
- if sort_nodes and sorted_nodes is None and cache is not None:
357
- cache.sorted_nodes = tuple(sorted(nodes, key=_node_repr))
358
- return nodes
359
-
360
-
361
- def cached_node_list(G: nx.Graph) -> tuple[Any, ...]:
362
- """Public wrapper returning the cached node tuple for ``G``."""
363
-
364
- return _cache_node_list(G)
365
-
366
-
367
- def _ensure_node_map(
368
- G,
369
- *,
370
- attrs: tuple[str, ...],
371
- sort: bool = False,
372
- ) -> dict[Any, int]:
373
- """Return cached node-to-index/offset mappings stored on ``NodeCache``."""
374
-
375
- graph = G.graph
376
- _cache_node_list(G)
377
- cache: NodeCache = graph["_node_list_cache"]
378
-
379
- missing = [attr for attr in attrs if getattr(cache, attr) is None]
380
- if missing:
381
- if sort:
382
- nodes = cache.sorted_nodes
383
- if nodes is None:
384
- nodes = cache.sorted_nodes = tuple(
385
- sorted(cache.nodes, key=_node_repr)
386
- )
387
- else:
388
- nodes = cache.nodes
389
- mappings: dict[str, dict[Any, int]] = {attr: {} for attr in missing}
390
- for idx, node in enumerate(nodes):
391
- for attr in missing:
392
- mappings[attr][node] = idx
393
- for attr in missing:
394
- setattr(cache, attr, mappings[attr])
395
- return getattr(cache, attrs[0])
396
-
397
-
398
- def ensure_node_index_map(G) -> dict[Any, int]:
399
- """Return cached node-to-index mapping for ``G``."""
400
-
401
- return _ensure_node_map(G, attrs=("idx",), sort=False)
402
-
403
-
404
- def ensure_node_offset_map(G) -> dict[Any, int]:
405
- """Return cached node-to-offset mapping for ``G``."""
406
-
407
- sort = bool(G.graph.get("SORT_NODES", False))
408
- return _ensure_node_map(G, attrs=("offset",), sort=sort)
409
-
410
-
411
- class EdgeCacheManager:
412
- """Coordinate cache storage and per-key locks for edge version caches."""
413
-
414
- _LOCK = threading.RLock()
415
-
416
- def __init__(self, graph: Any) -> None:
417
- self.graph = graph
418
- self.cache_key = "_edge_version_cache"
419
- self.locks_key = "_edge_version_cache_locks"
420
-
421
- def _validator(self, max_entries: int | None) -> Callable[[Any], bool]:
422
- if max_entries is None:
423
- return lambda value: value is not None and not isinstance(value, LRUCache)
424
- return lambda value: isinstance(value, LRUCache) and value.maxsize == max_entries
425
-
426
- def _factory(
427
- self,
428
- max_entries: int | None,
429
- locks: dict[Hashable, threading.RLock]
430
- | defaultdict[Hashable, threading.RLock],
431
- ) -> dict[Hashable, Any] | LRUCache[Hashable, Any]:
432
- if max_entries:
433
- return LockAwareLRUCache(max_entries, locks) # type: ignore[arg-type]
434
- return {}
435
-
436
- def get_cache(
437
- self,
438
- max_entries: int | None,
439
- *,
440
- create: bool = True,
441
- ) -> tuple[
442
- dict[Hashable, Any] | LRUCache[Hashable, Any] | None,
443
- dict[Hashable, threading.RLock]
444
- | defaultdict[Hashable, threading.RLock]
445
- | None,
446
- ]:
447
- """Return the cache and lock mapping for the manager's graph."""
448
-
449
- with self._LOCK:
450
- if not create:
451
- cache = self.graph.get(self.cache_key)
452
- locks = self.graph.get(self.locks_key)
453
- return cache, locks
454
-
455
- locks = _ensure_lock_mapping(self.graph, self.locks_key)
456
- cache = _ensure_graph_entry(
457
- self.graph,
458
- self.cache_key,
459
- factory=lambda: self._factory(max_entries, locks),
460
- validator=self._validator(max_entries),
461
- )
462
- if max_entries is None:
463
- _prune_locks(cache, locks)
464
- return cache, locks
465
-
466
-
467
- def edge_version_cache(
468
- G: Any,
469
- key: Hashable,
470
- builder: Callable[[], T],
471
- *,
472
- max_entries: int | None = 128,
473
- ) -> T:
474
- """Return cached ``builder`` output tied to the edge version of ``G``."""
475
-
476
- if max_entries is not None:
477
- max_entries = int(max_entries)
478
- if max_entries < 0:
479
- raise ValueError("max_entries must be non-negative or None")
480
- if max_entries is not None and max_entries == 0:
481
- return builder()
482
-
483
- graph = get_graph(G)
484
- manager = graph.get("_edge_cache_manager") # type: ignore[assignment]
485
- if not isinstance(manager, EdgeCacheManager) or manager.graph is not graph:
486
- manager = EdgeCacheManager(graph)
487
- graph["_edge_cache_manager"] = manager
488
-
489
- cache, locks = manager.get_cache(max_entries)
490
- edge_version = get_graph_version(graph, "_edge_version")
491
- lock = locks[key]
492
-
493
- with lock:
494
- entry = cache.get(key)
495
- if entry is not None and entry[0] == edge_version:
496
- return entry[1]
497
-
498
- try:
499
- value = builder()
500
- except (RuntimeError, ValueError) as exc: # pragma: no cover - logging side effect
501
- logger.exception("edge_version_cache builder failed for %r: %s", key, exc)
502
- raise
503
- else:
504
- with lock:
505
- entry = cache.get(key)
506
- if entry is not None and entry[0] == edge_version:
507
- return entry[1]
508
- cache[key] = (edge_version, value)
509
- return value
510
-
511
-
512
- def cached_nodes_and_A(
513
- G: nx.Graph, *, cache_size: int | None = 1, require_numpy: bool = False
514
- ) -> tuple[tuple[Any, ...], Any]:
515
- """Return cached nodes tuple and adjacency matrix for ``G``."""
516
-
517
- nodes = cached_node_list(G)
518
- graph = G.graph
519
-
520
- checksum = getattr(graph.get("_node_list_cache"), "checksum", None)
521
- if checksum is None:
522
- checksum = graph.get("_node_list_checksum")
523
- if checksum is None:
524
- node_set_cache = graph.get(NODE_SET_CHECKSUM_KEY)
525
- if isinstance(node_set_cache, tuple) and len(node_set_cache) >= 2:
526
- checksum = node_set_cache[1]
527
- if checksum is None:
528
- checksum = ""
529
-
530
- key = f"_dnfr_{len(nodes)}_{checksum}"
531
- graph["_dnfr_nodes_checksum"] = checksum
532
-
533
- def builder() -> tuple[tuple[Any, ...], Any]:
534
- np = get_numpy()
535
- if np is None:
536
- return nodes, None
537
- A = nx.to_numpy_array(G, nodelist=nodes, weight=None, dtype=float)
538
- return nodes, A
539
-
540
- nodes, A = edge_version_cache(G, key, builder, max_entries=cache_size)
541
-
542
- if require_numpy and A is None:
543
- raise RuntimeError("NumPy is required for adjacency caching")
544
-
545
- return nodes, A
546
-
547
-
548
- def _reset_edge_caches(graph: Any, G: Any) -> None:
549
- """Clear caches affected by edge updates."""
550
-
551
- cache, locks = EdgeCacheManager(graph).get_cache(None, create=False)
552
- if isinstance(cache, (dict, LRUCache)):
553
- cache.clear()
554
- if isinstance(locks, dict):
555
- locks.clear()
556
- mark_dnfr_prep_dirty(G)
557
- clear_node_repr_cache()
558
- for key in EDGE_VERSION_CACHE_KEYS:
559
- graph.pop(key, None)
560
-
561
-
562
- def increment_edge_version(G: Any) -> None:
563
- """Increment the edge version counter in ``G.graph``."""
59
+ from __future__ import annotations
564
60
 
565
- graph = get_graph(G)
566
- increment_graph_version(graph, "_edge_version")
567
- _reset_edge_caches(graph, G)
61
+ # Core cache infrastructure from tnfr.utils.cache
62
+ from .utils.cache import (
63
+ # Main classes
64
+ CacheManager,
65
+ CacheLayer,
66
+ MappingCacheLayer,
67
+ ShelveCacheLayer,
68
+ RedisCacheLayer,
69
+ InstrumentedLRUCache,
70
+ ManagedLRUCache,
71
+ EdgeCacheManager,
72
+ # Configuration and stats
73
+ CacheCapacityConfig,
74
+ CacheStatistics,
75
+ # Factory functions
76
+ build_cache_manager,
77
+ create_hmac_signer,
78
+ create_hmac_validator,
79
+ create_secure_shelve_layer,
80
+ create_secure_redis_layer,
81
+ # Graph-specific helpers
82
+ configure_graph_cache_limits,
83
+ configure_global_cache_layers,
84
+ reset_global_cache_manager,
85
+ edge_version_cache,
86
+ cached_node_list,
87
+ cached_nodes_and_A,
88
+ increment_edge_version,
89
+ edge_version_update,
90
+ # ΔNFR caching
91
+ DnfrCache,
92
+ DnfrPrepState,
93
+ new_dnfr_cache,
94
+ # Security
95
+ SecurityError,
96
+ SecurityWarning,
97
+ )
568
98
 
99
+ # Hierarchical cache with dependency tracking (now all in utils.cache)
100
+ from .utils.cache import (
101
+ TNFRHierarchicalCache,
102
+ CacheLevel,
103
+ CacheEntry,
104
+ cache_tnfr_computation,
105
+ invalidate_function_cache,
106
+ get_global_cache,
107
+ set_global_cache,
108
+ reset_global_cache,
109
+ GraphChangeTracker,
110
+ track_node_property_update,
111
+ PersistentTNFRCache,
112
+ )
569
113
 
570
- @contextmanager
571
- def edge_version_update(G: Any):
572
- """Scope a batch of edge mutations."""
114
+ # Hot-path cache configuration helpers
115
+ from .metrics.cache_utils import (
116
+ get_cache_config,
117
+ configure_hot_path_caches,
118
+ log_cache_metrics,
119
+ CacheStats,
120
+ )
573
121
 
574
- increment_edge_version(G)
575
- try:
576
- yield
577
- finally:
578
- increment_edge_version(G)
122
+ __all__ = [
123
+ # Core cache classes
124
+ "CacheManager",
125
+ "CacheLayer",
126
+ "MappingCacheLayer",
127
+ "ShelveCacheLayer",
128
+ "RedisCacheLayer",
129
+ "InstrumentedLRUCache",
130
+ "ManagedLRUCache",
131
+ "EdgeCacheManager",
132
+ # Configuration and stats
133
+ "CacheCapacityConfig",
134
+ "CacheStatistics",
135
+ "CacheStats",
136
+ # Factory functions
137
+ "build_cache_manager",
138
+ "create_hmac_signer",
139
+ "create_hmac_validator",
140
+ "create_secure_shelve_layer",
141
+ "create_secure_redis_layer",
142
+ # Graph-specific helpers
143
+ "configure_graph_cache_limits",
144
+ "configure_global_cache_layers",
145
+ "reset_global_cache_manager",
146
+ "edge_version_cache",
147
+ "cached_node_list",
148
+ "cached_nodes_and_A",
149
+ "increment_edge_version",
150
+ "edge_version_update",
151
+ "get_cache_config",
152
+ "configure_hot_path_caches",
153
+ "log_cache_metrics",
154
+ # ΔNFR caching
155
+ "DnfrCache",
156
+ "DnfrPrepState",
157
+ "new_dnfr_cache",
158
+ # Hierarchical cache
159
+ "TNFRHierarchicalCache",
160
+ "CacheLevel",
161
+ "CacheEntry",
162
+ "cache_tnfr_computation",
163
+ "invalidate_function_cache",
164
+ # Change tracking
165
+ "GraphChangeTracker",
166
+ "track_node_property_update",
167
+ "PersistentTNFRCache",
168
+ # Security
169
+ "SecurityError",
170
+ "SecurityWarning",
171
+ ]