"""Next-Nearest-neighbour spin-S models.
Uniform lattice of spin-S sites, coupled by next-nearest-neighbour interactions.
We have two variants implementing the same hamiltonian.
The :class:`SpinChainNNN` uses the
:class:`~tenpy.networks.site.GroupedSite` to keep it a
:class:`~tenpy.models.model.NearestNeighborModel` suitable for TEBD,
while the :class:`SpinChainNNN2` just involves longer-range couplings in the MPO.
The latter is preferable for pure DMRG calculations and avoids having to add each of the short
range couplings twice for the grouped sites.
Note that you can also get a :class:`~tenpy.models.model.NearestNeighborModel` for TEBD from the
latter by using :meth:`~tenpy.models.model.MPOModel.group_sites` and
:meth:`~tenpy.models.model.NearestNeighbormodel.from_MPOModel`.
An example for such a case is given in the file ``examples/c_tebd.py``.
"""
# Copyright 2018-2019 TeNPy Developers, GNU GPLv3
import numpy as np
from .lattice import Chain
from ..networks.site import SpinSite, GroupedSite
from .model import CouplingMPOModel, NearestNeighborModel
from ..tools.params import get_parameter, unused_parameters
from ..tools.misc import any_nonzero
__all__ = ['SpinChainNNN', 'SpinChainNNN2']
[docs]class SpinChainNNN(CouplingMPOModel, NearestNeighborModel):
r"""Spin-S sites coupled by (next-)nearest neighbour interactions on a `GroupedSite`.
The Hamiltonian reads:
.. math ::
H = \sum_{\langle i,j \rangle, i < j}
\mathtt{Jx} S^x_i S^x_j + \mathtt{Jy} S^y_i S^y_j + \mathtt{Jz} S^z_i S^z_j \\
+ \sum_{\langle \langle i,j \rangle \rangle, i< j}
\mathtt{Jxp} S^x_i S^x_j + \mathtt{Jyp} S^y_i S^y_j + \mathtt{Jzp} S^z_i S^z_j \\
- \sum_i
\mathtt{hx} S^x_i + \mathtt{hy} S^y_i + \mathtt{hz} S^z_i
Here, :math:`\langle i,j \rangle, i< j` denotes nearest neighbors and
:math:`\langle \langle i,j \rangle \rangle, i < j` denotes next nearest neighbors.
All parameters are collected in a single dictionary `model_params` and read out with
:func:`~tenpy.tools.params.get_parameter`.
Parameters
----------
L : int
Length of the chain in terms of :class:`~tenpy.networks.site.GroupedSite`,
i.e. we have ``2*L`` spin sites.
S : {0.5, 1, 1.5, 2, ...}
The 2S+1 local states range from m = -S, -S+1, ... +S.
conserve : 'best' | 'Sz' | 'parity' | None
What should be conserved. See :class:`~tenpy.networks.Site.SpinSite`.
Jx, Jy, Jz, Jxp, Jyp, Jzp, hx, hy, hz : float | array
Couplings as defined for the Hamiltonian above.
bc_MPS : {'finite' | 'infinte'}
MPS boundary conditions. Coupling boundary conditions are chosen appropriately.
"""
def __init__(self, model_params):
model_params.setdefault('lattice', "Chain")
CouplingMPOModel.__init__(self, model_params)
[docs] def init_sites(self, model_params):
S = get_parameter(model_params, 'S', 0.5, self.name)
conserve = get_parameter(model_params, 'conserve', 'best', self.name)
if conserve == 'best':
# check how much we can conserve
if not any_nonzero(model_params, [('Jx', 'Jy'), ('Jxp', 'Jyp'), 'hx', 'hy'],
"check Sz conservation"):
conserve = 'Sz'
elif not any_nonzero(model_params, ['hx', 'hy'], "check parity conservation"):
conserve = 'parity'
else:
conserve = None
if self.verbose >= 1.:
print(self.name + ": set conserve to", conserve)
spinsite = SpinSite(S, conserve)
site = GroupedSite([spinsite, spinsite], charges='same')
return site
[docs] def init_terms(self, model_params):
Jx = get_parameter(model_params, 'Jx', 1., self.name, True)
Jy = get_parameter(model_params, 'Jy', 1., self.name, True)
Jz = get_parameter(model_params, 'Jz', 1., self.name, True)
Jxp = get_parameter(model_params, 'Jxp', 1., self.name, True)
Jyp = get_parameter(model_params, 'Jyp', 1., self.name, True)
Jzp = get_parameter(model_params, 'Jzp', 1., self.name, True)
hx = get_parameter(model_params, 'hx', 0., self.name, True)
hy = get_parameter(model_params, 'hy', 0., self.name, True)
hz = get_parameter(model_params, 'hz', 0., self.name, True)
# Only valid for self.lat being a Chain...
self.add_onsite(-hx, 0, 'Sx0')
self.add_onsite(-hy, 0, 'Sy0')
self.add_onsite(-hz, 0, 'Sz0')
self.add_onsite(-hx, 0, 'Sx1')
self.add_onsite(-hy, 0, 'Sy1')
self.add_onsite(-hz, 0, 'Sz1')
# Sp = Sx + i Sy, Sm = Sx - i Sy, Sx = (Sp+Sm)/2, Sy = (Sp-Sm)/2i
# Sx.Sx = 0.25 ( Sp.Sm + Sm.Sp + Sp.Sp + Sm.Sm )
# Sy.Sy = 0.25 ( Sp.Sm + Sm.Sp - Sp.Sp - Sm.Sm )
# nearest neighbors
self.add_onsite((Jx + Jy) / 4., 0, 'Sp0 Sm1')
self.add_onsite(np.conj((Jx + Jy) / 4.), 0, 'Sp1 Sm0') # h.c.
self.add_onsite((Jx - Jy) / 4., 0, 'Sp0 Sp1')
self.add_onsite(np.conj((Jx - Jy) / 4.), 0, 'Sm1 Sm0') # h.c.
self.add_onsite(Jz, 0, 'Sz0 Sz1')
self.add_coupling((Jx + Jy) / 4., 0, 'Sp1', 0, 'Sm0', 1)
self.add_coupling(np.conj((Jx + Jy) / 4.), 0, 'Sp0', 0, 'Sm1', -1) # h.c.
self.add_coupling((Jx - Jy) / 4., 0, 'Sp1', 0, 'Sp0', 1)
self.add_coupling(np.conj((Jx - Jy) / 4.), 0, 'Sp0', 0, 'Sm1', -1) # h.c.
self.add_coupling(Jz, 0, 'Sz1', 0, 'Sz0', 1)
# next nearest neighbors
self.add_coupling((Jxp + Jyp) / 4., 0, 'Sp0', 0, 'Sm0', 1)
self.add_coupling(np.conj((Jxp + Jyp) / 4.), 0, 'Sp0', 0, 'Sm0', -1) # h.c.
self.add_coupling((Jxp - Jyp) / 4., 0, 'Sp0', 0, 'Sp0', 1)
self.add_coupling(np.conj((Jxp - Jyp) / 4.), 0, 'Sm0', 0, 'Sm0', -1) # h.c.
self.add_coupling(Jzp, 0, 'Sz0', 0, 'Sz0', 1)
self.add_coupling((Jxp + Jyp) / 4., 0, 'Sp1', 0, 'Sm1', 1)
self.add_coupling(np.conj((Jxp + Jyp) / 4.), 0, 'Sp1', 0, 'Sm1', -1) # h.c.
self.add_coupling((Jxp - Jyp) / 4., 0, 'Sp1', 0, 'Sp1', 1)
self.add_coupling(np.conj((Jxp - Jyp) / 4.), 0, 'Sm1', 0, 'Sm1', -1) # h.c.
self.add_coupling(Jzp, 0, 'Sz1', 0, 'Sz1', 1)
[docs]class SpinChainNNN2(CouplingMPOModel):
r"""Spin-S sites coupled by next-nearest neighbour interactions.
The Hamiltonian reads:
.. math ::
H = \sum_{\langle i,j \rangle, i < j}
\mathtt{Jx} S^x_i S^x_j + \mathtt{Jy} S^y_i S^y_j + \mathtt{Jz} S^z_i S^z_j \\
+ \sum_{\langle \langle i,j \rangle \rangle, i< j}
\mathtt{Jxp} S^x_i S^x_j + \mathtt{Jyp} S^y_i S^y_j + \mathtt{Jzp} S^z_i S^z_j \\
- \sum_i
\mathtt{hx} S^x_i + \mathtt{hy} S^y_i + \mathtt{hz} S^z_i
Here, :math:`\langle i,j \rangle, i< j` denotes nearest neighbors and
:math:`\langle \langle i,j \rangle \rangle, i < j` denotes next nearest neighbors.
All parameters are collected in a single dictionary `model_params` and read out with
:func:`~tenpy.tools.params.get_parameter`.
Parameters
----------
S : {0.5, 1, 1.5, 2, ...}
The 2S+1 local states range from m = -S, -S+1, ... +S.
conserve : 'best' | 'Sz' | 'parity' | None
What should be conserved. See :class:`~tenpy.networks.Site.SpinSite`.
For ``'best'``, we check the parameters what can be preserved.
Jx, Jy, Jz, Jxp, Jyp, Jzp, hx, hy, hz : float | array
Couplings as defined for the Hamiltonian above.
lattice : str | :class:`~tenpy.models.lattice.Lattice`
Instance of a lattice class for the underlaying geometry.
Alternatively a string being the name of one of the Lattices defined in
:mod:`~tenpy.models.lattice`, e.g. ``"Chain", "Square", "HoneyComb", ...``.
bc_MPS : {'finite' | 'infinte'}
MPS boundary conditions along the x-direction.
For 'infinite' boundary conditions, repeat the unit cell in x-direction.
Coupling boundary conditions in x-direction are chosen accordingly.
Only used if `lattice` is a string.
order : string
Ordering of the sites in the MPS, e.g. 'default', 'snake';
see :meth:`~tenpy.models.lattice.Lattice.ordering`.
Only used if `lattice` is a string.
L : int
Lenght of the lattice.
Only used if `lattice` is the name of a 1D Lattice.
Lx, Ly : int
Length of the lattice in x- and y-direction.
Only used if `lattice` is the name of a 2D Lattice.
bc_y : 'ladder' | 'cylinder'
Boundary conditions in y-direction.
Only used if `lattice` is the name of a 2D Lattice.
"""
def __init__(self, model_params):
CouplingMPOModel.__init__(self, model_params)
[docs] def init_sites(self, model_params):
S = get_parameter(model_params, 'S', 0.5, self.name)
conserve = get_parameter(model_params, 'conserve', 'best', self.name)
if conserve == 'best':
# check how much we can conserve
if not any_nonzero(model_params, [('Jx', 'Jy'), ('Jxp', 'Jyp'), 'hx', 'hy'],
"check Sz conservation"):
conserve = 'Sz'
elif not any_nonzero(model_params, ['hx', 'hy'], "check parity conservation"):
conserve = 'parity'
else:
conserve = None
if self.verbose >= 1.:
print(self.name + ": set conserve to", conserve)
site = SpinSite(S, conserve)
return site
[docs] def init_terms(self, model_params):
# 0) read out/set default parameters
Jx = get_parameter(model_params, 'Jx', 1., self.name, True)
Jy = get_parameter(model_params, 'Jy', 1., self.name, True)
Jz = get_parameter(model_params, 'Jz', 1., self.name, True)
Jxp = get_parameter(model_params, 'Jxp', 1., self.name, True)
Jyp = get_parameter(model_params, 'Jyp', 1., self.name, True)
Jzp = get_parameter(model_params, 'Jzp', 1., self.name, True)
hx = get_parameter(model_params, 'hx', 0., self.name, True)
hy = get_parameter(model_params, 'hy', 0., self.name, True)
hz = get_parameter(model_params, 'hz', 0., self.name, True)
for u in range(len(self.lat.unit_cell)):
self.add_onsite(-hx, u, 'Sx')
self.add_onsite(-hy, u, 'Sy')
self.add_onsite(-hz, u, 'Sz')
# Sp = Sx + i Sy, Sm = Sx - i Sy, Sx = (Sp+Sm)/2, Sy = (Sp-Sm)/2i
# Sx.Sx = 0.25 ( Sp.Sm + Sm.Sp + Sp.Sp + Sm.Sm )
# Sy.Sy = 0.25 ( Sp.Sm + Sm.Sp - Sp.Sp - Sm.Sm )
for u1, u2, dx in self.lat.pairs['nearest_neighbors']:
self.add_coupling((Jx + Jy) / 4., u1, 'Sp', u2, 'Sm', dx)
self.add_coupling(np.conj((Jx + Jy) / 4.), u2, 'Sp', u1, 'Sm', -dx) # h.c.
self.add_coupling((Jx - Jy) / 4., u1, 'Sp', u2, 'Sp', dx)
self.add_coupling(np.conj((Jx - Jy) / 4.), u2, 'Sm', u1, 'Sm', -dx) # h.c.
self.add_coupling(Jz, u1, 'Sz', u2, 'Sz', dx)
for u1, u2, dx in self.lat.pairs['next_nearest_neighbors']:
self.add_coupling((Jxp + Jyp) / 4., u1, 'Sp', u2, 'Sm', dx)
self.add_coupling(np.conj((Jxp + Jyp) / 4.), u2, 'Sp', u1, 'Sm', -dx) # h.c.
self.add_coupling((Jxp - Jyp) / 4., u1, 'Sp', u2, 'Sp', dx)
self.add_coupling(np.conj((Jxp - Jyp) / 4.), u2, 'Sm', u1, 'Sm', -dx) # h.c.
self.add_coupling(Jzp, u1, 'Sz', u2, 'Sz', dx)