# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 2019.
#
# 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.
"""Decompose an arbitrary 2*2 unitary into three rotation gates: U=R_zR_yR_z.
Note that the decomposition is up to a global phase shift.
(This is a well known decomposition, which can be found for example in Nielsen and Chuang's book
"Quantum computation and quantum information".)
"""
import cmath
import numpy as np
from qiskit.circuit import QuantumRegister, Qubit, QuantumCircuit
from qiskit.circuit.gate import Gate
from qiskit.circuit.exceptions import CircuitError
from qiskit.quantum_info.operators.predicates import is_unitary_matrix
from qiskit.exceptions import QiskitError
_EPS = 1e-10 # global variable used to chop very small numbers to zero
[docs]class SingleQubitUnitary(Gate):
"""Single-qubit unitary.
Args:
unitary_matrix: :math:`2 \times 2` unitary (given as a (complex) ``numpy.ndarray``).
mode: determines the used decomposition by providing the rotation axes.
up_to_diagonal: the single-qubit unitary is decomposed up to a diagonal matrix,
i.e. a unitary :math:`U'` is implemented such that there exists a diagonal
matrix :math:`D` with :math:`U = D U'`.
"""
def __init__(self, unitary_matrix, mode="ZYZ", up_to_diagonal=False):
"""Create a new single qubit gate based on the unitary ``u``."""
if mode not in ["ZYZ"]:
raise QiskitError("The decomposition mode is not known.")
# Check if the matrix u has the right dimensions and if it is a unitary
if unitary_matrix.shape != (2, 2):
raise QiskitError("The dimension of the input matrix is not equal to (2,2).")
if not is_unitary_matrix(unitary_matrix):
raise QiskitError("The 2*2 matrix is not unitary.")
self.mode = mode
self.up_to_diagonal = up_to_diagonal
self._diag = None
# Create new gate
super().__init__("squ", 1, [unitary_matrix])
[docs] def inverse(self):
"""Return the inverse.
Note that the resulting gate has an empty ``params`` property.
"""
inverse_gate = Gate(
name=self.name + "_dg", num_qubits=self.num_qubits, params=[]
) # removing the params because arrays are deprecated
definition = QuantumCircuit(*self.definition.qregs)
for inst in reversed(self._definition):
definition._append(inst.replace(operation=inst.operation.inverse()))
inverse_gate.definition = definition
return inverse_gate
@property
def diag(self):
"""Returns the diagonal gate D up to which the single-qubit unitary u is implemented.
I.e. u=D.u', where u' is the unitary implemented by the found circuit.
"""
if self._diag is None:
self._define()
return self._diag
def _define(self):
"""Define the gate using the decomposition."""
if self.mode == "ZYZ":
circuit, diag = self._zyz_circuit()
else:
raise QiskitError("The decomposition mode is not known.")
self._diag = diag
self.definition = circuit
def _zyz_circuit(self):
"""Get the circuit for the ZYZ decomposition."""
q = QuantumRegister(self.num_qubits)
qc = QuantumCircuit(q, name=self.name)
diag = [1.0, 1.0]
alpha, beta, gamma, _ = self._zyz_dec()
if abs(alpha) > _EPS:
qc.rz(alpha, q[0])
if abs(beta) > _EPS:
qc.ry(beta, q[0])
if abs(gamma) > _EPS:
if self.up_to_diagonal:
diag = [np.exp(-1j * gamma / 2.0), np.exp(1j * gamma / 2.0)]
else:
qc.rz(gamma, q[0])
return qc, diag
def _zyz_dec(self):
"""Finds rotation angles (a,b,c,d) in the decomposition u=exp(id)*Rz(c).Ry(b).Rz(a).
Note that where "." denotes matrix multiplication.
"""
unitary = self.params[0]
u00 = unitary.item(0, 0)
u01 = unitary.item(0, 1)
u10 = unitary.item(1, 0)
u11 = unitary.item(1, 1)
# Handle special case if the entry (0,0) of the unitary is equal to zero
if np.abs(u00) < _EPS:
# Note that u10 can't be zero, since u is unitary (and u00 == 0)
gamma = cmath.phase(-u01 / u10)
delta = cmath.phase(u01 * np.exp(-1j * gamma / 2))
return 0.0, -np.pi, -gamma, delta
# Handle special case if the entry (0,1) of the unitary is equal to zero
if np.abs(u01) < _EPS:
# Note that u11 can't be zero, since u is unitary (and u01 == 0)
gamma = cmath.phase(u00 / u11)
delta = cmath.phase(u00 * np.exp(-1j * gamma / 2))
return 0.0, 0.0, -gamma, delta
beta = 2 * np.arccos(np.abs(u00))
if np.sin(beta / 2) - np.cos(beta / 2) > 0:
gamma = cmath.phase(-u00 / u10)
alpha = cmath.phase(u00 / u01)
else:
gamma = -cmath.phase(-u10 / u00)
alpha = -cmath.phase(u01 / u00)
delta = cmath.phase(u00 * np.exp(-1j * (alpha + gamma) / 2))
# The decomposition works with another convention for the rotation gates
# (the one using negative angles).
# Therefore, we have to take the inverse of the angles at the end.
return -alpha, -beta, -gamma, delta
[docs] def validate_parameter(self, parameter):
"""Single-qubit unitary gate parameter has to be an ndarray."""
if isinstance(parameter, np.ndarray):
return parameter
else:
raise CircuitError(f"invalid param type {type(parameter)} in gate {self.name}")
def squ(
self,
unitary_matrix,
qubit,
mode="ZYZ",
up_to_diagonal=False,
):
"""Decompose an arbitrary 2*2 unitary into three rotation gates.
Note that the decomposition is up to a global phase shift.
(This is a well known decomposition which can be found for example in Nielsen and Chuang's book
"Quantum computation and quantum information".)
Args:
unitary_matrix (ndarray): 2*2 unitary (given as a (complex) ndarray).
qubit (QuantumRegister or Qubit): The qubit which the gate is acting on.
mode (string): determines the used decomposition by providing the rotation axes.
The allowed modes are: "ZYZ" (default)
up_to_diagonal (bool): if set to True, the single-qubit unitary is decomposed up to
a diagonal matrix, i.e. a unitary u' is implemented such that there exists a 2*2
diagonal gate d with u = d.dot(u')
Returns:
InstructionSet: The single-qubit unitary instruction attached to the circuit.
Raises:
QiskitError: if the format is wrong; if the array u is not unitary
"""
if isinstance(qubit, QuantumRegister):
qubit = qubit[:]
if len(qubit) == 1:
qubit = qubit[0]
else:
raise QiskitError(
"The target qubit is a QuantumRegister containing more than one qubit."
)
# Check if there is one target qubit provided
if not isinstance(qubit, Qubit):
raise QiskitError("The target qubit is not a single qubit from a QuantumRegister.")
return self.append(SingleQubitUnitary(unitary_matrix, mode, up_to_diagonal), [qubit], [])
QuantumCircuit.squ = squ