Skip to content

Commit 9c395c5

Browse files
Reverse J sign for TFIM
1 parent 4af90d8 commit 9c395c5

File tree

2 files changed

+332
-2
lines changed

2 files changed

+332
-2
lines changed

scripts/discrete_tfim_vqe.py

Lines changed: 330 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
1+
# Quantum chemistry example
2+
# Developed with help from (OpenAI custom GPT) Elara
3+
# (Requires PennyLane and OpenFermion)
4+
5+
import pennylane as qml
6+
from pennylane import numpy as np
7+
8+
import openfermion as of
9+
from openfermionpyscf import run_pyscf
10+
from openfermion.transforms import jordan_wigner
11+
12+
from PyQrackIsing import tfim_magnetization
13+
14+
# Step 0: Set environment variables (before running script)
15+
16+
# On command line or by .env file, you can set the following variables
17+
# QRACK_DISABLE_QUNIT_FIDELITY_GUARD=1: For large circuits, automatically "elide," for approximation
18+
# QRACK_NONCLIFFORD_ROUNDING_THRESHOLD=[0 to 1]: Sacrifices near-Clifford accuracy to reduce overhead
19+
# QRACK_QUNIT_SEPARABILITY_THRESHOLD=[0 to 1]: Rounds to separable states more aggressively
20+
# QRACK_QBDT_SEPARABILITY_THRESHOLD=[0 to 0.5]: Rounding for QBDD, if actually used
21+
22+
# Step 1: Define the molecule (Hydrogen, Helium, Lithium, Carbon, Nitrogen, Oxygen)
23+
24+
# basis = "sto-3g" # Minimal Basis Set
25+
basis = '6-31g' # Larger basis set
26+
# basis = 'cc-pVDZ' # Even larger basis set!
27+
multiplicity = 1 # singlet, closed shell, all electrons are paired (neutral molecules with full valence)
28+
# multiplicity = 2 # doublet, one unpaired electron (ex.: OH- radical)
29+
# multiplicity = 3 # triplet, two unpaired electrons (ex.: O2)
30+
charge = 0 # Excess +/- elementary charge, beyond multiplicity
31+
32+
# Hydrogen (and lighter):
33+
34+
geometry = [("H", (0.0, 0.0, 0.0)), ("H", (0.0, 0.0, 0.74))] # H2 Molecule
35+
36+
# Helium (and lighter):
37+
38+
# geometry = [('He', (0.0, 0.0, 0.0)), ('H', (0.0, 0.0, 7.74))] # HeH Molecule
39+
40+
# Lithium (and lighter):
41+
42+
# geometry = [('Li', (0.0, 0.0, 0.0)), ('H', (0.0, 0.0, 15.9))] # LiH Molecule
43+
44+
# Carbon (and lighter):
45+
46+
# Methane (CH4):
47+
# geometry = [
48+
# ('C', (0.0000, 0.0000, 0.0000)), # Central carbon
49+
# ('H', (1.0900, 0.0000, 0.0000)), # Hydrogen 1
50+
# ('H', (-0.3630, 1.0270, 0.0000)), # Hydrogen 2
51+
# ('H', (-0.3630, -0.5130, 0.8890)), # Hydrogen 3
52+
# ('H', (-0.3630, -0.5130, -0.8890)) # Hydrogen 4
53+
# ]
54+
55+
# Nitrogen (and lighter):
56+
57+
# geometry = [('N', (0.0, 0.0, 0.0)), ('N', (0.0, 0.0, 10.9))] # N2 Molecule
58+
59+
# Ammonia:
60+
# geometry = [
61+
# ('N', (0.0000, 0.0000, 0.0000)), # Nitrogen at center
62+
# ('H', (0.9400, 0.0000, -0.3200)), # Hydrogen 1
63+
# ('H', (-0.4700, 0.8130, -0.3200)), # Hydrogen 2
64+
# ('H', (-0.4700, -0.8130, -0.3200)) # Hydrogen 3
65+
# ]
66+
67+
# Oxygen (and lighter):
68+
69+
# geometry = [('O', (0.0, 0.0, 0.0)), ('H', (0.0, 0.0, 9.6))] # OH- Radical
70+
# geometry = [('O', (0.0000, 0.0000, 0.0000)), ('H', (0.7586, 0.0000, 0.5043)), ('H', (-0.7586, 0.0000, 0.5043))] # H2O Molecule
71+
# geometry = [('C', (0.0000, 0.0000, 0.0000)), ('O', (0.0000, 0.0000, 1.128))] # CO Molecule
72+
# geometry = [('C', (0.0000, 0.0000, 0.0000)), ('O', (0.0000, 0.0000, 1.16)), ('O', (0.0000, 0.0000, -1.16))] # CO2 Molecule
73+
# geometry = [('O', (0.0, 0.0, 0.0)), ('N', (0.0, 0.0, 1.55))] # NO Molecule
74+
# geometry = [('O', (0.0, 0.0, 0.0)), ('N', (0.0, 0.0, 11.5))] # NO+ Radical
75+
76+
# Nitrogen dioxide (toxic pollutant)
77+
# geometry = [
78+
# ('N', (0.0000, 0.0000, 0.0000)),
79+
# ('O', (1.2000, 0.0000, 0.0000)),
80+
# ('O', (-1.2000 * np.cos(np.deg2rad(134)), 1.2000 * np.sin(np.deg2rad(134)), 0.0000))
81+
# ]
82+
# geometry = [('N', (0.0000, 0.0000, 0.0000)), ('N', (1.128, 0.0000, 0.0000)), ('O', (2.241, 0.0000, 0.0000))] # N2O Molecule
83+
84+
# Ozone:
85+
# geometry = [
86+
# ('O', (0.0000, 0.0000, 0.0000)), # Central oxygen
87+
# ('O', (1.2780, 0.0000, 0.0000)), # One oxygen
88+
# ('O', (-1.2780 * np.cos(np.deg2rad(117)), 1.2780 * np.sin(np.deg2rad(117)), 0.0000)) # The other oxygen
89+
# ]
90+
91+
# H3O+ (Hydronium Ion):
92+
# bond_length = 10.3 # Scaled bond length (1.03 Å in script units)
93+
# bond_angle = 113.0 # Approximate bond angle of H3O+
94+
# Convert angles to radians
95+
# theta = np.deg2rad(bond_angle)
96+
# Final geometry list
97+
# geometry = [
98+
# ('O', (0.0, 0.0, 0.0)),
99+
# ('H', (bond_length, 0.0, 0.0)),
100+
# ('H', (-bond_length * np.cos(theta), bond_length * np.sin(theta), 0.0)),
101+
# ('H', (-bond_length * np.cos(theta), -bond_length * np.sin(theta), 0.0))
102+
# ]
103+
104+
# Carbonate ion (CO3--):
105+
# geometry = [
106+
# ('C', (0.0000, 0.0000, 0.0000)), # Carbon at center
107+
# ('O', (1.2900, 0.0000, 0.0000)), # Oxygen 1
108+
# ('O', (-0.6450, 1.1180, 0.0000)), # Oxygen 2
109+
# ('O', (-0.6450, -1.1180, 0.0000)) # Oxygen 3
110+
# ]
111+
112+
# Bicarbonate ion (HCO3-):
113+
# geometry = [
114+
# ('C', (0.0000, 0.0000, 0.0000)), # Carbon center
115+
# ('O', (1.2200, 0.0000, 0.0000)), # Oxygen (C=O)
116+
# ('O', (-0.6100, 1.0550, 0.0000)), # Oxygen (-O⁻)
117+
# ('O', (-0.6100, -1.0550, 0.0000)), # Oxygen (OH)
118+
# ('H', (-1.2200, -1.0550, 0.0000)) # Hydrogen in OH group
119+
# ]
120+
121+
# Sulfur chemistry:
122+
123+
# Hydrogen sulfide (rotten egg smell, major biologic sulfur compound):
124+
# geometry = [
125+
# ('S', (0.0000, 0.0000, 0.0000)),
126+
# ('H', (1.3400, 0.0000, 0.0000)),
127+
# ('H', (-1.3400 * np.cos(np.deg2rad(92)), 1.3400 * np.sin(np.deg2rad(92)), 0.0000))
128+
# ]
129+
130+
# Sulfur dioxide (major volcanic gas and pollutant):
131+
# geometry = [
132+
# ('S', (0.0000, 0.0000, 0.0000)), # Sulfur at center
133+
# ('O', (1.4300, 0.0000, 0.0000)), # Oxygen 1
134+
# ('O', (-1.4300 * np.cos(np.deg2rad(119)), 1.4300 * np.sin(np.deg2rad(119)), 0.0000)) # Oxygen 2
135+
# ]
136+
137+
# Sulfur trioxide (key in acid rain, forms H₂SO₄):
138+
# geometry = [
139+
# ('S', (0.0000, 0.0000, 0.0000)),
140+
# ('O', (1.4200, 0.0000, 0.0000)),
141+
# ('O', (-0.7100, 1.2290, 0.0000)),
142+
# ('O', (-0.7100, -1.2290, 0.0000))
143+
# ]
144+
145+
# Sulfate ion (SO4--, major oceanic anion, ionically bonds to Mg++):
146+
# geometry = [
147+
# ('S', (0.0000, 0.0000, 0.0000)),
148+
# ('O', (1.4900, 0.0000, 0.0000)),
149+
# ('O', (-0.7450, 1.2900, 0.0000)),
150+
# ('O', (-0.7450, -1.2900, 0.0000)),
151+
# ('O', (0.0000, 0.0000, 1.4900))
152+
# ]
153+
154+
# Oceanic electrolytes (consider isolating cations and anions as single atoms with excess charge):
155+
156+
# Sodium chloride:
157+
# geometry = [
158+
# ('Na', (0.0000, 0.0000, 0.0000)),
159+
# ('Cl', (2.3600, 0.0000, 0.0000))
160+
# ]
161+
162+
# Potassium chloride (biologically important):
163+
# geometry = [
164+
# ('K', (0.0000, 0.0000, 0.0000)),
165+
# ('Cl', (2.6700, 0.0000, 0.0000))
166+
# ]
167+
168+
# Calcium chloride:
169+
# geometry = [
170+
# ('Ca', (0.0000, 0.0000, 0.0000)),
171+
# ('Cl', (2.7800, 0.0000, 0.0000)),
172+
# ('Cl', (-2.7800, 0.0000, 0.0000))
173+
# ]
174+
175+
# Diatomic halogens:
176+
177+
# Fluorine gas (F2)
178+
# geometry = [('F', (0.0000, 0.0000, 0.0000)), ('F', (1.4200, 0.0000, 0.0000))]
179+
180+
# Chlorine gas (Cl2)
181+
# geometry = [('Cl', (0.0000, 0.0000, 0.0000)), ('Cl', (1.9900, 0.0000, 0.0000))]
182+
183+
# Silicon dioxide (quartz, sand, granite):
184+
# geometry = [
185+
# ('Si', (0.0000, 0.0000, 0.0000)), # Silicon at center
186+
# ('O', (1.6200, 0.0000, 0.0000)), # Oxygen 1
187+
# ('O', (-1.6200, 0.0000, 0.0000)) # Oxygen 2
188+
# ]
189+
190+
# The above are major atmospheric, oceanic, and soil components on Earth.
191+
# Proper organic chemistry is beyond the scope of this script,
192+
# but we give a memorable token example of a carbon ring.
193+
194+
# Benzene (C6H6)
195+
196+
# Define bond lengths (in angstroms, converted to script units)
197+
# C_C = 13.9 # Carbon-carbon bond (1.39 Å)
198+
# C_H = 10.9 # Carbon-hydrogen bond (1.09 Å)
199+
200+
# Angle of 120° between C-C bonds in the hexagonal ring
201+
# theta = np.deg2rad(120)
202+
203+
# Define carbon positions (hexagonal ring)
204+
# geometry = [
205+
# ('C', (C_C, 0.0, 0.0)), # First carbon at x-axis
206+
# ('C', (C_C * np.cos(theta), C_C * np.sin(theta), 0.0)),
207+
# ('C', (-C_C * np.cos(theta), C_C * np.sin(theta), 0.0)),
208+
# ('C', (-C_C, 0.0, 0.0)),
209+
# ('C', (-C_C * np.cos(theta), -C_C * np.sin(theta), 0.0)),
210+
# ('C', (C_C * np.cos(theta), -C_C * np.sin(theta), 0.0))
211+
# ]
212+
213+
# Define hydrogen positions (bonded to carbons)
214+
# for i in range(6):
215+
# x, y, z = geometry[i][1] # Get carbon position
216+
# hydrogen_x = x + (C_H * (x / C_C)) # Extend outward along C-C axis
217+
# hydrogen_y = y + (C_H * (y / C_C))
218+
# hydrogen_z = z # Planar
219+
# geometry.append(('H', (hydrogen_x, hydrogen_y, hydrogen_z)))
220+
221+
# Now, `geometry` contains all 6 carbons and 6 hydrogens!
222+
223+
# Step 2: Compute the Molecular Hamiltonian
224+
molecule = of.MolecularData(geometry, basis, multiplicity, charge)
225+
molecule = run_pyscf(molecule, run_scf=True, run_fci=True)
226+
fermionic_hamiltonian = molecule.get_molecular_hamiltonian()
227+
n_qubits = molecule.n_qubits # Auto-detect qubit count
228+
print(str(n_qubits) + " qubits...")
229+
230+
# Step 3: Convert to Qubit Hamiltonian (Jordan-Wigner)
231+
qubit_hamiltonian = jordan_wigner(fermionic_hamiltonian)
232+
233+
# Step 4: Setup localized TFIM from Hamiltonian
234+
235+
def estimate_local_parameters(qubit_hamiltonian, n_qubits):
236+
z = np.zeros(n_qubits, dtype=int)
237+
J = np.zeros(n_qubits)
238+
h = np.zeros(n_qubits)
239+
240+
for term, coeff in qubit_hamiltonian.terms.items():
241+
if len(term) == 1:
242+
q, pauli = term[0]
243+
if pauli == "X":
244+
h[q] += np.abs(coeff)
245+
elif pauli == "Y":
246+
h[q] += 0.5 * np.abs(coeff)
247+
elif len(term) == 2:
248+
(q1, p1), (q2, p2) = term
249+
if {p1, p2} <= {"Z"}:
250+
J[q1] += np.abs(coeff) / 2
251+
J[q2] += np.abs(coeff) / 2
252+
z[q1] += 1
253+
z[q2] += 1
254+
else:
255+
h[q1] += 0.25 * np.abs(coeff)
256+
h[q2] += 0.25 * np.abs(coeff)
257+
return z, J, h
258+
259+
def tfim_ground_state_angles(n_qubits, J_vec, h_vec, z_vec, t=10.0):
260+
ry_angles = np.zeros(n_qubits)
261+
for i in range(n_qubits):
262+
J=J_vec[i]
263+
h=h_vec[i]
264+
z=z_vec[i]
265+
ry_angles[i] = np.arcsin(max(min(1, abs(h) / (z * J)) if np.isclose(z * J, 0) else (1 if J > 0 else -1), -1))
266+
return ry_angles
267+
268+
def hybrid_tfim_vqe(qubit_hamiltonian, n_qubits, dev=None):
269+
"""
270+
Estimate energy from TFIM-predicted RY angles.
271+
"""
272+
z, J, h = estimate_local_parameters(qubit_hamiltonian, n_qubits)
273+
theta = tfim_ground_state_angles(n_qubits, J, h, z)
274+
weights_shape = {"weights": n_qubits}
275+
276+
if dev is None:
277+
dev = qml.device("default.qubit", wires=n_qubits)
278+
279+
coeffs = []
280+
observables = []
281+
for term, coefficient in qubit_hamiltonian.terms.items():
282+
pauli_operators = []
283+
for qubit_idx, pauli in term:
284+
if pauli == "X":
285+
pauli_operators.append(qml.PauliX(qubit_idx))
286+
elif pauli == "Y":
287+
pauli_operators.append(qml.PauliY(qubit_idx))
288+
elif pauli == "Z":
289+
pauli_operators.append(qml.PauliZ(qubit_idx))
290+
if pauli_operators:
291+
observable = pauli_operators[0]
292+
for op in pauli_operators[1:]:
293+
observable = observable @ op
294+
observables.append(observable)
295+
else:
296+
observables.append(qml.Identity(0)) # Default identity if no operators
297+
coeffs.append(qml.numpy.array(coefficient, requires_grad=False))
298+
299+
hamiltonian = qml.Hamiltonian(coeffs, observables)
300+
301+
@qml.qnode(dev)
302+
def circuit(delta):
303+
for i in range(n_qubits):
304+
qml.RY(theta[i] + delta[i], wires=i)
305+
for i in range(n_qubits - 1):
306+
qml.CZ(wires=[i, i + 1])
307+
return qml.expval(hamiltonian)
308+
309+
return circuit, weights_shape
310+
311+
# Step 5: Setup Qrack simulator and calculate energy expectation value
312+
dev = qml.device("qrack.simulator", wires=n_qubits)
313+
circuit, weights_shape = hybrid_tfim_vqe(qubit_hamiltonian, n_qubits)
314+
weights = np.zeros(weights_shape["weights"])
315+
combos = 3 ** len(weights)
316+
min_energy = circuit(weights)
317+
for combo in range(combos):
318+
c = combo
319+
for i in range(len(weights)):
320+
w = c % 3
321+
c = c / 3
322+
weights[i] = 0 if w == 0 else ((np.pi / 2) if w == 1 else (-np.pi / 2))
323+
energy = circuit(weights)
324+
print(f"Step {combo + 1}: Energy = {energy}")
325+
if energy < min_energy:
326+
min_energy = energy
327+
328+
print(f"Optimized Ground State Energy: {min_energy} Ha")
329+
print("Optimized parameters:")
330+
print(weights)

scripts/tfim_vqe.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,8 +246,8 @@ def estimate_local_parameters(qubit_hamiltonian, n_qubits):
246246
elif len(term) == 2:
247247
(q1, p1), (q2, p2) = term
248248
if {p1, p2} <= {"Z"}:
249-
J[q1] += np.abs(coeff) / 2
250-
J[q2] += np.abs(coeff) / 2
249+
J[q1] -= np.abs(coeff) / 2
250+
J[q2] -= np.abs(coeff) / 2
251251
z[q1] += 1
252252
z[q2] += 1
253253
else:

0 commit comments

Comments
 (0)