Skip to content

Commit

Permalink
Merge pull request #67 from purva-thakre/single_qubit_functions
Browse files Browse the repository at this point in the history
Adds single qubit decomposition functions
  • Loading branch information
BoxiLi committed Jul 19, 2021
2 parents ad6c2fc + 3d5354d commit 16818cc
Show file tree
Hide file tree
Showing 9 changed files with 515 additions and 3 deletions.
21 changes: 21 additions & 0 deletions doc/source/_apidoc/qutip_qip.decompose.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
qutip\_qip.decompose package
============================

Submodules
----------

qutip\_qip.decompose.decompose\_single\_qubit\_gate module
----------------------------------------------------------

.. automodule:: qutip_qip.decompose.decompose_single_qubit_gate
:members:
:undoc-members:
:show-inheritance:

Module contents
---------------

.. automodule:: qutip_qip.decompose
:members:
:undoc-members:
:show-inheritance:
9 changes: 9 additions & 0 deletions doc/source/_apidoc/qutip_qip.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Subpackages

qutip_qip.algorithms
qutip_qip.compiler
qutip_qip.decompose
qutip_qip.device
qutip_qip.operations
qutip_qip.transpiler
Expand Down Expand Up @@ -72,6 +73,14 @@ qutip\_qip.qubits module
:undoc-members:
:show-inheritance:

qutip\_qip.version module
-------------------------

.. automodule:: qutip_qip.version
:members:
:undoc-members:
:show-inheritance:

Module contents
---------------

Expand Down
28 changes: 26 additions & 2 deletions src/qutip_qip/circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
)
from .operations.gates import _gate_label
from qutip import basis, ket2dm, qeye
from qutip.qobj import Qobj
from qutip import Qobj
from qutip.measurement import measurement_statistics


