from functools import reduce
from typing import Optional, Union
import numpy as np
from qibo import symbols
from qibo.backends import Backend, _check_backend, matrices
from qibo.config import raise_error
from qibo.hamiltonians.hamiltonians import Hamiltonian, SymbolicHamiltonian
from qibo.hamiltonians.terms import HamiltonianTerm
[docs]def X(nqubits, dense: bool = True, backend=None):
"""Non-interacting Pauli-:math:`X` Hamiltonian.
.. math::
H = - \\sum _{k=0}^N \\, X_{k} \\, .
Args:
nqubits (int): number of qubits.
dense (bool, optional): If ``True`` it creates the Hamiltonian as a
:class:`qibo.core.hamiltonians.Hamiltonian`, otherwise it creates
a :class:`qibo.core.hamiltonians.SymbolicHamiltonian`.
Defaults to ``True``.
backend (:class:`qibo.backends.abstract.Backend`, optional): backend to be used
in the execution. If ``None``, it uses the current backend.
Defaults to ``None``.
"""
return _OneBodyPauli(nqubits, symbols.X, dense, backend=backend)
[docs]def Y(nqubits, dense: bool = True, backend=None):
"""Non-interacting Pauli-:math:`Y` Hamiltonian.
.. math::
H = - \\sum _{k=0}^{N} \\, Y_{k} \\, .
Args:
nqubits (int): number of qubits.
dense (bool): If ``True`` it creates the Hamiltonian as a
:class:`qibo.core.hamiltonians.Hamiltonian`, otherwise it creates
a :class:`qibo.core.hamiltonians.SymbolicHamiltonian`.
backend (:class:`qibo.backends.abstract.Backend`, optional): backend to be used
in the execution. If ``None``, it uses the current backend.
Defaults to ``None``.
"""
return _OneBodyPauli(nqubits, symbols.Y, dense, backend=backend)
[docs]def Z(nqubits, dense: bool = True, backend=None):
"""Non-interacting Pauli-:math:`Z` Hamiltonian.
.. math::
H = - \\sum _{k=0}^{N} \\, Z_{k} \\, .
Args:
nqubits (int): number of qubits.
dense (bool): If ``True`` it creates the Hamiltonian as a
:class:`qibo.core.hamiltonians.Hamiltonian`, otherwise it creates
a :class:`qibo.core.hamiltonians.SymbolicHamiltonian`.
backend (:class:`qibo.backends.abstract.Backend`, optional): backend to be used
in the execution. If ``None``, it uses the current backend.
Defaults to ``None``.
"""
return _OneBodyPauli(nqubits, symbols.Z, dense, backend=backend)
[docs]def TFIM(nqubits, h: float = 0.0, dense: bool = True, backend=None):
"""Transverse field Ising model with periodic boundary conditions.
.. math::
H = - \\sum _{k=0}^{N} \\, \\left(Z_{k} \\, Z_{k + 1} + h \\, X_{k}\\right) \\, .
Args:
nqubits (int): number of qubits.
h (float, optional): value of the transverse field. Defaults to :math:`0.0`.
dense (bool, optional): If ``True`` it creates the Hamiltonian as a
:class:`qibo.core.hamiltonians.Hamiltonian`, otherwise it creates
a :class:`qibo.core.hamiltonians.SymbolicHamiltonian`.
Defaults to ``True``.
"""
if nqubits < 2:
raise_error(ValueError, "Number of qubits must be larger than one.")
backend = _check_backend(backend)
if dense:
condition = lambda i, j: i in {j % nqubits, (j + 1) % nqubits}
ham = -_build_spin_model(nqubits, backend.matrices.Z, condition, backend)
if h != 0:
condition = lambda i, j: i == j % nqubits
ham -= h * _build_spin_model(
nqubits, backend.matrices.X, condition, backend
)
return Hamiltonian(nqubits, ham, backend=backend)
term = lambda q1, q2: symbols.Z(q1, backend=backend) * symbols.Z(
q2, backend=backend
) + h * symbols.X(q1, backend=backend)
form = -1 * sum(term(i, i + 1) for i in range(nqubits - 1)) - term(nqubits - 1, 0)
ham = SymbolicHamiltonian(form=form, nqubits=nqubits, backend=backend)
return ham
[docs]def MaxCut(
nqubits,
dense: bool = True,
adj_matrix: Optional[Union[list[list[float]], np.ndarray]] = None,
backend: Optional[Backend] = None,
):
"""Max Cut Hamiltonian.
.. math::
H = -\\frac{1}{2} \\, \\sum _{j, k = 0}^{N} \\, \\left(1 - Z_{j} \\, Z_{k}\\right) \\, .
Args:
nqubits (int): number of qubits.
dense (bool): If ``True`` it creates the Hamiltonian as a
:class:`qibo.core.hamiltonians.Hamiltonian`, otherwise it creates
a :class:`qibo.core.hamiltonians.SymbolicHamiltonian`.
adj_matrix (list[list[float]] | np.ndarray): Adjecency matrix of the graph. Defaults to a
homogeneous fully connected graph with all edges having an equal 1.0 weight.
backend (:class:`qibo.backends.abstract.Backend`, optional): backend to be used
in the execution. If ``None``, it uses the current backend.
Defaults to ``None``.
"""
if adj_matrix is None:
adj_matrix = np.ones((nqubits, nqubits))
elif len(adj_matrix) != nqubits:
raise_error(
RuntimeError,
f"Expected an adjacency matrix of shape ({nqubits},{nqubits}) for a {nqubits}-qubits system.",
)
form = -sum(
adj_matrix[i][j]
* (1 - symbols.Z(i, backend=backend) * symbols.Z(j, backend=backend))
for i in range(nqubits)
for j in range(nqubits)
)
form /= 2
ham = SymbolicHamiltonian(form, nqubits=nqubits, backend=backend)
if dense:
return ham.dense
return ham
[docs]def Heisenberg(
nqubits,
coupling_constants: Union[float, int, list, tuple],
external_field_strengths: Union[float, int],
dense: bool = True,
backend=None,
):
"""Heisenberg model on a :math:`1`-dimensional periodic lattice.
The general :math:`n`-qubit Hamiltonian is given by
.. math::
\\begin{align}
H &= -\\sum_{k = 1}^{n} \\, \\left(
J_{x} \\, X_{k} \\, X_{k + 1}
+ J_{y} \\, Y_{k} \\, Y_{k + 1}
+ J_{z} \\, Z_{k} \\, Z_{k + 1} \\right) \\\\
&\\quad\\,\\, - \\sum_{k = 1}^{n} \\left(
h_{x} \\, X_{k} + h_{y} \\, Y_{k} + h_{z} \\, Z_{k}
\\right) \\, ,
\\end{align}
where :math:`\\{J_{x}, \\, J_{y}, \\, J_{z}\\}` are called the ``coupling constants``,
:math:`\\{h_{x}, \\, h_{y}, \\, h_{z}\\}` are called the ``external field strengths``,
and :math:`\\{X, \\, Y, \\, Z\\}` are the usual Pauli operators.
Args:
nqubits (int): number of qubits.
coupling_constants (list or tuple or float or int): list or tuple with the
three coupling constants :math:`\\{J_{x}, J_{y}, J{z}\\}`.
If ``int`` or ``float``, then :math:`J_{x} = J_{y} = J_{z}`.
external_field_strength (list or tuple or float or int): list or tuple with the
external magnetic field strengths :math:`\\{h_{x}, \\, h_{y}, \\, h_{z}\\}`.
If ``int`` or ``float``, then :math:`h_{x} = h_{y} = h_{z}`.
dense (bool, optional): If ``True``, creates the Hamiltonian as a
:class:`qibo.core.hamiltonians.Hamiltonian`, otherwise it creates
a :class:`qibo.core.hamiltonians.SymbolicHamiltonian`.
Defaults to ``True``.
backend (:class:`qibo.backends.abstract.Backend`, optional): backend to be used
in the execution. If ``None``, it uses the current backend.
Defaults to ``None``.
Returns:
:class:`qibo.hamiltonians.Hamiltonian` or :class:`qibo.hamiltonians.SymbolicHamiltonian`:
Heisenberg Hamiltonian.
"""
if isinstance(coupling_constants, (list, tuple)) and len(coupling_constants) != 3:
raise_error(
ValueError,
f"When `coupling_constants` is type `int` or `list`, it must have length == 3.",
)
if isinstance(coupling_constants, (float, int)):
coupling_constants = [coupling_constants] * 3
if (
isinstance(external_field_strengths, (list, tuple))
and len(external_field_strengths) != 3
):
raise_error(
ValueError,
f"When `external_field_strengths` is type `int` or `list`, it must have length == 3.",
)
if isinstance(external_field_strengths, (float, int)):
external_field_strengths = [external_field_strengths] * 3
backend = _check_backend(backend)
paulis = (symbols.X, symbols.Y, symbols.Z)
if dense:
condition = lambda i, j: i in {j % nqubits, (j + 1) % nqubits}
matrix = np.zeros((2**nqubits, 2**nqubits), dtype=complex)
matrix = backend.cast(matrix, dtype=matrix.dtype)
for ind, pauli in enumerate(paulis):
double_term = _build_spin_model(
nqubits, pauli(0, backend=backend).matrix, condition, backend
)
double_term = backend.cast(double_term, dtype=double_term.dtype)
matrix = matrix - coupling_constants[ind] * double_term
matrix = (
matrix
+ external_field_strengths[ind]
* _OneBodyPauli(nqubits, pauli, dense, backend).matrix
)
return Hamiltonian(nqubits, matrix, backend=backend)
def h(symbol):
return lambda q1, q2: symbol(q1, backend=backend) * symbol(q2, backend=backend)
def term(q1, q2):
return sum(
coeff * h(operator)(q1, q2)
for coeff, operator in zip(coupling_constants, paulis)
)
form = -1 * sum(term(i, i + 1) for i in range(nqubits - 1)) - term(nqubits - 1, 0)
form -= sum(
field_strength * pauli(qubit)
for qubit in range(nqubits)
for field_strength, pauli in zip(external_field_strengths, paulis)
if field_strength != 0.0
)
ham = SymbolicHamiltonian(form=form, backend=backend)
return ham
[docs]def XXX(
nqubits,
coupling_constant: Union[float, int] = 1,
external_field_strengths: Union[float, int, list, tuple] = [0.5, 0, 0],
dense: bool = True,
backend=None,
):
"""Heisenberg :math:`\\mathrm{XXX}` model with periodic boundary conditions.
The :math:`n`-qubit :math:`\\mathrm{XXX}` Hamiltonian is given by
.. math::
H = -J \\, \\sum_{k = 1}^{n} \\, \\left(
X_{k} \\, X_{k + 1} + Y_{k} \\, Y_{k + 1} + Z_{k} \\, Z_{k + 1}
\\right)
- \\sum_{k = 1}^{n} \\left(h_{x} \\, X_{k} + h_{y} \\, Y_{k}
+ h_{z} \\, Z_{k} \\right) \\, ,
where :math:`J` is the ``coupling_constant``, :math:`\\{h_{x}, \\, h_{y}, \\, h_{z}\\}`
are called the ``external field strengths``, and :math:`\\{X, \\, Y, \\, Z\\}` are the usual
Pauli operators.
Args:
nqubits (int): number of qubits.
coupling_constant (float or int, optional): coupling constant :math:`J`.
Defaults to :math:`1`.
external_field_strengths (float or int or list or tuple, optional): list or tuple with the
external magnetic field strengths :math:`\\{h_{x}, \\, h_{y}, \\, h_{z}\\}`.
If ``int`` or ``float``, then :math:`h_{x} = h_{y} = h_{z}`.
Defaults to :math:`[1/2, \\, 0, \\, 0]`.
dense (bool, optional): If ``True``, creates the Hamiltonian as a
:class:`qibo.core.hamiltonians.Hamiltonian`, otherwise it creates
a :class:`qibo.core.hamiltonians.SymbolicHamiltonian`.
Defaults to ``True``.
backend (:class:`qibo.backends.abstract.Backend`, optional): backend to be used
in the execution. If ``None``, it uses the current backend.
Defaults to ``None``.
Returns:
:class:`qibo.hamiltonians.Hamiltonian` or :class:`qibo.hamiltonians.SymbolicHamiltonian`:
Heisenberg :math:`\\mathrm{XXX}` Hamiltonian.
"""
if not isinstance(coupling_constant, (float, int)):
raise_error(
TypeError,
"`coupling_constant` must be type float or int, "
+ f"but it is type {type(coupling_constant)}.",
)
return Heisenberg(
nqubits,
coupling_constants=coupling_constant,
external_field_strengths=external_field_strengths,
dense=dense,
backend=backend,
)
[docs]def XXZ(nqubits, delta=0.5, dense: bool = True, backend=None):
"""Heisenberg :math:`\\mathrm{XXZ}` model with periodic boundary conditions.
.. math::
H = \\sum _{k=0}^N \\, \\left( X_{k} \\, X_{k + 1} + Y_{k} \\, Y_{k + 1}
+ \\delta Z_{k} \\, Z_{k + 1} \\right) \\, .
Example:
.. testcode::
from qibo.hamiltonians import XXZ
h = XXZ(3) # initialized XXZ model with 3 qubits
Args:
nqubits (int): number of qubits.
delta (float, optional): coefficient for the :math:`Z` component.
Defaults to :math:`0.5`.
dense (bool, optional): If ``True``, creates the Hamiltonian as a
:class:`qibo.core.hamiltonians.Hamiltonian`, otherwise it creates
a :class:`qibo.core.hamiltonians.SymbolicHamiltonian`.
Defaults to ``True``.
backend (:class:`qibo.backends.abstract.Backend`, optional): backend to be used
in the execution. If ``None``, it uses the current backend.
Defaults to ``None``.
Returns:
:class:`qibo.hamiltonians.Hamiltonian` or :class:`qibo.hamiltonians.SymbolicHamiltonian`:
Heisenberg :math:`\\mathrm{XXZ}` Hamiltonian.
"""
if nqubits < 2:
raise_error(ValueError, "Number of qubits must be larger than one.")
return Heisenberg(nqubits, [-1, -1, -delta], 0, dense=dense, backend=backend)
def _multikron(matrix_list, backend):
"""Calculates Kronecker product of a list of matrices.
Args:
matrix_list (list): List of matrices as ``ndarray``.
Returns:
ndarray: Kronecker product of all matrices in ``matrix_list``.
"""
return reduce(backend.np.kron, matrix_list)
def _build_spin_model(nqubits, matrix, condition, backend):
"""Helper method for building nearest-neighbor spin model Hamiltonians."""
h = sum(
reduce(
backend.np.kron,
(
matrix if condition(i, j) else backend.matrices.I()
for j in range(nqubits)
),
)
for i in range(nqubits)
)
return h
def _OneBodyPauli(nqubits, operator, dense: bool = True, backend=None):
"""Helper method for constructing non-interacting
:math:`X`, :math:`Y`, and :math:`Z` Hamiltonians."""
backend = _check_backend(backend)
if dense:
condition = lambda i, j: i == j % nqubits
ham = -_build_spin_model(
nqubits, operator(0, backend=backend).matrix, condition, backend
)
return Hamiltonian(nqubits, ham, backend=backend)
form = sum([-1 * operator(i, backend=backend) for i in range(nqubits)])
ham = SymbolicHamiltonian(form=form, backend=backend)
return ham