Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #4182 Initializer should use at most 2^(n+1)-2n cnots #4183

Merged
merged 12 commits into from
Jun 18, 2020
27 changes: 19 additions & 8 deletions qiskit/extensions/quantum_initializer/initializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,19 @@ def gates_to_uncompute(self):

# perform the required rotations to decouple the LSB qubit (so that
# it can be "factored" out, leaving a shorter amplitude vector to peel away)
rz_mult = self._multiplex(RZGate, phis)
ry_mult = self._multiplex(RYGate, thetas)
circuit.append(rz_mult.to_instruction(), q[i:self.num_qubits])
circuit.append(ry_mult.to_instruction(), q[i:self.num_qubits])

add_last_cnot = True
if np.linalg.norm(phis) != 0 and np.linalg.norm(thetas) != 0:
add_last_cnot = False

if np.linalg.norm(phis) != 0:
rz_mult = self._multiplex(RZGate, phis, last_cnot=add_last_cnot)
circuit.append(rz_mult.to_instruction(), q[i:self.num_qubits])

if np.linalg.norm(thetas) != 0:
ry_mult = self._multiplex(RYGate, thetas, last_cnot=add_last_cnot)
circuit.append(ry_mult.to_instruction().mirror(), q[i:self.num_qubits])

return circuit

@staticmethod
Expand Down Expand Up @@ -178,7 +187,7 @@ def _bloch_angles(pair_of_complex):

return final_r * np.exp(1.J * final_t / 2), theta, phi

def _multiplex(self, target_gate, list_of_angles):
def _multiplex(self, target_gate, list_of_angles, last_cnot=True):
"""
Return a recursive implementation of a multiplexor circuit,
where each instruction itself has a decomposition based on
Expand All @@ -190,6 +199,7 @@ def _multiplex(self, target_gate, list_of_angles):
target_gate (Gate): Ry or Rz gate to apply to target qubit, multiplexed
over all other "select" qubits
list_of_angles (list[float]): list of rotation angles to apply Ry and Rz
last_cnot (bool): add the last cnot if last_cnot = True

Returns:
DAGCircuit: the circuit implementing the multiplexor's action
Expand Down Expand Up @@ -217,7 +227,7 @@ def _multiplex(self, target_gate, list_of_angles):
list_of_angles = angle_weight.dot(np.array(list_of_angles)).tolist()

# recursive step on half the angles fulfilling the above assumption
multiplex_1 = self._multiplex(target_gate, list_of_angles[0:(list_len // 2)])
multiplex_1 = self._multiplex(target_gate, list_of_angles[0:(list_len // 2)], False)
circuit.append(multiplex_1.to_instruction(), q[0:-1])

# attach CNOT as follows, thereby flipping the LSB qubit
Expand All @@ -226,14 +236,15 @@ def _multiplex(self, target_gate, list_of_angles):
# implement extra efficiency from the paper of cancelling adjacent
# CNOTs (by leaving out last CNOT and reversing (NOT inverting) the
# second lower-level multiplex)
multiplex_2 = self._multiplex(target_gate, list_of_angles[(list_len // 2):])
multiplex_2 = self._multiplex(target_gate, list_of_angles[(list_len // 2):], False)
if list_len > 1:
circuit.append(multiplex_2.to_instruction().mirror(), q[0:-1])
else:
circuit.append(multiplex_2.to_instruction(), q[0:-1])

# attach a final CNOT
circuit.append(CXGate(), [msb, lsb])
if last_cnot:
circuit.append(CXGate(), [msb, lsb])

return circuit

Expand Down
30 changes: 30 additions & 0 deletions test/python/circuit/test_initializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from qiskit import QuantumCircuit
from qiskit import QuantumRegister
from qiskit import ClassicalRegister
from qiskit import transpile
from qiskit import execute, assemble, BasicAer
from qiskit.quantum_info import state_fidelity
from qiskit.exceptions import QiskitError
Expand Down Expand Up @@ -314,6 +315,35 @@ def test_equivalence(self):

self.assertEqual(qc1, qc2)

def test_max_number_cnots(self):
"""
Check if the number of cnots <= 2^(n+1) - 2n (arXiv:quant-ph/0406176)
"""
num_qubits = 4
_optimization_level = 0

vector = np.array(
[0.1314346 + 0.j, 0.32078572 - 0.01542775j, 0.13146466 + 0.0945312j,
0.21090852 + 0.07935982j, 0.1700122 - 0.07905648j, 0.15570757 - 0.12309154j,
0.18039667 + 0.04904504j, 0.22227187 - 0.05055569j, 0.23573255 - 0.09894111j,
0.27307292 - 0.10372994j, 0.24162792 + 0.1090791j, 0.3115577 + 0.1211683j,
0.1851788 + 0.08679141j, 0.36226463 - 0.09940202j, 0.13863395 + 0.10558225j,
0.30767986 + 0.02073838j])

vector = vector / np.linalg.norm(vector)

qr = QuantumRegister(num_qubits, 'qr')
circuit = QuantumCircuit(qr)
circuit.initialize(vector, qr)

b = transpile(circuit, basis_gates=['u1', 'u2', 'u3', 'cx'],
optimization_level=_optimization_level)

number_cnots = b.count_ops()['cx']
max_cnots = 2 ** (num_qubits + 1) - 2 * num_qubits

self.assertLessEqual(number_cnots, max_cnots)


class TestInstructionParam(QiskitTestCase):
"""Test conversion of numpy type parameters."""
Expand Down