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/operators/jitter.py CHANGED
@@ -1,24 +1,31 @@
1
+ """Jitter operators for reproducible phase perturbations."""
2
+
1
3
  from __future__ import annotations
2
- from typing import Any, TYPE_CHECKING
3
4
 
4
- from cachetools import LRUCache
5
+ import threading
6
+ from typing import TYPE_CHECKING, Any, cast
5
7
 
6
- from ..cache import ensure_node_offset_map
8
+ from ..rng import base_seed, cache_enabled
9
+ from ..rng import clear_rng_cache as _clear_rng_cache
7
10
  from ..rng import (
8
- ScopedCounterCache,
9
11
  make_rng,
10
- base_seed,
11
- cache_enabled,
12
- clear_rng_cache as _clear_rng_cache,
13
12
  seed_hash,
14
13
  )
15
- from ..import_utils import get_nodonx
14
+ from ..types import NodeId, TNFRGraph
15
+ from ..utils import (
16
+ CacheManager,
17
+ InstrumentedLRUCache,
18
+ ScopedCounterCache,
19
+ build_cache_manager,
20
+ ensure_node_offset_map,
21
+ get_nodenx,
22
+ )
16
23
 
17
24
  if TYPE_CHECKING: # pragma: no cover - type checking only
18
- from ..node import NodoProtocol
25
+ from ..node import NodeProtocol
19
26
 
20
27
  # Guarded by the cache lock to ensure thread-safe access. ``seq`` stores
21
- # per-scope jitter sequence counters in an LRU cache bounded to avoid
28
+ # per-scope jitter sequence counters in an instrumented LRU cache bounded to avoid
22
29
  # unbounded memory usage.
23
30
  _JITTER_MAX_ENTRIES = 1024
24
31
 
@@ -26,18 +33,53 @@ _JITTER_MAX_ENTRIES = 1024
26
33
  class JitterCache:
27
34
  """Container for jitter-related caches."""
28
35
 
29
- def __init__(self, max_entries: int = _JITTER_MAX_ENTRIES) -> None:
30
- self._sequence = ScopedCounterCache("jitter", max_entries)
31
- self.settings: dict[str, Any] = {"max_entries": self._sequence.max_entries}
36
+ def __init__(
37
+ self,
38
+ max_entries: int = _JITTER_MAX_ENTRIES,
39
+ *,
40
+ manager: CacheManager | None = None,
41
+ ) -> None:
42
+ self._manager = manager or build_cache_manager()
43
+ if not self._manager.has_override("scoped_counter:jitter"):
44
+ self._manager.configure(
45
+ overrides={"scoped_counter:jitter": int(max_entries)}
46
+ )
47
+ self._sequence = ScopedCounterCache(
48
+ "jitter",
49
+ max_entries=None,
50
+ manager=self._manager,
51
+ default_max_entries=int(max_entries),
52
+ )
53
+ self._settings_key = "jitter_settings"
54
+ self._manager.register(
55
+ self._settings_key,
56
+ lambda: {"max_entries": self._sequence.max_entries},
57
+ reset=self._reset_settings,
58
+ )
59
+
60
+ def _reset_settings(self, settings: dict[str, Any] | None) -> dict[str, Any]:
61
+ return {"max_entries": self._sequence.max_entries}
62
+
63
+ def _refresh_settings(self) -> None:
64
+ self._manager.update(
65
+ self._settings_key,
66
+ lambda _: {"max_entries": self._sequence.max_entries},
67
+ )
68
+
69
+ @property
70
+ def manager(self) -> CacheManager:
71
+ """Expose the cache manager backing this cache."""
72
+
73
+ return self._manager
32
74
 
33
75
  @property
34
- def seq(self) -> LRUCache[tuple[int, int], int]:
35
- """Expose the sequence cache for tests and diagnostics."""
76
+ def seq(self) -> InstrumentedLRUCache[tuple[int, int], int]:
77
+ """Expose the instrumented sequence cache for tests and diagnostics."""
36
78
 
37
79
  return self._sequence.cache
38
80
 
39
81
  @property
