dense-evolution 8.2.0__tar.gz → 8.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/PKG-INFO +8 -2
  2. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/README.md +1 -1
  3. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/__init__.py +4 -2
  4. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/mitigation/__init__.py +2 -0
  5. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/mitigation/zne.py +95 -0
  6. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/native_hf/scf.py +51 -4
  7. dense_evolution-8.2.1/dense_evolution/utils/mass_decomposition.py +246 -0
  8. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution.egg-info/PKG-INFO +8 -2
  9. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution.egg-info/SOURCES.txt +2 -0
  10. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution.egg-info/requires.txt +8 -0
  11. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/pyproject.toml +38 -2
  12. dense_evolution-8.2.1/tools/ia_utils/rag.py +236 -0
  13. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/autodiff.py +0 -0
  14. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/backends/__init__.py +0 -0
  15. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/backends/chunk/__init__.py +0 -0
  16. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/backends/chunk/_engine_imports.py +0 -0
  17. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/backends/chunk/circuit_chunker.py +0 -0
  18. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/backends/chunk/core.py +0 -0
  19. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/backends/chunk/disk_overflow.py +0 -0
  20. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/backends/chunk/geometry.py +0 -0
  21. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/backends/chunk/guard.py +0 -0
  22. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/backends/chunk/kernels.py +0 -0
  23. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/backends/mps.py +0 -0
  24. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/backends/statevector.py +0 -0
  25. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/chunk.py +0 -0
  26. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/circuits/__init__.py +0 -0
  27. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/circuits/compiler.py +0 -0
  28. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/circuits/diagram.py +0 -0
  29. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/circuits/gates.py +0 -0
  30. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/circuits/parser.py +0 -0
  31. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/circuits/qft.py +0 -0
  32. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/circuits/random_circuit.py +0 -0
  33. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/circuits/registry.py +0 -0
  34. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/circuits/topology.py +0 -0
  35. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/circuits/trotter.py +0 -0
  36. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/circuits/uccsd.py +0 -0
  37. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/cli.py +0 -0
  38. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/compiler.py +0 -0
  39. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/config.py +0 -0
  40. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/drawing.py +0 -0
  41. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/entropy.py +0 -0
  42. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/fermions.py +0 -0
  43. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/gates.py +0 -0
  44. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/harrison_tb.py +0 -0
  45. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/healing.py +0 -0
  46. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/interop/__init__.py +0 -0
  47. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/interop/qiskit_pennylane.py +0 -0
  48. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/measurement.py +0 -0
  49. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/mitigation/healing.py +0 -0
  50. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/mitigation/kl_divergence.py +0 -0
  51. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/mitigation/magic_entropy.py +0 -0
  52. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/mitigation/magic_entropy_shadows.py +0 -0
  53. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/mitigation/renyi.py +0 -0
  54. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/mitigation/stabilizer_renyi_entropy.py +0 -0
  55. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/mps.py +0 -0
  56. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/native_hf/__init__.py +0 -0
  57. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/native_hf/assembly.py +0 -0
  58. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/native_hf/basis.py +0 -0
  59. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/native_hf/boys.py +0 -0
  60. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/native_hf/bridge.py +0 -0
  61. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/native_hf/cartesian.py +0 -0
  62. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/native_hf/coulomb.py +0 -0
  63. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/native_hf/differentiable.py +0 -0
  64. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/native_hf/gaussians.py +0 -0
  65. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/native_hf/kinetic.py +0 -0
  66. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/native_hf/libcint_bridge.py +0 -0
  67. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/native_hf/overlap.py +0 -0
  68. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/noise/__init__.py +0 -0
  69. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/noise/coherent_attack.py +0 -0
  70. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/noise/cosmic_ray.py +0 -0
  71. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/noise/density_matrix_channels.py +0 -0
  72. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/noise/differentiable.py +0 -0
  73. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/noise/kraus/__init__.py +0 -0
  74. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/noise/kraus/amplitude_damping.py +0 -0
  75. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/noise/kraus/bitflip.py +0 -0
  76. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/noise/kraus/combined.py +0 -0
  77. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/noise/kraus/depolarizing.py +0 -0
  78. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/noise/kraus/ideal.py +0 -0
  79. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/noise/kraus/phaseflip.py +0 -0
  80. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/noise/kraus_channels.py +0 -0
  81. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/noise/oscillating.py +0 -0
  82. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/observables.py +0 -0
  83. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/parser.py +0 -0
  84. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/physics/__init__.py +0 -0
  85. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/physics/entropy.py +0 -0
  86. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/physics/fermions.py +0 -0
  87. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/physics/observables.py +0 -0
  88. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/physics/qec.py +0 -0
  89. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/physics/spectral.py +0 -0
  90. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/physics/states.py +0 -0
  91. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/qec.py +0 -0
  92. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/qft.py +0 -0
  93. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/qmmm/__init__.py +0 -0
  94. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/qmmm/ase_bridge.py +0 -0
  95. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/qmmm/forces.py +0 -0
  96. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/qmmm/propagation.py +0 -0
  97. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/qmmm/region.py +0 -0
  98. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/random_circuit.py +0 -0
  99. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/registry.py +0 -0
  100. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/simulator.py +0 -0
  101. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/solvers/__init__.py +0 -0
  102. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/solvers/autodiff.py +0 -0
  103. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/solvers/harrison_tb.py +0 -0
  104. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/solvers/vhd_tb.py +0 -0
  105. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/states.py +0 -0
  106. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/topology.py +0 -0
  107. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/trotter.py +0 -0
  108. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/utils/__init__.py +0 -0
  109. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/utils/drawing.py +0 -0
  110. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/utils/measurement.py +0 -0
  111. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution/vhd_tb.py +0 -0
  112. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution.egg-info/dependency_links.txt +0 -0
  113. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution.egg-info/entry_points.txt +0 -0
  114. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/dense_evolution.egg-info/top_level.txt +0 -0
  115. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/license.md +0 -0
  116. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/research/local_site/__init__.py +0 -0
  117. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/research/local_site/app/__init__.py +0 -0
  118. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/research/local_site/app/server.py +0 -0
  119. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/setup.cfg +0 -0
  120. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/tools/dashboard/core/__init__.py +0 -0
  121. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/tools/dashboard/core/_gate_tables.py +0 -0
  122. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/tools/dashboard/core/band_structure.py +0 -0
  123. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/tools/dashboard/core/circuit_builder_component.py +0 -0
  124. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/tools/dashboard/core/circuit_diagram.py +0 -0
  125. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/tools/dashboard/core/engine.py +0 -0
  126. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/tools/dashboard/core/graphical_builder.py +0 -0
  127. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/tools/dashboard/core/hamiltonians.py +0 -0
  128. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/tools/dashboard/core/mitigation.py +0 -0
  129. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/tools/dashboard/core/noise_tools.py +0 -0
  130. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/tools/dashboard/core/qasm_library.py +0 -0
  131. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/tools/dashboard/core/qmmm.py +0 -0
  132. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/tools/dashboard/core/state_visuals.py +0 -0
  133. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/tools/dashboard/core/system_limits.py +0 -0
  134. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/tools/dashboard/core/vector_healing.py +0 -0
  135. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/tools/dashboard/core/visuals.py +0 -0
  136. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/tools/dashboard/core/vqe.py +0 -0
  137. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/tools/dashboard/core/wormhole.py +0 -0
  138. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/tools/ia_utils/__init__.py +0 -0
  139. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/tools/ia_utils/adversarial_vector_attack.py +0 -0
  140. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/tools/ia_utils/vector_healing.py +0 -0
  141. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/tools/mcp_server/__init__.py +0 -0
  142. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/tools/mcp_server/client.py +0 -0
  143. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/tools/mcp_server/config.py +0 -0
  144. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/tools/mcp_server/models.py +0 -0
  145. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/tools/mcp_server/molecules.py +0 -0
  146. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/tools/mcp_server/server.py +0 -0
  147. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/tools/mcp_server/tools/__init__.py +0 -0
  148. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/tools/mcp_server/tools/chemistry_tools.py +0 -0
  149. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/tools/mcp_server/tools/circuit_tools.py +0 -0
  150. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/tools/mcp_server/tools/mitigation_tools.py +0 -0
  151. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/tools/mcp_server/tools/noise_tools.py +0 -0
  152. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/tools/mcp_server/tools/system_tools.py +0 -0
  153. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/tools/mcp_server/tools/wormhole_tools.py +0 -0
  154. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/tools/mcp_server/utils/__init__.py +0 -0
  155. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/tools/mcp_server/utils/cache.py +0 -0
  156. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/tools/mcp_server/utils/images.py +0 -0
  157. {dense_evolution-8.2.0 → dense_evolution-8.2.1}/tools/mcp_server/utils/truncation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dense-evolution
