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 X, Y, Z, math
from pennylane.fermi import FermiWord
from .lattice import _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 ``J`` is the coupling parameter defined for the Hamiltonian, ``h`` is the strength of the
transverse magnetic field and ``i,j`` represent the indices for neighbouring spins.
Args:
lattice (str): Shape of the lattice. Input values can be ``'chain'``, ``'square'``,
``'rectangle'``, ``'honeycomb'``, ``'triangle'``, or ``'kagome'``.
n_cells (List[int]): Number of cells in each direction of the grid.
coupling (float or List[float] or List[math.array[float]]): Coupling between spins. It can
be a number, a list 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]): Defines boundary conditions for 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 ``J`` is the coupling constant defined for the Hamiltonian, and ``i,j`` represent the
indices for neighbouring spins.
Args:
lattice (str): Shape of the lattice. Input values can be ``'chain'``, ``'square'``,
``'rectangle'``, ``'honeycomb'``, ``'triangle'``, or ``'kagome'``.
n_cells (List[int]): Number of cells in each direction of the grid.
coupling (List[List[float]] or List[math.array[float]]): Coupling between spins. It can be a
2D array of shape ``(neighbour_order, 3)`` or a 3D array of shape
``(3, num_spins, num_spins)``, where ``num_spins`` is the total number of spins.
boundary_condition (bool or list[bool]): Defines boundary conditions for 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 = [[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 ``t`` is the hopping term representing the kinetic energy of electrons, ``U`` is the
on-site Coulomb interaction, representing the repulsion between electrons, ``i,j`` represent the
indices for 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 ``i``. This function assumes there are two fermions with opposite spins on each lattice
site.
Args:
lattice (str): Shape of the lattice. Input values can be ``'chain'``, ``'square'``,
``'rectangle'``, ``'honeycomb'``, ``'triangle'``, or ``'kagome'``.
n_cells (List[int]): Number of cells in each direction of the grid.
hopping (float or List[float] or List[math.array(float)]): Hopping strength between
neighbouring sites, it can be a number, a list of length equal to ``neighbour_order`` or
a square matrix of size ``(num_spins, num_spins)``, where ``num_spins`` is the total
number of spins. Default value is 1.0.
coulomb (float or List[float]): Coulomb interaction between spins. It can be a constant or a
list of length equal to number of spins.
boundary_condition (bool or list[bool]): Defines boundary conditions for 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]
>>> h = [0.5]
>>> u = 1.0
>>> spin_ham = qml.spin.fermi_hubbard("chain", n_cells, hopping=h, 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()
_modules/pennylane/spin/spin_hamiltonian
Download Python script
Download Notebook
View on GitHub