40
- def lock(self):
82
+ def lock(self) -> threading.Lock | threading.RLock:
41
83
  """Return the lock protecting the sequence cache."""
42
84
 
43
85
  return self._sequence.lock
@@ -53,21 +95,26 @@ class JitterCache:
53
95
  """Set the maximum number of cached jitter sequences."""
54
96
 
55
97
  self._sequence.configure(max_entries=int(value))
56
- self.settings["max_entries"] = self._sequence.max_entries
98
+ self._refresh_settings()
57
99
 
58
- def setup(
59
- self, force: bool = False, max_entries: int | None = None
60
- ) -> None:
100
+ @property
101
+ def settings(self) -> dict[str, Any]:
102
+ """Return jitter cache settings stored on the manager."""
103
+
104
+ return cast(dict[str, Any], self._manager.get(self._settings_key))
105
+
106
+ def setup(self, force: bool = False, max_entries: int | None = None) -> None:
61
107
  """Ensure jitter cache matches the configured size."""
62
108
 
63
109
  self._sequence.configure(force=force, max_entries=max_entries)
64
- self.settings["max_entries"] = self._sequence.max_entries
110
+ self._refresh_settings()
65
111
 
66
112
  def clear(self) -> None:
67
113
  """Clear cached RNGs and jitter state."""
68
114
 
69
115
  _clear_rng_cache()
70
116
  self._sequence.clear()
117
+ self._manager.clear(self._settings_key)
71
118
 
72
119
  def bump(self, key: tuple[int, int]) -> int:
73
120
  """Return current jitter sequence counter for ``key`` and increment it."""
@@ -78,40 +125,57 @@ class JitterCache:
78
125
  class JitterCacheManager:
79
126
  """Manager exposing the jitter cache without global reassignment."""
80
127
 
81
- def __init__(self, cache: JitterCache | None = None) -> None:
82
- self.cache = cache or JitterCache()
128
+ def __init__(
129
+ self,
130
+ cache: JitterCache | None = None,
131
+ *,
132
+ manager: CacheManager | None = None,
133
+ ) -> None:
134
+ if cache is not None:
135
+ self.cache = cache
136
+ self._manager = cache.manager
137
+ else:
138
+ self._manager = manager or build_cache_manager()
139
+ self.cache = JitterCache(manager=self._manager)
83
140
 
84
141
  # Convenience passthrough properties
85
142
  @property
86
- def seq(self) -> LRUCache[tuple[int, int], int]:
143
+ def seq(self) -> InstrumentedLRUCache[tuple[int, int], int]:
144
+ """Expose the underlying instrumented jitter sequence cache."""
145
+
87
146
  return self.cache.seq
88
147
 
89
148
  @property
90
149
  def settings(self) -> dict[str, Any]:
150
+ """Return persisted jitter cache configuration."""
151
+
91
152
  return self.cache.settings
92
153
 
93
154
  @property
94
- def lock(self):
155
+ def lock(self) -> threading.Lock | threading.RLock:
156
+ """Return the lock associated with the jitter cache."""
157
+
95
158
  return self.cache.lock
96
159
 
97
160
  @property
98
161
  def max_entries(self) -> int:
99
162
  """Return the maximum number of cached jitter entries."""
163
+
100
164
  return self.cache.max_entries
101
165
 
102
166
  @max_entries.setter
103
167
  def max_entries(self, value: int) -> None:
104
168
  """Set the maximum number of cached jitter entries."""
169
+
105
170
  self.cache.max_entries = value
106
171
 
107
- def setup(
108
- self, force: bool = False, max_entries: int | None = None
109
- ) -> None:
172
+ def setup(self, force: bool = False, max_entries: int | None = None) -> None:
110
173
  """Ensure jitter cache matches the configured size.
111
174
 
112
175
  ``max_entries`` may be provided to explicitly resize the cache.
113
176
  When omitted the existing ``cache.max_entries`` is preserved.
114
177
  """
178
+
115
179
  if max_entries is not None:
116
180
  self.cache.setup(force=True, max_entries=max_entries)
117
181
  else:
@@ -119,6 +183,7 @@ class JitterCacheManager:
119
183
 
