Skip to content

Commit c4bbd43

Browse files
Positive control
1 parent 68bb50f commit c4bbd43

1 file changed

Lines changed: 196 additions & 0 deletions

File tree

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
# Positive control: VQE implementation validation
2+
# Developed with help from Claude (Anthropic)
3+
#
4+
# Purpose: Demonstrate that the VQE implementation in clifford_vqe_ig_entangled.py
5+
# is capable of finding energy improvements when the bootstrap starting point is
6+
# deliberately wrong. This addresses the referee question:
7+
# "How do we know VQE would ever return a non-null variational update?"
8+
#
9+
# Method: Run VQE on H2 at equilibrium (STO-3G, 4 qubits) starting from:
10+
# (a) all-zeros state (vacuum, definitely wrong)
11+
# (b) random state
12+
# (c) correct bootstrap state (should already be near ground state)
13+
#
14+
# Expected result:
15+
# (a) and (b): VQE finds substantial improvement toward known ground state energy
16+
# (c): VQE finds little or no improvement (consistent with main paper results)
17+
#
18+
# Reference: H2 STO-3G FCI ground state energy ~ -1.1373 Ha
19+
# Hartree-Fock energy ~ -1.1175 Ha
20+
21+
from openfermion import MolecularData, FermionOperator, jordan_wigner, get_fermion_operator
22+
from openfermionpyscf import run_pyscf
23+
24+
import itertools
25+
import multiprocessing
26+
import numpy as np
27+
import os
28+
import random
29+
30+
import pennylane as qml
31+
from pennylane import numpy as nppl
32+
33+
34+
# ── molecule: H2 at equilibrium ──────────────────────────────────────────────
35+
36+
basis = "sto-3g"
37+
multiplicity = 1
38+
charge = 0
39+
40+
geometry = [("H", (0.0, 0.0, 0.0)), ("H", (0.0, 0.0, 0.74))] # H2 equilibrium
41+
42+
print("H2 STO-3G positive control for VQE implementation validation")
43+
print("Reference FCI ground state energy: ~ -1.1373 Ha")
44+
print()
45+
46+
47+
def geometry_to_atom_str(geometry):
48+
return "; ".join(
49+
f"{symbol} {x:.10f} {y:.10f} {z:.10f}"
50+
for symbol, (x, y, z) in geometry
51+
)
52+
53+
54+
# ── build Hamiltonians ────────────────────────────────────────────────────────
55+
56+
molecule_of = MolecularData(geometry, basis, multiplicity=multiplicity, charge=charge)
57+
molecule_of = run_pyscf(molecule_of, run_scf=True, run_mp2=False, run_cisd=False,
58+
run_ccsd=False, run_fci=False)
59+
fermion_ham = get_fermion_operator(molecule_of.get_molecular_hamiltonian())
60+
n_electrons = molecule_of.n_electrons
61+
n_qubits = molecule_of.n_qubits
62+
63+
print(f"Hartree-Fock energy: {molecule_of.hf_energy:.10f} Ha")
64+
print(f"{n_electrons} electrons, {n_qubits} qubits")
65+
66+
# Interaction graph
67+
edges = set()
68+
for term, coeff in fermion_ham.terms.items():
69+
jw_term = jordan_wigner(FermionOperator(term=term, coefficient=coeff))
70+
for pauli_string, _ in jw_term.terms.items():
71+
qubits = [q for q, op in pauli_string if op != 'I']
72+
for a, b in itertools.combinations(sorted(qubits), 2):
73+
edges.add((a, b))
74+
ig_edges = sorted(edges)
75+
n_edges = len(ig_edges)
76+
print(f"Interaction graph: {n_edges} undirected edges")
77+
78+
# Z-only Hamiltonian for bootstrap energy evaluation
79+
z_hamiltonian = []
80+
for term, coeff in fermion_ham.terms.items():
81+
jw_term = jordan_wigner(FermionOperator(term=term, coefficient=coeff))
82+
for pauli_string, jw_coeff in jw_term.terms.items():
83+
if any(op in ('X', 'Y') for _, op in pauli_string):
84+
continue
85+
q = [qubit for qubit, op in pauli_string if op == 'Z']
86+
z_hamiltonian.append((q, jw_coeff.real))
87+
88+
def z_energy(theta):
89+
energy = 0.0
90+
for qubits, coeff in z_hamiltonian:
91+
c = coeff
92+
for q in qubits:
93+
if theta[q]:
94+
c *= -1
95+
energy += c
96+
return energy
97+
98+
# PennyLane Hamiltonian
99+
coeffs = []
100+
observables = []
101+
for term, coeff in fermion_ham.terms.items():
102+
jw_term = jordan_wigner(FermionOperator(term=term, coefficient=coeff))
103+
for pauli_string, jw_coeff in jw_term.terms.items():
104+
ops = []
105+
for qubit_idx, pauli in pauli_string:
106+
if pauli == 'X': ops.append(qml.PauliX(qubit_idx))
107+
elif pauli == 'Y': ops.append(qml.PauliY(qubit_idx))
108+
elif pauli == 'Z': ops.append(qml.PauliZ(qubit_idx))
109+
observables.append(ops[0] if ops else qml.Identity(0))
110+
if len(ops) > 1:
111+
for op in ops[1:]:
112+
observables[-1] = observables[-1] @ op
113+
coeffs.append(nppl.array(jw_coeff.real, requires_grad=False))
114+
pl_hamiltonian = qml.Hamiltonian(coeffs, observables)
115+
116+
117+
# ── VQE runner ────────────────────────────────────────────────────────────────
118+
119+
def run_vqe(label, bootstrap_theta, n_steps=200, stepsize=np.pi/1800):
120+
print(f"\n── {label} ──")
121+
print(f" Initial θ: {bootstrap_theta.astype(int)}")
122+
initial_energy = z_energy(bootstrap_theta)
123+
print(f" Initial Z-basis energy: {initial_energy:.10f} Ha")
124+
125+
dev = qml.device("lightning.qubit", wires=n_qubits)
126+
127+
@qml.qnode(dev)
128+
def circuit(delta, gamma):
129+
for i in range(n_qubits):
130+
if bootstrap_theta[i]:
131+
qml.X(wires=i)
132+
for i in range(n_qubits):
133+
qml.RY(delta[i], wires=i)
134+
for idx, (a, b) in enumerate(ig_edges):
135+
qml.IsingZZ(gamma[idx], wires=[a, b])
136+
return qml.expval(pl_hamiltonian)
137+
138+
delta = nppl.array(np.random.uniform(-0.1, 0.1, n_qubits), requires_grad=True)
139+
gamma = nppl.array(np.random.uniform(-0.1, 0.1, n_edges), requires_grad=True)
140+
141+
opt = qml.AdamOptimizer(stepsize=stepsize)
142+
min_energy = initial_energy
143+
best_delta = delta.copy()
144+
best_gamma = gamma.copy()
145+
146+
for step in range(n_steps):
147+
(delta, gamma), energy = opt.step_and_cost(circuit, delta, gamma)
148+
if float(energy) < min_energy:
149+
min_energy = float(energy)
150+
best_delta = delta.copy()
151+
best_gamma = gamma.copy()
152+
if (step + 1) % 50 == 0:
153+
print(f" Step {step+1:3d}: Energy = {float(energy):.10f} Ha")
154+
155+
print(f" Initial energy: {initial_energy:.10f} Ha")
156+
print(f" Final energy: {min_energy:.10f} Ha")
157+
print(f" Improvement: {initial_energy - min_energy:.10f} Ha")
158+
print(f" δ (RY): {np.array(best_delta).round(4)}")
159+
print(f" γ (RZZ): {np.array(best_gamma).round(4)}")
160+
161+
return min_energy
162+
163+
164+
# ── Bootstrap state (correct priors) ─────────────────────────────────────────
165+
# H2 STO-3G: 2 electrons in 4 spin-orbitals
166+
# Correct HF state: qubits 0,1 occupied (alpha/beta of bonding orbital)
167+
168+
bootstrap_theta = np.array([True, True, False, False])
169+
170+
# ── Run three cases ───────────────────────────────────────────────────────────
171+
172+
e_zeros = run_vqe("Case (a): all-zeros initial state (vacuum)",
173+
np.array([False, False, False, False]))
174+
175+
e_random = run_vqe("Case (b): random initial state",
176+
np.array([bool(random.randint(0,1)) for _ in range(n_qubits)]))
177+
178+
e_bootstrap = run_vqe("Case (c): correct bootstrap state",
179+
bootstrap_theta)
180+
181+
# ── Summary ───────────────────────────────────────────────────────────────────
182+
183+
print(f"\n{'─'*60}")
184+
print(f"Hartree-Fock energy: {molecule_of.hf_energy:.10f} Ha")
185+
print(f"Reference FCI energy: ~ -1.1373000000 Ha")
186+
print(f"{'─'*60}")
187+
print(f"Case (a) vacuum → VQE final: {e_zeros:.10f} Ha")
188+
print(f"Case (b) random → VQE final: {e_random:.10f} Ha")
189+
print(f"Case (c) bootstrap → VQE final: {e_bootstrap:.10f} Ha")
190+
print(f"{'─'*60}")
191+
print()
192+
print("Interpretation:")
193+
print(" Cases (a) and (b) should show substantial VQE improvement,")
194+
print(" demonstrating the optimizer is functional.")
195+
print(" Case (c) should show little or no improvement,")
196+
print(" consistent with the bootstrap state being a convex minimum.")

0 commit comments

Comments
 (0)