Source code for pennylane.spin.spin_hamiltonian
# Copyright 2018-2024 Xanadu Quantum Technologies Inc.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
This file contains functions to create spin Hamiltonians.
"""
import pennylane as qml
from pennylane import I, X, Y, Z, math
from pennylane.fermi import FermiWord
from .lattice import Lattice, generate_lattice
# pylint: disable=too-many-arguments, too-many-branches
[docs]def transverse_ising(
lattice, n_cells, coupling=1.0, h=1.0, boundary_condition=False, neighbour_order=1
):
r"""Generates the Hamiltonian for the transverse-field Ising model on a lattice.
The Hamiltonian is represented as:
.. math::
\hat{H} = -J \sum_{<i,j>} \sigma_i^{z} \sigma_j^{z} - h\sum_{i} \sigma_{i}^{x}
where :math:`J` is the coupling parameter defined for the Hamiltonian, :math:`h` is the strength of the
transverse magnetic field, :math:`<i,j>` represents the indices for neighbouring sites and
:math:`\sigma` is a Pauli operator.
Args:
lattice (str): Shape of the lattice. Input values can be ``'chain'``, ``'square'``,
``'rectangle'``, ``'triangle'``, ``'honeycomb'``, ``'kagome'``, ``'lieb'``,
``'cubic'``, ``'bcc'``, ``'fcc'`` or ``'diamond'``.
n_cells (List[int]): Number of cells in each direction of the grid.
coupling (float or tensor_like[float]): Coupling between spins. It should
be a number, an array of length equal to ``neighbour_order`` or a square matrix of shape
``(num_spins, num_spins)``, where ``num_spins`` is the total number of spins. Default
value is 1.0.
h (float): Value of external magnetic field. Default is 1.0.
boundary_condition (bool or list[bool]): Specifies whether or not to enforce periodic
boundary conditions for the different lattice axes. Default is ``False`` indicating
open boundary condition.
neighbour_order (int): Specifies the interaction level for neighbors within the lattice.
Default is 1, indicating nearest neighbours.
Returns:
~ops.op_math.Sum: Hamiltonian for the transverse-field Ising model.
**Example**
>>> n_cells = [2,2]
>>> j = 0.5
>>> h = 0.1
>>> spin_ham = qml.spin.transverse_ising("square", n_cells, coupling=j, h=h)
>>> spin_ham
(
-0.5 * (Z(0) @ Z(1))
+ -0.5 * (Z(0) @ Z(2))
+ -0.5 * (Z(1) @ Z(3))
+ -0.5 * (Z(2) @ Z(3))
+ -0.1 * X(0)
+ -0.1 * X(1)
+ -0.1 * X(2)
+ -0.1 * X(3)
)
"""
lattice = generate_lattice(lattice, n_cells, boundary_condition, neighbour_order)
if isinstance(coupling, (int, float, complex)):
coupling = [coupling]
coupling = math.asarray(coupling)
hamiltonian = 0.0 * qml.I(0)
if coupling.shape not in [(neighbour_order,), (lattice.n_sites, lattice.n_sites)]:
raise ValueError(
f"The coupling parameter should be a number or an array of shape ({neighbour_order},) or ({lattice.n_sites},{lattice.n_sites})"
)
if coupling.shape == (neighbour_order,):
for edge in lattice.edges:
i, j, order = edge
hamiltonian += -coupling[order] * (Z(i) @ Z(j))
else:
for edge in lattice.edges:
i, j = edge[0:2]
hamiltonian += -coupling[i][j] * (Z(i) @ Z(j))
for vertex in range(lattice.n_sites):
hamiltonian += -h * X(vertex)
return hamiltonian.simplify()
[docs]def heisenberg(lattice, n_cells, coupling=None, boundary_condition=False, neighbour_order=1):
r"""Generates the Hamiltonian for the Heisenberg model on a lattice.
The Hamiltonian is represented as:
.. math::
\hat{H} = J\sum_{<i,j>}(\sigma_i^x\sigma_j^x + \sigma_i^y\sigma_j^y + \sigma_i^z\sigma_j^z)
where :math:`J` is the coupling constant defined for the Hamiltonian, :math:`<i,j>` represents the
indices for neighbouring sites and :math:`\sigma` is a Pauli operator.
Args:
lattice (str): Shape of the lattice. Input values can be ``'chain'``, ``'square'``,
``'rectangle'``, ``'triangle'``, ``'honeycomb'``, ``'kagome'``, ``'lieb'``,
``'cubic'``, ``'bcc'``, ``'fcc'`` or ``'diamond'``.
n_cells (List[int]): Number of cells in each direction of the grid.
coupling (tensor_like[float]): Coupling between spins. It can be an
array of shape ``(neighbour_order, 3)`` or
``(3, num_spins, num_spins)``, where ``num_spins`` is the total number of spins.
boundary_condition (bool or list[bool]): Specifies whether or not to enforce periodic
boundary conditions for the different lattice axes. Default is ``False`` indicating
open boundary condition.
neighbour_order (int): Specifies the interaction level for neighbors within the lattice.
Default is 1, indicating nearest neighbours.
Returns:
~ops.op_math.Sum: Hamiltonian for the heisenberg model.
**Example**
>>> n_cells = [2,2]
>>> j = np.array([0.5, 0.5, 0.5])
>>> spin_ham = qml.spin.heisenberg("square", n_cells, coupling=j)
>>> spin_ham
(
0.5 * (X(0) @ X(1))
+ 0.5 * (Y(0) @ Y(1))
+ 0.5 * (Z(0) @ Z(1))
+ 0.5 * (X(0) @ X(2))
+ 0.5 * (Y(0) @ Y(2))
+ 0.5 * (Z(0) @ Z(2))
+ 0.5 * (X(1) @ X(3))
+ 0.5 * (Y(1) @ Y(3))
+ 0.5 * (Z(1) @ Z(3))
+ 0.5 * (X(2) @ X(3))
+ 0.5 * (Y(2) @ Y(3))
+ 0.5 * (Z(2) @ Z(3))
)
"""
lattice = generate_lattice(lattice, n_cells, boundary_condition, neighbour_order)
if coupling is None:
coupling = [[1.0, 1.0, 1.0]]
coupling = math.asarray(coupling)
if coupling.ndim == 1:
coupling = math.asarray([coupling])
if coupling.shape not in [(neighbour_order, 3), (3, lattice.n_sites, lattice.n_sites)]:
raise ValueError(
f"The coupling parameter shape should be equal to ({neighbour_order},3) or (3,{lattice.n_sites},{lattice.n_sites})"
)
hamiltonian = 0.0 * qml.I(0)
if coupling.shape == (neighbour_order, 3):
for edge in lattice.edges:
i, j, order = edge
hamiltonian += (
coupling[order][0] * (X(i) @ X(j))
+ coupling[order][1] * (Y(i) @ Y(j))
+ coupling[order][2] * (Z(i) @ Z(j))
)
else:
for edge in lattice.edges:
i, j = edge[0:2]
hamiltonian += (
coupling[0][i][j] * X(i) @ X(j)
+ coupling[1][i][j] * Y(i) @ Y(j)
+ coupling[2][i][j] * Z(i) @ Z(j)
)
return hamiltonian.simplify()
[docs]def fermi_hubbard(
lattice,
n_cells,
hopping=1.0,
coulomb=1.0,
boundary_condition=False,
neighbour_order=1,
mapping="jordan_wigner",
):
r"""Generates the Hamiltonian for the Fermi-Hubbard model on a lattice.
The Hamiltonian is represented as:
.. math::
\hat{H} = -t\sum_{<i,j>, \sigma} c_{i\sigma}^{\dagger}c_{j\sigma} + U\sum_{i}n_{i \uparrow} n_{i\downarrow}
where :math:`t` is the hopping term representing the kinetic energy of electrons, :math:`U` is the
on-site Coulomb interaction representing the repulsion between electrons, :math:`<i,j>` represents the
indices of neighbouring spins, :math:`\sigma` is the spin degree of freedom, and
:math:`n_{i \uparrow}, n_{i \downarrow}` are number operators for spin-up and spin-down fermions
at site :math:`i`. This function assumes two fermions with opposite spins on each lattice
site.
Args:
lattice (str): Shape of the lattice. Input values can be ``'chain'``, ``'square'``,
``'rectangle'``, ``'triangle'``, ``'honeycomb'``, ``'kagome'``, ``'lieb'``,
``'cubic'``, ``'bcc'``, ``'fcc'`` or ``'diamond'``.
n_cells (List[int]): Number of cells in each direction of the grid.
hopping (float or tensor_like[float]): Hopping strength between
neighbouring sites. It can be a number, an array of length equal to ``neighbour_order`` or
a square matrix of shape ``(num_spins, num_spins)``, where ``num_spins`` is the total
number of spins. Default value is 1.0.
coulomb (float or tensor_like[float]): Coulomb interaction between spins. It can be a constant or an
array of length equal to the number of spins.
boundary_condition (bool or list[bool]): Specifies whether or not to enforce periodic
boundary conditions for the different lattice axes. Default is ``False`` indicating
open boundary condition.
neighbour_order (int): Specifies the interaction level for neighbors within the lattice.
Default is 1, indicating nearest neighbours.
mapping (str): Specifies the fermion-to-qubit mapping. Input values can be
``'jordan_wigner'``, ``'parity'`` or ``'bravyi_kitaev'``.
Returns:
~ops.op_math.Sum: Hamiltonian for the Fermi-Hubbard model.
**Example**
>>> n_cells = [2]
>>> t = 0.5
>>> u = 1.0
>>> spin_ham = qml.spin.fermi_hubbard("chain", n_cells, hopping=t, coulomb=u)
>>> spin_ham
(
-0.25 * (Y(0) @ Z(1) @ Y(2))
+ -0.25 * (X(0) @ Z(1) @ X(2))
+ 0.5 * I(0)
+ -0.25 * (Y(1) @ Z(2) @ Y(3))
+ -0.25 * (X(1) @ Z(2) @ X(3))
+ -0.25 * Z(1)
+ -0.25 * Z(0)
+ 0.25 * (Z(0) @ Z(1))
+ -0.25 * Z(3)
+ -0.25 * Z(2)
+ 0.25 * (Z(2) @ Z(3))
)
"""
lattice = generate_lattice(lattice, n_cells, boundary_condition, neighbour_order)
if isinstance(hopping, (int, float, complex)):
hopping = [hopping]
hopping = math.asarray(hopping)
if hopping.shape not in [(neighbour_order,), (lattice.n_sites, lattice.n_sites)]:
raise ValueError(
f"The hopping parameter should be a number or an array of shape ({neighbour_order},) or ({lattice.n_sites},{lattice.n_sites})"
)
spin = 2
hopping_ham = 0.0 * FermiWord({})
if hopping.shape == (neighbour_order,):
for edge in lattice.edges:
for s in range(spin):
i, j, order = edge
s1 = i * spin + s
s2 = j * spin + s
hopping_term = -hopping[order] * (
FermiWord({(0, s1): "+", (1, s2): "-"})
+ FermiWord({(0, s2): "+", (1, s1): "-"})
)
hopping_ham += hopping_term
else:
for edge in lattice.edges:
for s in range(spin):
i, j = edge[0:2]
s1 = i * spin + s
s2 = j * spin + s
hopping_term = -hopping[i][j] * (
FermiWord({(0, s1): "+", (1, s2): "-"})
+ FermiWord({(0, s2): "+", (1, s1): "-"})
)
hopping_ham += hopping_term
int_term = 0.0 * FermiWord({})
if isinstance(coulomb, (int, float, complex)):
coulomb = math.ones(lattice.n_sites) * coulomb
for i in range(lattice.n_sites):
up_spin = i * spin
down_spin = i * spin + 1
int_term += coulomb[i] * FermiWord(
{(0, up_spin): "+", (1, up_spin): "-", (2, down_spin): "+", (3, down_spin): "-"}
)
hamiltonian = hopping_ham + int_term
if mapping not in ["jordan_wigner", "parity", "bravyi_kitaev"]:
raise ValueError(
f"The '{mapping}' transformation is not available."
f"Please set mapping to 'jordan_wigner', 'parity', or 'bravyi_kitaev'"
)
qubit_ham = qml.qchem.qubit_observable(hamiltonian, mapping=mapping)
return qubit_ham.simplify()
[docs]def emery(
lattice,
n_cells,
hopping=1.0,
coulomb=1.0,
intersite_coupling=1.0,
boundary_condition=False,
neighbour_order=1,
mapping="jordan_wigner",
):
r"""Generates the Hamiltonian for the Emery model on a lattice.
The `Hamiltonian <https://arxiv.org/pdf/2309.11786>`_ for the `Emery model <https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.58.2794>`_ is represented as:
.. math::
\begin{align*}
\hat{H} & = - t \sum_{\langle i,j \rangle, \sigma} c_{i\sigma}^{\dagger}c_{j\sigma}
+ U \sum_{i} n_{i \uparrow} n_{i\downarrow} + V \sum_{<i,j>} (n_{i \uparrow} +
n_{i \downarrow})(n_{j \uparrow} + n_{j \downarrow})\ ,
\end{align*}
where :math:`t` is the hopping term representing the kinetic energy of electrons,
:math:`U` is the on-site Coulomb interaction representing the repulsion between electrons,
:math:`V` is the intersite coupling, :math:`<i,j>` represents the indices for neighbouring sites,
:math:`\sigma` is the spin degree of freedom, and :math:`n_{k \uparrow}`, :math:`n_{k \downarrow}`
are number operators for spin-up and spin-down fermions at site :math:`k`.
This function assumes two fermions with opposite spins on each lattice site.
Args:
lattice (str): Shape of the lattice. Input values can be ``'chain'``, ``'square'``,
``'rectangle'``, ``'triangle'``, ``'honeycomb'``, ``'kagome'``, ``'lieb'``,
``'cubic'``, ``'bcc'``, ``'fcc'`` or ``'diamond'``.
n_cells (list[int]): Number of cells in each direction of the grid.
hopping (float or tensor_like[float]): Hopping strength between
neighbouring sites. It can be a number, an array of length equal to ``neighbour_order`` or
a square matrix of shape ``(n_sites, n_sites)``, where ``n_sites`` is the total
number of sites. Default value is 1.0.
coulomb (float or tensor_like[float]): Coulomb interaction between spins. It can be a number or an
array of length equal to the number of spins. Default value is 1.0.
intersite_coupling (float or tensor_like[float]): Interaction strength between spins on
neighbouring sites. It can be a number, an array with length equal to ``neighbour_order`` or
a square matrix of size ``(n_sites, n_sites)``, where ``n_sites`` is the total
number of sites. Default value is 1.0.
boundary_condition (bool or list[bool]): Specifies whether or not to enforce periodic
boundary conditions for the different lattice axes. Default is ``False`` indicating
open boundary condition.
neighbour_order (int): Specifies the interaction level for neighbors within the lattice.
Default is 1, indicating nearest neighbours.
mapping (str): Specifies the fermion-to-qubit mapping. Input values can be
``'jordan_wigner'``, ``'parity'`` or ``'bravyi_kitaev'``.
Raises:
ValueError:
If ``hopping``, ``coulomb``, or ``intersite_coupling`` doesn't have correct dimensions,
or if ``mapping`` is not available.
Returns:
~ops.op_math.Sum: Hamiltonian for the Emery model.
**Example**
>>> n_cells = [2]
>>> h = 0.5
>>> u = 1.0
>>> v = 0.2
>>> spin_ham = qml.spin.emery("chain", n_cells, hopping=h, coulomb=u, intersite_coupling=v)
>>> spin_ham
(
-0.25 * (Y(0) @ Z(1) @ Y(2))
+ -0.25 * (X(0) @ Z(1) @ X(2))
+ 0.7000000000000002 * I(0)
+ -0.25 * (Y(1) @ Z(2) @ Y(3))
+ -0.25 * (X(1) @ Z(2) @ X(3))
+ -0.35 * Z(1)
+ -0.35 * Z(0)
+ 0.25 * (Z(0) @ Z(1))
+ -0.35 * Z(3)
+ -0.35 * Z(2)
+ 0.25 * (Z(2) @ Z(3))
+ 0.05 * (Z(0) @ Z(2))
+ 0.05 * (Z(0) @ Z(3))
+ 0.05 * (Z(1) @ Z(2))
+ 0.05 * (Z(1) @ Z(3))
)
"""
lattice = generate_lattice(lattice, n_cells, boundary_condition, neighbour_order)
hopping = (
math.asarray([hopping])
if isinstance(hopping, (int, float, complex))
else math.asarray(hopping)
)
intersite_coupling = (
math.asarray([intersite_coupling])
if isinstance(intersite_coupling, (int, float, complex))
else math.asarray(intersite_coupling)
)
if hopping.shape not in [(neighbour_order,), (lattice.n_sites, lattice.n_sites)]:
raise ValueError(
f"The hopping parameter should be a number or an "
f"array of shape ({neighbour_order},) or ({lattice.n_sites},{lattice.n_sites})."
)
if intersite_coupling.shape not in [(neighbour_order,), (lattice.n_sites, lattice.n_sites)]:
raise ValueError(
f"The intersite_coupling parameter should be a number or "
f"an array of shape ({neighbour_order},) or ({lattice.n_sites},{lattice.n_sites})."
)
spin = 2
hopping_term = 0.0 * FermiWord({})
intersite_term = 0.0 * FermiWord({})
for i, j, order in lattice.edges:
hop = hopping[order] if hopping.shape == (neighbour_order,) else hopping[i][j]
for s in range(spin):
s1 = i * spin + s
s2 = j * spin + s
hopping_term -= hop * (
FermiWord({(0, s1): "+", (1, s2): "-"}) + FermiWord({(0, s2): "+", (1, s1): "-"})
)
intersite = (
intersite_coupling[order]
if intersite_coupling.shape == (neighbour_order,)
else intersite_coupling[i][j]
)
intersite_term += (
intersite
* (
FermiWord({(0, i * spin): "+", (1, i * spin): "-"})
+ FermiWord({(0, i * spin + 1): "+", (1, i * spin + 1): "-"})
)
* (
FermiWord({(0, j * spin): "+", (1, j * spin): "-"})
+ FermiWord({(0, j * spin + 1): "+", (1, j * spin + 1): "-"})
)
)
if isinstance(coulomb, (int, float, complex)):
coulomb = math.ones(lattice.n_sites) * coulomb
coulomb_term = 0.0 * FermiWord({})
for i in range(lattice.n_sites):
up_spin = i * spin
down_spin = i * spin + 1
coulomb_term += coulomb[i] * FermiWord(
{(0, up_spin): "+", (1, up_spin): "-", (2, down_spin): "+", (3, down_spin): "-"}
)
hamiltonian = hopping_term + coulomb_term + intersite_term
if mapping not in ["jordan_wigner", "parity", "bravyi_kitaev"]:
raise ValueError(
f"The '{mapping}' transformation is not available."
f"Please set mapping to 'jordan_wigner', 'parity', or 'bravyi_kitaev'."
)
qubit_ham = qml.qchem.qubit_observable(hamiltonian, mapping=mapping)
return qubit_ham.simplify()
[docs]def haldane(
lattice,
n_cells,
hopping=1.0,
hopping_next=1.0,
phi=1.0,
boundary_condition=False,
mapping="jordan_wigner",
):
r"""Generates the Hamiltonian for the Haldane model on a lattice.
The `Hamiltonian <https://arxiv.org/pdf/2211.13615>`_ for the
`Haldane model <https://journals.aps.org/prl/pdf/10.1103/PhysRevLett.61.2015>`_
is represented as:
.. math::
\begin{align*}
\hat{H} & = - t^{1} \sum_{\langle i,j \rangle, \sigma}
c_{i\sigma}^\dagger c_{j\sigma}
- t^{2} \sum_{\langle\langle i,j \rangle\rangle, \sigma}
\left( e^{i\phi} c_{i\sigma}^\dagger c_{j\sigma} + e^{-i\phi} c_{j\sigma}^\dagger c_{i\sigma} \right)
\end{align*}
where :math:`t^{1}` is the hopping amplitude between neighbouring
sites :math:`\langle i,j \rangle`, :math:`t^{2}` is the hopping amplitude between
next-nearest neighbour sites :math:`\langle \langle i,j \rangle \rangle`, :math:`\phi` is
the phase factor that breaks time-reversal symmetry in the system, and :math:`\sigma` is the
spin degree of freedom. This function assumes two fermions with opposite spins on each lattice
site.
Args:
lattice (str): Shape of the lattice. Input values can be ``'chain'``, ``'square'``,
``'rectangle'``, ``'triangle'``, ``'honeycomb'``, ``'kagome'``, ``'lieb'``,
``'cubic'``, ``'bcc'``, ``'fcc'`` or ``'diamond'``.
n_cells (list[int]): Number of cells in each direction of the grid.
hopping (float or tensor_like[float]): Hopping strength between
nearest neighbouring sites. It can be a number, or
a square matrix of size ``(n_sites, n_sites)``, where ``n_sites`` is the total
number of sites. Default value is 1.0.
hopping_next (float or tensor_like[float]): Hopping strength between next-nearest
neighbouring sites. It can be a number, or
a square matrix of size ``(n_sites, n_sites)``, where ``n_sites`` is the total
number of sites. Default value is 1.0.
phi (float or tensor_like[float]): Phase factor in the system. It can be a number, or
a square matrix of size ``(n_sites, n_sites)``, where ``n_sites`` is the total
number of sites. Default value is 1.0.
boundary_condition (bool or list[bool]): Specifies whether or not to enforce periodic
boundary conditions for the different lattice axes. Default is ``False`` indicating
open boundary condition.
mapping (str): Specifies the fermion-to-qubit mapping. Input values can be
``'jordan_wigner'``, ``'parity'`` or ``'bravyi_kitaev'``.
Raises:
ValueError:
If ``hopping``, ``hopping_next``, or ``phi`` doesn't have correct dimensions,
or if ``mapping`` is not available.
Returns:
~ops.op_math.Sum: Hamiltonian for the Haldane model.
**Example**
>>> n_cells = [2]
>>> h1 = 0.5
>>> h2 = 1.0
>>> phi = 0.1
>>> spin_ham = qml.spin.haldane("chain", n_cells, hopping=h1, hopping_next=h2, phi=phi)
>>> spin_ham
(
-0.25 * (Y(0) @ Z(1) @ Y(2))
+ -0.25 * (X(0) @ Z(1) @ X(2))
+ -0.25 * (Y(1) @ Z(2) @ Y(3))
+ -0.25 * (X(1) @ Z(2) @ X(3))
)
"""
lattice = generate_lattice(lattice, n_cells, boundary_condition, neighbour_order=2)
hopping = (
math.asarray([hopping])
if isinstance(hopping, (int, float, complex))
else math.asarray(hopping)
)
hopping_next = (
math.asarray([hopping_next])
if isinstance(hopping_next, (int, float, complex))
else math.asarray(hopping_next)
)
phi = math.asarray([phi]) if isinstance(phi, (int, float, complex)) else math.asarray(phi)
if hopping.shape not in [(1,), (lattice.n_sites, lattice.n_sites)]:
raise ValueError(
f"The hopping parameter should be a constant or an array of shape ({lattice.n_sites},{lattice.n_sites})."
)
if hopping_next.shape not in [(1,), (lattice.n_sites, lattice.n_sites)]:
raise ValueError(
f"The hopping_next parameter should be a constant or an array of shape ({lattice.n_sites},{lattice.n_sites})."
)
if phi.shape not in [(1,), (lattice.n_sites, lattice.n_sites)]:
raise ValueError(
f"The phi parameter should be a constant or an array of shape ({lattice.n_sites},{lattice.n_sites})."
)
spin = 2
hamiltonian = 0.0 * FermiWord({})
for i, j, order in lattice.edges:
hop1 = hopping[0] if hopping.shape == (1,) else hopping[i][j]
hop2 = hopping_next[0] if hopping_next.shape == (1,) else hopping_next[i][j]
phi_term = phi[0] if phi.shape == (1,) else phi[i][j]
for s in range(spin):
s1 = i * spin + s
s2 = j * spin + s
if order == 0:
hamiltonian -= hop1 * (
FermiWord({(0, s1): "+", (1, s2): "-"})
+ FermiWord({(0, s2): "+", (1, s1): "-"})
)
else:
hamiltonian -= hop2 * (
math.exp(1j * phi_term) * FermiWord({(0, s1): "+", (1, s2): "-"})
)
hamiltonian -= hop2 * (
math.exp(-1j * phi_term) * FermiWord({(0, s2): "+", (1, s1): "-"})
)
if mapping not in ["jordan_wigner", "parity", "bravyi_kitaev"]:
raise ValueError(
f"The '{mapping}' transformation is not available."
f"Please set mapping to 'jordan_wigner', 'parity', or 'bravyi_kitaev'."
)
qubit_ham = qml.qchem.qubit_observable(hamiltonian, mapping=mapping)
return qubit_ham.simplify()
[docs]def kitaev(n_cells, coupling=None, boundary_condition=False):
r"""Generates the Hamiltonian for the Kitaev model on the honeycomb lattice.
The `Kitaev <https://arxiv.org/abs/cond-mat/0506438>`_ model Hamiltonian is represented as:
.. math::
\begin{align*}
\hat{H} = K_X \sum_{\langle i,j \rangle \in X}\sigma_i^x\sigma_j^x +
\:\: K_Y \sum_{\langle i,j \rangle \in Y}\sigma_i^y\sigma_j^y +
\:\: K_Z \sum_{\langle i,j \rangle \in Z}\sigma_i^z\sigma_j^z
\end{align*}
where :math:`\sigma` is a Pauli operator and :math:`<i,j>` represents the indices for
neighbouring spins. The parameters :math:`K_X`, :math:`K_Y`, :math:`K_Z` are the coupling
constants defined for the Hamiltonian, where :math:`X`, :math:`Y`, :math:`Z` represent the set
of edges in the Honeycomb lattice between spins :math:`i` and :math:`j` with real-space bond
directions :math:`[0, 1], [\frac{\sqrt{3}}{2}, \frac{1}{2}], [\frac{\sqrt{3}}{2}, -\frac{1}{2}]`,
respectively.
Args:
n_cells (list[int]): Number of cells in each direction of the grid.
coupling (tensor_like[float]): Coupling between spins. It can be an array of length 3 defining
:math:`K_X`, :math:`K_Y`, :math:`K_Z` coupling constants. Default value is 1.0 for each
coupling constant.
boundary_condition (bool or list[bool]): Specifies whether or not to enforce periodic
boundary conditions for the different lattice axes. Default is ``False`` indicating
open boundary condition.
Raises:
ValueError: if ``coupling`` doesn't have correct dimensions.
Returns:
~ops.op_math.Sum: Hamiltonian for the Kitaev model.
**Example**
>>> n_cells = [2, 2]
>>> k = np.array([0.5, 0.6, 0.7])
>>> spin_ham = qml.spin.kitaev(n_cells, coupling=k)
>>> spin_ham
(
0.5 * (X(0) @ X(1))
+ 0.5 * (X(2) @ X(3))
+ 0.5 * (X(4) @ X(5))
+ 0.5 * (X(6) @ X(7))
+ 0.6 * (Y(1) @ Y(2))
+ 0.6 * (Y(5) @ Y(6))
+ 0.7 * (Z(1) @ Z(4))
+ 0.7 * (Z(3) @ Z(6))
)
"""
if coupling is None:
coupling = [1.0, 1.0, 1.0]
if len(coupling) != 3:
raise ValueError("The coupling parameter should be a list of length 3.")
vectors = [[1, 0], [0.5, 0.75**0.5]]
positions = [[0, 0], [0.5, 0.5 / 3**0.5]]
custom_edges = [
[(0, 1), ("XX", coupling[0])],
[(1, 2), ("YY", coupling[1])],
[(1, n_cells[1] * 2), ("ZZ", coupling[2])],
]
lattice = Lattice(
n_cells=n_cells[0:2],
vectors=vectors,
positions=positions,
boundary_condition=boundary_condition,
custom_edges=custom_edges,
)
opmap = {"X": X, "Y": Y, "Z": Z}
hamiltonian = 0.0 * qml.I(0)
for edge in lattice.edges:
v1, v2 = edge[0:2]
op1, op2 = edge[2][0]
coeff = edge[2][1]
hamiltonian += coeff * (opmap[op1](v1) @ opmap[op2](v2))
return hamiltonian.simplify()
[docs]def spin_hamiltonian(lattice):
r"""Generates a spin Hamiltonian for a custom :class:`~pennylane.spin.Lattice` object.
Args:
lattice (Lattice): custom lattice defined with ``custom_edges``
Raises:
ValueError: if the provided Lattice does not have ``custom_edges`` defined with operators
Returns:
~ops.op_math.Sum: Hamiltonian for the lattice
**Example**
.. code-block:: python
>>> lattice = qml.spin.Lattice(
... n_cells=[2, 2],
... vectors=[[1, 0], [0, 1]],
... positions=[[0, 0], [1, 5]],
... boundary_condition=False,
... custom_edges=[[(0, 1), ("XX", 0.5)], [(1, 2), ("YY", 0.6)], [(1, 4), ("ZZ", 0.7)]],
... custom_nodes=[[0, ("X", 0.5)], [1, ("Y", 0.3)]],
... )
>>> qml.spin.spin_hamiltonian(lattice=lattice)
(
0.5 * (X(0) @ X(1))
+ 0.5 * (X(2) @ X(3))
+ 0.5 * (X(4) @ X(5))
+ 0.5 * (X(6) @ X(7))
+ 0.6 * (Y(1) @ Y(2))
+ 0.6 * (Y(5) @ Y(6))
+ 0.7 * (Z(1) @ Z(4))
+ 0.7 * (Z(3) @ Z(6))
+ 0.5 * X(0)
+ 0.3 * Y(1)
)
"""
if not isinstance(lattice.edges[0][2], tuple):
raise ValueError(
"Custom edges need to be defined and should have an operator defined as a `str`"
)
opmap = {"I": I, "X": X, "Y": Y, "Z": Z}
hamiltonian = 0.0 * qml.I(0)
for edge in lattice.edges:
v1, v2 = edge[0:2]
op1, op2 = edge[2][0]
coeff = edge[2][1]
hamiltonian += coeff * (opmap[op1](v1) @ opmap[op2](v2))
if lattice.nodes is not None:
for node in lattice.nodes:
n = node[0]
op = node[1][0]
coeff = node[1][1]
hamiltonian += coeff * (opmap[op](n))
return hamiltonian.simplify()
_modules/pennylane/spin/spin_hamiltonian
Download Python script
Download Notebook
View on GitHub