120
184
  def clear(self) -> None:
121
185
  """Clear cached RNGs and jitter state."""
186
+
122
187
  self.cache.clear()
123
188
 
124
189
  def bump(self, key: tuple[int, int]) -> int:
@@ -148,27 +213,31 @@ def reset_jitter_manager() -> None:
148
213
  _JITTER_MANAGER = None
149
214
 
150
215
 
151
- def _node_offset(G, n) -> int:
216
+ def _node_offset(G: TNFRGraph, n: NodeId) -> int:
152
217
  """Deterministic node index used for jitter seeds."""
153
218
  mapping = ensure_node_offset_map(G)
154
219
  return int(mapping.get(n, 0))
155
220
 
156
221
 
157
- def _resolve_jitter_seed(node: NodoProtocol) -> tuple[int, int]:
158
- NodoNX = get_nodonx()
159
- if NodoNX is None:
160
- raise ImportError("NodoNX is unavailable")
161
- if isinstance(node, NodoNX):
162
- return _node_offset(node.G, node.n), id(node.G)
222
+ def _resolve_jitter_seed(node: NodeProtocol) -> tuple[int, int]:
223
+ node_nx_type = get_nodenx()
224
+ if node_nx_type is None:
225
+ raise ImportError("NodeNX is unavailable")
226
+ if isinstance(node, node_nx_type):
227
+ graph = cast(TNFRGraph, getattr(node, "G"))
228
+ node_id = cast(NodeId, getattr(node, "n"))
229
+ return _node_offset(graph, node_id), id(graph)
163
230
  uid = getattr(node, "_noise_uid", None)
164
231
  if uid is None:
165
232
  uid = id(node)
166
233
  setattr(node, "_noise_uid", uid)
167
- return int(uid), id(node)
234
+ graph = cast(TNFRGraph | None, getattr(node, "G", None))
235
+ scope = graph if graph is not None else node
236
+ return int(uid), id(scope)
168
237
 
169
238
 
170
239
  def random_jitter(
171
- node: NodoProtocol,
240
+ node: NodeProtocol,
172
241
  amplitude: float,
173
242
  ) -> float:
