forked from roshansingh77/VQF
-
Notifications
You must be signed in to change notification settings - Fork 1
/
vqe.py
335 lines (278 loc) · 14.1 KB
/
vqe.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
#!/usr/bin/env python
# coding: utf-8
# In[1]:
##############################################################################
# Copyright 2016-2017 Rigetti Computing
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##############################################################################
from collections import Counter
from typing import List, Union
import funcsigs
import numpy as np
from pyquil import Program
from pyquil.api import QuantumComputer, WavefunctionSimulator
from pyquil.api._qvm import QVM
from pyquil.gates import RX, RY, MEASURE, STANDARD_GATES
from pyquil.paulis import PauliTerm, PauliSum
class OptResults(dict):
"""
Object for holding optimization results from VQE.
"""
def __getattr__(self, name):
try:
return self[name]
except KeyError:
raise AttributeError(name)
__setattr__ = dict.__setitem__
__delattr__ = dict.__delitem__
class VQE(object):
"""
The Variational-Quantum-Eigensolver algorithm
VQE is an object that encapsulates the VQE algorithm (functional
minimization). The main components of the VQE algorithm are a minimizer
function for performing the functional minimization, a function that takes a
vector of parameters and returns a pyQuil program, and a
Hamiltonian of which to calculate the expectation value.
Using this object:
1) initialize with `inst = VQE(minimizer)` where `minimizer` is a
function that performs a gradient free minization--i.e
scipy.optimize.minimize(. , ., method='Nelder-Mead')
2) call `inst.vqe_run(variational_state_evolve, hamiltonian,
initial_parameters)`. Returns the optimal parameters and minimum
expecation
:param minimizer: function that minimizes objective f(obj, param). For
example the function scipy.optimize.minimize() needs
at least two parameters, the objective and an initial
point for the optimization. The args for minimizer
are the cost function (provided by this class),
initial parameters (passed to vqe_run() method, and
jacobian (defaulted to None). kwargs can be passed
in below.
:param minimizer_args: (list) arguments for minimizer function. Default=None
:param minimizer_kwargs: (dict) arguments for keyword args.
Default=None
"""
def __init__(self, minimizer, minimizer_args=[], minimizer_kwargs={}):
self.minimizer = minimizer
self.minimizer_args = minimizer_args
self.minimizer_kwargs = minimizer_kwargs
self.n_qubits = None
def vqe_run(self, variational_state_evolve, hamiltonian, initial_params,
gate_noise=None, measurement_noise=None,
jacobian=None, qc=None, disp=None, samples=None, return_all=False):
"""
functional minimization loop.
:param variational_state_evolve: function that takes a set of parameters
and returns a pyQuil program.
:param hamiltonian: (PauliSum) object representing the hamiltonian of
which to take the expectation value.
:param initial_params: (ndarray) vector of initial parameters for the
optimization
:param gate_noise: list of Px, Py, Pz probabilities of gate being
applied to every gate after each get application
:param measurement_noise: list of Px', Py', Pz' probabilities of a X, Y
or Z being applied before a measurement.
:param jacobian: (optional) method of generating jacobian for parameters
(Default=None).
:param qc: (optional) QuantumComputer object.
:param disp: (optional, bool) display level. If True then each iteration
expectation and parameters are printed at each
optimization iteration.
:param samples: (int) Number of samples for calculating the expectation
value of the operators. If `None` then faster method
,dotting the wave function with the operator, is used.
Default=None.
:param return_all: (optional, bool) request to return all intermediate
parameters determined during the optimization.
:return: (vqe.OptResult()) object :func:`OptResult <vqe.OptResult>`.
The following fields are initialized in OptResult:
-x: set of w.f. ansatz parameters
-fun: scalar value of the objective function
-iteration_params: a list of all intermediate parameter vectors. Only
returned if 'return_all=True' is set as a vqe_run()
option.
-expectation_vals: a list of all intermediate expectation values. Only
returned if 'return_all=True' is set as a
vqe_run() option.
"""
self.number_of_evaluations = 0
self._disp_fun = disp if disp is not None else lambda x: None
iteration_params = []
expectation_vals = []
self._current_expectation = None
if samples is None:
print("""WARNING: Fast method for expectation will be used. Noise
models will be ineffective""")
if qc is None:
qubits = hamiltonian.get_qubits()
qc = QuantumComputer(name=f"{len(qubits)}q-noisy-qvm",
qam=QVM(gate_noise=gate_noise,
measurement_noise=measurement_noise))
else:
self.qc = qc
def objective_function(params):
"""
closure representing the functional
:param params: (ndarray) vector of parameters for generating the
the function of the functional.
:return: (float) expectation value
"""
pyquil_prog = variational_state_evolve(params)
mean_value = self.expectation(pyquil_prog, hamiltonian, samples, qc)
self._current_expectation = mean_value # store for printing
self.number_of_evaluations += 1
return mean_value
def print_current_iter(iter_vars):
self._disp_fun("\tParameters: {} ".format(iter_vars))
if jacobian is not None:
grad = jacobian(iter_vars)
self._disp_fun("\tGrad-L1-Norm: {}".format(np.max(np.abs(grad))))
self._disp_fun("\tGrad-L2-Norm: {} ".format(np.linalg.norm(grad)))
self._disp_fun("\tE => {}".format(self._current_expectation))
if return_all:
iteration_params.append(iter_vars)
expectation_vals.append(self._current_expectation)
# using self.minimizer
arguments = funcsigs.signature(self.minimizer).parameters.keys()
if disp is not None and 'callback' in arguments:
self.minimizer_kwargs['callback'] = print_current_iter
args = [objective_function, initial_params]
# args.extend(self.minimizer_args)
self.minimizer_kwargs.update(self.minimizer_args)
if 'jac' in arguments:
self.minimizer_kwargs['jac'] = jacobian
# print ('args:', args)
# print ('self minimizer args:', self.minimizer_args)
# print ('self minimizer kwargs:', self.minimizer_kwargs)
result = self.minimizer(*args, **self.minimizer_kwargs)
if hasattr(result, 'status'):
if result.status != 0:
self._disp_fun("Classical optimization exited with an error index: %i"
% result.status)
results = OptResults()
if hasattr(result, 'x'):
results.x = result.x
results.fun = result.fun
else:
results.x = result
if return_all:
results.iteration_params = iteration_params
results.expectation_vals = expectation_vals
return results
@staticmethod
def expectation(pyquil_prog: Program,
pauli_sum: Union[PauliSum, PauliTerm, np.ndarray],
samples: int,
qc: QuantumComputer) -> float:
"""
Compute the expectation value of pauli_sum over the distribution generated from pyquil_prog.
:param pyquil_prog: The state preparation Program to calculate the expectation value of.
:param pauli_sum: PauliSum representing the operator of which to calculate the expectation
value or a numpy matrix representing the Hamiltonian tensored up to the appropriate
size.
:param samples: The number of samples used to calculate the expectation value. If samples
is None then the expectation value is calculated by calculating <psi|O|psi>. Error
models will not work if samples is None.
:param qc: The QuantumComputer object.
:return: A float representing the expectation value of pauli_sum given the distribution
generated from quil_prog.
"""
if isinstance(pauli_sum, np.ndarray):
# debug mode by passing an array
wf = WavefunctionSimulator().wavefunction(pyquil_prog)
wf = np.reshape(wf.amplitudes, (-1, 1))
average_exp = np.conj(wf).T.dot(pauli_sum.dot(wf)).real
return average_exp
else:
if not isinstance(pauli_sum, (PauliTerm, PauliSum)):
raise TypeError("pauli_sum variable must be a PauliTerm or PauliSum object")
if isinstance(pauli_sum, PauliTerm):
pauli_sum = PauliSum([pauli_sum])
if samples is None:
expectation = WavefunctionSimulator().expectation(pyquil_prog, pauli_sum)
return expectation.real
else:
if not isinstance(samples, int):
raise TypeError("samples variable must be an integer")
if samples <= 0:
raise ValueError("samples variable must be a positive integer")
# normal execution via fake sampling
# stores the sum of contributions to the energy from each operator term
expectation = 0.0
for j, term in enumerate(pauli_sum.terms):
meas_basis_change = Program()
qubits_to_measure = []
if term.id() == "":
meas_outcome = 1.0
else:
for index, gate in term:
qubits_to_measure.append(index)
if gate == 'X':
meas_basis_change.inst(RY(-np.pi / 2, index))
elif gate == 'Y':
meas_basis_change.inst(RX(np.pi / 2, index))
meas_outcome = expectation_from_sampling(pyquil_prog + meas_basis_change,
qubits_to_measure,
qc,
samples)
expectation += term.coefficient * meas_outcome
return expectation.real
def parity_even_p(state, marked_qubits):
"""
Calculates the parity of elements at indexes in marked_qubits
Parity is relative to the binary representation of the integer state.
:param state: The wavefunction index that corresponds to this state.
:param marked_qubits: The indexes to be considered in the parity sum.
:returns: A boolean corresponding to the parity.
"""
assert isinstance(state, int), f"{state} is not an integer. Must call parity_even_p with an integer state."
mask = 0
for q in marked_qubits:
mask |= 1 << q
return bin(mask & state).count("1") % 2 == 0
def expectation_from_sampling(pyquil_program: Program,
marked_qubits: List[int],
qc: QuantumComputer,
samples: int) -> float:
"""
Calculation of Z_{i} at marked_qubits
Given a wavefunctions, this calculates the expectation value of the Zi
operator where i ranges over all the qubits given in marked_qubits.
:param pyquil_program: pyQuil program generating some state
:param marked_qubits: The qubits within the support of the Z pauli
operator whose expectation value is being calculated
:param qc: A QuantumComputer object.
:param samples: Number of bitstrings collected to calculate expectation
from sampling.
:returns: The expectation value as a float.
"""
program = Program()
ro = program.declare('ro', 'BIT', max(marked_qubits) + 1)
program += pyquil_program
program += [MEASURE(qubit, r) for qubit, r in zip(list(range(max(marked_qubits) + 1)), ro)]
program.wrap_in_numshots_loop(samples)
executable = qc.compile(program)
bitstring_samples = qc.run(executable)
bitstring_tuples = list(map(tuple, bitstring_samples))
freq = Counter(bitstring_tuples)
# perform weighted average
expectation = 0
for bitstring, count in freq.items():
bitstring_int = int("".join([str(x) for x in bitstring[::-1]]), 2)
if parity_even_p(bitstring_int, marked_qubits):
expectation += float(count) / samples
else:
expectation -= float(count) / samples
return expectation
# In[ ]: