diff --git a/qiskit/extensions/quantum_initializer/isometry.py b/qiskit/extensions/quantum_initializer/isometry.py index cb3bb1f12311..5d17398ab3a3 100644 --- a/qiskit/extensions/quantum_initializer/isometry.py +++ b/qiskit/extensions/quantum_initializer/isometry.py @@ -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 @@ -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 @@ -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 @@ -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 @@ -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) @@ -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) @@ -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: @@ -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 @@ -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] @@ -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] @@ -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 diff --git a/qiskit/extensions/quantum_initializer/uc.py b/qiskit/extensions/quantum_initializer/uc.py index f432a7ebd082..61618efb3563 100644 --- a/qiskit/extensions/quantum_initializer/uc.py +++ b/qiskit/extensions/quantum_initializer/uc.py @@ -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. @@ -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) @@ -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), diff --git a/releasenotes/notes/global-phase-ucgate-cd61355e314a3e64.yaml b/releasenotes/notes/global-phase-ucgate-cd61355e314a3e64.yaml new file mode 100644 index 000000000000..536b66acfa6a --- /dev/null +++ b/releasenotes/notes/global-phase-ucgate-cd61355e314a3e64.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + Fixed a global phase bug in :class:`~.UCGate`. diff --git a/test/python/circuit/test_uc.py b/test/python/circuit/test_uc.py index a53367132833..cedff9f4ac41 100644 --- a/test/python/circuit/test_uc.py +++ b/test/python/circuit/test_uc.py @@ -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], ) @@ -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)