Skip to content

Commit 243aa2d

Browse files
VQE supports initialization by computer (#263)
* VQE supports initialization by computer
1 parent 2035e07 commit 243aa2d

File tree

3 files changed

+107
-39
lines changed

3 files changed

+107
-39
lines changed

src/qforte/abc/algorithm.py

+42-9
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,31 @@ def __init__(
9393
self._refprep = build_refprep(self._ref)
9494
self._Uprep = reference
9595

96+
elif self._state_prep_type == "computer":
97+
if not isinstance(reference, qf.Computer):
98+
raise ValueError("computer reference must be a Computer.")
99+
if not fast:
100+
raise ValueError(
101+
"`self._fast = False` specifies not to skip steps, but `self._state_prep_type = computer` specifies to skip state initialization. That's inconsistent."
102+
)
103+
if reference.get_nqubit() != len(system.hf_reference):
104+
raise ValueError(
105+
f"Computer needs {len(system.hf_reference)} qubits, found {reference.get_nqubit()}."
106+
)
107+
if (
108+
not hasattr(self, "computer_initializable")
109+
or not self.computer_initializable
110+
):
111+
raise ValueError("Class cannot be initialized with a computer.")
112+
113+
self._ref = system.hf_reference
114+
self._refprep = build_refprep(self._ref)
115+
self._Uprep = qf.Circuit()
116+
self.computer = reference
117+
96118
else:
97119
raise ValueError(
98-
"QForte only suppors references as occupation lists and Circuits."
120+
"QForte only supports references as occupation lists, Circuits, or Computers."
99121
)
100122

101123
self._nqb = len(self._ref)
@@ -284,21 +306,26 @@ def fill_pool(self):
284306
len(operator.jw_transform().terms()) for _, operator in self._pool_obj
285307
]
286308

287-
def measure_energy(self, Ucirc):
309+
def measure_energy(self, Ucirc, computer=None):
288310
"""
289311
This function returns the energy expectation value of the state
290-
Uprep|0>.
312+
Ucirc|Ψ>.
291313
292314
Parameters
293315
----------
294316
Ucirc : Circuit
295317
The state preparation circuit.
296318
"""
297319
if self._fast:
298-
myQC = qforte.Computer(self._nqb)
299-
myQC.apply_circuit(Ucirc)
300-
val = np.real(myQC.direct_op_exp_val(self._qb_ham))
320+
if computer is None:
321+
computer = qf.Computer(self._nqb)
322+
computer.apply_circuit(Ucirc)
323+
val = np.real(computer.direct_op_exp_val(self._qb_ham))
301324
else:
325+
if compute is not None:
326+
raise TypeError(
327+
"measure_energy in slow mode does not support custom Computer."
328+
)
302329
Exp = qforte.Experiment(self._nqb, Ucirc, self._qb_ham, 2000)
303330
val = Exp.perfect_experimental_avg()
304331

@@ -431,8 +458,8 @@ def __init__(
431458
def energy_feval(self, params):
432459
"""
433460
This function returns the energy expectation value of the state
434-
Uprep(params)|0>, where params are parameters that can be optimized
435-
for some purpouse such as energy minimizaiton.
461+
Uprep(params)|Ψ>, where params are parameters that can be optimized
462+
for some purpouse such as energy minimization.
436463
437464
Parameters
438465
----------
@@ -441,7 +468,13 @@ def energy_feval(self, params):
441468
the state preparation circuit.
442469
"""
443470
Ucirc = self.build_Uvqc(amplitudes=params)
444-
Energy = self.measure_energy(Ucirc)
471+
Energy = self.measure_energy(Ucirc, self.get_initial_computer())
445472

446473
self._curr_energy = Energy
447474
return Energy
475+
476+
def get_initial_computer(self) -> qf.Computer:
477+
if hasattr(self, "computer"):
478+
return qf.Computer(self.computer)
479+
else:
480+
return qf.Computer(self._nqb)

src/qforte/abc/uccvqeabc.py

+19-30
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class UCCVQE(UCC, VQE):
2626
.. math::
2727
E(\\mathbf{t}) = \\langle \\Phi_0 | \\hat{U}^\\dagger(\\mathbf{\\mathbf{t}}) \\hat{H} \\hat{U}(\\mathbf{\\mathbf{t}}) | \\Phi_0 \\rangle
2828
29-
using a disentagled UCC type ansatz
29+
using a disentangled UCC type ansatz
3030
3131
.. math::
3232
\\hat{U}(\\mathbf{t}) = \\prod_\\mu e^{t_\\mu (\\hat{\\tau}_\\mu - \\hat{\\tau}_\\mu^\\dagger)},
@@ -71,6 +71,7 @@ class UCCVQE(UCC, VQE):
7171
"""
7272

7373
def __init__(self, *args, **kwargs):
74+
self.computer_initializable = True
7475
super().__init__(*args, **kwargs)
7576

7677
@abstractmethod
@@ -104,7 +105,7 @@ def measure_operators(self, operators, Ucirc, idxs=[]):
104105
"""
105106

106107
if self._fast:
107-
myQC = qforte.Computer(self._nqb)
108+
myQC = self.get_initial_computer()
108109
myQC.apply_circuit(Ucirc)
109110
if not idxs:
110111
grads = myQC.direct_oppl_exp_val(operators)
@@ -120,7 +121,8 @@ def measure_operators(self, operators, Ucirc, idxs=[]):
120121

121122
def measure_gradient(self, params=None):
122123
"""Returns the disentangled (factorized) UCC gradient, using a
123-
recursive approach.
124+
recursive approach, as described in Section D of the Appendix of
125+
10.1038/s41467-019-10988-2
124126
125127
Parameters
126128
----------
@@ -140,18 +142,11 @@ def measure_gradient(self, params=None):
140142
else:
141143
Utot = self.build_Uvqc(params)
142144

143-
qc_psi = qforte.Computer(
144-
self._nqb
145-
) # build | sig_N > according ADAPT-VQE analytical grad section
145+
qc_psi = self.get_initial_computer()
146146
qc_psi.apply_circuit(Utot)
147-
qc_sig = qforte.Computer(
148-
self._nqb
149-
) # build | psi_N > according ADAPT-VQE analytical grad section
150-
psi_i = copy.deepcopy(qc_psi.get_coeff_vec())
151-
qc_sig.set_coeff_vec(
152-
copy.deepcopy(psi_i)
153-
) # not sure if copy is faster or reapplication of state
147+
qc_sig = qf.Computer(qc_psi)
154148
qc_sig.apply_operator(self._qb_ham)
149+
qc_temp = qf.Computer(qc_psi)
155150

156151
mu = M - 1
157152

@@ -161,15 +156,13 @@ def measure_gradient(self, params=None):
161156
)
162157
Kmu_prev.mult_coeffs(self._pool_obj[self._tops[mu]][0])
163158

164-
qc_psi.apply_operator(Kmu_prev)
159+
qc_temp.apply_operator(Kmu_prev)
165160
grads[mu] = 2.0 * np.real(
166-
np.vdot(qc_sig.get_coeff_vec(), qc_psi.get_coeff_vec())
161+
np.vdot(qc_sig.get_coeff_vec(), qc_temp.get_coeff_vec())
167162
)
168163

169-
# reset Kmu_prev |psi_i> -> |psi_i>
170-
qc_psi.set_coeff_vec(copy.deepcopy(psi_i))
171-
172164
for mu in reversed(range(M - 1)):
165+
qc_temp = qf.Computer(qc_psi)
173166
# mu => N-1 => M-2
174167
# mu+1 => N => M-1
175168
# Kmu => KN-1
@@ -243,15 +236,14 @@ def measure_gradient(self, params=None):
243236

244237
qc_sig.apply_circuit(Umu)
245238
qc_psi.apply_circuit(Umu)
246-
psi_i = copy.deepcopy(qc_psi.get_coeff_vec())
239+
qc_temp = qf.Computer(qc_psi)
247240

248-
qc_psi.apply_operator(Kmu)
241+
qc_temp.apply_operator(Kmu)
249242
grads[mu] = 2.0 * np.real(
250-
np.vdot(qc_sig.get_coeff_vec(), qc_psi.get_coeff_vec())
243+
np.vdot(qc_sig.get_coeff_vec(), qc_temp.get_coeff_vec())
251244
)
252245

253246
# reset Kmu |psi_i> -> |psi_i>
254-
qc_psi.set_coeff_vec(copy.deepcopy(psi_i))
255247
Kmu_prev = Kmu
256248

257249
np.testing.assert_allclose(np.imag(grads), np.zeros_like(grads), atol=1e-7)
@@ -269,25 +261,22 @@ def measure_gradient3(self):
269261
raise ValueError("self._fast must be True for gradient measurement.")
270262

271263
Utot = self.build_Uvqc()
272-
qc_psi = qforte.Computer(self._nqb)
264+
qc_psi = self.get_initial_computer()
273265
qc_psi.apply_circuit(Utot)
274-
psi_i = copy.deepcopy(qc_psi.get_coeff_vec())
275266

276-
qc_sig = qforte.Computer(self._nqb)
277-
# TODO: Check if it's faster to recompute psi_i or copy it.
278-
qc_sig.set_coeff_vec(copy.deepcopy(psi_i))
267+
qc_sig = qforte.Computer(qc_psi)
279268
qc_sig.apply_operator(self._qb_ham)
280269

281270
grads = np.zeros(len(self._pool_obj))
282271

283272
for mu, (coeff, operator) in enumerate(self._pool_obj):
273+
qc_temp = qf.Computer(qc_psi)
284274
Kmu = operator.jw_transform(self._qubit_excitations)
285275
Kmu.mult_coeffs(coeff)
286-
qc_psi.apply_operator(Kmu)
276+
qc_temp.apply_operator(Kmu)
287277
grads[mu] = 2.0 * np.real(
288-
np.vdot(qc_sig.get_coeff_vec(), qc_psi.get_coeff_vec())
278+
np.vdot(qc_sig.get_coeff_vec(), qc_temp.get_coeff_vec())
289279
)
290-
qc_psi.set_coeff_vec(copy.deepcopy(psi_i))
291280

292281
np.testing.assert_allclose(np.imag(grads), np.zeros_like(grads), atol=1e-7)
293282

tests/test_computer_init.py

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import numpy as np
2+
from pytest import approx
3+
from qforte import ADAPTVQE, UCCNVQE
4+
from qforte import Circuit, Computer, gate, system_factory
5+
6+
import os
7+
8+
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
9+
data_path = os.path.join(THIS_DIR, "H4-sto6g-075a.json")
10+
11+
12+
class TestComputerInit:
13+
# @mark.skip(reason="long")
14+
def test_H4_VQE(self):
15+
mol = system_factory(
16+
system_type="molecule",
17+
build_type="external",
18+
basis="sto-6g",
19+
filename=data_path,
20+
)
21+
nqubits = len(mol.hf_reference)
22+
fci_energy = -2.162897881184882
23+
24+
computer = Computer(nqubits)
25+
coeff_vec = np.zeros(2**nqubits)
26+
coeff_vec[int("00001111", 2)] = 1
27+
coeff_vec[int("00110011", 2)] = 0.2
28+
coeff_vec[int("00111100", 2)] = 0.1
29+
coeff_vec[int("11001100", 2)] = 0.04
30+
coeff_vec /= np.linalg.norm(coeff_vec)
31+
computer.set_coeff_vec(coeff_vec)
32+
33+
# Analytic and fin dif gradients agree
34+
analytic = UCCNVQE(mol, reference=computer, state_prep_type="computer")
35+
analytic.run(use_analytic_grad=False, pool_type="SD")
36+
findif = UCCNVQE(mol, reference=computer, state_prep_type="computer")
37+
findif.run(use_analytic_grad=True, pool_type="SD")
38+
assert analytic.get_gs_energy() == approx(findif.get_gs_energy(), abs=1.0e-8)
39+
40+
# Computer-based and non-compute based agree
41+
hf = ADAPTVQE(mol)
42+
hf.run(use_analytic_grad=True, pool_type="GSD", avqe_thresh=1e-5)
43+
comp = ADAPTVQE(mol, reference=computer, state_prep_type="computer")
44+
comp.run(use_analytic_grad=True, pool_type="GSD", avqe_thresh=1e-5)
45+
assert hf.get_gs_energy() == approx(comp.get_gs_energy(), abs=1.0e-8)
46+
assert hf.get_gs_energy() == approx(fci_energy, abs=1.0e-8)

0 commit comments

Comments
 (0)