dense-evolution 8.1.80__tar.gz → 8.2.0__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 (155) hide show
  1. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/PKG-INFO +7 -2
  2. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/README.md +1 -1
  3. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/__init__.py +1 -1
  4. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/backends/chunk/geometry.py +11 -1
  5. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/native_hf/basis.py +2 -0
  6. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/native_hf/libcint_bridge.py +63 -20
  7. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/native_hf/scf.py +41 -11
  8. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/physics/__init__.py +2 -0
  9. dense_evolution-8.2.0/dense_evolution/physics/spectral.py +143 -0
  10. dense_evolution-8.2.0/dense_evolution/qmmm/__init__.py +13 -0
  11. dense_evolution-8.2.0/dense_evolution/qmmm/ase_bridge.py +97 -0
  12. dense_evolution-8.1.80/tools/dashboard/core/qmmm.py → dense_evolution-8.2.0/dense_evolution/qmmm/forces.py +49 -8
  13. dense_evolution-8.2.0/dense_evolution/qmmm/propagation.py +80 -0
  14. dense_evolution-8.2.0/dense_evolution/qmmm/region.py +137 -0
  15. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution.egg-info/PKG-INFO +7 -2
  16. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution.egg-info/SOURCES.txt +6 -0
  17. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution.egg-info/requires.txt +7 -0
  18. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/pyproject.toml +43 -3
  19. dense_evolution-8.2.0/tools/dashboard/core/qmmm.py +16 -0
  20. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/dashboard/core/system_limits.py +8 -7
  21. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/ia_utils/vector_healing.py +64 -10
  22. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/mcp_server/client.py +106 -13
  23. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/mcp_server/models.py +40 -1
  24. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/mcp_server/server.py +15 -1
  25. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/autodiff.py +0 -0
  26. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/backends/__init__.py +0 -0
  27. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/backends/chunk/__init__.py +0 -0
  28. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/backends/chunk/_engine_imports.py +0 -0
  29. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/backends/chunk/circuit_chunker.py +0 -0
  30. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/backends/chunk/core.py +0 -0
  31. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/backends/chunk/disk_overflow.py +0 -0
  32. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/backends/chunk/guard.py +0 -0
  33. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/backends/chunk/kernels.py +0 -0
  34. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/backends/mps.py +0 -0
  35. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/backends/statevector.py +0 -0
  36. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/chunk.py +0 -0
  37. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/circuits/__init__.py +0 -0
  38. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/circuits/compiler.py +0 -0
  39. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/circuits/diagram.py +0 -0
  40. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/circuits/gates.py +0 -0
  41. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/circuits/parser.py +0 -0
  42. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/circuits/qft.py +0 -0
  43. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/circuits/random_circuit.py +0 -0
  44. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/circuits/registry.py +0 -0
  45. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/circuits/topology.py +0 -0
  46. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/circuits/trotter.py +0 -0
  47. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/circuits/uccsd.py +0 -0
  48. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/cli.py +0 -0
  49. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/compiler.py +0 -0
  50. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/config.py +0 -0
  51. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/drawing.py +0 -0
  52. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/entropy.py +0 -0
  53. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/fermions.py +0 -0
  54. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/gates.py +0 -0
  55. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/harrison_tb.py +0 -0
  56. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/healing.py +0 -0
  57. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/interop/__init__.py +0 -0
  58. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/interop/qiskit_pennylane.py +0 -0
  59. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/measurement.py +0 -0
  60. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/mitigation/__init__.py +0 -0
  61. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/mitigation/healing.py +0 -0
  62. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/mitigation/kl_divergence.py +0 -0
  63. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/mitigation/magic_entropy.py +0 -0
  64. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/mitigation/magic_entropy_shadows.py +0 -0
  65. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/mitigation/renyi.py +0 -0
  66. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/mitigation/stabilizer_renyi_entropy.py +0 -0
  67. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/mitigation/zne.py +0 -0
  68. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/mps.py +0 -0
  69. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/native_hf/__init__.py +0 -0
  70. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/native_hf/assembly.py +0 -0
  71. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/native_hf/boys.py +0 -0
  72. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/native_hf/bridge.py +0 -0
  73. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/native_hf/cartesian.py +0 -0
  74. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/native_hf/coulomb.py +0 -0
  75. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/native_hf/differentiable.py +0 -0
  76. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/native_hf/gaussians.py +0 -0
  77. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/native_hf/kinetic.py +0 -0
  78. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/native_hf/overlap.py +0 -0
  79. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/noise/__init__.py +0 -0
  80. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/noise/coherent_attack.py +0 -0
  81. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/noise/cosmic_ray.py +0 -0
  82. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/noise/density_matrix_channels.py +0 -0
  83. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/noise/differentiable.py +0 -0
  84. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/noise/kraus/__init__.py +0 -0
  85. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/noise/kraus/amplitude_damping.py +0 -0
  86. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/noise/kraus/bitflip.py +0 -0
  87. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/noise/kraus/combined.py +0 -0
  88. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/noise/kraus/depolarizing.py +0 -0
  89. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/noise/kraus/ideal.py +0 -0
  90. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/noise/kraus/phaseflip.py +0 -0
  91. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/noise/kraus_channels.py +0 -0
  92. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/noise/oscillating.py +0 -0
  93. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/observables.py +0 -0
  94. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/parser.py +0 -0
  95. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/physics/entropy.py +0 -0
  96. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/physics/fermions.py +0 -0
  97. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/physics/observables.py +0 -0
  98. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/physics/qec.py +0 -0
  99. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/physics/states.py +0 -0
  100. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/qec.py +0 -0
  101. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/qft.py +0 -0
  102. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/random_circuit.py +0 -0
  103. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/registry.py +0 -0
  104. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/simulator.py +0 -0
  105. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/solvers/__init__.py +0 -0
  106. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/solvers/autodiff.py +0 -0
  107. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/solvers/harrison_tb.py +0 -0
  108. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/solvers/vhd_tb.py +0 -0
  109. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/states.py +0 -0
  110. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/topology.py +0 -0
  111. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/trotter.py +0 -0
  112. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/utils/__init__.py +0 -0
  113. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/utils/drawing.py +0 -0
  114. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/utils/measurement.py +0 -0
  115. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/vhd_tb.py +0 -0
  116. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution.egg-info/dependency_links.txt +0 -0
  117. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution.egg-info/entry_points.txt +0 -0
  118. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution.egg-info/top_level.txt +0 -0
  119. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/license.md +0 -0
  120. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/research/local_site/__init__.py +0 -0
  121. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/research/local_site/app/__init__.py +0 -0
  122. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/research/local_site/app/server.py +0 -0
  123. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/setup.cfg +0 -0
  124. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/dashboard/core/__init__.py +0 -0
  125. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/dashboard/core/_gate_tables.py +0 -0
  126. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/dashboard/core/band_structure.py +0 -0
  127. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/dashboard/core/circuit_builder_component.py +0 -0
  128. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/dashboard/core/circuit_diagram.py +0 -0
  129. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/dashboard/core/engine.py +0 -0
  130. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/dashboard/core/graphical_builder.py +0 -0
  131. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/dashboard/core/hamiltonians.py +0 -0
  132. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/dashboard/core/mitigation.py +0 -0
  133. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/dashboard/core/noise_tools.py +0 -0
  134. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/dashboard/core/qasm_library.py +0 -0
  135. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/dashboard/core/state_visuals.py +0 -0
  136. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/dashboard/core/vector_healing.py +0 -0
  137. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/dashboard/core/visuals.py +0 -0
  138. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/dashboard/core/vqe.py +0 -0
  139. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/dashboard/core/wormhole.py +0 -0
  140. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/ia_utils/__init__.py +0 -0
  141. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/ia_utils/adversarial_vector_attack.py +0 -0
  142. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/mcp_server/__init__.py +0 -0
  143. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/mcp_server/config.py +0 -0
  144. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/mcp_server/molecules.py +0 -0
  145. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/mcp_server/tools/__init__.py +0 -0
  146. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/mcp_server/tools/chemistry_tools.py +0 -0
  147. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/mcp_server/tools/circuit_tools.py +0 -0
  148. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/mcp_server/tools/mitigation_tools.py +0 -0
  149. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/mcp_server/tools/noise_tools.py +0 -0
  150. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/mcp_server/tools/system_tools.py +0 -0
  151. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/mcp_server/tools/wormhole_tools.py +0 -0
  152. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/mcp_server/utils/__init__.py +0 -0
  153. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/mcp_server/utils/cache.py +0 -0
  154. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/mcp_server/utils/images.py +0 -0
  155. {dense_evolution-8.1.80 → dense_evolution-8.2.0}/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.1.80
