/
polynomial_pauli_rotations.py
249 lines (194 loc) · 8.78 KB
/
polynomial_pauli_rotations.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
# -*- coding: utf-8 -*-
# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 2020.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
# pylint: disable=no-member
"""Polynomially controlled Pauli-rotations."""
from typing import List, Optional, Dict, Sequence
from itertools import product
from sympy.ntheory.multinomial import multinomial_coefficients
from qiskit.circuit import QuantumRegister
from qiskit.circuit.exceptions import CircuitError
from .functional_pauli_rotations import FunctionalPauliRotations
class PolynomialPauliRotations(FunctionalPauliRotations):
r"""A circuit implementing polynomial Pauli rotations.
For a polynomial :math`p(x)`, a basis state :math:`|i\rangle` and a target qubit
:math:`|0\rangle` this operator acts as:
.. math::
|i\rangle |0\rangle \mapsto \cos(p(i)) |i\rangle |0\rangle + \sin(p(i)) |i\rangle |1\rangle
Let n be the number of qubits representing the state, d the degree of p(x) and q_i the qubits,
where q_0 is the least significant qubit. Then for
.. math::
x = \sum_{i=0}^{n-1} 2^i q_i,
we can write
.. math::
p(x) = \sum_{j=0}^{j=d} c_j x_j
where :math:`c` are the input coefficients, ``coeffs``.
"""
def __init__(self, num_state_qubits: Optional[int] = None,
coeffs: Optional[List[float]] = None,
basis: str = 'Y',
reverse: bool = False,
name: str = 'poly') -> None:
"""Prepare an approximation to a state with amplitudes specified by a polynomial.
Args:
num_state_qubits: The number of qubits representing the state.
coeffs: The coefficients of the polynomial. ``coeffs[i]`` is the coefficient of the
i-th power of x. Defaults to linear: [0, 1].
basis: The type of Pauli rotation ('X', 'Y', 'Z').
reverse: If True, apply the polynomial with the reversed list of qubits
(i.e. q_n as q_0, q_n-1 as q_1, etc).
name: The name of the circuit.
"""
# set default internal parameters
self._coeffs = coeffs or [0, 1]
self._reverse = reverse
# initialize super (after setting coeffs)
super().__init__(num_state_qubits=num_state_qubits, basis=basis, name=name)
@property
def coeffs(self) -> List[float]:
"""The multiplicative factor in the rotation angle of the controlled rotations.
The rotation angles are ``slope * 2^0``, ``slope * 2^1``, ... , ``slope * 2^(n-1)`` where
``n`` is the number of state qubits.
Returns:
The rotation angle common in all controlled rotations.
"""
return self._coeffs
@coeffs.setter
def coeffs(self, coeffs: List[float]) -> None:
"""Set the multiplicative factor of the rotation angles.
Args:
The slope of the rotation angles.
"""
self._invalidate()
self._coeffs = coeffs
# the number of ancilla's depends on the number of coefficients, so update if necessary
if coeffs and self.num_state_qubits:
self._reset_registers(self.num_state_qubits)
@property
def degree(self) -> int:
"""Return the degree of the polynomial, equals to the number of coefficients minus 1.
Returns:
The degree of the polynomial. If the coefficients have not been set, return 0.
"""
if self.coeffs:
return len(self.coeffs) - 1
return 0
@property
def reverse(self) -> bool:
"""Whether to apply the rotations on the reversed list of qubits.
Returns:
True, if the rotations are applied on the reversed list, False otherwise.
"""
return self._reverse
@reverse.setter
def reverse(self, reverse: bool) -> None:
"""Set to True to reverse the list of qubits.
Args:
reverse: If True, the rotations are applied on the reversed list. If False, then not.
"""
if self._reverse is None or reverse != self._reverse:
self._invalidate()
self._reverse = reverse
@property
def num_ancilla_qubits(self) -> int:
"""The number of ancilla qubits in this circuit.
Returns:
The number of ancilla qubits.
"""
return max(1, self.degree - 1)
def _reset_registers(self, num_state_qubits):
if num_state_qubits:
# set new register of appropriate size
qr_state = QuantumRegister(num_state_qubits, name='state')
qr_target = QuantumRegister(1, name='target')
qr_ancilla = QuantumRegister(self.num_ancilla_qubits, name='ancilla')
self.qregs = [qr_state, qr_target, qr_ancilla]
else:
self.qregs = []
def _check_configuration(self, raise_on_failure: bool = True) -> bool:
valid = True
if self.num_state_qubits is None:
valid = False
if raise_on_failure:
raise AttributeError('The number of qubits has not been set.')
if self.num_qubits < self.num_state_qubits + 1:
valid = False
if raise_on_failure:
raise CircuitError('Not enough qubits in the circuit, need at least '
'{}.'.format(self.num_state_qubits + 1))
return valid
def _get_rotation_coefficients(self) -> Dict[Sequence[int], float]:
"""Compute the coefficient of each monomial.
Returns:
A dictionary with pairs ``{control_state: rotation angle}`` where ``control_state``
is a tuple of ``0`` or ``1`` bits.
"""
# determine the control states
all_combinations = list(product([0, 1], repeat=self.num_state_qubits))
valid_combinations = []
for combination in all_combinations:
if 0 < sum(combination) <= self.degree:
valid_combinations += [combination]
rotation_coeffs = {control_state: 0 for control_state in valid_combinations}
# compute the coefficients for the control states
for i, coeff in enumerate(self.coeffs[1:]):
i += 1 # since we skip the first element we need to increase i by one
# iterate over the multinomial coefficients
for comb, num_combs in multinomial_coefficients(self.num_state_qubits, i).items():
control_state = ()
power = 1
for j, qubit in enumerate(comb):
if qubit > 0: # means we control on qubit i
control_state += (1,)
power *= 2 ** (j * qubit)
else:
control_state += (0,)
# Add angle
rotation_coeffs[control_state] += coeff * num_combs * power
return rotation_coeffs
def _build(self):
super()._build()
qr_state = self.qubits[:self.num_state_qubits]
qr_target = self.qubits[self.num_state_qubits]
qr_ancilla = self.qubits[self.num_state_qubits + 1:]
rotation_coeffs = self._get_rotation_coefficients()
if self.basis == 'x':
self.rx(self.coeffs[0], qr_target)
elif self.basis == 'y':
self.ry(self.coeffs[0], qr_target)
else:
self.rz(self.coeffs[0], qr_target)
for c in rotation_coeffs:
qr_control = []
if self.reverse:
for i, _ in enumerate(c):
if c[i] > 0:
qr_control.append(qr_state[qr_state.size - i - 1])
else:
for i, _ in enumerate(c):
if c[i] > 0:
qr_control.append(qr_state[i])
# apply controlled rotations
if len(qr_control) > 1:
if self.basis == 'x':
self.mcrx(rotation_coeffs[c], qr_control, qr_target, qr_ancilla)
elif self.basis == 'y':
self.mcry(rotation_coeffs[c], qr_control, qr_target, qr_ancilla)
else:
self.mcrz(rotation_coeffs[c], qr_control, qr_target, qr_ancilla)
elif len(qr_control) == 1:
if self.basis == 'x':
self.crx(rotation_coeffs[c], qr_control[0], qr_target)
elif self.basis == 'y':
self.cry(rotation_coeffs[c], qr_control[0], qr_target)
else:
self.crz(rotation_coeffs[c], qr_control[0], qr_target)