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.
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/PKG-INFO +7 -2
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/README.md +1 -1
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/__init__.py +1 -1
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/backends/chunk/geometry.py +11 -1
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/native_hf/basis.py +2 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/native_hf/libcint_bridge.py +63 -20
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/native_hf/scf.py +41 -11
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/physics/__init__.py +2 -0
- dense_evolution-8.2.0/dense_evolution/physics/spectral.py +143 -0
- dense_evolution-8.2.0/dense_evolution/qmmm/__init__.py +13 -0
- dense_evolution-8.2.0/dense_evolution/qmmm/ase_bridge.py +97 -0
- dense_evolution-8.1.80/tools/dashboard/core/qmmm.py → dense_evolution-8.2.0/dense_evolution/qmmm/forces.py +49 -8
- dense_evolution-8.2.0/dense_evolution/qmmm/propagation.py +80 -0
- dense_evolution-8.2.0/dense_evolution/qmmm/region.py +137 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution.egg-info/PKG-INFO +7 -2
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution.egg-info/SOURCES.txt +6 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution.egg-info/requires.txt +7 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/pyproject.toml +43 -3
- dense_evolution-8.2.0/tools/dashboard/core/qmmm.py +16 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/dashboard/core/system_limits.py +8 -7
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/ia_utils/vector_healing.py +64 -10
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/mcp_server/client.py +106 -13
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/mcp_server/models.py +40 -1
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/mcp_server/server.py +15 -1
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/autodiff.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/backends/__init__.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/backends/chunk/__init__.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/backends/chunk/_engine_imports.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/backends/chunk/circuit_chunker.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/backends/chunk/core.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/backends/chunk/disk_overflow.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/backends/chunk/guard.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/backends/chunk/kernels.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/backends/mps.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/backends/statevector.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/chunk.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/circuits/__init__.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/circuits/compiler.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/circuits/diagram.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/circuits/gates.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/circuits/parser.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/circuits/qft.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/circuits/random_circuit.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/circuits/registry.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/circuits/topology.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/circuits/trotter.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/circuits/uccsd.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/cli.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/compiler.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/config.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/drawing.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/entropy.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/fermions.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/gates.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/harrison_tb.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/healing.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/interop/__init__.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/interop/qiskit_pennylane.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/measurement.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/mitigation/__init__.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/mitigation/healing.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/mitigation/kl_divergence.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/mitigation/magic_entropy.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/mitigation/magic_entropy_shadows.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/mitigation/renyi.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/mitigation/stabilizer_renyi_entropy.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/mitigation/zne.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/mps.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/native_hf/__init__.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/native_hf/assembly.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/native_hf/boys.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/native_hf/bridge.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/native_hf/cartesian.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/native_hf/coulomb.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/native_hf/differentiable.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/native_hf/gaussians.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/native_hf/kinetic.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/native_hf/overlap.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/noise/__init__.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/noise/coherent_attack.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/noise/cosmic_ray.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/noise/density_matrix_channels.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/noise/differentiable.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/noise/kraus/__init__.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/noise/kraus/amplitude_damping.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/noise/kraus/bitflip.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/noise/kraus/combined.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/noise/kraus/depolarizing.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/noise/kraus/ideal.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/noise/kraus/phaseflip.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/noise/kraus_channels.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/noise/oscillating.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/observables.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/parser.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/physics/entropy.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/physics/fermions.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/physics/observables.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/physics/qec.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/physics/states.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/qec.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/qft.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/random_circuit.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/registry.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/simulator.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/solvers/__init__.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/solvers/autodiff.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/solvers/harrison_tb.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/solvers/vhd_tb.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/states.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/topology.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/trotter.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/utils/__init__.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/utils/drawing.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/utils/measurement.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/vhd_tb.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution.egg-info/dependency_links.txt +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution.egg-info/entry_points.txt +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution.egg-info/top_level.txt +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/license.md +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/research/local_site/__init__.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/research/local_site/app/__init__.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/research/local_site/app/server.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/setup.cfg +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/dashboard/core/__init__.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/dashboard/core/_gate_tables.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/dashboard/core/band_structure.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/dashboard/core/circuit_builder_component.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/dashboard/core/circuit_diagram.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/dashboard/core/engine.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/dashboard/core/graphical_builder.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/dashboard/core/hamiltonians.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/dashboard/core/mitigation.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/dashboard/core/noise_tools.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/dashboard/core/qasm_library.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/dashboard/core/state_visuals.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/dashboard/core/vector_healing.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/dashboard/core/visuals.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/dashboard/core/vqe.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/dashboard/core/wormhole.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/ia_utils/__init__.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/ia_utils/adversarial_vector_attack.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/mcp_server/__init__.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/mcp_server/config.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/mcp_server/molecules.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/mcp_server/tools/__init__.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/mcp_server/tools/chemistry_tools.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/mcp_server/tools/circuit_tools.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/mcp_server/tools/mitigation_tools.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/mcp_server/tools/noise_tools.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/mcp_server/tools/system_tools.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/mcp_server/tools/wormhole_tools.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/mcp_server/utils/__init__.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/mcp_server/utils/cache.py +0 -0
- {dense_evolution-8.1.80 → dense_evolution-8.2.0}/tools/mcp_server/utils/images.py +0 -0
- {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.
|
|
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,
|
|
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,
|
|
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.
|
|
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
|
-
|
|
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))
|
{dense_evolution-8.1.80 → dense_evolution-8.2.0}/dense_evolution/native_hf/libcint_bridge.py
RENAMED
|
@@ -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
|
|
138
|
-
"""
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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,
|
|
157
|
-
B = jnp.where(jnp.eye(diis_dim, dtype=bool) & ~mask2d,
|
|
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, -
|
|
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(-
|
|
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],
|
|
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,
|
|
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
|
-
|
|
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()
|