3
+ Version: 8.2.0
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
@@ -60,6 +60,10 @@ Requires-Dist: httpx>=0.27; extra == "mcp"
60
60
  Requires-Dist: pydantic>=2.0.0; extra == "mcp"
61
61
  Provides-Extra: libcint
62
62
  Requires-Dist: pyscf>=2.0.0; extra == "libcint"
63
+ Provides-Extra: qmmm
64
+ Requires-Dist: rdkit>=2023.9.1; extra == "qmmm"
65
+ Provides-Extra: ase
66
+ Requires-Dist: ase>=3.22.0; extra == "ase"
63
67
  Provides-Extra: full
64
68
  Requires-Dist: cupy-cuda12x>=12.0.0; extra == "full"
65
69
  Requires-Dist: streamlit>=1.30.0; extra == "full"
@@ -85,6 +89,7 @@ Requires-Dist: ruff>=0.4.0; extra == "docs"
85
89
  Requires-Dist: pennylane>=0.35.0; extra == "docs"
86
90
  Requires-Dist: basis_set_exchange>=0.9; extra == "docs"
87
91
  Requires-Dist: plotly>=5.0.0; extra == "docs"
92
+ Requires-Dist: rdkit>=2023.9.1; extra == "docs"
88
93
  Dynamic: license-file
89
94
 
90
95
  <p align="center">