Expand Down Expand Up @@ -321,7 +321,6 @@ def add_gate(self, gate, targets=None, controls=None, arg_value=None,
2 ** len(classical_controls) - 1
(i.e. all classical controls are 1).
"""

if isinstance(gate, Gate):
name = gate.name
targets = gate.targets
Expand Down Expand Up @@ -349,6 +348,19 @@ def add_gate(self, gate, targets=None, controls=None, arg_value=None,
control_value=control_value)
self.gates.insert(position, gate)

def add_gates(self, gates):
"""
Adds a sequence of gates to the circuit in a positive order, i.e.
the first gate in the sequence will be applied first to the state.
Parameters
----------
gates: Iterable (e.g., list)
The sequence of gates to be added.
"""
for g in gates:
self.add_gate(g)

def add_1q_gate(self, name, start=0, end=None, qubits=None,
arg_value=None, arg_label=None,
classical_controls=None, control_value=None):
Expand Down Expand Up @@ -1486,6 +1498,18 @@ def _propagators_no_expand(self):

return U_list

def compute_unitary(self):
"""Evaluates the matrix of all the gates in a quantum circuit.
Returns
-------
circuit_unitary : :class:`qutip.Qobj`
Product of all gate arrays in the quantum circuit.
"""
gate_list = self.propagators()
circuit_unitary = gate_sequence_product(gate_list)
return circuit_unitary

def latex_code(self):
rows = []

Expand Down
1 change: 1 addition & 0 deletions src/qutip_qip/decompose/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .decompose_single_qubit_gate import *
31 changes: 31 additions & 0 deletions src/qutip_qip/decompose/_utility.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from qutip import Qobj


class MethodError(Exception):
"""When invalid method is chosen, this error is raised."""

pass


def check_gate(gate, num_qubits):
"""Verifies input is a valid quantum gate.
Parameters
----------
gate : :class:`qutip.Qobj`
The matrix that's supposed to be decomposed should be a Qobj.
num_qubits:
Total number of qubits in the circuit.
Raises
------
TypeError
If the gate is not a Qobj.
ValueError
If the gate is not a unitary operator on qubits.
"""
if not isinstance(gate, Qobj):
raise TypeError("The input matrix is not a Qobj.")
if not gate.check_isunitary():
raise ValueError("Input is not unitary.")
if gate.dims != [[2] * num_qubits] * 2:
raise ValueError(f"Input is not a unitary on {num_qubits} qubits.")
256 changes: 256 additions & 0 deletions src/qutip_qip/decompose/decompose_single_qubit_gate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
import numpy as np
import cmath


from qutip_qip.decompose._utility import (
check_gate,
MethodError,
)

from qutip_qip.circuit import Gate


def _angles_for_ZYZ(input_gate):
"""Finds and returns the angles for ZYZ rotation matrix. These are
used to change ZYZ to other combinations.
Parameters
----------
input_gate : :class:`qutip.Qobj`
The gate matrix that's supposed to be decomposed should be a Qobj.
"""
input_array = input_gate.full()
normalization_constant = np.sqrt(np.linalg.det(input_array))
global_phase_angle = -cmath.phase(1 / normalization_constant)
input_array = input_array * (1 / normalization_constant)

# U = np.array([[a,b],[-b*,a*]])
# If a = x+iy and b = p+iq, alpha = inv_tan(-y/x) - inv_tan(-q/p)
a_negative = np.real(input_array[0][0]) - 1j * np.imag(input_array[0][0])
b_negative = np.real(input_array[0][1]) - 1j * np.imag(input_array[0][1])

# find alpha, beta and theta
alpha = cmath.phase(a_negative) - cmath.phase(b_negative)
beta = cmath.phase(a_negative) + cmath.phase(b_negative)
theta = 2 * np.arctan2(np.absolute(b_negative), np.absolute(a_negative))

return (alpha, -theta, beta, global_phase_angle)


def _ZYZ_rotation(input_gate):
r"""An input 1-qubit gate is expressed as a product of rotation matrices
:math:`\textrm{R}_z` and :math:`\textrm{R}_y`.
Parameters
----------
input_gate : :class:`qutip.Qobj`
The matrix that's supposed to be decomposed should be a Qobj.
"""
check_gate(input_gate, num_qubits=1)
alpha, theta, beta, global_phase_angle = _angles_for_ZYZ(input_gate)

Phase_gate = Gate(
"GLOBALPHASE",
targets=[0],
arg_value=global_phase_angle,
arg_label=r"{:0.2f} \times \pi".format(global_phase_angle / np.pi),
)
Rz_beta = Gate(
"RZ",
targets=[0],
arg_value=beta,
arg_label=r"{:0.2f} \times \pi".format(beta / np.pi),
)
Ry_theta = Gate(
"RY",
targets=[0],
arg_value=theta,
arg_label=r"{:0.2f} \times \pi".format(theta / np.pi),
)
Rz_alpha = Gate(
"RZ",
targets=[0],
arg_value=alpha,
arg_label=r"{:0.2f} \times \pi".format(alpha / np.pi),
)

return (Rz_alpha, Ry_theta, Rz_beta, Phase_gate)


def _ZXZ_rotation(input_gate):
r"""An input 1-qubit gate is expressed as a product of rotation matrices
:math:`\textrm{R}_z` and :math:`\textrm{R}_x`.
Parameters
----------
input_gate : :class:`qutip.Qobj`
The matrix that's supposed to be decomposed should be a Qobj.
"""
check_gate(input_gate, num_qubits=1)
alpha, theta, beta, global_phase_angle = _angles_for_ZYZ(input_gate)
alpha = alpha - np.pi / 2
beta = beta + np.pi / 2
# theta and global phase are same as ZYZ values

Phase_gate = Gate(
"GLOBALPHASE",
targets=[0],
arg_value=global_phase_angle,
arg_label=r"{:0.2f} \times \pi".format(global_phase_angle / np.pi),
)
Rz_alpha = Gate(
"RZ",
targets=[0],
arg_value=alpha,
arg_label=r"{:0.2f} \times \pi".format(alpha / np.pi),
)
Rx_theta = Gate(
"RX",
targets=[0],
arg_value=theta,
arg_label=r"{:0.2f} \times \pi".format(theta / np.pi),
)
Rz_beta = Gate(
"RZ",
targets=[0],
arg_value=beta,
arg_label=r"{:0.2f} \times \pi".format(beta / np.pi),
)

return (Rz_alpha, Rx_theta, Rz_beta, Phase_gate)


# Functions for ABC_decomposition


def _ZYZ_pauli_X(input_gate):
"""Returns a 1 qubit unitary as a product of ZYZ rotation matrices and
Pauli X."""
check_gate(input_gate, num_qubits=1)
alpha, theta, beta, global_phase_angle = _angles_for_ZYZ(input_gate)

Phase_gate = Gate(
"GLOBALPHASE",
targets=[0],
arg_value=global_phase_angle,
arg_label=r"{:0.2f} \times \pi".format(global_phase_angle / np.pi),
)
Rz_A = Gate(
"RZ",
targets=[0],
arg_value=alpha,
arg_label=r"{:0.2f} \times \pi".format(alpha / np.pi),
)
Ry_A = Gate(
"RY",
targets=[0],
arg_value=theta / 2,
arg_label=r"{:0.2f} \times \pi".format(theta / np.pi),
)
Pauli_X = Gate("X", targets=[0])
Ry_B = Gate(
"RY",
targets=[0],
arg_value=-theta / 2,
arg_label=r"{:0.2f} \times \pi".format(-theta / np.pi),
)
Rz_B = Gate(
"RZ",
targets=[0],
arg_value=-(alpha + beta) / 2,
arg_label=r"{:0.2f} \times \pi".format(-(alpha + beta) / (2*np.pi)),
)
Rz_C = Gate(
"RZ",
targets=[0],
arg_value=(-alpha + beta) / 2,
arg_label=r"{:0.2f} \times \pi".format((-alpha + beta) / (2*np.pi)),
)

return (Rz_A, Ry_A, Pauli_X, Ry_B, Rz_B, Pauli_X, Rz_C, Phase_gate)


_single_decompositions_dictionary = {
"ZYZ": _ZYZ_rotation,
"ZXZ": _ZXZ_rotation,
"ZYZ_PauliX": _ZYZ_pauli_X,
} # other combinations to add here


def decompose_one_qubit_gate(input_gate, method):
r""" An input 1-qubit gate is expressed as a product of rotation matrices
:math:`\textrm{R}_i` and :math:`\textrm{R}_j` or as a product of rotation
matrices :math:`\textrm{R}_i` and :math:`\textrm{R}_j` and a
Pauli :math:`\sigma_k`.
Here, :math:`i \neq j` and :math:`i, j, k \in {x, y, z}`.
Based on Lemma 4.1 and Lemma 4.3 of
https://arxiv.org/abs/quant-ph/9503016v1 respectively.
.. math::
U = \begin{bmatrix}
a & b \\
-b^* & a^* \\
\end{bmatrix} = \textrm{R}_i(\alpha) \textrm{R}_j(\theta) \textrm{R}_i(\beta) = \textrm{A} \sigma_k \textrm{B} \sigma_k \textrm{C}
Here,
* :math:`\textrm{A} = \textrm{R}_i(\alpha) \textrm{R}_j\left(\frac{\theta}{2} \right)`
* :math:`\textrm{B} = \textrm{R}_j \left(\frac{-\theta}{2} \right)\textrm{R}_i \left(\frac{- \left(\alpha + \beta \right)}{2} \right)`
* :math:`\textrm{C} = \textrm{R}_i \left(\frac{\left(-\alpha + \beta\right)}{2} \right)`
Parameters
----------
input_gate : :class:`qutip.Qobj`
The matrix to be decomposed.
method : string
Name of the preferred decomposition method
.. list-table::
:widths: auto
:header-rows: 1
* - Method Key
- Method
* - ZYZ
- :math:`\textrm{R}_z(\alpha) \textrm{R}_y(\theta) \textrm{R}_z(\beta)`
* - ZXZ
- :math:`\textrm{R}_z(\alpha) \textrm{R}_x(\theta) \textrm{R}_z(\beta)`
* - ZYZ_PauliX
- :math:`\textrm{A} \sigma_k \textrm{B} \sigma_k \textrm{C}` :math:`\forall k =x, i =z, j=y`
.. note::
This function is under construction. As more combinations are
added, above table will be updated with their respective keys.
Returns
-------
tuple
The gates in the decomposition are returned as a tuple of :class:`Gate`
objects.
When the input gate is decomposed to product of rotation matrices -
tuple will contain 4 elements per each :math:`1 \times 1`
qubit gate - :math:`\textrm{R}_i(\alpha)`, :math:`\textrm{R}_j(\theta)`
, :math:`\textrm{R}_i(\beta)`, and some global phase gate.
When the input gate is decomposed to product of rotation matrices and
Pauli - tuple will contain 6 elements per each :math:`1 \times 1`
qubit gate - 2 gates forming :math:`\textrm{A}`, 2 gates forming
:math:`\textrm{B}`, 1 gates forming :math:`\textrm{C}`, and some global
phase gate.
"""
check_gate(input_gate, num_qubits=1)
f = _single_decompositions_dictionary.get(method, None)
if f is None:
raise MethodError(f"Invalid decomposition method: {method!r}")
return f(input_gate)

0 comments on commit 16818cc

Please sign in to comment.