import numpy as np
import sympy
from qibo import gates
from qibo.backends import matrices
from qibo.config import raise_error
[docs]class Symbol(sympy.Symbol):
"""Qibo specialization for ``sympy`` symbols.
These symbols can be used to create :class:`qibo.hamiltonians.hamiltonians.SymbolicHamiltonian`.
See :ref:`How to define custom Hamiltonians using symbols? <symbolicham-example>`
for more details.
Example:
.. testcode::
from qibo import hamiltonians
from qibo.symbols import X, Y, Z
# construct a XYZ Hamiltonian on two qubits using Qibo symbols
form = X(0) * X(1) + Y(0) * Y(1) + Z(0) * Z(1)
ham = hamiltonians.SymbolicHamiltonian(form)
Args:
q (int): Target qubit id.
matrix (np.ndarray): 2x2 matrix represented by this symbol.
name (str): Name of the symbol which defines how it is represented in
symbolic expressions.
commutative (bool): If ``True`` the constructed symbols commute with
each other. Default is ``False``.
This argument should be used with caution because quantum operators
are not commutative objects and therefore switching this to ``True``
may lead to wrong results. It is useful for improving performance
in symbolic calculations in cases where the user is sure that
the operators participating in the Hamiltonian form are commuting
(for example when the Hamiltonian consists of Z terms only).
"""
def __new__(cls, q, matrix=None, name="Symbol", commutative=False, **assumptions):
name = f"{name}{q}"
assumptions["commutative"] = commutative
return super().__new__(cls=cls, name=name, **assumptions)
def __init__(self, q, matrix=None, name="Symbol", commutative=False):
self.target_qubit = q
self._gate = None
if not (
matrix is None
or isinstance(matrix, np.ndarray)
or isinstance(
matrix,
(
int,
float,
complex,
np.int32,
np.int64,
np.float32,
np.float64,
np.complex64,
np.complex128,
),
)
):
raise_error(TypeError, f"Invalid type {type(matrix)} of symbol matrix.")
self.matrix = matrix
def __getstate__(self):
return {
"target_qubit": self.target_qubit,
"matrix": self.matrix,
"name": self.name,
}
def __setstate__(self, data):
self.target_qubit = data.get("target_qubit")
self.matrix = data.get("matrix")
self.name = data.get("name")
self._gate = None
@property
def gate(self):
"""Qibo gate that implements the action of the symbol on states."""
if self._gate is None:
self._gate = self.calculate_gate()
return self._gate
def calculate_gate(self): # pragma: no cover
return gates.Unitary(self.matrix, self.target_qubit)
[docs] def full_matrix(self, nqubits):
"""Calculates the full dense matrix corresponding to the symbol as part of a bigger system.
Args:
nqubits (int): Total number of qubits in the system.
Returns:
Matrix of dimension (2^nqubits, 2^nqubits) composed of the Kronecker
product between identities and the symbol's single-qubit matrix.
"""
from qibo.hamiltonians.models import _multikron
matrix_list = self.target_qubit * [matrices.I]
matrix_list.append(self.matrix)
n = nqubits - self.target_qubit - 1
matrix_list.extend(matrices.I for _ in range(n))
return _multikron(matrix_list)
class PauliSymbol(Symbol):
def __new__(cls, q, commutative=False, **assumptions):
matrix = getattr(matrices, cls.__name__)
return super().__new__(cls, q, matrix, cls.__name__, commutative, **assumptions)
def __init__(self, q, commutative=False):
name = self.__class__.__name__
matrix = getattr(matrices, name)
super().__init__(q, matrix, name, commutative)
def calculate_gate(self):
name = self.__class__.__name__
return getattr(gates, name)(self.target_qubit)
[docs]class I(PauliSymbol):
"""Qibo symbol for the identity operator.
Args:
q (int): Target qubit id.
"""
pass
[docs]class X(PauliSymbol):
"""Qibo symbol for the Pauli-X operator.
Args:
q (int): Target qubit id.
"""
pass
[docs]class Y(PauliSymbol):
"""Qibo symbol for the Pauli-X operator.
Args:
q (int): Target qubit id.
"""
pass
[docs]class Z(PauliSymbol):
"""Qibo symbol for the Pauli-X operator.
Args:
q (int): Target qubit id.
"""
pass