174
243
  """Return deterministic noise in ``[-amplitude, amplitude]`` for ``node``.
@@ -184,7 +253,7 @@ def random_jitter(
184
253
  seed_root = base_seed(node.G)
185
254
  seed_key, scope_id = _resolve_jitter_seed(node)
186
255
 
187
- cache_key = (seed_root, scope_id)
256
+ cache_key = (seed_root, scope_id, seed_key)
188
257
  seq = 0
189
258
  if cache_enabled(node.G):
190
259
  manager = get_jitter_manager()
@@ -0,0 +1,11 @@
1
+ from typing import Any
2
+
3
+ __all__: Any
4
+
5
+ def __getattr__(name: str) -> Any: ...
6
+
7
+ JitterCache: Any
8
+ JitterCacheManager: Any
9
+ get_jitter_manager: Any
10
+ random_jitter: Any
11
+ reset_jitter_manager: Any
@@ -0,0 +1,314 @@
1
+ """Node lifecycle management for TNFR canonical theory.
2
+
3
+ According to TNFR theory (El pulso que nos atraviesa, p.44), nodes follow
4
+ a canonical lifecycle:
5
+
6
+ 1. Activation - Node emerges through sufficient reorganization
7
+ 2. Stabilization - Finds coherent phase and form
8
+ 3. Propagation - Reorganizes its network environment
9
+ 4. Mutation - Transforms through dissonance
10
+ 5. Collapse - Loses phase/frequency and dissolves
11
+
12
+ This module provides lifecycle state tracking and transition validation.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from enum import Enum
18
+ from typing import TYPE_CHECKING, Any
19
+
20
+ if TYPE_CHECKING:
21
+ from ..types import NodeId, TNFRGraph
22
+
23
+ from ..alias import get_attr
24
+ from ..constants.aliases import ALIAS_EPI, ALIAS_VF, ALIAS_DNFR, ALIAS_THETA
25
+
26
+ __all__ = [
27
+ "LifecycleState",
28
+ "CollapseReason",
29
+ "get_lifecycle_state",
30
+ "check_collapse_conditions",
31
+ "should_collapse",
32
+ ]
33
+
34
+ # Default thresholds for lifecycle state determination
35
+ DEFAULT_MIN_PHASE_COUPLING = 0.1 # Minimum phase coupling before decoupling collapse
36
+
37
+
38
+ class LifecycleState(Enum):
39
+ """Canonical TNFR node lifecycle states.
40
+
41
+ These states correspond to the fundamental phases of node existence
42
+ in the Resonant Fractal Nature paradigm.
43
+ """
44
+
45
+ DORMANT = "dormant"
46
+ """Node exists but has minimal structural frequency (νf < activation_threshold)."""
47
+
48
+ ACTIVATION = "activation"
49
+ """Node is emerging with increasing νf and ΔNFR."""
50
+
51
+ STABILIZATION = "stabilization"
52
+ """Node is finding coherent form (high C(t), decreasing |ΔNFR|)."""
53
+
54
+ PROPAGATION = "propagation"
55
+ """Node is reorganizing its environment (high phase coupling)."""
56
+
57
+ MUTATION = "mutation"
58
+ """Node is undergoing phase transformation (high |ΔNFR|, phase shifts)."""
59
+
60
+ COLLAPSING = "collapsing"
61
+ """Node is losing coherence and approaching dissolution."""
62
+
63
+ COLLAPSED = "collapsed"
64
+ """Node has dissolved (νf → 0 or extreme dissonance)."""
65
+
66
+
67
+ class CollapseReason(Enum):
68
+ """Canonical reasons for node collapse in TNFR.
69
+
70
+ These correspond to the fundamental ways structural coherence can fail.
71
+ """
72
+
73
+ FREQUENCY_FAILURE = "frequency_failure"
74
+ """Structural frequency dropped below collapse threshold (νf → 0)."""
75
+
76
+ EXTREME_DISSONANCE = "extreme_dissonance"
77
+ """ΔNFR magnitude exceeded bifurcation threshold."""
78
+
79
+ NETWORK_DECOUPLING = "network_decoupling"
80
+ """Phase coherence with network dropped below coupling threshold."""
81
+
82
+ EPI_DISSOLUTION = "epi_dissolution"
83
+ """Primary Information Structure lost coherence (EPI → 0)."""
84
+
85
+
86
+ def _get_node_attr(
87
+ G: TNFRGraph, node: NodeId, aliases: tuple[str, ...], default: float = 0.0
88
+ ) -> float:
89
+ """Get node attribute using alias fallback."""
90
+ return float(get_attr(G.nodes[node], aliases, default))
91
+
92
+
93
+ def get_lifecycle_state(
94
+ G: TNFRGraph,
95
+ node: NodeId,
96
+ *,
97
+ config: dict[str, Any] | None = None,
98
+ ) -> LifecycleState:
99
+ """Determine current lifecycle state of a node.
100
+
101
+ Analyzes node's structural parameters (νf, ΔNFR, EPI, θ) to determine
102
+ its position in the canonical TNFR lifecycle.
103
+
104
+ Parameters
105
+ ----------
106
+ G : TNFRGraph
107
+ Graph containing the node
108
+ node : NodeId
109
+ Node to analyze
110
+ config : dict, optional
111
+ Configuration overrides for thresholds:
112
+ - activation_threshold: Min νf for activation (default: 0.1)
113
+ - collapse_threshold: Min νf to avoid collapse (default: 0.01)
114
+ - bifurcation_threshold: Max |ΔNFR| before bifurcation (default: 10.0)
115
+ - stabilization_dnfr: Max |ΔNFR| for stabilization (default: 1.0)
116
+ - stabilization_coherence: Min coherence for stabilization (default: 0.8)
117
+ - propagation_coupling: Min phase coupling for propagation (default: 0.7)
118
+ - mutation_dnfr: Min |ΔNFR| for mutation state (default: 5.0)
119
+
120
+ Returns
121
+ -------
122
+ LifecycleState
123
+ Current lifecycle state
124
+
125
+ Notes
126
+ -----
127
+ Collapse conditions are checked first. Among active states, the state
128
+ with the strongest indicators is returned (e.g., high |ΔNFR| → mutation
129
+ takes precedence over stabilization).
130
+
131
+ Examples
132
+ --------
133
+ >>> from tnfr.structural import create_nfr
134
+ >>> G, node = create_nfr("test", epi=0.5, vf=1.0)
135
+ >>> G.nodes[node]["ΔNFR"] = 0.5
136
+ >>> state = get_lifecycle_state(G, node)
137
+ >>> state.value
138
+ 'activation'
139
+ """
140
+ if config is None:
141
+ config = {}
142
+
143
+ # Get thresholds from config or graph or defaults
144
+ def _get_threshold(key: str, default: float) -> float:
145
+ return float(config.get(key, G.graph.get(key.upper(), default)))
146
+
147
+ activation_threshold = _get_threshold("activation_threshold", 0.1)
148
+ collapse_threshold = _get_threshold("collapse_threshold", 0.01)
149
+ bifurcation_threshold = _get_threshold("bifurcation_threshold", 10.0)
150
+ stabilization_dnfr = _get_threshold("stabilization_dnfr", 1.0)
151
+ stabilization_coherence = _get_threshold("stabilization_coherence", 0.8)
152
+ propagation_coupling = _get_threshold("propagation_coupling", 0.7)
153
+ mutation_dnfr = _get_threshold("mutation_dnfr", 5.0)
154
+
155
+ # Get node structural parameters
156
+ vf = _get_node_attr(G, node, ALIAS_VF)
157
+ dnfr = _get_node_attr(G, node, ALIAS_DNFR)
158
+ epi = _get_node_attr(G, node, ALIAS_EPI)
159
+ theta = _get_node_attr(G, node, ALIAS_THETA)
160
+
161
+ # Check for collapse conditions first
162
+ if vf < collapse_threshold:
163
+ return LifecycleState.COLLAPSING
164
+
165
+ if abs(dnfr) > bifurcation_threshold:
166
+ return LifecycleState.COLLAPSING
167
+
168
+ # Compute phase coupling (simplified - could use full network coupling)
169
+ neighbors = list(G.neighbors(node))
170
+ if neighbors:
171
+ import math
172
+
173
+ neighbor_phases = [_get_node_attr(G, n, ALIAS_THETA) for n in neighbors]
174
+ mean_neighbor_phase = sum(neighbor_phases) / len(neighbor_phases)
175
+ phase_diff = abs(theta - mean_neighbor_phase)
176
+ # Normalize to [0, 1] where 1 is perfect alignment
177
+ phase_coupling = 1.0 - min(phase_diff, math.pi) / math.pi
178
+ else:
179
+ phase_coupling = 0.0
180
+
181
+ # Check for decoupling collapse
182
+ if neighbors and phase_coupling < DEFAULT_MIN_PHASE_COUPLING:
183
+ return LifecycleState.COLLAPSING
184
+
185
+ # Check active states (priority: mutation > propagation > stabilization > activation)
186
+
187
+ # Mutation: High dissonance with sufficient frequency
188
+ if abs(dnfr) > mutation_dnfr and vf > activation_threshold:
189
+ return LifecycleState.MUTATION
190
+
191
+ # Propagation: Strong network coupling
192
+ if phase_coupling > propagation_coupling and vf > activation_threshold:
193
+ return LifecycleState.PROPAGATION
194
+
195
+ # Stabilization: High coherence, low dissonance
196
+ # Note: C(t) computation would require full graph state, using EPI as proxy
197
+ if abs(dnfr) < stabilization_dnfr and epi > stabilization_coherence:
198
+ return LifecycleState.STABILIZATION
199
+
200
+ # Activation: Above activation threshold but not yet stabilized
201
+ if vf >= activation_threshold:
202
+ return LifecycleState.ACTIVATION
203
+
204
+ # Dormant: Below activation threshold but above collapse
205
+ return LifecycleState.DORMANT
206
+
207
+
208
+ def check_collapse_conditions(
209
+ G: TNFRGraph,
210
+ node: NodeId,
211
+ *,
212
+ config: dict[str, Any] | None = None,
213
+ ) -> tuple[bool, CollapseReason | None]:
214
+ """Check if node meets any collapse conditions.
215
+
216
+ Parameters
217
+ ----------
218
+ G : TNFRGraph
219
+ Graph containing the node
220
+ node : NodeId
221
+ Node to check
222
+ config : dict, optional
223
+ Configuration overrides for collapse thresholds
224
+
225
+ Returns
226
+ -------
227
+ should_collapse : bool
228
+ True if node should collapse
229
+ reason : CollapseReason | None
230
+ Reason for collapse, or None if not collapsing
231
+
232
+ Notes
233
+ -----
234
+ Multiple collapse conditions may be met simultaneously. This function
235
+ returns the first detected condition in priority order:
236
+ 1. Frequency failure (most fundamental)
237
+ 2. Extreme dissonance (structural instability)
238
+ 3. Network decoupling (loss of resonance)
239
+ 4. EPI dissolution (form loss)
240
+ """
241
+ if config is None:
242
+ config = {}
243
+
244
+ def _get_threshold(key: str, default: float) -> float:
245
+ return float(config.get(key, G.graph.get(key.upper(), default)))
246
+
247
+ collapse_threshold = _get_threshold("collapse_threshold", 0.01)
248
+ bifurcation_threshold = _get_threshold("bifurcation_threshold", 10.0)
249
+ min_coupling = _get_threshold("min_phase_coupling", 0.1)
250
+ min_epi = _get_threshold("min_epi", 0.01)
251
+
252
+ # Get node parameters
253
+ vf = _get_node_attr(G, node, ALIAS_VF)
254
+ dnfr = _get_node_attr(G, node, ALIAS_DNFR)
255
+ epi = _get_node_attr(G, node, ALIAS_EPI)
256
+ theta = _get_node_attr(G, node, ALIAS_THETA)
257
+
258
+ # Check frequency failure (most fundamental)
259
+ if vf < collapse_threshold:
260
+ return (True, CollapseReason.FREQUENCY_FAILURE)
261
+
262
+ # Check extreme dissonance
263
+ if abs(dnfr) > bifurcation_threshold:
264
+ return (True, CollapseReason.EXTREME_DISSONANCE)
265
+
266
+ # Check network decoupling
267
+ neighbors = list(G.neighbors(node))
268
+ if neighbors:
269
+ import math
270
+
271
+ neighbor_phases = [_get_node_attr(G, n, ALIAS_THETA) for n in neighbors]
272
+ mean_neighbor_phase = sum(neighbor_phases) / len(neighbor_phases)
273
+ phase_diff = abs(theta - mean_neighbor_phase)
274
+ phase_coupling = 1.0 - min(phase_diff, math.pi) / math.pi
275
+
276
+ if phase_coupling < min_coupling:
277
+ return (True, CollapseReason.NETWORK_DECOUPLING)
278
+
279
+ # Check EPI dissolution
280
+ if epi < min_epi:
281
+ return (True, CollapseReason.EPI_DISSOLUTION)
282
+
283
+ return (False, None)
284
+
285
+
286
+ def should_collapse(
287
+ G: TNFRGraph,
288
+ node: NodeId,
289
+ *,
290
+ config: dict[str, Any] | None = None,
291
+ ) -> bool:
292
+ """Check if node should collapse (simplified interface).
293
+
294
+ Parameters
295
+ ----------
296
+ G : TNFRGraph
297
+ Graph containing the node
298
+ node : NodeId
299
+ Node to check
300
+ config : dict, optional
301
+ Configuration overrides
302
+
303
+ Returns
304
+ -------
305
+ bool
306
+ True if node meets collapse conditions
307
+
308
+ See Also
309
+ --------
310
+ check_collapse_conditions : Full collapse check with reason
311
+ get_lifecycle_state : Complete lifecycle state determination
312
+ """
313
+ should_collapse_flag, _ = check_collapse_conditions(G, node, config=config)
314
+ return should_collapse_flag