dense-evolution 8.3.0__py3-none-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dashboard_core/__init__.py +115 -0
- dashboard_core/_gate_tables.py +30 -0
- dashboard_core/band_structure.py +71 -0
- dashboard_core/circuit_builder_component.py +232 -0
- dashboard_core/circuit_diagram.py +216 -0
- dashboard_core/crypto_protocols.py +77 -0
- dashboard_core/engine.py +326 -0
- dashboard_core/graphical_builder.py +114 -0
- dashboard_core/hamiltonians.py +593 -0
- dashboard_core/mass_decomposition_tool.py +47 -0
- dashboard_core/mitigation.py +343 -0
- dashboard_core/native_hf_diagnostics.py +62 -0
- dashboard_core/noise_tools.py +125 -0
- dashboard_core/qasm_library.py +233 -0
- dashboard_core/qmmm.py +16 -0
- dashboard_core/rag_tool.py +45 -0
- dashboard_core/state_visuals.py +288 -0
- dashboard_core/system_limits.py +60 -0
- dashboard_core/vector_healing.py +102 -0
- dashboard_core/visuals.py +158 -0
- dashboard_core/vqe.py +533 -0
- dashboard_core/wormhole.py +580 -0
- dense_evolution/__init__.py +114 -0
- dense_evolution/autodiff.py +10 -0
- dense_evolution/backends/__init__.py +5 -0
- dense_evolution/backends/chunk/__init__.py +37 -0
- dense_evolution/backends/chunk/_engine_imports.py +57 -0
- dense_evolution/backends/chunk/circuit_chunker.py +55 -0
- dense_evolution/backends/chunk/core.py +432 -0
- dense_evolution/backends/chunk/disk_overflow.py +232 -0
- dense_evolution/backends/chunk/geometry.py +95 -0
- dense_evolution/backends/chunk/guard.py +190 -0
- dense_evolution/backends/chunk/kernels.py +531 -0
- dense_evolution/backends/mps.py +1569 -0
- dense_evolution/backends/statevector.py +616 -0
- dense_evolution/chunk.py +25 -0
- dense_evolution/circuits/__init__.py +20 -0
- dense_evolution/circuits/compiler.py +488 -0
- dense_evolution/circuits/diagram.py +94 -0
- dense_evolution/circuits/gates.py +91 -0
- dense_evolution/circuits/parser.py +632 -0
- dense_evolution/circuits/qft.py +66 -0
- dense_evolution/circuits/random_circuit.py +85 -0
- dense_evolution/circuits/registry.py +74 -0
- dense_evolution/circuits/topology.py +79 -0
- dense_evolution/circuits/trotter.py +265 -0
- dense_evolution/circuits/uccsd.py +275 -0
- dense_evolution/cli.py +199 -0
- dense_evolution/compiler.py +9 -0
- dense_evolution/config.py +49 -0
- dense_evolution/drawing.py +10 -0
- dense_evolution/entropy.py +9 -0
- dense_evolution/fermions.py +9 -0
- dense_evolution/gates.py +9 -0
- dense_evolution/harrison_tb.py +16 -0
- dense_evolution/healing.py +18 -0
- dense_evolution/interop/__init__.py +18 -0
- dense_evolution/interop/qiskit_pennylane.py +406 -0
- dense_evolution/measurement.py +10 -0
- dense_evolution/mitigation/__init__.py +54 -0
- dense_evolution/mitigation/healing.py +215 -0
- dense_evolution/mitigation/kl_divergence.py +93 -0
- dense_evolution/mitigation/magic_entropy.py +163 -0
- dense_evolution/mitigation/magic_entropy_shadows.py +262 -0
- dense_evolution/mitigation/renyi.py +168 -0
- dense_evolution/mitigation/stabilizer_renyi_entropy.py +103 -0
- dense_evolution/mitigation/zne.py +990 -0
- dense_evolution/mps.py +9 -0
- dense_evolution/native_hf/__init__.py +26 -0
- dense_evolution/native_hf/_libcint/LICENSE-libcint +10 -0
- dense_evolution/native_hf/_libcint/libdecint.dll +0 -0
- dense_evolution/native_hf/assembly.py +304 -0
- dense_evolution/native_hf/basis.py +117 -0
- dense_evolution/native_hf/boys.py +35 -0
- dense_evolution/native_hf/bridge.py +112 -0
- dense_evolution/native_hf/cartesian.py +64 -0
- dense_evolution/native_hf/coulomb.py +196 -0
- dense_evolution/native_hf/differentiable.py +53 -0
- dense_evolution/native_hf/gaussians.py +79 -0
- dense_evolution/native_hf/kinetic.py +52 -0
- dense_evolution/native_hf/libcint_bridge.py +167 -0
- dense_evolution/native_hf/overlap.py +91 -0
- dense_evolution/native_hf/scf.py +404 -0
- dense_evolution/noise/__init__.py +79 -0
- dense_evolution/noise/coherent_attack.py +264 -0
- dense_evolution/noise/cosmic_ray.py +61 -0
- dense_evolution/noise/density_matrix_channels.py +78 -0
- dense_evolution/noise/differentiable.py +66 -0
- dense_evolution/noise/kraus/__init__.py +6 -0
- dense_evolution/noise/kraus/amplitude_damping.py +47 -0
- dense_evolution/noise/kraus/bitflip.py +22 -0
- dense_evolution/noise/kraus/combined.py +16 -0
- dense_evolution/noise/kraus/depolarizing.py +47 -0
- dense_evolution/noise/kraus/ideal.py +10 -0
- dense_evolution/noise/kraus/phaseflip.py +21 -0
- dense_evolution/noise/kraus_channels.py +285 -0
- dense_evolution/noise/oscillating.py +32 -0
- dense_evolution/noise/pink.py +80 -0
- dense_evolution/observables.py +11 -0
- dense_evolution/parser.py +9 -0
- dense_evolution/physics/__init__.py +27 -0
- dense_evolution/physics/entropy.py +161 -0
- dense_evolution/physics/fermions.py +322 -0
- dense_evolution/physics/observables.py +523 -0
- dense_evolution/physics/qec.py +1113 -0
- dense_evolution/physics/spectral.py +143 -0
- dense_evolution/physics/states.py +43 -0
- dense_evolution/protocols/__init__.py +27 -0
- dense_evolution/protocols/bb84.py +133 -0
- dense_evolution/protocols/di_qkd_ghz.py +199 -0
- dense_evolution/protocols/dicka_protocol2.py +124 -0
- dense_evolution/qec.py +20 -0
- dense_evolution/qft.py +9 -0
- dense_evolution/qmmm/__init__.py +13 -0
- dense_evolution/qmmm/ase_bridge.py +97 -0
- dense_evolution/qmmm/forces.py +388 -0
- dense_evolution/qmmm/propagation.py +80 -0
- dense_evolution/qmmm/region.py +137 -0
- dense_evolution/random_circuit.py +15 -0
- dense_evolution/registry.py +9 -0
- dense_evolution/simulator.py +10 -0
- dense_evolution/solvers/__init__.py +19 -0
- dense_evolution/solvers/autodiff.py +169 -0
- dense_evolution/solvers/harrison_tb.py +189 -0
- dense_evolution/solvers/vhd_tb.py +187 -0
- dense_evolution/states.py +9 -0
- dense_evolution/topology.py +9 -0
- dense_evolution/trotter.py +9 -0
- dense_evolution/utils/__init__.py +13 -0
- dense_evolution/utils/drawing.py +101 -0
- dense_evolution/utils/mass_decomposition.py +246 -0
- dense_evolution/utils/measurement.py +94 -0
- dense_evolution/vhd_tb.py +16 -0
- dense_evolution-8.3.0.dist-info/METADATA +366 -0
- dense_evolution-8.3.0.dist-info/RECORD +165 -0
- dense_evolution-8.3.0.dist-info/WHEEL +5 -0
- dense_evolution-8.3.0.dist-info/entry_points.txt +2 -0
- dense_evolution-8.3.0.dist-info/licenses/license.md +58 -0
- dense_evolution-8.3.0.dist-info/top_level.txt +5 -0
- ia_utils/__init__.py +0 -0
- ia_utils/adversarial_vector_attack.py +196 -0
- ia_utils/rag.py +288 -0
- ia_utils/vector_healing.py +399 -0
- local_site/__init__.py +0 -0
- local_site/app/__init__.py +0 -0
- local_site/app/server.py +1009 -0
- mcp_server/__init__.py +0 -0
- mcp_server/client.py +324 -0
- mcp_server/config.py +32 -0
- mcp_server/models.py +347 -0
- mcp_server/molecules.py +71 -0
- mcp_server/server.py +119 -0
- mcp_server/tools/__init__.py +0 -0
- mcp_server/tools/chemistry_tools.py +225 -0
- mcp_server/tools/circuit_tools.py +83 -0
- mcp_server/tools/crypto_tools.py +66 -0
- mcp_server/tools/mitigation_tools.py +81 -0
- mcp_server/tools/noise_tools.py +60 -0
- mcp_server/tools/retrieval_tools.py +44 -0
- mcp_server/tools/system_tools.py +149 -0
- mcp_server/tools/wormhole_tools.py +142 -0
- mcp_server/utils/__init__.py +0 -0
- mcp_server/utils/cache.py +55 -0
- mcp_server/utils/images.py +67 -0
- mcp_server/utils/truncation.py +38 -0
dashboard_core/vqe.py
ADDED
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Real, dynamically-generated VQE ansatz circuits for molecular Hamiltonians
|
|
3
|
+
-- no fixed/hardcoded rotation angles. Every circuit this module returns
|
|
4
|
+
is produced by an actual classical optimization run against the real
|
|
5
|
+
molecular Hamiltonian for the requested geometry/mapping, not a stored
|
|
6
|
+
constant.
|
|
7
|
+
|
|
8
|
+
Two real ansatz families:
|
|
9
|
+
|
|
10
|
+
- **hardware-efficient** (Kandala et al., Nature 2017): a Hartree-Fock
|
|
11
|
+
computational-basis initial state, then n_layers of single-qubit RY
|
|
12
|
+
rotations followed by a linear CNOT entangling ladder. Generic --
|
|
13
|
+
doesn't know anything about the molecule's own fermionic structure,
|
|
14
|
+
just a NISQ-friendly template. Optimized entirely on dense_evolution's
|
|
15
|
+
own engine: the ansatz is built as real OpenQASM, parsed with
|
|
16
|
+
dense_evolution.QASMParser, and turned into a JAX-differentiable energy
|
|
17
|
+
function via dense_evolution.autodiff.circuit_to_energy_fn (the exact
|
|
18
|
+
pattern already used and tested in this project's own
|
|
19
|
+
feature/streamlit-dashboard history, dashboard_core/vqe_engine.py --
|
|
20
|
+
reused here without its unrelated QM/MM-telemetry code, not
|
|
21
|
+
reinvented). A hand-rolled Adam loop (jax.value_and_grad, jax.jit)
|
|
22
|
+
optimizes it -- no PennyLane device/QNode/optimizer involved at all
|
|
23
|
+
for this ansatz; PennyLane's only remaining role anywhere in this
|
|
24
|
+
module is the real Hartree-Fock + Jordan-Wigner mapping itself
|
|
25
|
+
(dashboard_core.hamiltonians), which isn't something worth
|
|
26
|
+
reimplementing (see research/quantum_chemistry_vqe_pipeline.md).
|
|
27
|
+
Verified to match the PennyLane-optimized version's convergence (same
|
|
28
|
+
order of residual error against the exact energy, same physics).
|
|
29
|
+
- **UCCSD** (Unitary Coupled-Cluster Singles and Doubles): the standard
|
|
30
|
+
chemically-motivated VQE ansatz. Built from the molecule's *real*
|
|
31
|
+
single/double fermionic excitation operators
|
|
32
|
+
(dense_evolution.find_excitations -- pure combinatorics, verified to
|
|
33
|
+
reproduce qml.qchem.excitations exactly), applied to the Hartree-Fock
|
|
34
|
+
reference via dense_evolution.single_excitation_ops/
|
|
35
|
+
double_excitation_ops -- exact closed-form circuits derived directly
|
|
36
|
+
against dense_evolution's own Jordan-Wigner mapping
|
|
37
|
+
(physics.fermions.majorana_pauli_terms), not PennyLane's decomposition;
|
|
38
|
+
see dense_evolution/circuits/uccsd.py for the derivation and the exact
|
|
39
|
+
scope of the closed form vs. its (also verified exact) per-term
|
|
40
|
+
fallback. Fewer parameters than hardware-efficient for the same
|
|
41
|
+
molecule (H2: 3 vs 32), and converges to the exact energy faster
|
|
42
|
+
because the ansatz form actually matches the physics. Also optimized
|
|
43
|
+
entirely on dense_evolution's own engine, same as hardware-efficient --
|
|
44
|
+
the obstacle was that these excitation circuits reuse the same weight
|
|
45
|
+
across several RY/RZ gates per excitation (single_excitation_ops' CRY
|
|
46
|
+
is 2 RY gates; double_excitation_ops' per-term path is up to 8 RZ
|
|
47
|
+
gates), whereas circuit_to_energy_fn treats every parametric gate
|
|
48
|
+
occurrence as an independent free parameter. Solved with an affine
|
|
49
|
+
parameter expansion (_uccsd_native_expansion): probing
|
|
50
|
+
_uccsd_native_ops at weights=0 and at each basis vector gives a fixed
|
|
51
|
+
(baseline, expansion_matrix) pair such that
|
|
52
|
+
full_gate_values = baseline + expansion_matrix @ real_weights exactly
|
|
53
|
+
reproduces the real per-gate values for any weights (verified by
|
|
54
|
+
direct probing, not derived from theory) -- composed with
|
|
55
|
+
circuit_to_energy_fn this is still JAX-differentiable in the small real
|
|
56
|
+
weight vector by ordinary chain rule, so the same hand-rolled Adam loop
|
|
57
|
+
optimizes it with no PennyLane device/QNode/optimizer, or PennyLane
|
|
58
|
+
import of any kind, involved.
|
|
59
|
+
|
|
60
|
+
The Hartree-Fock initial state (computed via qml.qchem.hf_state) only
|
|
61
|
+
has a simple X-gate encoding under the Jordan-Wigner mapping, so VQE
|
|
62
|
+
generation here is JW-only. Bravyi-Kitaev stays available for exact
|
|
63
|
+
ground-state-energy queries in hamiltonians.py, where the eigenvalue
|
|
64
|
+
spectrum is mapping-invariant.
|
|
65
|
+
|
|
66
|
+
PennyLane's only remaining role anywhere in this module is the real
|
|
67
|
+
Hartree-Fock + Jordan-Wigner Hamiltonian construction itself
|
|
68
|
+
(dashboard_core.hamiltonians), which isn't something worth
|
|
69
|
+
reimplementing (see research/quantum_chemistry_vqe_pipeline.md) -- the
|
|
70
|
+
ansatz circuits themselves (hardware-efficient and UCCSD alike) never
|
|
71
|
+
touch PennyLane at all.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
import numpy as np
|
|
75
|
+
|
|
76
|
+
import dense_evolution as de
|
|
77
|
+
|
|
78
|
+
from .hamiltonians import _get_pennylane_hamiltonian, build_molecular_hamiltonian
|
|
79
|
+
|
|
80
|
+
__all__ = ['run_vqe', 'scan_hardware_efficient_energy_landscape']
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _hardware_efficient_ansatz(params, n_qubits, n_layers, hf_occupation):
|
|
84
|
+
import pennylane as qml
|
|
85
|
+
for q, occ in enumerate(hf_occupation):
|
|
86
|
+
if occ:
|
|
87
|
+
qml.PauliX(wires=q)
|
|
88
|
+
idx = 0
|
|
89
|
+
for _layer in range(n_layers):
|
|
90
|
+
for q in range(n_qubits):
|
|
91
|
+
qml.RY(params[idx], wires=q)
|
|
92
|
+
idx += 1
|
|
93
|
+
for q in range(n_qubits - 1):
|
|
94
|
+
qml.CNOT(wires=[q, q + 1])
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _hardware_efficient_qasm(params, n_qubits, n_layers, hf_occupation):
|
|
98
|
+
lines = ['OPENQASM 2.0;', 'include "qelib1.inc";', f'qreg q[{n_qubits}];', f'creg c[{n_qubits}];']
|
|
99
|
+
for q, occ in enumerate(hf_occupation):
|
|
100
|
+
if occ:
|
|
101
|
+
lines.append(f'x q[{q}];')
|
|
102
|
+
idx = 0
|
|
103
|
+
for _layer in range(n_layers):
|
|
104
|
+
for q in range(n_qubits):
|
|
105
|
+
lines.append(f'ry({params[idx]:.10f}) q[{q}];')
|
|
106
|
+
idx += 1
|
|
107
|
+
for q in range(n_qubits - 1):
|
|
108
|
+
lines.append(f'cx q[{q}],q[{q + 1}];')
|
|
109
|
+
lines.append('measure q -> c;')
|
|
110
|
+
return '\n'.join(lines)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _uccsd_excitations(electrons, n_qubits):
|
|
114
|
+
"""Which single/double excitations exist for this electron count --
|
|
115
|
+
pure combinatorics (occupied/virtual orbital pairing respecting spin
|
|
116
|
+
conservation), no quantum circuit involved. de.find_excitations is
|
|
117
|
+
dense_evolution's own reimplementation, verified to reproduce
|
|
118
|
+
qml.qchem.excitations exactly (see tests/unit/test_uccsd.py) --
|
|
119
|
+
kept native rather than calling PennyLane here for the same reason
|
|
120
|
+
the circuits themselves are native: this is chemistry index-finding,
|
|
121
|
+
not the deliberately-kept PennyLane dependency (Hartree-Fock +
|
|
122
|
+
Jordan-Wigner Hamiltonian construction, see module docstring)."""
|
|
123
|
+
return de.find_excitations(electrons, n_qubits)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _uccsd_native_ops(weights, n_qubits, singles, doubles, hf_occupation):
|
|
127
|
+
"""Real UCCSD circuit for the given weights, built entirely from
|
|
128
|
+
dense_evolution.single_excitation_ops/double_excitation_ops (exact
|
|
129
|
+
closed-form / exact per-term circuits, see
|
|
130
|
+
dense_evolution/circuits/uccsd.py) -- no PennyLane device, QNode, or
|
|
131
|
+
gate decomposition involved anywhere in this function. Doubles
|
|
132
|
+
always use the ancilla-free path (omitting ancilla1/ancilla2) so the
|
|
133
|
+
circuit's qubit count stays exactly n_qubits, matching H_dense's own
|
|
134
|
+
dimension with no padding needed."""
|
|
135
|
+
ops = []
|
|
136
|
+
for wire, occ in enumerate(hf_occupation):
|
|
137
|
+
if occ:
|
|
138
|
+
ops.append(('x', wire))
|
|
139
|
+
idx = 0
|
|
140
|
+
for (p, q) in singles:
|
|
141
|
+
ops.extend(de.single_excitation_ops(p, q, weights[idx]))
|
|
142
|
+
idx += 1
|
|
143
|
+
for (p, q, r, s) in doubles:
|
|
144
|
+
ops.extend(de.double_excitation_ops(p, q, r, s, weights[idx]))
|
|
145
|
+
idx += 1
|
|
146
|
+
return ops
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _ops_to_qasm(ops, n_qubits):
|
|
150
|
+
"""Gate-tuple list -> OpenQASM 2.0 text. Handles every gate name
|
|
151
|
+
dense_evolution.single_excitation_ops/double_excitation_ops can
|
|
152
|
+
produce: 2-qubit no-param (cx), 1-qubit no-param (x, h, s, sdg, ...),
|
|
153
|
+
1-qubit with param (ry, rz, ...)."""
|
|
154
|
+
lines = ['OPENQASM 2.0;', 'include "qelib1.inc";', f'qreg q[{n_qubits}];', f'creg c[{n_qubits}];']
|
|
155
|
+
for op in ops:
|
|
156
|
+
name, rest = op[0], op[1:]
|
|
157
|
+
if name == 'cx':
|
|
158
|
+
q0, q1 = rest
|
|
159
|
+
lines.append(f'cx q[{q0}],q[{q1}];')
|
|
160
|
+
elif len(rest) == 2:
|
|
161
|
+
q0, param = rest
|
|
162
|
+
lines.append(f'{name}({float(param):.12f}) q[{q0}];')
|
|
163
|
+
else:
|
|
164
|
+
(q0,) = rest
|
|
165
|
+
lines.append(f'{name} q[{q0}];')
|
|
166
|
+
lines.append('measure q -> c;')
|
|
167
|
+
return '\n'.join(lines)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _uccsd_tape_to_qasm(weights, n_qubits, singles, doubles, hf_occupation):
|
|
171
|
+
"""Builds the real UCCSD circuit for the given (converged) weights
|
|
172
|
+
and translates it to OpenQASM 2.0 -- the literal native circuit
|
|
173
|
+
dense_evolution runs, not an approximation of it."""
|
|
174
|
+
return _ops_to_qasm(_uccsd_native_ops(weights, n_qubits, singles, doubles, hf_occupation), n_qubits)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _uccsd_native_expansion(n_qubits, singles, doubles, hf_occupation, n_params):
|
|
178
|
+
"""Makes UCCSD optimizable through dense_evolution's own
|
|
179
|
+
circuit_to_energy_fn (not a black-box optimizer) despite
|
|
180
|
+
circuit_to_energy_fn treating every parametric gate occurrence as an
|
|
181
|
+
independent free value: _uccsd_native_ops's own excitation circuits
|
|
182
|
+
reuse the *same* weight across several RY/RZ gates per excitation
|
|
183
|
+
(single_excitation_ops' CRY is 2 RY gates; double_excitation_ops'
|
|
184
|
+
per-term path is up to 8 RZ gates, one per Pauli-string term), so the
|
|
185
|
+
true relationship between the small real weight vector (length
|
|
186
|
+
n_params, one per excitation) and the full per-gate value vector
|
|
187
|
+
(length n_params_full, one per parametric gate occurrence -- most of
|
|
188
|
+
them *not* free parameters at all, but fixed pi/2 basis-change
|
|
189
|
+
rotations) is affine: full = baseline + expansion_matrix @ weights.
|
|
190
|
+
|
|
191
|
+
Verified exact (not approximate) by direct probing rather than
|
|
192
|
+
derived from theory -- same technique this function always used,
|
|
193
|
+
just probing dense_evolution's own native circuit builder now
|
|
194
|
+
instead of PennyLane's UCCSD decomposition: evaluating
|
|
195
|
+
_uccsd_native_ops at weights=0 (-> baseline) and at each basis
|
|
196
|
+
vector e_i (-> baseline's i-th deviation, i.e. expansion_matrix's
|
|
197
|
+
i-th column) reproduces the real downstream energy for arbitrary
|
|
198
|
+
weight vectors to floating-point precision when fed through this
|
|
199
|
+
affine map into circuit_to_energy_fn (see
|
|
200
|
+
tests/integration/test_dashboard_vqe.py).
|
|
201
|
+
|
|
202
|
+
Since expansion_matrix/baseline are fixed (non-trainable) arrays, the
|
|
203
|
+
composition `energy_fn_full(baseline + expansion_matrix @ real_theta,
|
|
204
|
+
h_matrix)` is itself JAX-differentiable w.r.t. real_theta by ordinary
|
|
205
|
+
chain rule -- no special-cased gradient logic needed.
|
|
206
|
+
|
|
207
|
+
Returns (qasm_structure, baseline, expansion_matrix). qasm_structure
|
|
208
|
+
uses the weights=0 reference circuit -- gate order/wires depend only
|
|
209
|
+
on singles/doubles/hf_occupation, never on the numeric weight
|
|
210
|
+
values, so any reference weight vector would produce the same
|
|
211
|
+
structure.
|
|
212
|
+
|
|
213
|
+
Cost (prog.txt, dashboard_core audit point 4d): probing builds the
|
|
214
|
+
full gate list n_params + 1 times (the zero_weights baseline, then
|
|
215
|
+
one basis vector e_i per excitation) -- necessary for correctness
|
|
216
|
+
(expansion_matrix is derived empirically here, not assumed), but
|
|
217
|
+
each build is O(n_qubits) Python-level gate-tuple construction, so
|
|
218
|
+
this is real, linear-in-n_params up-front cost before optimization
|
|
219
|
+
even starts. Fine for the handful-to-dozens of excitations typical
|
|
220
|
+
molecules in MOLECULE_CATALOG produce; a molecule with many more
|
|
221
|
+
excitations would feel this as a real, if one-time, per-run delay.
|
|
222
|
+
Not parallelized (e.g. via jax.vmap over the n_params probing
|
|
223
|
+
vectors) -- this is plain Python/NumPy gate-tuple assembly, not a
|
|
224
|
+
JAX computation, so vmap would need restructuring this as a JAX-
|
|
225
|
+
traceable operation first, not just wrapping the existing loop."""
|
|
226
|
+
def param_values(weights):
|
|
227
|
+
ops = _uccsd_native_ops(weights, n_qubits, singles, doubles, hf_occupation)
|
|
228
|
+
# 'cx' is also a 3-tuple (name, control, target) -- must be
|
|
229
|
+
# excluded explicitly, not just by tuple length, or its target
|
|
230
|
+
# qubit index gets misread as a rotation angle.
|
|
231
|
+
return np.array([float(op[-1]) for op in ops if op[0] in ('ry', 'rz', 'rx')])
|
|
232
|
+
|
|
233
|
+
zero_weights = np.zeros(n_params)
|
|
234
|
+
baseline = param_values(zero_weights)
|
|
235
|
+
columns = []
|
|
236
|
+
for i in range(n_params):
|
|
237
|
+
e_i = np.zeros(n_params)
|
|
238
|
+
e_i[i] = 1.0
|
|
239
|
+
columns.append(param_values(e_i) - baseline)
|
|
240
|
+
expansion_matrix = np.array(columns).T if n_params else np.zeros((len(baseline), 0))
|
|
241
|
+
|
|
242
|
+
qasm_structure = _uccsd_tape_to_qasm(zero_weights, n_qubits, singles, doubles, hf_occupation)
|
|
243
|
+
return qasm_structure, baseline, expansion_matrix
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def run_vqe(symbols, geometry, charge=0, ansatz_type="hardware_efficient", n_layers=8, maxiter=200,
|
|
247
|
+
step_size=0.1, beta1=0.9, beta2=0.999, active_electrons=None, active_orbitals=None, seed=0):
|
|
248
|
+
"""Runs a real VQE optimization (hand-rolled Adam over
|
|
249
|
+
dense_evolution's own JAX-differentiable circuit_to_energy_fn, no
|
|
250
|
+
PennyLane optimizer/device involved) for the molecule's
|
|
251
|
+
Jordan-Wigner qubit Hamiltonian. step_size/beta1/beta2 are Adam's own
|
|
252
|
+
real hyperparameters (learning rate and first/second moment decay),
|
|
253
|
+
not cosmetic -- they change the real optimization trajectory computed
|
|
254
|
+
below, the same way they would in any other Adam implementation.
|
|
255
|
+
ansatz_type is
|
|
256
|
+
"hardware_efficient" (generic, n_layers deep) or "uccsd" (chemically
|
|
257
|
+
motivated, real fermionic single/double excitations -- n_layers is
|
|
258
|
+
ignored, the parameter count comes from the molecule's own occupied/
|
|
259
|
+
virtual orbital structure). Returns a dict with the real energy
|
|
260
|
+
convergence trace, the final variational energy, the exact ground-
|
|
261
|
+
state energy (dense diagonalization -- feasible for every qubit
|
|
262
|
+
count this function is meant to be called with, capped by the
|
|
263
|
+
caller's active-space choice), and the OpenQASM circuit for the
|
|
264
|
+
converged parameters.
|
|
265
|
+
|
|
266
|
+
maxiter=0 (or, for hardware_efficient, n_layers=0) is a real fast
|
|
267
|
+
path, not a special case faked up separately: with zero ansatz
|
|
268
|
+
parameters there's nothing for Adam to optimize, so this returns the
|
|
269
|
+
bare Hartree-Fock reference circuit and its (real, exact) HF energy
|
|
270
|
+
immediately -- the "pick a molecule, get a circuit" mechanic the UI
|
|
271
|
+
uses before committing to a minutes-long optimization.
|
|
272
|
+
|
|
273
|
+
Requires the optional `pennylane` extra (used internally to build the
|
|
274
|
+
molecular Hamiltonian and Hartree-Fock reference state -- the native
|
|
275
|
+
UCCSD ansatz circuits themselves, see `dense_evolution.circuits.uccsd`,
|
|
276
|
+
do not need PennyLane, but Hamiltonian construction still does):
|
|
277
|
+
`pip install dense-evolution[pennylane]`.
|
|
278
|
+
|
|
279
|
+
Parameters
|
|
280
|
+
----------
|
|
281
|
+
symbols : list of str
|
|
282
|
+
Atomic symbols, e.g. `["H", "H"]`.
|
|
283
|
+
geometry : list of [float, float, float]
|
|
284
|
+
Cartesian coordinates in Angstrom, one triplet per atom, same
|
|
285
|
+
order as `symbols`.
|
|
286
|
+
charge : int, optional
|
|
287
|
+
Molecular charge. Defaults to 0.
|
|
288
|
+
ansatz_type : str, optional
|
|
289
|
+
`"hardware_efficient"` (generic, `n_layers` deep) or `"uccsd"`
|
|
290
|
+
(chemically motivated; `n_layers` is ignored, the parameter count
|
|
291
|
+
comes from the molecule's own occupied/virtual orbital
|
|
292
|
+
structure). Defaults to `"hardware_efficient"`.
|
|
293
|
+
n_layers : int, optional
|
|
294
|
+
Ansatz depth (`hardware_efficient` only). Defaults to 8.
|
|
295
|
+
maxiter : int, optional
|
|
296
|
+
Adam iterations. Defaults to 200.
|
|
297
|
+
step_size, beta1, beta2 : float, optional
|
|
298
|
+
Adam hyperparameters (learning rate, first/second moment decay).
|
|
299
|
+
active_electrons, active_orbitals : int, optional
|
|
300
|
+
Active-space restriction, forwarded to PennyLane's Hamiltonian
|
|
301
|
+
builder. Defaults to the molecule's full space.
|
|
302
|
+
seed : int, optional
|
|
303
|
+
RNG seed for the initial ansatz parameters. Defaults to 0.
|
|
304
|
+
|
|
305
|
+
Returns
|
|
306
|
+
-------
|
|
307
|
+
dict
|
|
308
|
+
`vqe_energy_hartree` (final variational energy),
|
|
309
|
+
`exact_energy_hartree` (dense-diagonalization ground state, for
|
|
310
|
+
comparison), `energy_history` (per-iteration trace), `qasm` (the
|
|
311
|
+
converged circuit as OpenQASM 2.0), plus `n_qubits`, `n_params`,
|
|
312
|
+
`ansatz_type`, `n_layers`, `hf_occupation`.
|
|
313
|
+
|
|
314
|
+
Examples
|
|
315
|
+
--------
|
|
316
|
+
>>> from dashboard_core.vqe import run_vqe
|
|
317
|
+
>>> result = run_vqe(
|
|
318
|
+
... symbols=["H", "H"],
|
|
319
|
+
... geometry=[[0, 0, 0], [0, 0, 0.7414]],
|
|
320
|
+
... ansatz_type="hardware_efficient",
|
|
321
|
+
... n_layers=4,
|
|
322
|
+
... maxiter=200,
|
|
323
|
+
... )
|
|
324
|
+
>>> round(result["vqe_energy_hartree"], 4) # doctest: +SKIP
|
|
325
|
+
-1.1373
|
|
326
|
+
"""
|
|
327
|
+
import pennylane as qml
|
|
328
|
+
|
|
329
|
+
H, n_qubits = _get_pennylane_hamiltonian(symbols, geometry, charge, "jordan_wigner",
|
|
330
|
+
active_electrons, active_orbitals)
|
|
331
|
+
|
|
332
|
+
# Molecule() is cheap (~15ms, no HF solve) -- just needed here for its
|
|
333
|
+
# real total-electron count, to pick the right Hartree-Fock occupation
|
|
334
|
+
# when the caller didn't already fix it via an active-space choice.
|
|
335
|
+
if active_electrons is not None:
|
|
336
|
+
electrons = active_electrons
|
|
337
|
+
else:
|
|
338
|
+
molecule = qml.qchem.Molecule(symbols, np.asarray(geometry), charge=charge, unit="angstrom")
|
|
339
|
+
electrons = molecule.n_electrons
|
|
340
|
+
hf_occupation = qml.qchem.hf_state(electrons, n_qubits)
|
|
341
|
+
|
|
342
|
+
singles = doubles = None
|
|
343
|
+
if ansatz_type == "uccsd":
|
|
344
|
+
singles, doubles = _uccsd_excitations(electrons, n_qubits)
|
|
345
|
+
n_params = len(singles) + len(doubles)
|
|
346
|
+
else:
|
|
347
|
+
n_params = n_qubits * n_layers
|
|
348
|
+
|
|
349
|
+
rng = np.random.default_rng(seed)
|
|
350
|
+
|
|
351
|
+
if n_params == 0 or maxiter == 0:
|
|
352
|
+
dev = qml.device("lightning.qubit", wires=n_qubits)
|
|
353
|
+
|
|
354
|
+
@qml.qnode(dev)
|
|
355
|
+
def hf_energy_fn():
|
|
356
|
+
_hardware_efficient_ansatz(np.zeros(0), n_qubits, 0, hf_occupation)
|
|
357
|
+
return qml.expval(H)
|
|
358
|
+
|
|
359
|
+
final_energy = float(hf_energy_fn())
|
|
360
|
+
energy_history = [final_energy]
|
|
361
|
+
params = np.zeros(0)
|
|
362
|
+
n_layers = 0
|
|
363
|
+
n_params = 0
|
|
364
|
+
elif ansatz_type == "uccsd":
|
|
365
|
+
import jax
|
|
366
|
+
import jax.numpy as jnp
|
|
367
|
+
|
|
368
|
+
H_dense, _ = build_molecular_hamiltonian(symbols, geometry, charge, "jordan_wigner",
|
|
369
|
+
active_electrons, active_orbitals)
|
|
370
|
+
qasm_structure, baseline, expansion_matrix = _uccsd_native_expansion(
|
|
371
|
+
n_qubits, singles, doubles, hf_occupation, n_params,
|
|
372
|
+
)
|
|
373
|
+
parsed = de.QASMParser().parse(qasm_structure)
|
|
374
|
+
energy_fn_full, n_params_full = de.circuit_to_energy_fn(parsed, n_qubits)
|
|
375
|
+
if n_params_full != len(baseline):
|
|
376
|
+
# BUG FIX: was `assert`, silently stripped under python -O,
|
|
377
|
+
# letting a UCCSD/circuit_to_energy_fn desync through to a
|
|
378
|
+
# shape mismatch far downstream instead of failing here with
|
|
379
|
+
# a clear cause.
|
|
380
|
+
raise ValueError(
|
|
381
|
+
f"circuit_to_energy_fn found {n_params_full} parametric gates, "
|
|
382
|
+
f"expected {len(baseline)} from the UCCSD decomposition probe"
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
h_matrix = jnp.array(H_dense)
|
|
386
|
+
baseline_jax = jnp.array(baseline)
|
|
387
|
+
expansion_matrix_jax = jnp.array(expansion_matrix)
|
|
388
|
+
|
|
389
|
+
def real_energy_fn(real_theta, h_mat):
|
|
390
|
+
theta_full = baseline_jax + expansion_matrix_jax @ real_theta
|
|
391
|
+
return energy_fn_full(theta_full, h_mat)
|
|
392
|
+
|
|
393
|
+
theta = jnp.array(rng.uniform(-0.1, 0.1, size=n_params))
|
|
394
|
+
m_moment = jnp.zeros(n_params)
|
|
395
|
+
v_moment = jnp.zeros(n_params)
|
|
396
|
+
eps = 1e-8
|
|
397
|
+
energy_and_grad = jax.jit(jax.value_and_grad(real_energy_fn, argnums=0, has_aux=True))
|
|
398
|
+
|
|
399
|
+
energy_history = []
|
|
400
|
+
for t in range(1, maxiter + 1):
|
|
401
|
+
(energy, _sv), grad = energy_and_grad(theta, h_matrix)
|
|
402
|
+
m_moment = beta1 * m_moment + (1 - beta1) * grad
|
|
403
|
+
v_moment = beta2 * v_moment + (1 - beta2) * (grad ** 2)
|
|
404
|
+
m_hat = m_moment / (1 - beta1 ** t)
|
|
405
|
+
v_hat = v_moment / (1 - beta2 ** t)
|
|
406
|
+
theta = theta - step_size * m_hat / (jnp.sqrt(v_hat) + eps)
|
|
407
|
+
energy_history.append(float(energy))
|
|
408
|
+
final_energy_jax, _sv_final = real_energy_fn(theta, h_matrix)
|
|
409
|
+
final_energy = float(final_energy_jax)
|
|
410
|
+
energy_history.append(final_energy)
|
|
411
|
+
params = np.asarray(theta)
|
|
412
|
+
else:
|
|
413
|
+
import jax
|
|
414
|
+
import jax.numpy as jnp
|
|
415
|
+
|
|
416
|
+
H_dense, _ = build_molecular_hamiltonian(symbols, geometry, charge, "jordan_wigner",
|
|
417
|
+
active_electrons, active_orbitals)
|
|
418
|
+
qasm_template = _hardware_efficient_qasm(np.zeros(n_params), n_qubits, n_layers, hf_occupation)
|
|
419
|
+
parsed = de.QASMParser().parse(qasm_template)
|
|
420
|
+
energy_fn, n_params_native = de.circuit_to_energy_fn(parsed, n_qubits)
|
|
421
|
+
if n_params_native != n_params:
|
|
422
|
+
# BUG FIX: was `assert`, silently stripped under python -O.
|
|
423
|
+
raise ValueError(
|
|
424
|
+
f"circuit_to_energy_fn found {n_params_native} parametric gates, expected {n_params}"
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
h_matrix = jnp.array(H_dense)
|
|
428
|
+
theta = jnp.array(rng.uniform(-0.1, 0.1, size=n_params))
|
|
429
|
+
m_moment = jnp.zeros(n_params)
|
|
430
|
+
v_moment = jnp.zeros(n_params)
|
|
431
|
+
eps = 1e-8
|
|
432
|
+
energy_and_grad = jax.jit(jax.value_and_grad(energy_fn, argnums=0, has_aux=True))
|
|
433
|
+
|
|
434
|
+
energy_history = []
|
|
435
|
+
for t in range(1, maxiter + 1):
|
|
436
|
+
(energy, _sv), grad = energy_and_grad(theta, h_matrix)
|
|
437
|
+
m_moment = beta1 * m_moment + (1 - beta1) * grad
|
|
438
|
+
v_moment = beta2 * v_moment + (1 - beta2) * (grad ** 2)
|
|
439
|
+
m_hat = m_moment / (1 - beta1 ** t)
|
|
440
|
+
v_hat = v_moment / (1 - beta2 ** t)
|
|
441
|
+
theta = theta - step_size * m_hat / (jnp.sqrt(v_hat) + eps)
|
|
442
|
+
energy_history.append(float(energy))
|
|
443
|
+
final_energy_jax, _sv_final = energy_fn(theta, h_matrix)
|
|
444
|
+
final_energy = float(final_energy_jax)
|
|
445
|
+
energy_history.append(final_energy)
|
|
446
|
+
params = np.asarray(theta)
|
|
447
|
+
|
|
448
|
+
exact_energy = None
|
|
449
|
+
dim = 2 ** n_qubits
|
|
450
|
+
if dim <= 4096: # dense diagonalization budget: 4096^2 complex128 = 128 MB
|
|
451
|
+
# Both the uccsd and hardware_efficient-with-params branches above
|
|
452
|
+
# already built H_dense with these exact same arguments -- only
|
|
453
|
+
# the n_params==0 (Hartree-Fock-only, no ansatz built at all)
|
|
454
|
+
# branch never did. Reusing it there instead of a second
|
|
455
|
+
# "H_dense_check" call was previously a redundant cache lookup
|
|
456
|
+
# every time, not a bug (build_molecular_hamiltonian is cached,
|
|
457
|
+
# so it returned the identical matrix either way), but confusing
|
|
458
|
+
# flow (prog.txt, dashboard_core audit point 3c).
|
|
459
|
+
if n_params == 0:
|
|
460
|
+
H_dense, _ = build_molecular_hamiltonian(symbols, geometry, charge, "jordan_wigner",
|
|
461
|
+
active_electrons, active_orbitals)
|
|
462
|
+
exact_energy = float(np.linalg.eigvalsh(H_dense).min())
|
|
463
|
+
|
|
464
|
+
if n_params == 0:
|
|
465
|
+
qasm = _hardware_efficient_qasm(params, n_qubits, 0, hf_occupation)
|
|
466
|
+
elif ansatz_type == "uccsd":
|
|
467
|
+
qasm = _uccsd_tape_to_qasm(params, n_qubits, singles, doubles, hf_occupation)
|
|
468
|
+
else:
|
|
469
|
+
qasm = _hardware_efficient_qasm(params, n_qubits, n_layers, hf_occupation)
|
|
470
|
+
|
|
471
|
+
return {
|
|
472
|
+
'n_qubits': n_qubits,
|
|
473
|
+
'ansatz_type': ansatz_type if n_params > 0 else 'hartree_fock',
|
|
474
|
+
'n_layers': n_layers if ansatz_type != "uccsd" else None,
|
|
475
|
+
'n_params': n_params,
|
|
476
|
+
'hf_occupation': [int(b) for b in hf_occupation],
|
|
477
|
+
'energy_history': energy_history,
|
|
478
|
+
'vqe_energy_hartree': final_energy,
|
|
479
|
+
'exact_energy_hartree': exact_energy,
|
|
480
|
+
'qasm': qasm,
|
|
481
|
+
'params': params.tolist(),
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
def scan_hardware_efficient_energy_landscape(
|
|
486
|
+
symbols, geometry, charge, n_layers, hf_occupation, base_params,
|
|
487
|
+
param_i, param_j, values_i, values_j,
|
|
488
|
+
active_electrons=None, active_orbitals=None,
|
|
489
|
+
):
|
|
490
|
+
"""Real 2D energy-landscape scan around a converged hardware_efficient
|
|
491
|
+
VQE result: re-evaluates <psi(theta)|H|psi(theta)> on
|
|
492
|
+
dense_evolution's own circuit_to_energy_fn for every (values_i,
|
|
493
|
+
values_j) grid point, holding every parameter except param_i/param_j
|
|
494
|
+
fixed at its converged value from base_params -- the same real
|
|
495
|
+
Hamiltonian and ansatz circuit run_vqe itself used for this molecule,
|
|
496
|
+
not a separate or approximate model.
|
|
497
|
+
|
|
498
|
+
hardware_efficient only: UCCSD's parameter space is an affine
|
|
499
|
+
expansion over full per-gate values (_uccsd_native_expansion), not a
|
|
500
|
+
direct one-parameter-per-rotation-gate mapping, so "parameter i"
|
|
501
|
+
doesn't correspond to a single rotation angle the way it does here.
|
|
502
|
+
|
|
503
|
+
Returns a (len(values_i), len(values_j)) numpy array of energies in
|
|
504
|
+
Hartree.
|
|
505
|
+
"""
|
|
506
|
+
import jax
|
|
507
|
+
import jax.numpy as jnp
|
|
508
|
+
|
|
509
|
+
n_qubits = len(hf_occupation)
|
|
510
|
+
n_params = n_qubits * n_layers
|
|
511
|
+
H_dense, _ = build_molecular_hamiltonian(
|
|
512
|
+
symbols, geometry, charge, "jordan_wigner", active_electrons, active_orbitals,
|
|
513
|
+
)
|
|
514
|
+
qasm_template = _hardware_efficient_qasm(np.zeros(n_params), n_qubits, n_layers, hf_occupation)
|
|
515
|
+
parsed = de.QASMParser().parse(qasm_template)
|
|
516
|
+
energy_fn, n_params_native = de.circuit_to_energy_fn(parsed, n_qubits)
|
|
517
|
+
if n_params_native != n_params:
|
|
518
|
+
raise ValueError(
|
|
519
|
+
f"circuit_to_energy_fn found {n_params_native} parametric gates, expected {n_params}"
|
|
520
|
+
)
|
|
521
|
+
|
|
522
|
+
h_matrix = jnp.array(H_dense)
|
|
523
|
+
base = jnp.array(base_params)
|
|
524
|
+
energy_fn_jit = jax.jit(energy_fn)
|
|
525
|
+
|
|
526
|
+
energies = np.zeros((len(values_i), len(values_j)))
|
|
527
|
+
for a, vi in enumerate(values_i):
|
|
528
|
+
theta_a = base.at[param_i].set(vi)
|
|
529
|
+
for b, vj in enumerate(values_j):
|
|
530
|
+
theta_ab = theta_a.at[param_j].set(vj)
|
|
531
|
+
energy, _sv = energy_fn_jit(theta_ab, h_matrix)
|
|
532
|
+
energies[a, b] = float(energy)
|
|
533
|
+
return energies
|