Skip to content

Commit

Permalink
Fix Uc gate global phase (#8231) (#8378)
Browse files Browse the repository at this point in the history
* global phase UCGate

* Create global-phase-ucgate-cd61355e314a3e64.yaml

* Update qiskit/extensions/quantum_initializer/isometry.py

* Update qiskit/extensions/quantum_initializer/isometry.py

Co-authored-by: ewinston <ewinston@us.ibm.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
(cherry picked from commit f2c2fe2)

Co-authored-by: Adenilton Silva <7927558+adjs@users.noreply.github.com>
Co-authored-by: Matthew Treinish <mtreinish@kortar.org>
  • Loading branch information
3 people authored Jul 19, 2022
1 parent e8a63dd commit dd64d70
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 18 deletions.
37 changes: 25 additions & 12 deletions qiskit/extensions/quantum_initializer/isometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@

import itertools
import numpy as np

from qiskit.circuit.exceptions import CircuitError
from qiskit.circuit.instruction import Instruction
from qiskit.circuit.quantumcircuit import QuantumCircuit
Expand Down Expand Up @@ -73,6 +72,8 @@ def __init__(self, isometry, num_ancillas_zero, num_ancillas_dirty, epsilon=_EPS
if len(isometry.shape) == 1:
isometry = isometry.reshape(isometry.shape[0], 1)

self.iso_data = isometry

self.num_ancillas_zero = num_ancillas_zero
self.num_ancillas_dirty = num_ancillas_dirty
self._inverse = None
Expand Down Expand Up @@ -103,15 +104,23 @@ def __init__(self, isometry, num_ancillas_zero, num_ancillas_dirty, epsilon=_EPS
super().__init__("isometry", num_qubits, 0, [isometry])

def _define(self):

# TODO The inverse().inverse() is because there is code to uncompute (_gates_to_uncompute)
# an isometry, but not for generating its decomposition. It would be cheaper to do the
# later here instead.
gate = self.inverse().inverse()
gate = self.inv_gate()
gate = gate.inverse()
q = QuantumRegister(self.num_qubits)
iso_circuit = QuantumCircuit(q)
iso_circuit.append(gate, q[:])
self.definition = iso_circuit

def inverse(self):
self.params = []
inv = super().inverse()
self.params = [self.iso_data]
return inv

def _gates_to_uncompute(self):
"""
Call to create a circuit with gates that take the desired isometry to the first 2^m columns
Expand All @@ -127,9 +136,9 @@ def _gates_to_uncompute(self):
) = self._define_qubit_role(q)
# Copy the isometry (this is computationally expensive for large isometries but guarantees
# to keep a copyof the input isometry)
remaining_isometry = self.params[0].astype(complex) # note: "astype" does copy the isometry
remaining_isometry = self.iso_data.astype(complex) # note: "astype" does copy the isometry
diag = []
m = int(np.log2((self.params[0]).shape[1]))
m = int(np.log2((self.iso_data).shape[1]))
# Decompose the column with index column_index and attache the gate to the circuit object.
# Return the isometry that is left to decompose, where the columns up to index column_index
# correspond to the firstfew columns of the identity matrix up to diag, and hence we only
Expand All @@ -148,7 +157,7 @@ def _decompose_column(self, circuit, q, diag, remaining_isometry, column_index):
"""
Decomposes the column with index column_index.
"""
n = int(np.log2(self.params[0].shape[0]))
n = int(np.log2(self.iso_data.shape[0]))
for s in range(n):
self._disentangle(circuit, q, diag, remaining_isometry, column_index, s)

Expand All @@ -163,7 +172,7 @@ def _disentangle(self, circuit, q, diag, remaining_isometry, column_index, s):
# (note that we remove columns of the isometry during the procedure for efficiency)
k_prime = 0
v = remaining_isometry
n = int(np.log2(self.params[0].shape[0]))
n = int(np.log2(self.iso_data.shape[0]))

# MCG to set one entry to zero (preparation for disentangling with UCGate):
index1 = 2 * _a(k, s + 1) * 2**s + _b(k, s + 1)
Expand Down Expand Up @@ -215,7 +224,7 @@ def _disentangle(self, circuit, q, diag, remaining_isometry, column_index, s):
# The qubit with label n-s-1 is disentangled into the basis state k_s(k,s).
def _find_squs_for_disentangling(self, v, k, s):
k_prime = 0
n = int(np.log2(self.params[0].shape[0]))
n = int(np.log2(self.iso_data.shape[0]))
if _b(k, s + 1) == 0:
i_start = _a(k, s + 1)
else:
Expand All @@ -242,7 +251,7 @@ def _append_ucg_up_to_diagonal(self, circ, q, single_qubit_gates, control_labels
q_ancillas_zero,
q_ancillas_dirty,
) = self._define_qubit_role(q)
n = int(np.log2(self.params[0].shape[0]))
n = int(np.log2(self.iso_data.shape[0]))
qubits = q_input + q_ancillas_for_output
# Note that we have to reverse the control labels, since controls are provided by
# increasing qubit number toa UCGate by convention
Expand All @@ -264,7 +273,7 @@ def _append_mcg_up_to_diagonal(self, circ, q, gate, control_labels, target_label
q_ancillas_zero,
q_ancillas_dirty,
) = self._define_qubit_role(q)
n = int(np.log2(self.params[0].shape[0]))
n = int(np.log2(self.iso_data.shape[0]))
qubits = q_input + q_ancillas_for_output
control_qubits = _reverse_qubit_oder(_get_qubits_by_label(control_labels, qubits, n))
target_qubit = _get_qubits_by_label([target_label], qubits, n)[0]
Expand All @@ -284,8 +293,10 @@ def _append_mcg_up_to_diagonal(self, circ, q, gate, control_labels, target_label
return mcg_up_to_diag._get_diagonal()

def _define_qubit_role(self, q):
n = int(np.log2((self.params[0]).shape[0]))
m = int(np.log2((self.params[0]).shape[1]))

n = int(np.log2(self.iso_data.shape[0]))
m = int(np.log2(self.iso_data.shape[1]))

# Define the role of the qubits
q_input = q[:m]
q_ancillas_for_output = q[m:n]
Expand All @@ -297,10 +308,12 @@ def validate_parameter(self, parameter):
"""Isometry parameter has to be an ndarray."""
if isinstance(parameter, np.ndarray):
return parameter
if isinstance(parameter, (list, int)):
return parameter
else:
raise CircuitError(f"invalid param type {type(parameter)} for gate {self.name}")

def inverse(self):
def inv_gate(self):
"""Return the adjoint of the unitary."""
if self._inverse is None:
# call to generate the circuit that takes the isometry to the first 2^m columns
Expand Down
6 changes: 3 additions & 3 deletions qiskit/extensions/quantum_initializer/uc.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,7 @@ def _dec_ucg(self):
circuit = QuantumCircuit(q)
# If there is no control, we use the ZYZ decomposition
if not q_controls:
theta, phi, lamb = _DECOMPOSER1Q.angles(self.params[0])
circuit.u(theta, phi, lamb, q)
circuit.unitary(self.params[0], [q])
return circuit, diag
# If there is at least one control, first,
# we find the single qubit gates of the decomposition.
Expand All @@ -160,7 +159,7 @@ def _dec_ucg(self):
.dot(HGate().to_matrix())
)
# Add single-qubit gate
circuit.squ(squ, q_target)
circuit.unitary(squ, [q_target])
# The number of the control qubit is given by the number of zeros at the end
# of the binary representation of (i+1)
binary_rep = np.binary_repr(i + 1)
Expand All @@ -169,6 +168,7 @@ def _dec_ucg(self):
# Add C-NOT gate
if not i == len(single_qubit_gates) - 1:
circuit.cx(q_controls[q_contr_index], q_target)
circuit.global_phase -= 0.25 * np.pi
if not self.up_to_diagonal:
# Important: the diagonal gate is given in the computational basis of the qubits
# q[k-1],...,q[0],q_target (ordered with decreasing significance),
Expand Down
4 changes: 4 additions & 0 deletions releasenotes/notes/global-phase-ucgate-cd61355e314a3e64.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
fixes:
- |
Fixed a global phase bug in :class:`~.UCGate`.
21 changes: 18 additions & 3 deletions test/python/circuit/test_uc.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ class TestUCGate(QiskitTestCase):
[_id, _id],
[_id, 1j * _id],
[_id, _not, _id, _not],
[random_unitary(2, seed=541234).data for i in range(2**2)],
[random_unitary(2, seed=975163).data for i in range(2**3)],
[random_unitary(2, seed=629462).data for i in range(2**4)],
[random_unitary(2, seed=541234).data for _ in range(2**2)],
[random_unitary(2, seed=975163).data for _ in range(2**3)],
[random_unitary(2, seed=629462).data for _ in range(2**4)],
],
up_to_diagonal=[True, False],
)
Expand All @@ -70,6 +70,21 @@ def test_ucg(self, squs, up_to_diagonal):
unitary_desired = _get_ucg_matrix(squs)
self.assertTrue(matrix_equal(unitary_desired, unitary, ignore_phase=True))

def test_global_phase_ucg(self):
""" "Test global phase of uniformly controlled gates"""
gates = [random_unitary(2).data for _ in range(2**2)]
num_con = int(np.log2(len(gates)))
q = QuantumRegister(num_con + 1)
qc = QuantumCircuit(q)

qc.uc(gates, q[1:], q[0], up_to_diagonal=False)
simulator = BasicAer.get_backend("unitary_simulator")
result = execute(qc, simulator).result()
unitary = result.get_unitary(qc)
unitary_desired = _get_ucg_matrix(gates)

self.assertTrue(np.allclose(unitary_desired, unitary))


def _get_ucg_matrix(squs):
return block_diag(*squs)
Expand Down

0 comments on commit dd64d70

Please sign in to comment.