@@ -285,7 +290,7 @@ sim.run_chunk(circuit.to_tuples(), chunk_size_gates=500)
285
290
  - **Real noise, real mitigation.** Stochastic Kraus channels, real-device noise imported from Qiskit backends, and Zero-Noise Extrapolation to correct for it. [Noise](https://tatopenn-cell.github.io/Dense-Evolution/api/noise/) · [Mitigation](https://tatopenn-cell.github.io/Dense-Evolution/api/mitigation/) · [what noise/mitigation/healing each mean](https://tatopenn-cell.github.io/Dense-Evolution/concepts/).
286
291
  - **Differentiable VQE, from scratch.** `circuit_to_energy_fn` is the same JAX-differentiable engine real molecular VQE runs on — real Hartree-Fock Hamiltonians, UCCSD/hardware-efficient ansätze, Adam optimization. [Autodiff](https://tatopenn-cell.github.io/Dense-Evolution/api/autodiff/).
287
292
  - **OpenQASM 2.0/3.0, both directions.** A real parser, plus Qiskit/PennyLane interop bridges. [QASM Parser](https://tatopenn-cell.github.io/Dense-Evolution/api/parser/) · [Interop](https://tatopenn-cell.github.io/Dense-Evolution/api/interop/).
288
- - **Code-agnostic QEC decoding**, Majorana/Jordan-Wigner fermion mapping, from-scratch Hartree-Fock for elements outside PennyLane's own basis set, and a traversable-wormhole-inspired teleportation protocol — see the [full API reference](https://tatopenn-cell.github.io/Dense-Evolution/api/) for all of it.
293
+ - **Code-agnostic QEC decoding**, Majorana/Jordan-Wigner fermion mapping, from-scratch Hartree-Fock for elements outside PennyLane's own basis set, a traversable-wormhole-inspired teleportation protocol, and real QM/MM region partitioning (ring-safe BFS, Diffuse2Seg-derived relevance propagation) — see the [full API reference](https://tatopenn-cell.github.io/Dense-Evolution/api/) for all of it.
289
294
 
290
295
  ## ▍ Benchmarks
291
296
 
@@ -196,7 +196,7 @@ sim.run_chunk(circuit.to_tuples(), chunk_size_gates=500)
196
196
  - **Real noise, real mitigation.** Stochastic Kraus channels, real-device noise imported from Qiskit backends, and Zero-Noise Extrapolation to correct for it. [Noise](https://tatopenn-cell.github.io/Dense-Evolution/api/noise/) · [Mitigation](https://tatopenn-cell.github.io/Dense-Evolution/api/mitigation/) · [what noise/mitigation/healing each mean](https://tatopenn-cell.github.io/Dense-Evolution/concepts/).
197
197
  - **Differentiable VQE, from scratch.** `circuit_to_energy_fn` is the same JAX-differentiable engine real molecular VQE runs on — real Hartree-Fock Hamiltonians, UCCSD/hardware-efficient ansätze, Adam optimization. [Autodiff](https://tatopenn-cell.github.io/Dense-Evolution/api/autodiff/).
198
198
  - **OpenQASM 2.0/3.0, both directions.** A real parser, plus Qiskit/PennyLane interop bridges. [QASM Parser](https://tatopenn-cell.github.io/Dense-Evolution/api/parser/) · [Interop](https://tatopenn-cell.github.io/Dense-Evolution/api/interop/).
199
- - **Code-agnostic QEC decoding**, Majorana/Jordan-Wigner fermion mapping, from-scratch Hartree-Fock for elements outside PennyLane's own basis set, and a traversable-wormhole-inspired teleportation protocol — see the [full API reference](https://tatopenn-cell.github.io/Dense-Evolution/api/) for all of it.
199
+ - **Code-agnostic QEC decoding**, Majorana/Jordan-Wigner fermion mapping, from-scratch Hartree-Fock for elements outside PennyLane's own basis set, a traversable-wormhole-inspired teleportation protocol, and real QM/MM region partitioning (ring-safe BFS, Diffuse2Seg-derived relevance propagation) — see the [full API reference](https://tatopenn-cell.github.io/Dense-Evolution/api/) for all of it.
200
200
 
201
201
  ## ▍ Benchmarks
202
202
 
@@ -57,7 +57,7 @@ from .physics.qec import (pauli_commutes, compute_syndrome, erasure_aware_decode
57
57
  blind_minimum_weight_decode, decode_with_erasure_fallback,
58
58
  counts_in_intervals_dimension, nearest_coset_decode)
59
59
 
60
- __version__ = "8.1.80"
60
+ __version__ = "8.1.81"
61
61
 
62
62
  __all__ = [
63
63
  "__version__",
@@ -19,7 +19,17 @@ def get_dynamic_chunk(dtype_target) -> int:
19
19
  bpe = 8
20
20
  max_elements = safe_bytes / bpe
21
21
  max_bits = int(np.floor(np.log2(max(max_elements, 2.0))))
22
- return max(16, min(max_bits, 27))
22
+ # Upper bound raised 27 -> 30 (prog.txt / user request): the real
23
+ # constraint is already _device_memory_budget_bytes's own VRAM/RAM
24
+ # reading (a 27-bit chunk is only 2.1GB regardless of dtype -- any
25
+ # GPU with more than ~2.5GB free was being capped well below what it
26
+ # could actually hold). 30 keeps a ceiling at all (avoids ever
27
+ # returning a chunk_size_bits so large a single chunk's own indexing
28
+ # arithmetic elsewhere risks int32 overflow) without arbitrarily
29
+ # discarding real headroom on 16GB+ GPUs (a Kaggle T4/P100 at
30
+ # ~0.85*16GB/16B per complex128 element already computes max_bits
31
+ # near 30 on its own -- this line was the thing throwing that away).
32
+ return max(16, min(max_bits, 30))
23
33
 
24
34
 
25
35
  def _dtype_for_qubits(n_qubits: int):
@@ -22,6 +22,7 @@ import jax
22
22
  import jax.numpy as jnp
23
23
  import numpy as np
24
24
 
25
+ from dense_evolution.config import ensure_x64
25
26
  from dense_evolution.native_hf.cartesian import cartesian_powers
26
27
  from dense_evolution.native_hf.gaussians import GaussianShell3D
27
28
  from dense_evolution.native_hf.overlap import overlap_3d
@@ -105,6 +106,7 @@ def load_element_shells(basis_name: str, atomic_number: int, center: jax.Array,
105
106
 
106
107
  def build_molecule_shells(atomic_numbers: list[int], geometry_bohr: np.ndarray, basis_name: str) -> list[ContractedShell]:
107
108
  """geometry_bohr: shape (n_atoms, 3), atomic units."""
109
+ ensure_x64()
108
110
  shells = []
109
111
  for i, (z, r) in enumerate(zip(atomic_numbers, geometry_bohr)):
110
112
  shells.extend(load_element_shells(basis_name, z, jnp.asarray(r), i))
@@ -1,4 +1,10 @@
1
- """Optional PySCF/libcint bridge for native_hf's electron-repulsion tensor.
1
+ """Optional PySCF/libcint bridge for native_hf's electron-repulsion tensor,
2
+ and (build_overlap_and_core_hamiltonian_libcint) its one-electron overlap
3
+ and core-Hamiltonian integrals too -- found necessary, not just faster in
4
+ principle: a larger basis (6-31G*) ran native_hf's own one-electron JAX
5
+ assembly out of memory during XLA JIT compilation on a real Kaggle CPU
6
+ kernel, even with the ERI tensor already libcint-backed (see that
7
+ function's own docstring for the concrete case).
2
8
 
3
9
  native_hf's own build_repulsion_tensor (assembly.py) computes the ERI
4
10
  tensor via JAX-jitted Obara-Saika recursions -- correct, differentiable,
@@ -134,19 +140,15 @@ def _import_pyscf():
134
140
  return gto
135
141
 
136
142
 
137
- def build_repulsion_tensor_libcint(atomic_numbers: list, geometry_bohr: np.ndarray, basis_name: str) -> np.ndarray:
138
- """Same contract as assembly.build_repulsion_tensor, but computed via
139
- PySCF/libcint instead of native_hf's own JAX recursions -- takes
140
- (atomic_numbers, geometry_bohr, basis_name) rather than a shells list
141
- since PySCF needs to build its own `Mole` from the same spec, not
142
- native_hf's ContractedShell objects.
143
-
144
- geometry_bohr: shape (n_atoms, 3), atomic units, same convention as
145
- build_molecule_shells."""
143
+ def _libcint_mol_and_perm_rescale(atomic_numbers: list, geometry_bohr: np.ndarray, basis_name: str):
144
+ """Shared Mole/permutation/rescale setup for every one- and two-electron
145
+ libcint integral below -- S, H_core, and the ERI tensor all live in the
146
+ same AO basis and need the identical native_hf<->libcint AO reordering
147
+ and per-AO normalization (see module docstring), so this factors out
148
+ what build_repulsion_tensor_libcint used to compute standalone."""
146
149
  gto = _import_pyscf()
147
150
  shells = build_molecule_shells(atomic_numbers, geometry_bohr, basis_name)
148
151
  n = n_cartesian_functions(shells)
149
-
150
152
  atom_spec = [
151
153
  (int(z), (float(r[0]), float(r[1]), float(r[2])))
152
154
  for z, r in zip(atomic_numbers, geometry_bohr)
@@ -158,9 +160,58 @@ def build_repulsion_tensor_libcint(atomic_numbers: list, geometry_bohr: np.ndarr
158
160
  f"for basis {basis_name!r} -- the two engines disagree on this basis "
159
161
  f"set's shell composition, not just component ordering."
160
162
  )
161
-
162
163
  overlap_diag = np.diag(mol.intor("int1e_ovlp"))
163
164
  rescale = 1.0 / np.sqrt(overlap_diag)
165
+ ao_starts = _match_pyscf_shell_ao_starts(mol, shells)
166
+ perm = np.empty(n, dtype=np.int64)
167
+ offset = 0
168
+ for shell, pyscf_start in zip(shells, ao_starts):
169
+ block_size = len(cartesian_powers(shell.degree))
170
+ perm[offset : offset + block_size] = pyscf_start + _permutation_libcint_to_native_hf(shell.degree)
171
+ offset += block_size
172
+ return mol, perm, rescale
173
+
174
+
175
+ def build_overlap_and_core_hamiltonian_libcint(
176
+ atomic_numbers: list, geometry_bohr: np.ndarray, basis_name: str
177
+ ) -> tuple[np.ndarray, np.ndarray]:
178
+ """Same contract as assembly.build_overlap_matrix + build_core_hamiltonian
179
+ combined, but computed via PySCF/libcint instead of native_hf's own
180
+ JAX-jitted one-electron integrals.
181
+
182
+ Found necessary, not just faster in principle: on a real Kaggle CPU
183
+ kernel, native_hf's own one-electron assembly ran out of memory during
184
+ XLA JIT compilation for a 4-heavy-atom molecule at 6-31G* -- even with
185
+ the (much more expensive) ERI tensor already routed through
186
+ build_repulsion_tensor_libcint above, the one-electron path alone was
187
+ enough to exhaust available memory on that larger basis. Extending the
188
+ same libcint bridge to S/H_core fixed it (verified: the same molecule/
189
+ basis that crashed before completed in this repo's own Discovery
190
+ isodesmic-scission-energy experiment once this function existed).
191
+
192
+ Returns (S, H_core) in native_hf's own AO ordering/normalization, ready
193
+ to pass directly into scf.run_scf alongside build_repulsion_tensor_libcint's
194
+ own output.
195
+ """
196
+ mol, perm, rescale = _libcint_mol_and_perm_rescale(atomic_numbers, geometry_bohr, basis_name)
197
+ S = mol.intor("int1e_ovlp") * rescale[:, None] * rescale[None, :]
198
+ T = mol.intor("int1e_kin") * rescale[:, None] * rescale[None, :]
199
+ V_nuc = mol.intor("int1e_nuc") * rescale[:, None] * rescale[None, :]
200
+ H_core = T + V_nuc
201
+ return S[perm][:, perm], H_core[perm][:, perm]
202
+
203
+
204
+ def build_repulsion_tensor_libcint(atomic_numbers: list, geometry_bohr: np.ndarray, basis_name: str) -> np.ndarray:
205
+ """Same contract as assembly.build_repulsion_tensor, but computed via
206
+ PySCF/libcint instead of native_hf's own JAX recursions -- takes
207
+ (atomic_numbers, geometry_bohr, basis_name) rather than a shells list
208
+ since PySCF needs to build its own `Mole` from the same spec, not
209
+ native_hf's ContractedShell objects.
210
+
211
+ geometry_bohr: shape (n_atoms, 3), atomic units, same convention as
212
+ build_molecule_shells."""
213
+ mol, perm, rescale = _libcint_mol_and_perm_rescale(atomic_numbers, geometry_bohr, basis_name)
214
+ n = perm.shape[0]
164
215
 
165
216
  V = np.asarray(mol.intor("int2e", aosym="s1")).reshape(n, n, n, n)
166
217
  V = (
@@ -171,12 +222,4 @@ def build_repulsion_tensor_libcint(atomic_numbers: list, geometry_bohr: np.ndarr
171
222
  * rescale[None, None, None, :]
172
223
  )
173
224
 
174
- ao_starts = _match_pyscf_shell_ao_starts(mol, shells)
175
- perm = np.empty(n, dtype=np.int64)
176
- offset = 0
177
- for shell, pyscf_start in zip(shells, ao_starts):
178
- block_size = len(cartesian_powers(shell.degree))
179
- perm[offset : offset + block_size] = pyscf_start + _permutation_libcint_to_native_hf(shell.degree)
180
- offset += block_size
181
-
182
225
  return V[perm][:, perm][:, :, perm][:, :, :, perm]
@@ -92,6 +92,8 @@ import functools
92
92
  import jax
93
93
  import jax.numpy as jnp
94
94
 
95
+ from dense_evolution.config import ensure_x64
96
+
95
97
  _DIIS_DIM = 8
96
98
 
97
99
 
@@ -148,29 +150,54 @@ def _diis_extrapolate(fock_history: jax.Array, error_history: jax.Array, history
148
150
  of the augmented linear system an identity-like equation instead of
149
151
  a real DIIS constraint, so they can't contribute to the solution
150
152
  regardless of their (garbage/zero) content."""
153
+ dtype = fock_history.dtype
154
+ zero = jnp.zeros((), dtype=dtype)
155
+ one = jnp.ones((), dtype=dtype)
151
156
  valid = jnp.arange(diis_dim) >= (diis_dim - history_count)
152
157
 
153
158
  errs_flat = error_history.reshape(diis_dim, -1)
154
159
  B_full = errs_flat @ errs_flat.T
155
160
  mask2d = valid[:, None] & valid[None, :]
156
- B = jnp.where(mask2d, B_full, 0.0)
157
- B = jnp.where(jnp.eye(diis_dim, dtype=bool) & ~mask2d, 1.0, B)
161
+ B = jnp.where(mask2d, B_full, zero)
162
+ B = jnp.where(jnp.eye(diis_dim, dtype=bool) & ~mask2d, one, B)
158
163
 
159
- A = jnp.zeros((diis_dim + 1, diis_dim + 1))
164
+ A = jnp.zeros((diis_dim + 1, diis_dim + 1), dtype=dtype)
160
165
  A = A.at[:diis_dim, :diis_dim].set(B)
161
- col = jnp.where(valid, -1.0, 0.0)
166
+ col = jnp.where(valid, -one, zero)
162
167
  A = A.at[:diis_dim, diis_dim].set(col)
163
168
  A = A.at[diis_dim, :diis_dim].set(col)
164
- b = jnp.zeros(diis_dim + 1).at[diis_dim].set(-1.0)
169
+ b = jnp.zeros(diis_dim + 1, dtype=dtype).at[diis_dim].set(-one)
165
170
 
166
171
  solution = jnp.linalg.solve(A, b)
167
172
  is_finite = jnp.all(jnp.isfinite(solution))
168
- coeffs = jnp.where(valid, jnp.where(is_finite, solution[:diis_dim], 0.0), 0.0)
173
+ coeffs = jnp.where(valid, jnp.where(is_finite, solution[:diis_dim], zero), zero)
169
174
 
170
175
  F_diis = jnp.tensordot(coeffs, fock_history, axes=1)
171
176
  return jnp.where(is_finite, F_diis, fock_history[-1])
172
177
 
173
178
 
179
+ def _level_shift_fock(F_ao: jax.Array, C_prev: jax.Array, S: jax.Array, n_occupied_pairs: int, level_shift: float) -> jax.Array:
180
+ """Saunders & Hillier level shifting (Int. J. Quantum Chem. 7, 699
181
+ (1973)): push the virtual orbitals of the PREVIOUS iteration's MO
182
+ basis up by `level_shift` before this iteration's diagonalization,
183
+ to open a numerical gap and stop the occupied/virtual split from
184
+ flip-flopping across a near-degeneracy (see this module's own Si2
185
+ docstring above -- damping/DIIS already fix the textbook case, but a
186
+ real, harder case (a 30-atom aromatic fragment from the CASMI26
187
+ wiring kernel) still took 1114 iterations, swinging through three
188
+ wildly different intermediate energies first). Exact no-op at
189
+ level_shift=0.0: since C_prev is a full, S-orthonormal basis
190
+ (C_prev.T @ S @ C_prev = I, hence C_prev @ C_prev.T = S^{-1}), the
191
+ round-trip S @ C_prev @ (C_prev.T @ F_ao @ C_prev) @ C_prev.T @ S
192
+ reduces algebraically to exactly F_ao before any shift is added."""
193
+ F_mo_prev = C_prev.T @ F_ao @ C_prev
194
+ n = F_mo_prev.shape[0]
195
+ level_shift = jnp.asarray(level_shift, dtype=F_ao.dtype)
196
+ shift_diag = jnp.where(jnp.arange(n) >= n_occupied_pairs, level_shift, jnp.zeros((), dtype=F_ao.dtype))
197
+ F_mo_prev_shifted = F_mo_prev + jnp.diag(shift_diag)
198
+ return S @ C_prev @ F_mo_prev_shifted @ C_prev.T @ S
199
+
200
+
174
201
  def run_scf(
175
202
  S: jax.Array,
176
203
  H_core: jax.Array,
@@ -183,7 +210,9 @@ def run_scf(
183
210
  energy_tol: float = 1e-10,
184
211
  damping: float = 0.5,
185
212
  diis_dim: int = _DIIS_DIM,
213
+ level_shift: float = 0.0,
186
214
  ) -> HFResult:
215
+ ensure_x64()
187
216
  if n_electrons % 2 != 0:
188
217
  raise ValueError("Only closed-shell (even electron count) systems are supported.")
189
218
  n_occupied_pairs = n_electrons // 2
@@ -206,7 +235,7 @@ def run_scf(
206
235
  return jnp.logical_and(jnp.logical_not(converged), iteration < max_iterations)
207
236
 
208
237
  def body_fun(state):
209
- iteration, P, _C, _orbital_energies, energy_prev, _converged, fock_history, error_history = state
238
+ iteration, P, C_prev, _orbital_energies, energy_prev, _converged, fock_history, error_history = state
210
239
 
211
240
  F, energy = _fock_and_energy(P)
212
241
  error = _diis_error(F, P, S, X)
@@ -221,7 +250,8 @@ def run_scf(
221
250
  F,
222
251
  )
223
252
 
224
- orbital_energies, C_ortho = jnp.linalg.eigh(X.T @ F_step @ X)
253
+ F_diag = _level_shift_fock(F_step, C_prev, S, n_occupied_pairs, level_shift)
254
+ orbital_energies, C_ortho = jnp.linalg.eigh(X.T @ F_diag @ X)
225
255
  C = X @ C_ortho
226
256
  P_new = _density_from_coefficients(C, n_occupied_pairs)
227
257
 
@@ -239,10 +269,10 @@ def run_scf(
239
269
  P0,
240
270
  C0,
241
271
  orbital_energies0,
242
- jnp.array(jnp.inf),
272
+ jnp.array(jnp.inf, dtype=H_core.dtype),
243
273
  jnp.array(False),
244
- jnp.zeros((diis_dim, n_basis, n_basis)),
245
- jnp.zeros((diis_dim, n_basis, n_basis)),
274
+ jnp.zeros((diis_dim, n_basis, n_basis), dtype=H_core.dtype),
275
+ jnp.zeros((diis_dim, n_basis, n_basis), dtype=H_core.dtype),
246
276
  )
247
277
  iteration, P, C, orbital_energies, _energy_prev, converged, _fh, _eh = jax.lax.while_loop(cond_fun, body_fun, init_state)
248
278
 
@@ -8,6 +8,7 @@ from .fermions import majorana_pauli_terms, total_parity_operator, hubbard_hamil
8
8
  from .qec import (pauli_commutes, compute_syndrome, erasure_aware_decode, pymatching_decode,
9
9
  blind_minimum_weight_decode, decode_with_erasure_fallback,
10
10
  counts_in_intervals_dimension, nearest_coset_decode)
11
+ from .spectral import has_exact_degeneracy, matrix_function_eigh, spectral_evolve
11
12
 
12
13
  __all__ = [
13
14
  "ghz_state",
@@ -18,4 +19,5 @@ __all__ = [
18
19
  "majorana_pauli_terms", "total_parity_operator", "hubbard_hamiltonian_pauli_terms", "square_lattice_edges",
19
20
  "pauli_commutes", "compute_syndrome", "erasure_aware_decode", "pymatching_decode", "blind_minimum_weight_decode",
20
21
  "decode_with_erasure_fallback", "counts_in_intervals_dimension", "nearest_coset_decode",
22
+ "has_exact_degeneracy", "matrix_function_eigh", "spectral_evolve",
21
23
  ]
@@ -0,0 +1,143 @@
1
+ """Gauge-safe gradients for spectral functions of a Hermitian matrix
2
+ (V f(Lambda) V^dagger, e.g. time evolution exp(-iHt)) at exact eigenvalue
3
+ degeneracy.
4
+
5
+ `jnp.linalg.eigh`'s reverse-mode gradient divides by `lambda_i - lambda_j`
6
+ for every eigenvector pair. When two eigenvalues are exactly degenerate,
7
+ this does not raise and does not always produce NaN -- it can silently
8
+ return a finite, WRONG gradient, because the eigenvectors spanning a
9
+ degenerate eigenspace are not themselves uniquely defined (any orthonormal
10
+ basis of that subspace is an equally valid `eigh` output). Measured on a
11
+ real Kaggle CPU kernel (Dense-Evolution-Discovery, PR #173): std eigh
12
+ gradient error 0.98 vs Kato 4e-10 on an H with four exact doubly-degenerate
13
+ eigenvalues -- several orders of magnitude, not a rounding difference.
14
+
15
+ REFERENCES (verified against the actual paper text, not trusted at face
16
+ value from a citation string alone):
17
+ Kasim, M. F., "Derivatives of partial eigendecomposition of a real
18
+ symmetric matrix for degenerate cases", arXiv:2011.04366 (2020).
19
+ Kato, T., "Perturbation Theory for Linear Operators", Springer (1995),
20
+ Ch. II.5.6 (the classical divided-difference formula for matrix
21
+ function derivatives, predating Kasim by decades).
22
+
23
+ `matrix_function_eigh` uses a `jax.custom_jvp` based on Kato's
24
+ divided-difference formula for matrix functions:
25
+
26
+ d/deps [ V(eps) f(Lambda(eps)) V(eps)^dagger ] = V (F o (V^dagger dH V)) V^dagger
27
+
28
+ with F the matrix of divided differences of f:
29
+
30
+ F[i,j] = (f(lambda_i) - f(lambda_j)) / (lambda_i - lambda_j) if lambda_i != lambda_j
31
+ F[i,j] = f'(lambda_i) if lambda_i == lambda_j (incl. i == j)
32
+
33
+ This formula does not pass through eigenvectors as an intermediate OUTPUT,
34
+ so it is gauge-invariant: the contribution from a degenerate block uses
35
+ f'(lambda) directly, and there is no gauge choice to make -- unlike a
36
+ `custom_vjp` built directly on top of `eigh`'s own eigenvector output,
37
+ which needs a compatibility condition on the perturbation direction
38
+ (Kasim's Eq. 4.72, confirmed present in the actual paper text) that this
39
+ formula does not.
40
+
41
+ WHEN TO USE THIS:
42
+ - H has (or might have) exactly degenerate eigenvalues, AND
43
+ - the function L(H) you differentiate depends on H through eigh, AND
44
+ - the function is not trivially constant on degenerate blocks.
45
+
46
+ For a Hamiltonian with only near-degeneracy (e.g. min_gap ~1e-5, no exact
47
+ tie), plain `jnp.linalg.eigh` is correct and faster -- use
48
+ `has_exact_degeneracy` to check before reaching for `spectral_evolve`.
49
+ """
50
+ import functools
51
+
52
+ import jax
53
+ import jax.numpy as jnp
54
+
55
+ from dense_evolution.config import ensure_x64
56
+
57
+ __all__ = ["has_exact_degeneracy", "matrix_function_eigh", "spectral_evolve"]
58
+
59
+ _DEGENERACY_TOL = 1e-8
60
+
61
+
62
+ def has_exact_degeneracy(H: jax.Array, tol: float = _DEGENERACY_TOL) -> bool:
63
+ """True if H has at least one pair of eigenvalues closer than tol.
64
+
65
+ Diagnostic only -- call this before choosing spectral_evolve (Kato)
66
+ over plain jnp.linalg.eigh (std). The threshold is the same one the
67
+ JVP rule below uses internally, so this is the exact condition under
68
+ which the two methods disagree."""
69
+ ensure_x64()
70
+ w = jnp.linalg.eigvalsh(H)
71
+ gaps = jnp.abs(jnp.diff(jnp.sort(w)))
72
+ return bool((gaps < tol).any())
73
+
74
+
75
+ def _matrix_function_divided_differences(fw, f_prime_w, w, tol):
76
+ lam_i = w[:, None]
77
+ lam_j = w[None, :]
78
+ gap = lam_i - lam_j
79
+ is_deg = jnp.abs(gap) < tol
80
+ safe_gap = jnp.where(is_deg, 1.0, gap)
81
+ F_quot = (fw[:, None] - fw[None, :]) / safe_gap
82
+ F_limit = f_prime_w[:, None]
83
+ return jnp.where(is_deg, F_limit, F_quot)
84
+
85
+
86
+ @functools.partial(jax.custom_jvp, nondiff_argnums=(1, 2))
87
+ def matrix_function_eigh(H: jax.Array, f, f_prime) -> jax.Array:
88
+ """V f(Lambda) V^dagger with a gauge-safe gradient at exact degeneracy.
89
+
90
+ Parameters
91
+ ----------
92
+ H : (n, n) Hermitian matrix.
93
+ f : callable, lambda (array) -> array. Applied elementwise to the
94
+ eigenvalues.
95
+ f_prime : callable, lambda (array) -> array. Analytic derivative of f,
96
+ used only in the JVP rule for degenerate blocks. Passed via
97
+ nondiff_argnums since a Python closure is not a valid JAX type to
98
+ trace.
99
+
100
+ Returns
101
+ -------
102
+ (n, n) complex128 matrix.
103
+
104
+ Example
105
+ -------
106
+ >>> import jax.numpy as jnp
107
+ >>> H = jnp.diag(jnp.array([1.0, 1.0, 2.0, 2.0], dtype=jnp.complex128))
108
+ >>> U = matrix_function_eigh(H, lambda w: jnp.exp(-1j * w), lambda w: -1j * jnp.exp(-1j * w))
109
+ """
110
+ ensure_x64()
111
+ w, v = jnp.linalg.eigh(H)
112
+ return v @ jnp.diag(f(w)) @ v.conj().T
113
+
114
+
115
+ @matrix_function_eigh.defjvp
116
+ def _matrix_function_eigh_jvp(f, f_prime, primals, tangents):
117
+ (H,) = primals
118
+ (dH,) = tangents
119
+
120
+ w, v = jnp.linalg.eigh(H)
121
+ fw = f(w)
122
+ f_prime_w = f_prime(w)
123
+
124
+ F = _matrix_function_divided_differences(fw, f_prime_w, w, _DEGENERACY_TOL)
125
+
126
+ X = v.conj().T @ dH @ v
127
+ dU = v @ (F * X) @ v.conj().T
128
+ U = v @ jnp.diag(fw) @ v.conj().T
129
+ return U, dU
130
+
131
+
132
+ def spectral_evolve(H: jax.Array, t: float) -> jax.Array:
133
+ """exp(-i H t) with a gauge-safe gradient at exact degeneracy.
134
+
135
+ Equivalent forward to `V @ diag(exp(-1j*w*t)) @ V.conj().T` where
136
+ (w, V) is `jnp.linalg.eigh(H)`. Backward uses Kato's divided-difference
137
+ rule (see module docstring) instead of `eigh`'s own reverse-mode rule.
138
+ """
139
+ return matrix_function_eigh(
140
+ H,
141
+ f=lambda w: jnp.exp(-1j * w * t),
142
+ f_prime=lambda w: -1j * t * jnp.exp(-1j * w * t),
143
+ )
@@ -0,0 +1,13 @@
1
+ from .forces import (
2
+ ATOMIC_MASSES_AMU, compute_hellmann_feynman_forces, md_step, run_md_trajectory,
3
+ MIN_NUCLEAR_DISTANCE_ANGSTROM, ACCEL_CONVERSION,
4
+ )
5
+ from .region import partition_qm_mm_region, sliced_geometry, ANGSTROM_TO_BOHR, CH_BOND_BOHR
6
+ from .propagation import propagate_relevance
7
+
8
+ __all__ = [
9
+ "ATOMIC_MASSES_AMU", "compute_hellmann_feynman_forces", "md_step", "run_md_trajectory",
10
+ "MIN_NUCLEAR_DISTANCE_ANGSTROM", "ACCEL_CONVERSION",
11
+ "partition_qm_mm_region", "sliced_geometry", "ANGSTROM_TO_BOHR", "CH_BOND_BOHR",
12
+ "propagate_relevance",
13
+ ]
@@ -0,0 +1,97 @@
1
+ """
2
+ ASE Calculator bridge for dense_evolution.native_hf (issue #288).
3
+
4
+ Motivation: QM/MM point 5's basis-flexibility test (Dense-Evolution-
5
+ Discovery, docs/qmmm_bond_order_and_embedding.md) needed a richer basis
6
+ (6-31G*) to test a real hypothesis about STO-3G's rigidity -- native_hf
7
+ already supports arbitrary basis names via `build_energy_fn`'s own
8
+ `basis_name` string parameter (nothing was actually hardcoded at the
9
+ library level; the QM/MM scripts' own `BASIS = "sto-3g"` constants were
10
+ just a script-level convenience). What native_hf does NOT give for free
11
+ is interop with the wider Python computational-chemistry ecosystem (ASE
12
+ Atoms objects, ASE's own optimizers/MD drivers, other engines' Atoms
13
+ representations) -- this bridge is exactly that interop layer, not a new
14
+ basis-set capability.
15
+
16
+ Requires ASE (the `ase` extra: pip install dense-evolution[ase]).
17
+ """
18
+ import numpy as np
19
+
20
+ HARTREE_TO_EV = 27.211386245988 # CODATA, matches this project's own use of scipy.constants elsewhere (dense_evolution.qmmm.forces.ACCEL_CONVERSION)
21
+ ANGSTROM_TO_BOHR = 1.8897259886
22
+
23
+
24
+ def _import_ase_calculator():
25
+ try:
26
+ from ase.calculators.calculator import Calculator, all_changes
27
+ except ImportError as exc:
28
+ raise ImportError(
29
+ "dense_evolution.qmmm.ase_bridge needs ASE, an optional dependency "
30
+ "(pip install dense-evolution[ase]); it is not installed."
31
+ ) from exc
32
+ return Calculator, all_changes
33
+
34
+
35
+ class DenseEvolutionCalculator:
36
+ """An ASE Calculator backed by dense_evolution.native_hf's own
37
+ differentiable RHF energy (build_energy_fn) -- real Obara-Saika
38
+ integrals and SCF, not a stub. `basis_name` is a plain string, passed
39
+ straight through to native_hf (e.g. "sto-3g", "6-31g*", any basis
40
+ basis_set_exchange has data for).
41
+
42
+ ASE units: eV, Angstrom (this class converts from native_hf's own
43
+ Hartree/Bohr at the boundary; everything inside native_hf itself
44
+ stays atomic units).
45
+
46
+ Only `energy` is implemented -- native_hf's forces come from
47
+ dense_evolution.qmmm.forces.compute_hellmann_feynman_forces (a
48
+ separate, already-real Hellmann-Feynman implementation with its own
49
+ finite-difference derivative and its own molecule-catalog-shaped
50
+ calling convention); this bridge does not reimplement or wrap that
51
+ here.
52
+
53
+ Examples
54
+ --------
55
+ >>> from ase import Atoms
56
+ >>> from dense_evolution.qmmm.ase_bridge import DenseEvolutionCalculator
57
+ >>> h2 = Atoms('H2', positions=[[0, 0, 0], [0, 0, 0.7414]])
58
+ >>> h2.calc = DenseEvolutionCalculator(atomic_numbers=[1, 1], nuclear_charges=[1.0, 1.0],
59
+ ... n_electrons=2, basis_name="sto-3g")
60
+ >>> round(h2.get_potential_energy(), 2)
61
+ -30.39
62
+ """
63
+
64
+ implemented_properties = ["energy"]
65
+
66
+ def __new__(cls, atomic_numbers, nuclear_charges, n_electrons, basis_name):
67
+ Calculator, all_changes = _import_ase_calculator()
68
+
69
+ class _Impl(Calculator):
70
+ implemented_properties = ["energy"]
71
+
72
+ def __init__(self):
73
+ super().__init__()
74
+ self._atomic_numbers = atomic_numbers
75
+ self._nuclear_charges = nuclear_charges
76
+ self._n_electrons = n_electrons
77
+ self._basis_name = basis_name
78
+ self._energy_fn = None
79
+ self._reference_geometry_bohr = None
80
+
81
+ def calculate(self, atoms=None, properties=("energy",), system_changes=all_changes):
82
+ super().calculate(atoms, properties, system_changes)
83
+ from dense_evolution.config import ensure_x64
84
+ from dense_evolution.native_hf.differentiable import build_energy_fn
85
+ ensure_x64()
86
+
87
+ geometry_bohr = np.asarray(atoms.get_positions()) * ANGSTROM_TO_BOHR
88
+ if self._energy_fn is None or "numbers" in system_changes:
89
+ self._reference_geometry_bohr = geometry_bohr
90
+ self._energy_fn = build_energy_fn(
91
+ self._atomic_numbers, self._nuclear_charges, self._n_electrons,
92
+ self._basis_name, self._reference_geometry_bohr,
93
+ )
94
+ energy_hartree = float(self._energy_fn(geometry_bohr))
95
+ self.results["energy"] = energy_hartree * HARTREE_TO_EV
96
+
97
+ return _Impl()