3
- Version: 8.2.0
3
+ Version: 8.2.1
4
4
  Summary: High-performance quantum simulation toolkit -- Statevector/MPS engines with JIT compilation, noise modeling, VQE, QEC, quantum chemistry, and agent-native tooling
5
5
  Author-email: Salvatore Pennacchio <jtatopenn@libero.it>
6
6
  License: Business Source License 1.1
@@ -64,6 +64,11 @@ Provides-Extra: qmmm
64
64
  Requires-Dist: rdkit>=2023.9.1; extra == "qmmm"
65
65
  Provides-Extra: ase
66
66
  Requires-Dist: ase>=3.22.0; extra == "ase"
67
+ Provides-Extra: rag
68
+ Requires-Dist: scikit-learn>=1.0.0; extra == "rag"
69
+ Requires-Dist: sentence-transformers>=2.2.0; extra == "rag"
70
+ Provides-Extra: armor
71
+ Requires-Dist: dense-armor>=1.1.18; extra == "armor"
67
72
  Provides-Extra: full
68
73
  Requires-Dist: cupy-cuda12x>=12.0.0; extra == "full"
69
74
  Requires-Dist: streamlit>=1.30.0; extra == "full"
@@ -80,6 +85,7 @@ Requires-Dist: httpx>=0.27; extra == "full"
80
85
  Provides-Extra: dev
81
86
  Requires-Dist: pytest>=7.0.0; extra == "dev"
82
87
  Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
88
+ Requires-Dist: pytest-env>=1.0.0; extra == "dev"
83
89
  Provides-Extra: docs
84
90
  Requires-Dist: mkdocs>=1.6.0; extra == "docs"
85
91
  Requires-Dist: mkdocs-material>=9.5.0; extra == "docs"
