# This code is part of qmatchatea.
#
# 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.
r"""
Operations that can be applied on the Qcircuit class.
The idea of the module is the following:
- An Operation is whichever kind of operation applied to the circuit
- A ClassicalCondition is a condition on the application of an Operation
based on the measurement of previous qudits
The available operations in the library at the moment are:
- ``QCMeasureProjective``, apply a projective measurement on the system
- ``QCRenormalize``, renormalize the quantum state to :math:`\sqrt{<\psi|\psi>}=1`
- ``QCAddSite``, add a site to the MPS.
- ``QCRemoveSite``, remove a site from the MPS. It must be in a product state with the rest.
- ``QCObservablesStep``, perform a step of measurement of the observables
The available gates instead are:
- ``QCZpauli``,
- ``QCXpauli``,
- ``QCYpauli``,
- ``QCHadamard``,
- ``QCSgate``,
- ``QCTgate``,
- ``QCSadggate``,
- ``QCTadggate``,
- ``QCPgate``,
- ``QCSwap``,
- ``QCCnot``,
- ``QCCz``
- ``QCCp``
You can always define your own gate through :class:`QCOperation`.
In particular, for the definition of noisy gates, the procedure of the definition of a new
operation is to be preferred over modifying an exsisting one.
Then, the list `[QCOperation, sites]` is called **Instruction**.
"""
import numpy as np
__all__ = [
"QCOperation",
"ClassicalCondition",
"QCMeasureProjective",
"QCRenormalize",
"QCAddSite",
"QCRemoveSite",
"QCZpauli",
"QCXpauli",
"QCYpauli",
"QCSwap",
"QCCnot",
"QCCz",
"QCHadamard",
"QCSgate",
"QCTgate",
"QCSadggate",
"QCTadggate",
"QCPgate",
"QCCp",
]
[docs]
class ClassicalCondition:
"""
Class to handle conditions on classically-controlled
operations
Parameters
----------
cregister : str, optional
Name of the classical register relative to the condition
value : int, optional
Value that the register should assume
idx : int, optional
If provided, the index of the register to check.
If not provided, the decimal number connected to the
binary representation of the register is used.
"""
def __init__(self, cregister=None, value=None, idx=None):
self.creg = cregister
self.value = value
self.idx = idx
if cregister is None:
self._is_initialized = False
else:
self._is_initialized = True
@property
def is_initialized(self):
"""Initialization property"""
if self.creg is None:
self._is_initialized = False
else:
self._is_initialized = True
return self._is_initialized
[docs]
def is_satisfied(self, qcirc):
"""
Check if the classical condition is satisfied
Parameters
----------
qcirc : :py:class:`QCircuit`
Quantum circuit class
Returns
-------
bool
True if the condition is satisfied, False otherwise
"""
# If the condition is not initialized the condition
# is automatically satisfied
if self.is_initialized:
value = qcirc.inspect_cregister(self.creg, self.idx)
is_satisfied = value == self.value
else:
is_satisfied = True
return is_satisfied
[docs]
class QCOperation:
"""
Operation on the circuit class.
name: str
Unique name of the operation
operator: function
Python function which returns the array of the operator as np.ndarray
operator_parameters: list, optional
List of operator parameters if needed. Default to None.
conditioned : :py:class:`ClassicalCondition`, optional
Classical condition to be checked for applying the operation on the
simulation.
"""
def __init__(
self, name, operator, operator_parameters=None, conditioned=ClassicalCondition()
):
self._name = name
self._operator = operator
self._operator_parameters = (
[] if operator_parameters is None else operator_parameters
)
if np.isscalar(self._operator_parameters):
self._operator_parameters = [self._operator_parameters]
self._condition = conditioned
def __repr__(self):
"""
Return the class name as representation
"""
return self.__class__.__name__
@property
def operator(self):
"""
Returns the operator matrix as np.ndarray
"""
return self._operator(*self._operator_parameters)
@property
def name(self):
"""Name property"""
return self._name
@property
def is_conditioned(self):
"""True if there is a classical condition on the application of the gate"""
return self._condition.is_initialized
@property
def c_if(self):
"""True if there is a classical condition on the application of the gate"""
return self._condition
[docs]
def is_parametric(self):
"""Returns True if the operation is parametrized"""
is_param = len(self._operator_parameters) == 0
return is_param
@property
def string_rep(self):
"""Return the string representation for printing"""
str_rep = f"─ {self.name} ─"
void = " " * len(str_rep)
string = void + "\n" + str_rep + "\n" + void
return string
[docs]
class QCRenormalize(QCOperation):
"""
Renormalize the state when this operation is encountered
Parameters
----------
conditioned : :py:class:`ClassicalCondition`, optional
Classical condition to be checked for applying the operation on the
simulation.
"""
def __init__(self, conditioned=ClassicalCondition()):
QCOperation.__init__(self, "renormalize", None, conditioned=conditioned)
[docs]
class QCAddSite(QCOperation):
"""
Add a site to the Qcircuit
Parameters
----------
position : int
Position of the link where the site will be added
conditioned : :py:class:`ClassicalCondition`, optional
Classical condition to be checked for applying the operation on the
simulation.
"""
def __init__(self, position, conditioned=ClassicalCondition()):
self.position = position
QCOperation.__init__(self, "add_site", None, conditioned=conditioned)
[docs]
class QCRemoveSite(QCOperation):
"""
Remove a site to the Qcircuit
Parameters
----------
position : int
Position of the site to remove
conditioned : :py:class:`ClassicalCondition`, optional
Classical condition to be checked for applying the operation on the
simulation.
"""
def __init__(self, position, conditioned=ClassicalCondition()):
self.position = position
QCOperation.__init__(self, "remove_site", None, conditioned=conditioned)
[docs]
class QCMeasureProjective(QCOperation):
"""
Apply a projective measurement to the circuit, possibly
selecting its output
Parameters
----------
cl_idx : int
Position of the classical register where the measurement
will be stored
selected_output : int, optional
Output selected a priori. Default to None
cregister : str, optional
Classical register where the result of the measurement
is stored. Default to 'default'
conditioned : :py:class:`ClassicalCondition`, optional
Classical condition to be checked for applying the operation on the
simulation.
"""
def __init__(
self,
cl_idx,
selected_output=None,
cregister="default",
conditioned=ClassicalCondition(),
):
QCOperation.__init__(self, "measure", None, conditioned=conditioned)
self._selected_output = selected_output
self.cregister = cregister
self.cl_idx = cl_idx
@property
def selected_output(self):
"""A priori selected output"""
return self._selected_output
[docs]
class QCSwap(QCOperation):
"""
Swap operation between adjacent qubits.
Its matrix representation is as follows:
.. math::
\\mathrm{SWAP} =
\\begin{pmatrix}
1 & 0 & 0 & 0 \\\\
0 & 0 & 1 & 0 \\\\
0 & 1 & 0 & 0 \\\\
0 & 0 & 0 & 1 \\\\
\\end{pmatrix}
Parameters
----------
conditioned : :py:class:`ClassicalCondition`, optional
Classical condition to be checked for applying the operation on the
simulation.
"""
def __init__(self, conditioned=ClassicalCondition()):
QCOperation.__init__(self, "swap", None, conditioned=conditioned)
@property
def operator(self):
"""
Overwriting operator with swap
"""
return np.array([[1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], [0, 0, 0, 1]])
[docs]
class QCCnot(QCOperation):
"""
Cnot operation between adjacent qubits.
Its matrix representation is as follows:
.. math::
\\mathrm{CNOT} =
\\begin{pmatrix}
1 & 0 & 0 & 0 \\\\
0 & 1 & 0 & 0 \\\\
0 & 0 & 0 & 1 \\\\
0 & 0 & 1 & 0 \\\\
\\end{pmatrix}
Parameters
----------
conditioned : :py:class:`ClassicalCondition`, optional
Classical condition to be checked for applying the operation on the
simulation.
"""
def __init__(self, swap=True, conditioned=ClassicalCondition()):
self.swap = swap
QCOperation.__init__(self, "cx", None, conditioned=conditioned)
@property
def operator(self):
"""
Overwriting operator with cnot
"""
cnot = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]])
if self.swap:
swap = QCSwap().operator
cnot = swap @ cnot @ swap
return cnot
[docs]
class QCCz(QCOperation):
"""
Cz operation between adjacent qubits.
Its matrix representation is as follows:
.. math::
\\mathrm{CZ} =
\\begin{pmatrix}
1 & 0 & 0 & 0 \\\\
0 & 1 & 0 & 0 \\\\
0 & 0 & 1 & 0 \\\\
0 & 0 & 0 & -1 \\\\
\\end{pmatrix}
Parameters
----------
conditioned : :py:class:`ClassicalCondition`, optional
Classical condition to be checked for applying the operation on the
simulation.
"""
def __init__(self, conditioned=ClassicalCondition()):
QCOperation.__init__(self, "cz", None, conditioned=conditioned)
@property
def operator(self):
"""
Overwriting operator with cz
"""
cz_op = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, -1]])
return cz_op
@property
def string_rep(self):
"""Return the string representation for printing"""
str_rep = f"─ {self.name} ─"
void = " " * len(str_rep)
string = void + "\n" + str_rep + "\n" + void + "\n"
string += void + "\n" + str_rep + "\n" + void
return string
class QCHadamard(QCOperation):
"""
Hadamard operation.
Its matrix representation is as follows:
.. math::
\\mathrm{H} =\\frac{1}{\\sqrt{2}}
\\begin{pmatrix}
1 & 1 \\\\
1 & -1 \\\\
\\end{pmatrix}
Parameters
----------
conditioned : :py:class:`ClassicalCondition`, optional
Classical condition to be checked for applying the operation on the
simulation.
"""
def __init__(self, conditioned=ClassicalCondition()):
QCOperation.__init__(self, "h", None, conditioned=conditioned)
@property
def operator(self):
"""
Overwriting operator with hadamard
"""
return np.array([[1, 1], [1, -1]]) / np.sqrt(2)
[docs]
class QCZpauli(QCOperation):
"""
Pauli Z operation.
Its matrix representation is as follows:
.. math::
\\mathrm{Z} =
\\begin{pmatrix}
1 & 0 \\\\
0 & -1 \\\\
\\end{pmatrix}
Parameters
----------
conditioned : :py:class:`ClassicalCondition`, optional
Classical condition to be checked for applying the operation on the
simulation.
"""
def __init__(self, conditioned=ClassicalCondition()):
QCOperation.__init__(self, "z", None, conditioned=conditioned)
@property
def operator(self):
"""
Overwriting operator with sigma_z
"""
return np.array([[1, 0], [0, -1]])
[docs]
class QCXpauli(QCOperation):
"""
Pauli X operation.
Its matrix representation is as follows:
.. math::
\\mathrm{X} =
\\begin{pmatrix}
0 & 1 \\\\
1 & 0 \\\\
\\end{pmatrix}
Parameters
----------
conditioned : :py:class:`ClassicalCondition`, optional
Classical condition to be checked for applying the operation on the
simulation.
"""
def __init__(self, conditioned=ClassicalCondition()):
QCOperation.__init__(self, "x", None, conditioned=conditioned)
@property
def operator(self):
"""
Overwriting operator with sigma_x
"""
return np.array([[0, 1], [1, 0]])
[docs]
class QCYpauli(QCOperation):
"""
Pauli Y operation.
Its matrix representation is as follows:
.. math::
\\mathrm{Y} =
\\begin{pmatrix}
0 & -i \\\\
i & 0 \\\\
\\end{pmatrix}
Parameters
----------
conditioned : :py:class:`ClassicalCondition`, optional
Classical condition to be checked for applying the operation on the
simulation.
"""
def __init__(self, conditioned=ClassicalCondition()):
QCOperation.__init__(self, "y", None, conditioned=conditioned)
@property
def operator(self):
"""
Overwriting operator with sigma_y
"""
return np.array([[0, -1j], [1j, 0]])
[docs]
class QCSgate(QCOperation):
"""
Phase gate with phase :math:`\\pi/2`.
Its matrix representation is as follows:
.. math::
\\mathrm{S} =
\\begin{pmatrix}
1 & 0 \\\\
0 & i \\\\
\\end{pmatrix}
Parameters
----------
conditioned : :py:class:`ClassicalCondition`, optional
Classical condition to be checked for applying the operation on the
simulation.
"""
def __init__(self, conditioned=ClassicalCondition()):
QCOperation.__init__(self, "s", None, conditioned=conditioned)
@property
def operator(self):
"""
Overwriting operator with phase gate
"""
return np.array([[1, 0], [0, 1j]])
[docs]
class QCTgate(QCOperation):
"""
Phase gate with phase :math:`\\pi/4`.
Its matrix representation is as follows:
.. math::
\\mathrm{T} =
\\begin{pmatrix}
1 & 0 \\\\
0 & \\frac{1}{\\sqrt{2}}(1+i) \\\\
\\end{pmatrix}
Parameters
----------
conditioned : :py:class:`ClassicalCondition`, optional
Classical condition to be checked for applying the operation on the
simulation.
"""
def __init__(self, conditioned=ClassicalCondition()):
QCOperation.__init__(self, "t", None, conditioned=conditioned)
@property
def operator(self):
"""
Overwriting operator with phase gate
"""
return np.array([[1, 0], [0, (1 + 1j) / np.sqrt(2)]])
[docs]
class QCTadggate(QCOperation):
"""
Phase gate with phase :math:`-\\pi/4`.
Its matrix representation is as follows:
.. math::
\\mathrm{T}^\\dagger =
\\begin{pmatrix}
1 & 0 \\\\
0 & \\frac{1}{\\sqrt{2}}(1-i) \\\\
\\end{pmatrix}
Parameters
----------
conditioned : :py:class:`ClassicalCondition`, optional
Classical condition to be checked for applying the operation on the
simulation.
"""
def __init__(self, conditioned=ClassicalCondition()):
QCOperation.__init__(self, "tdg", None, conditioned=conditioned)
@property
def operator(self):
"""
Overwriting operator with adjoint t gate
"""
return np.array([[1, 0], [0, (1 - 1j) / np.sqrt(2)]])
[docs]
class QCSadggate(QCOperation):
"""
Adjoint of the phase gate with phase :math:`3\\pi/2`.
Its matrix representation is as follows:
.. math::
\\mathrm{S}^\\dagger =
\\begin{pmatrix}
1 & 0 \\\\
0 & -i \\\\
\\end{pmatrix}
Parameters
----------
conditioned : :py:class:`ClassicalCondition`, optional
Classical condition to be checked for applying the operation on the
simulation.
"""
def __init__(self, conditioned=ClassicalCondition()):
QCOperation.__init__(self, "sadg", None, conditioned=conditioned)
@property
def operator(self):
"""
Overwriting operator with adjoint s gate
"""
return np.array([[1, 0], [0, -1j]])
[docs]
class QCPgate(QCOperation):
"""
Phase gate with phase :math:`\\theta`.
Its matrix representation is as follows:
.. math::
\\mathrm{P}(\\theta) =
\\begin{pmatrix}
1 & 0 \\\\
0 & e^{i\\theta} \\\\
\\end{pmatrix}
Parameters
----------
conditioned : :py:class:`ClassicalCondition`, optional
Classical condition to be checked for applying the operation on the
simulation.
"""
def __init__(self, theta, conditioned=ClassicalCondition()):
QCOperation.__init__(self, "p", self.matrix, theta, conditioned=conditioned)
@staticmethod
def matrix(theta):
"""
Overwriting operator with phase gate
"""
return np.array([[1, 0], [0, np.exp(theta * 1j)]])
[docs]
class QCCp(QCOperation):
"""
Controlled Phase gate with phase :math:`\\theta`.
Its matrix representation is as follows:
.. math::
\\mathrm{CP}(\\theta) =
\\begin{pmatrix}
1 & 0 & 0 & 0 \\\\
0 & 1 & 0 & 0 \\\\
0 & 0 & 1 & 0 \\\\
0 & 0 & 0 & e^{i\\theta} \\\\
\\end{pmatrix}
Parameters
----------
conditioned : :py:class:`ClassicalCondition`, optional
Classical condition to be checked for applying the operation on the
simulation.
"""
def __init__(self, theta, conditioned=ClassicalCondition()):
QCOperation.__init__(self, "cp", self.matrix, theta, conditioned=conditioned)
@staticmethod
def matrix(theta):
"""
Overwriting operator with controlled phase gate
"""
cz_op = np.array(
[[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, np.exp(theta * 1j)]]
)
return cz_op