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.
"""
from pennylane import math, qchem
from pennylane.fermi import FermiWord
from pennylane.ops import X, Y, Z
from pennylane.ops.identity import I
from .lattice import Lattice, generate_lattice
# pylint: disable=too-many-arguments
[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 * 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 * 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
mapping = mapping.strip().lower()
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 = 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
mapping = mapping.strip().lower()
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 = 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): "-"})
)
mapping = mapping.strip().lower()
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 = 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 * 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 * 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