@@ -343,7 +349,7 @@ If Dense-Evolution is useful in academic work, please cite it via the metadata i
343
349
  Archived on [Zenodo](https://zenodo.org/):
344
350
 
345
351
  - **Concept DOI** (always resolves to the latest version): [10.5281/zenodo.21855643](https://doi.org/10.5281/zenodo.21855643)
346
- - **This release (v8.1.78)**: [10.5281/zenodo.22706039](https://doi.org/10.5281/zenodo.22706039)
352
+ - **This release (v8.2.0)**: [10.5281/zenodo.22818319](https://doi.org/10.5281/zenodo.22818319)
347
353
 
348
354
  ---
349
355
 
@@ -249,7 +249,7 @@ If Dense-Evolution is useful in academic work, please cite it via the metadata i
249
249
  Archived on [Zenodo](https://zenodo.org/):
250
250
 
251
251
  - **Concept DOI** (always resolves to the latest version): [10.5281/zenodo.21855643](https://doi.org/10.5281/zenodo.21855643)
252
- - **This release (v8.1.78)**: [10.5281/zenodo.22706039](https://doi.org/10.5281/zenodo.22706039)
252
+ - **This release (v8.2.0)**: [10.5281/zenodo.22818319](https://doi.org/10.5281/zenodo.22818319)
253
253
 
254
254
  ---
255
255
 
@@ -32,6 +32,7 @@ from .mitigation.zne import (richardson_extrapolate, richardson_amplification_fa
32
32
  bounded_exponential_extrapolate,
33
33
  project_to_physical, uhlmann_fidelity, zne_density_matrix,
34
34
  jsd_predictive_zne_density_matrix,
35
+ coherence_predictive_zne_density_matrix,
35
36
  richardson_extrapolate_jit, zero_noise_extrapolation_jit,
36
37
  polynomial_extrapolate_jit, uhlmann_fidelity_jit, zne_density_matrix_jit)
37
38
  from .circuits.diagram import plot_circuit
@@ -57,7 +58,7 @@ from .physics.qec import (pauli_commutes, compute_syndrome, erasure_aware_decode
57
58
  blind_minimum_weight_decode, decode_with_erasure_fallback,
58
59
  counts_in_intervals_dimension, nearest_coset_decode)
59
60
 
60
- __version__ = "8.1.81"
61
+ __version__ = "8.2.1"
61
62
 
62
63
  __all__ = [
63
64
  "__version__",
@@ -87,7 +88,8 @@ __all__ = [
87
88
  "zero_noise_extrapolation", "polynomial_extrapolate",
88
89
  "bounded_exponential_extrapolate",
89
90
  "project_to_physical", "uhlmann_fidelity", "zne_density_matrix",
90
- "jsd_predictive_zne_density_matrix", "global_depolarizing_channel", "amplitude_damping_channel",
91
+ "jsd_predictive_zne_density_matrix", "coherence_predictive_zne_density_matrix",
92
+ "global_depolarizing_channel", "amplitude_damping_channel",
91
93
  "cosmic_ray_burst_profile", "oscillating_p_eff",
92
94
  "richardson_extrapolate_jit", "zero_noise_extrapolation_jit",
93
95
  "polynomial_extrapolate_jit", "uhlmann_fidelity_jit", "zne_density_matrix_jit",
@@ -13,6 +13,7 @@ from .zne import (
13
13
  bounded_exponential_extrapolate,
14
14
  project_to_physical, uhlmann_fidelity, zne_density_matrix,
15
15
  jsd_predictive_zne_density_matrix,
16
+ coherence_predictive_zne_density_matrix,
16
17
  richardson_extrapolate_jit, zero_noise_extrapolation_jit,
17
18
  polynomial_extrapolate_jit, uhlmann_fidelity_jit, zne_density_matrix_jit,
18
19
  )
@@ -36,6 +37,7 @@ __all__ = [
36
37
  "bounded_exponential_extrapolate",
37
38
  "project_to_physical", "uhlmann_fidelity", "zne_density_matrix",
38
39
  "jsd_predictive_zne_density_matrix",
40
+ "coherence_predictive_zne_density_matrix",
39
41
  "richardson_extrapolate_jit", "zero_noise_extrapolation_jit",
40
42
  "polynomial_extrapolate_jit", "uhlmann_fidelity_jit", "zne_density_matrix_jit",
41
43
  "calculate_advanced_sigma", "calculate_phi_ab", "calculate_vettore_dinamico",
@@ -836,3 +836,98 @@ def _jsd_predictive_zne_density_matrix_core(rho_at_scales: jnp.ndarray, nudge_sc
836
836
  c3 = 1.0 - nudge_scale * rectified
837
837
  extrapolated = (c1 * e_l1 + c2 * e_l2 + c3 * e_l3) / (c1 + c2 + c3)
838
838
  return project_to_physical(extrapolated)
839
+
840
+
841
+ def _coherence_l1(rho: jnp.ndarray) -> jnp.ndarray:
842
+ """l1-norm of coherence (Baumgratz, Cramer & Plenio, Phys. Rev. Lett.
843
+ 113, 140401, 2014): the sum of the magnitudes of every off-diagonal
844
+ density-matrix entry. A standard, basis-dependent measure of how much
845
+ quantum coherence a state carries in the computational basis --
846
+ unlike `_js_divergence`, which only ever sees the diagonal
847
+ (populations), this is sensitive to exactly what dephasing destroys."""
848
+ n = rho.shape[0]
849
+ return jnp.sum(jnp.abs(rho) * (1.0 - jnp.eye(n, dtype=rho.dtype)))
850
+
851
+
852
+ def coherence_predictive_zne_density_matrix(rho_at_scales, noise_factors) -> jnp.ndarray:
853
+ """Density-matrix ZNE with a coherence-informed coefficient nudge --
854
+ the same adaptive-nonlinearity mechanism as
855
+ `jsd_predictive_zne_density_matrix`, but signaled by the l1-norm of
856
+ coherence (`_coherence_l1`) instead of the Jensen-Shannon divergence
857
+ of the diagonal populations.
858
+
859
+ Motivation: `jsd_predictive_zne_density_matrix`'s signal is the
860
+ density-matrix diagonal only. Any purely dephasing-type noise
861
+ (phase-flip, or a coherent Z-axis over-rotation) is diagonal in the
862
+ computational basis -- it moves phase, never populations -- so that
863
+ signal is blind to it BY CONSTRUCTION, not merely weak: verified
864
+ directly in Dense-Evolution-Discovery's
865
+ scripts/jsd_zne_noise_generalization.py, the fidelity delta from the
866
+ classical-JSD nudge is exactly 0.0 at every tested phase-flip noise
867
+ strength and every tested coherent-rotation angle. A quantum-JSD
868
+ variant (von Neumann entropy of the full density matrix instead of
869
+ Shannon entropy of the diagonal) was tried there too and rejected: it
870
+ weakens the already-working amplitude-damping/combined-noise case
871
+ without fixing the coherent-error case, since a smooth deterministic
872
+ function of the noise-scale factor has `jsd_12~=jsd_23` regardless of
873
+ which divergence measures it -- the nonlinearity trigger this whole
874
+ family of methods relies on is structurally near-zero there no
875
+ matter the signal.
876
+
877
+ Validated scope, checked directly rather than assumed universal: real
878
+ effect on phase-flip/dephasing-dominated noise for `base_p<=0.10`;
879
+ NOT validated (and not claimed) for amplitude-damping-dominated noise
880
+ (use `jsd_predictive_zne_density_matrix` there instead) or for
881
+ coherent/deterministic errors (out of reach for this entire family of
882
+ methods, not just this signal -- see above).
883
+
884
+ Verified at 200 independent seeds on phase-flip noise (GHZ(4),
885
+ `base_p=0.05`, K=150 trajectories/scale) before promotion: the nudge
886
+ activates (rectified nonlinearity > 0) on 63/200 seeds (31.5%) --
887
+ among those, 63/63 improve over plain `zne_density_matrix`, mean
888
+ fidelity gain +0.014892, one-sample t-test against zero
889
+ p=1.07e-08, a 20000-resample permutation test finding no resample
890
+ matching or exceeding the observed effect (p<0.00005). Confirmed
891
+ across a noise-level sweep (100 seeds/level, GHZ(4)): significant by
892
+ both tests, 100% win rate among active points, at base_p in (0.03,
893
+ 0.05, 0.08, 0.10); NOT significant at base_p=0.15 (p=0.288 t-test,
894
+ p=0.301 permutation) -- a real, honest upper boundary, not a
895
+ universal effect at any noise strength. Confirmed on a second circuit
896
+ family (hardware-efficient VQE-style ansatz, 2 layers) at
897
+ `base_p=0.05`: 69/150 active (46%, a higher activation rate than
898
+ GHZ), 67/69 positive, p=4.4e-06 -- the effect is not GHZ-specific.
899
+ When inactive, reduces EXACTLY to `zne_density_matrix` at 3
900
+ equally-spaced scales (verified: max deviation ~1e-8, floating-point
901
+ noise) -- zero risk in that regime, by construction, the same safety
902
+ property `jsd_predictive_zne_density_matrix` has.
903
+
904
+ Only defined for exactly 3 equally-spaced noise factors (1x, 2x, 3x),
905
+ same restriction and reason as `jsd_predictive_zne_density_matrix`:
906
+ the Lagrange coefficients (3, -3, 1) this nudges are specific to that
907
+ spacing."""
908
+ rho_at_scales = jnp.asarray(rho_at_scales, dtype=jnp.complex128)
909
+ noise_factors = jnp.asarray(noise_factors, dtype=jnp.float64)
910
+ if noise_factors.shape[0] != 3:
911
+ raise NotImplementedError(
912
+ "coherence_predictive_zne_density_matrix is only defined for exactly 3 noise "
913
+ "factors; call zne_density_matrix(...) directly for the plain N-point case."
914
+ )
915
+ return _coherence_predictive_zne_density_matrix_core(rho_at_scales)
916
+
917
+
918
+ def _coherence_predictive_zne_density_matrix_core(rho_at_scales: jnp.ndarray, nudge_scale: float = 0.5) -> jnp.ndarray:
919
+ """`jax.jit`-traceable core of `coherence_predictive_zne_density_matrix`
920
+ -- `rho_at_scales` already complex128. See the public wrapper's
921
+ docstring for the method and its validation."""
922
+ c1_, c2_, c3_ = (_coherence_l1(rho_at_scales[i]) for i in range(3))
923
+ gap_12 = jnp.abs(c1_ - c2_)
924
+ gap_23 = jnp.abs(c2_ - c3_)
925
+ nonlinearity = (gap_23 - gap_12) / (gap_23 + gap_12 + 1e-12)
926
+ rectified = jnp.maximum(nonlinearity, 0.0)
927
+
928
+ e_l1, e_l2, e_l3 = rho_at_scales[0], rho_at_scales[1], rho_at_scales[2]
929
+ c1 = 3.0 - nudge_scale * rectified
930
+ c2 = -3.0 + 2.0 * nudge_scale * rectified
931
+ c3 = 1.0 - nudge_scale * rectified
932
+ extrapolated = (c1 * e_l1 + c2 * e_l2 + c3 * e_l3) / (c1 + c2 + c3)
933
+ return project_to_physical(extrapolated)
@@ -90,6 +90,7 @@ import dataclasses
90
90
  import functools
91
91
 
92
92
  import jax
93
+ import numpy as np
93
94
  import jax.numpy as jnp
94
95
 
95
96
  from dense_evolution.config import ensure_x64
@@ -107,6 +108,7 @@ class HFResult:
107
108
  orbital_energies: jax.Array
108
109
  orbital_coefficients: jax.Array # C, shape (n_basis, n_basis)
109
110
  density_matrix: jax.Array
111
+ energy_history: jax.Array # shape (max_iterations,), NaN past n_iterations
110
112
 
111
113
 
112
114
  def nuclear_repulsion_energy(nuclear_charges: list[float], nuclear_positions: jax.Array) -> jax.Array:
@@ -231,11 +233,11 @@ def run_scf(
231
233
  return F, energy
232
234
 
233
235
  def cond_fun(state):
234
- iteration, _P, _C, _orbital_energies, _energy_prev, converged, _fock_history, _error_history = state
236
+ iteration, _P, _C, _orbital_energies, _energy_prev, converged, _fock_history, _error_history, _energy_history = state
235
237
  return jnp.logical_and(jnp.logical_not(converged), iteration < max_iterations)
236
238
 
237
239
  def body_fun(state):
238
- iteration, P, C_prev, _orbital_energies, energy_prev, _converged, fock_history, error_history = state
240
+ iteration, P, C_prev, _orbital_energies, energy_prev, _converged, fock_history, error_history, energy_history = state
239
241
 
240
242
  F, energy = _fock_and_energy(P)
241
243
  error = _diis_error(F, P, S, X)
@@ -262,7 +264,9 @@ def run_scf(
262
264
  P_damped = jnp.where(history_count < 2, damping * P_new + (1.0 - damping) * P, P_new)
263
265
  P_next = jnp.where(converged, P_new, P_damped)
264
266
 
265
- return (iteration + 1, P_next, C, orbital_energies, energy, converged, fock_history, error_history)
267
+ energy_history = energy_history.at[iteration].set(energy)
268
+
269
+ return (iteration + 1, P_next, C, orbital_energies, energy, converged, fock_history, error_history, energy_history)
266
270
 
267
271
  init_state = (
268
272
  jnp.array(0),
@@ -273,8 +277,9 @@ def run_scf(
273
277
  jnp.array(False),
274
278
  jnp.zeros((diis_dim, n_basis, n_basis), dtype=H_core.dtype),
275
279
  jnp.zeros((diis_dim, n_basis, n_basis), dtype=H_core.dtype),
280
+ jnp.full((max_iterations,), jnp.nan, dtype=H_core.dtype),
276
281
  )
277
- iteration, P, C, orbital_energies, _energy_prev, converged, _fh, _eh = jax.lax.while_loop(cond_fun, body_fun, init_state)
282
+ iteration, P, C, orbital_energies, _energy_prev, converged, _fh, _eh, energy_history = jax.lax.while_loop(cond_fun, body_fun, init_state)
278
283
 
279
284
  _F, electronic_energy = _fock_and_energy(P)
280
285
  e_nuc = nuclear_repulsion_energy(nuclear_charges, nuclear_positions)
@@ -288,9 +293,51 @@ def run_scf(
288
293
  orbital_energies=orbital_energies,
289
294
  orbital_coefficients=C,
290
295
  density_matrix=P,
296
+ energy_history=energy_history,
291
297
  )
292
298
 
293
299
 
300
+ def _import_dense_armor_robust_filters():
301
+ try:
302
+ from dense_armor.utility.robust_filters import hampel_filter, tukey_fences
303
+ except ImportError as exc:
304
+ raise ImportError(
305
+ "native_hf.scf.diagnose_convergence needs Dense-Armor (pip install dense-evolution[armor])"
306
+ ) from exc
307
+ return hampel_filter, tukey_fences
308
+
309
+
310
+ def diagnose_convergence(result: HFResult, radius: int = 5, n_sigmas: float = 3.0) -> dict:
311
+ """Real automatic guard on HFResult.energy_history, instead of trusting
312
+ the final `converged` flag in isolation: Dense-Armor's Hampel filter
313
+ and Tukey fences (dense_evolution.utility.robust_filters -- the sister
314
+ project's own anomaly detectors, Chauvenet/Tukey/Hampel/sigma-clipping,
315
+ validated with 0 false positives on real H2 dissociation-curve
316
+ chemistry) applied to the real per-iteration electronic energy trace.
317
+
318
+ Validated on a real non-converging case (a 28-heavy-atom CASMI26
319
+ fragment, level_shift=0.0): the broken run flags 19-24% of its 200
320
+ iterations as anomalous; the same fragment fixed with level_shift=0.5
321
+ (converges in 55 iterations) flags only ~4% -- background noise, not a
322
+ false-alarm storm. Needs the `armor` extra (pip install
323
+ dense-evolution[armor]) -- Dense-Armor is not a hard dependency of this
324
+ module."""
325
+ hampel_filter, tukey_fences = _import_dense_armor_robust_filters()
326
+ trace = np.asarray(result.energy_history[:result.n_iterations])
327
+ n = max(1, result.n_iterations)
328
+
329
+ _cleaned_h, anomalies_h = hampel_filter(trace, radius=radius, n_sigmas=n_sigmas)
330
+ _cleaned_t, anomalies_t = tukey_fences(trace, radius=len(trace))
331
+
332
+ return {
333
+ "n_iterations": result.n_iterations,
334
+ "n_anomalies_hampel": len(anomalies_h),
335
+ "n_anomalies_tukey": len(anomalies_t),
336
+ "anomaly_fraction_hampel": len(anomalies_h) / n,
337
+ "anomaly_fraction_tukey": len(anomalies_t) / n,
338
+ }
339
+
340
+
294
341
  @functools.partial(jax.custom_vjp, nondiff_argnums=(3,))
295
342
  def scf_electronic_energy(S: jax.Array, H_core: jax.Array, repulsion: jax.Array, n_electrons: int) -> jax.Array:
296
343
  """The RHF electronic energy (run_scf's own `electronic_energy`, not
@@ -0,0 +1,246 @@
1
+ """
2
+ Exact bounded mass-decomposition scoring: given a target mass difference
3
+ and a molecular formula's atom budget, find the closest achievable
4
+ sub-formula mass and its RDBE (degree-of-unsaturation) validity.
5
+
6
+ Promoted from Dense-Evolution-Discovery's CASMI26 spectral-identification
7
+ experiments (real MS/MS mass-spectrometry data, OTRF/Enveda CASMI26
8
+ Kaggle dataset). Real, verified need: testing whether a peak-to-peak mass
9
+ difference in a spectrum corresponds to a chemically real neutral loss
10
+ requires knowing whether SOME integer combination of the precursor's own
11
+ atoms reaches that mass -- a bounded integer subset-sum problem, solved
12
+ here exactly via iterative Minkowski sums (not a continuous relaxation:
13
+ the element-count space is small enough, typically under 10^5 states,
14
+ to enumerate directly).
15
+
16
+ A candidate decomposition is chemically valid only if its Ring-plus-
17
+ Double-Bond-Equivalent (degree of unsaturation) is non-negative:
18
+
19
+ RDBE = 1 + sum_i( count_i * (valence_i - 2) ) / 2
20
+
21
+ (standard organic chemistry -- the same closed-shell constraint already
22
+ used in this package's own isodesmic bond-scission work). Verified on
23
+ real CASMI26 data: without this filter, an unrelated (wrong) molecule's
24
+ formula "explains" 58.5% of real peak-pair differences by coincidence
25
+ alone (mean, n=1952 real spectra) -- RDBE-filtering doesn't remove that
26
+ gap, but does remove chemically impossible matches from both sides,
27
+ so any downstream feature/threshold built on this function is scoring
28
+ real, buildable neutral losses only, not arithmetic coincidences.
29
+
30
+ build_reachable_density_fft below builds the same reachable-mass
31
+ landscape a second way, via the convolution theorem (an FFT instead of
32
+ direct enumeration) with a physically motivated ppm-level Gaussian
33
+ tolerance instead of a fixed absolute one -- same physics, a different
34
+ (and for large state spaces, cheaper) construction.
35
+ """
36
+ import re
37
+ from typing import Dict, Optional, Tuple
38
+
39
+ import numpy as np
40
+
41
+ __all__ = [
42
+ "parse_formula", "rdbe", "build_reachable_masses", "nearest_reachable_mass",
43
+ "build_reachable_density_fft", "density_at_mass",
44
+ ]
45
+
46
+ ATOMIC_MASS: Dict[str, float] = {
47
+ "C": 12.000000, "H": 1.007825, "N": 14.003074, "O": 15.994915,
48
+ "S": 31.972071, "P": 30.973762, "Cl": 34.968853, "F": 18.998403,
49
+ "Br": 78.918338, "I": 126.904473, "Na": 22.989770, "K": 38.963707,
50
+ "Si": 27.976927, "B": 11.009305,
51
+ }
52
+ VALENCE: Dict[str, int] = {
53
+ "C": 4, "H": 1, "N": 3, "O": 2, "S": 2, "P": 3, "Cl": 1, "F": 1,
54
+ "Br": 1, "I": 1, "Na": 1, "K": 1, "Si": 4, "B": 3,
55
+ }
56
+
57
+ _FORMULA_TOKEN_RE = re.compile(r"([A-Z][a-z]?)(\d*)")
58
+
59
+
60
+ def parse_formula(formula: str) -> Dict[str, int]:
61
+ """Parse a molecular formula string (e.g. "C20H15N3O2") into
62
+ {element: count}. Repeated elements in the string are summed,
63
+ matching how real formula strings from different sources
64
+ occasionally repeat a symbol instead of merging it. Elements not in
65
+ ATOMIC_MASS/VALENCE are kept in the returned dict (parsing doesn't
66
+ know or care which elements downstream functions recognize) --
67
+ rdbe()/build_reachable_masses()/build_reachable_density_fft() each
68
+ skip an unrecognized element explicitly, not this function."""
69
+ counts: Dict[str, int] = {}
70
+ for elem, num in _FORMULA_TOKEN_RE.findall(formula or ""):
71
+ counts[elem] = counts.get(elem, 0) + (int(num) if num else 1)
72
+ return counts
73
+
74
+
75
+ def rdbe(formula_counts: Dict[str, int]) -> float:
76
+ """Ring-plus-Double-Bond-Equivalent (degree of unsaturation) of a
77
+ formula: RDBE = 1 + sum(count_i * (valence_i - 2)) / 2. A real,
78
+ non-negative closed-shell molecule/fragment always has RDBE >= 0;
79
+ negative RDBE means the atom counts cannot form any valid closed-
80
+ shell structure (too many univalent atoms for the given carbon/
81
+ nitrogen skeleton) -- unrecognized elements are ignored, not
82
+ counted as an error, since a formula may legitimately include an
83
+ element this module has no valence table entry for."""
84
+ acc = 0
85
+ for elem, count in formula_counts.items():
86
+ val = VALENCE.get(elem)
87
+ if val is not None and count > 0:
88
+ acc += count * (val - 2)
89
+ return 1.0 + acc / 2.0
90
+
91
+
92
+ def build_reachable_masses(
93
+ formula_counts: Dict[str, int],
94
+ max_mass: float,
95
+ resolution: float = 0.0005,
96
+ require_rdbe_valid: bool = True,
97
+ max_states: int = 150_000,
98
+ ) -> np.ndarray:
99
+ """All sub-formula masses reachable by using 0..count_i atoms of
100
+ each element in formula_counts (0 <= f <= formula_counts,
101
+ component-wise), sorted ascending. Exact bounded subset-sum via
102
+ iterative Minkowski sums over each element in turn -- correct
103
+ because element counts here are small enough (real molecules,
104
+ typically under a few hundred atoms total) that the reachable set
105
+ never needs more than max_states entries after resolution-binning.
106
+
107
+ require_rdbe_valid=True (default) keeps, for each mass bin, only
108
+ whether ANY chemically valid (RDBE >= 0) combination reaches it --
109
+ the physically meaningful question for neutral-loss matching.
110
+ Pass False to get the pure arithmetic reachable set instead (e.g.
111
+ for a sanity check against a hand-verified subset-sum test case).
112
+
113
+ max_mass bounds the search (usually the precursor's own observed
114
+ mass) so element counts far beyond what's physically possible for
115
+ that precursor are never explored."""
116
+ import pandas as pd # local import: only this function needs it
117
+
118
+ reach = np.array([0.0])
119
+ rdbe2 = np.array([0]) # 2*(RDBE-1); RDBE = 1 + rdbe2/2, starts at rdbe2=0 (empty formula, RDBE=1)
120
+
121
+ for elem, count in formula_counts.items():
122
+ mass = ATOMIC_MASS.get(elem)
123
+ val = VALENCE.get(elem)
124
+ if mass is None or count <= 0:
125
+ continue
126
+ count = min(count, 60) # sanity cap; real formulas here are far below this
127
+ incr_mass = np.arange(0, count + 1) * mass
128
+ incr_rdbe2 = np.arange(0, count + 1) * ((val - 2) if val is not None else 0)
129
+
130
+ reach = np.add.outer(reach, incr_mass).ravel()
131
+ rdbe2 = np.add.outer(rdbe2, incr_rdbe2).ravel()
132
+
133
+ # No emptiness guard needed here: the "use zero atoms of this
134
+ # element" branch (incr_mass's k=0 term) always carries every
135
+ # prior surviving entry through unchanged, and RDBE contribution
136
+ # 0 always satisfies `>= -2` -- so `reach`/`rdbe2` can shrink but
137
+ # never become empty as long as they started non-empty (which
138
+ # the initial reach=[0.0] guarantees for any max_mass >= -1).
139
+ keep = reach <= max_mass + 1.0
140
+ reach, rdbe2 = reach[keep], rdbe2[keep]
141
+
142
+ bins = np.round(reach / resolution).astype(np.int64)
143
+ if require_rdbe_valid:
144
+ valid = rdbe2 >= -2 # RDBE >= 0 <=> 1 + rdbe2/2 >= 0
145
+ bins, reach, rdbe2 = bins[valid], reach[valid], rdbe2[valid]
146
+ df = pd.DataFrame({"bin": bins, "mass": reach, "rdbe2": rdbe2})
147
+ df = df.drop_duplicates(subset="bin")
148
+ if len(df) > max_states:
149
+ df = df.iloc[:: max(1, len(df) // max_states)]
150
+ reach, rdbe2 = df["mass"].to_numpy(), df["rdbe2"].to_numpy()
151
+
152
+ return np.sort(reach)
153
+
154
+
155
+ def nearest_reachable_mass(target: float, reachable_sorted: np.ndarray) -> Optional[float]:
156
+ """The single closest value in a (sorted, ascending) reachable-mass
157
+ array to `target`, or None if the array is empty/trivial (only the
158
+ zero-formula placeholder). O(log n) via binary search."""
159
+ if reachable_sorted.size <= 1:
160
+ return None
161
+ i = np.searchsorted(reachable_sorted, target)
162
+ candidates = [k for k in (i - 1, i) if 0 <= k < reachable_sorted.size]
163
+ if not candidates:
164
+ return None
165
+ return float(min((reachable_sorted[k] for k in candidates), key=lambda m: abs(m - target)))
166
+
167
+
168
+ def build_reachable_density_fft(
169
+ formula_counts: Dict[str, int],
170
+ max_mass: float,
171
+ grid_resolution: float = 0.001,
172
+ relative_tolerance: float = 2e-5,
173
+ ) -> Tuple[np.ndarray, np.ndarray]:
174
+ """Plausibility density over achievable sub-formula masses, via the
175
+ convolution theorem instead of the exact Minkowski-sum enumeration
176
+ in build_reachable_masses.
177
+
178
+ Real precedent, not a novel trick: this is the same FFT technique
179
+ Rockwood & Van Orden use to compute isotope distributions
180
+ (Anal. Chem. 1996, "Ultrahigh-Speed Calculation of Isotope
181
+ Distributions") -- using 0..count_i copies of element i as a spike
182
+ train instead of an isotope-abundance distribution, but the same
183
+ convolution-theorem trick applies identically: the reachable-mass
184
+ landscape of "any combination of atoms up to the formula's budget"
185
+ is the CONVOLUTION of each element's own spike train, and
186
+ convolution in mass-space is a plain pointwise PRODUCT in frequency
187
+ space -- so combining K element types costs K FFTs and one inverse
188
+ FFT, not an enumeration over the (potentially huge) product of
189
+ per-element choices that build_reachable_masses must truncate
190
+ (max_states) for.
191
+
192
+ A single Gaussian broadening is applied ONCE, after combining every
193
+ element -- not per-element -- because real mass-measurement
194
+ uncertainty (ppm-level, `relative_tolerance`) is a property of the
195
+ FINAL observed mass difference, not of each element's contribution
196
+ individually. This replaces build_reachable_masses's arbitrary fixed
197
+ absolute tolerance with a physically motivated relative (ppm) one.
198
+
199
+ Returns (mass_grid, density). density is a plausibility landscape,
200
+ not a normalized probability distribution: many sub-formulas can
201
+ coincide near the same mass and add constructively, so its peak
202
+ height reflects how many/how well-supported combinations reach that
203
+ mass, not a probability that sums to 1.
204
+
205
+ See test_mass_decomposition.py for a direct verification that this
206
+ FFT-based construction agrees with brute-force convolution and with
207
+ build_reachable_masses's own exact enumeration on the same formula.
208
+ """
209
+ n_points = int(max_mass / grid_resolution) + 2
210
+ combined_fft = None
211
+
212
+ for elem, count in formula_counts.items():
213
+ mass = ATOMIC_MASS.get(elem)
214
+ if mass is None or count <= 0:
215
+ continue
216
+ count = min(count, 60)
217
+ spikes = np.zeros(n_points)
218
+ for k in range(count + 1):
219
+ idx = int(round(k * mass / grid_resolution))
220
+ if idx < n_points:
221
+ spikes[idx] += 1.0
222
+ elem_fft = np.fft.rfft(spikes)
223
+ combined_fft = elem_fft if combined_fft is None else combined_fft * elem_fft
224
+
225
+ mass_grid = np.arange(n_points) * grid_resolution
226
+ if combined_fft is None:
227
+ return mass_grid, np.zeros(n_points)
228
+
229
+ density = np.fft.irfft(combined_fft, n=n_points)
230
+ density = np.maximum(density, 0.0) # guards float round-off, true density is never negative
231
+
232
+ sigma_mass = max(relative_tolerance * max_mass, grid_resolution)
233
+ sigma_points = sigma_mass / grid_resolution
234
+ half = max(int(6 * sigma_points), 1)
235
+ kx = np.arange(-half, half + 1)
236
+ kernel = np.exp(-0.5 * (kx / sigma_points) ** 2)
237
+ kernel /= kernel.sum()
238
+ density = np.convolve(density, kernel, mode="same")
239
+
240
+ return mass_grid, density
241
+
242
+
243
+ def density_at_mass(mass_grid: np.ndarray, density: np.ndarray, target: float) -> float:
244
+ """Linearly-interpolated plausibility density at an arbitrary target
245
+ mass (need not land exactly on the grid)."""
246
+ return float(np.interp(target, mass_grid, density, left=0.0, right=0.0))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dense-evolution
3
- Version: 8.2.0
3
+ Version: 8.2.1
4
4
  Summary: High-performance quantum simulation toolkit -- Statevector/MPS engines with JIT compilation, noise modeling, VQE, QEC, quantum chemistry, and agent-native tooling
5
5
  Author-email: Salvatore Pennacchio <jtatopenn@libero.it>
6
6
  License: Business Source License 1.1
@@ -64,6 +64,11 @@ Provides-Extra: qmmm
64
64
  Requires-Dist: rdkit>=2023.9.1; extra == "qmmm"
65
65
  Provides-Extra: ase
66
66
  Requires-Dist: ase>=3.22.0; extra == "ase"
67
+ Provides-Extra: rag
68
+ Requires-Dist: scikit-learn>=1.0.0; extra == "rag"
69
+ Requires-Dist: sentence-transformers>=2.2.0; extra == "rag"
70
+ Provides-Extra: armor
71
+ Requires-Dist: dense-armor>=1.1.18; extra == "armor"
67
72
  Provides-Extra: full
68
73
  Requires-Dist: cupy-cuda12x>=12.0.0; extra == "full"
69
74
  Requires-Dist: streamlit>=1.30.0; extra == "full"
@@ -80,6 +85,7 @@ Requires-Dist: httpx>=0.27; extra == "full"
80
85
  Provides-Extra: dev
81
86
  Requires-Dist: pytest>=7.0.0; extra == "dev"
82
87
  Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
88
+ Requires-Dist: pytest-env>=1.0.0; extra == "dev"
83
89
  Provides-Extra: docs
84
90
  Requires-Dist: mkdocs>=1.6.0; extra == "docs"
85
91
  Requires-Dist: mkdocs-material>=9.5.0; extra == "docs"
@@ -343,7 +349,7 @@ If Dense-Evolution is useful in academic work, please cite it via the metadata i
343
349
  Archived on [Zenodo](https://zenodo.org/):
344
350
 
345
351
  - **Concept DOI** (always resolves to the latest version): [10.5281/zenodo.21855643](https://doi.org/10.5281/zenodo.21855643)
346
- - **This release (v8.1.78)**: [10.5281/zenodo.22706039](https://doi.org/10.5281/zenodo.22706039)
352
+ - **This release (v8.2.0)**: [10.5281/zenodo.22818319](https://doi.org/10.5281/zenodo.22818319)
347
353
 
348
354
  ---
349
355
 
@@ -109,6 +109,7 @@ dense_evolution/solvers/harrison_tb.py
109
109
  dense_evolution/solvers/vhd_tb.py
110
110
  dense_evolution/utils/__init__.py
111
111
  dense_evolution/utils/drawing.py
112
+ dense_evolution/utils/mass_decomposition.py
112
113
  dense_evolution/utils/measurement.py
113
114
  research/local_site/__init__.py
114
115
  research/local_site/app/__init__.py
@@ -133,6 +134,7 @@ tools/dashboard/core/vqe.py
133
134
  tools/dashboard/core/wormhole.py
134
135
  tools/ia_utils/__init__.py
135
136
  tools/ia_utils/adversarial_vector_attack.py
137
+ tools/ia_utils/rag.py
136
138
  tools/ia_utils/vector_healing.py
137
139
  tools/mcp_server/__init__.py
138
140
  tools/mcp_server/client.py
@@ -5,6 +5,9 @@ scipy>=1.9.0
5
5
  jax>=0.4.0
6
6
  jaxlib>=0.4.0
7
7
 
8
+ [armor]
9
+ dense-armor>=1.1.18
10
+
8
11
  [ase]
9
12
  ase>=3.22.0
10
13
 
@@ -24,6 +27,7 @@ plotly>=5.0.0
24
27
  [dev]
25
28
  pytest>=7.0.0
26
29
  pytest-cov>=4.0.0
30
+ pytest-env>=1.0.0
27
31
 
28
32
  [docs]
29
33
  mkdocs>=1.6.0
@@ -78,5 +82,9 @@ qiskit>=1.0.0
78
82
  [qmmm]
79
83
  rdkit>=2023.9.1
80
84
 
85
+ [rag]
86
+ scikit-learn>=1.0.0
87
+ sentence-transformers>=2.2.0
88
+
81
89
  [stim]
82
90
  stim>=1.13.0