# ================================== LICENSE ===================================
# Magnopy - Python package for magnons.
#
# Copyright (C) 2023 Magnopy Team
#
# e-mail: anry@uv.es, web: magnopy.org
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <https://www.gnu.org/licenses/>.
# ================================ END LICENSE =================================
from copy import deepcopy
from math import ceil
import numpy as np
from wulfric import add_sugar
from magnopy._spinham._legacy_code import (
_add_1,
_add_21,
_add_22,
_add_31,
_add_41,
_remove_1,
_remove_21,
_remove_22,
_remove_31,
_remove_41,
)
from magnopy._spinham._convention import Convention
from magnopy._data_validation import (
_validate_atom_index,
_validate_unit_cell_index,
_validated_units,
)
from magnopy._parameters._interaction_parameters import (
_InteractionParameters,
_InteractionParametersIterator,
_get_specs,
)
from magnopy._parameters._equivalent_sets import (
_get_equivalent,
_get_missing_parameters,
_set_distribution,
)
from magnopy._constants._units import _PARAMETER_UNITS, _PARAMETER_UNITS_MAKEUP
from magnopy._constants._si import BOHR_MAGNETON, ANGSTROM, VACUUM_MAGNETIC_PERMEABILITY
# Save local scope at this moment
old_dir = set(dir())
old_dir.add("old_dir")
[docs]
class SpinHamiltonian:
r"""
Spin Hamiltonian.
Implements the :ref:`user-guide_theory-behind_spin-hamiltonian`.
Parameters
----------
convention : :py:class:`.Convention` or str
A convention of the spin Hamiltonian. See :ref:`user-guide_usage_convention` for
more details.
cell : (3, 3) |array-like|_
Matrix of a cell, rows are interpreted as vectors. See
:ref:`user-guide_usage_cell` for more details.
atoms : dict
Dictionary with atoms. See :ref:`user-guide_usage_atoms` for more details.
units : str, default "meV"
.. versionadded:: 0.3.0
Units of the Hamiltonian's parameters. See :py:attr:`.SpinHamiltonian.units`
for more details. Case-insensitive.
Examples
--------
See :ref:`user-guide_usage_spin-hamiltonian` for examples.
"""
def __init__(self, cell, atoms, convention, units="meV") -> None:
self._cell = np.array(cell)
self._atoms = add_sugar(atoms)
# Only the magnetic sites
self._magnetic_atoms = None
self._map_to_magnetic = None
self._map_to_all = None
# To keep track of the external magnetic field
self._magnetic_field = np.zeros(3, dtype=float)
self._zeeman_parameters = _InteractionParameters()
self._convention = convention
if units.lower() not in _PARAMETER_UNITS:
raise ValueError(
f'Given units ("{units}") are not supported. Please use one of\n * '
+ "\n * ".join(list(_PARAMETER_UNITS))
)
self._units = units.lower()
self._parameters = _InteractionParameters()
############################################################################
# Cell and Atoms #
############################################################################
@property
def cell(self):
r"""
Cell of the crystal on which the Hamiltonian is build.
See :ref:`user-guide_usage_cell` for more details.
Returns
-------
cell : (3, 3) :numpy:`ndarray`
Matrix of the cell, rows are lattice vectors.
Notes
-----
If is not recommended to change the ``cell`` property after the creation of
:py:class:`.SpinHamiltonian`. In fact an attempt to do so will raise an
``AttributeError``:
.. doctest::
>>> import numpy as np
>>> import magnopy
>>> convention = magnopy.Convention()
>>> spinham = magnopy.SpinHamiltonian(
... cell=np.eye(3), atoms={}, convention=convention
... )
>>> spinham.cell = 2 * np.eye(3)
Traceback (most recent call last):
...
AttributeError: Change of the cell attribute is not allowed after the creation of SpinHamiltonian instance. SpinHamiltonian.cell is immutable.
"""
return self._cell
@cell.setter
def cell(self, new_value):
raise AttributeError(
"Change of the cell attribute is not allowed after the creation of SpinHamiltonian instance. SpinHamiltonian.cell is immutable."
)
@property
def atoms(self) -> dict:
r"""
Atoms of the crystal on which the Hamiltonian is build.
See :ref:`user-guide_usage_atoms` for more details.
Returns
-------
atoms : dict (with added sugar)
Dictionary with the atoms.
See Also
--------
M_prime
Notes
-----
If is not recommended to change the atoms property after the creation of
:py:class:`.SpinHamiltonian`. In fact an attempt to do so raises an
``AttributeError``:
.. doctest::
>>> import numpy as np
>>> import magnopy
>>> convention = magnopy.Convention()
>>> spinham = magnopy.SpinHamiltonian(
... cell=np.eye(3), atoms={}, convention=convention
... )
>>> spinham.atoms = {"names": ["Cr"]}
Traceback (most recent call last):
...
AttributeError: Change of the atoms dictionary is not allowed after the creation of SpinHamiltonian instance. SpinHamiltonian.atoms is immutable.
"""
return self._atoms
@atoms.setter
def atoms(self, new_value):
raise AttributeError(
"Change of the atoms dictionary is not allowed after the creation of SpinHamiltonian instance. SpinHamiltonian.atoms is immutable."
)
@property
def M_prime(self):
r"""
Amount of atoms in the unit cell.
.. versionadded:: 0.5.2
Both magnetic and non-magnetic atoms are counted.
Returns
-------
M_prime : int
Amount of atoms (magnetic and non-magnetic) in the unit cell.
See Also
--------
atoms
"""
return len(self.atoms.names)
def _reset_internals(self):
self._map_to_magnetic = None
self._map_to_all = None
self._magnetic_atoms = None
def _update_internals(self):
# Identify magnetic sites
indices = set()
for specs, _ in self._parameters._container:
for alpha in specs[3]:
indices.add(alpha)
indices = sorted(list(indices))
# Create index map from all to magnetic
self._map_to_magnetic = [None for _ in range(len(self.atoms.names))]
for i in range(len(indices)):
self._map_to_magnetic[indices[i]] = i
# Create index map from magnetic to all
self._map_to_all = indices
# Create magnetic atoms dictionary
self._magnetic_atoms = add_sugar({})
for key in self.atoms:
self._magnetic_atoms[key] = []
for full_index in indices:
self._magnetic_atoms[key].append(self.atoms[key][full_index])
@property
def map_to_magnetic(self):
r"""
Index map from all atoms to the magnetic ones.
Returns
-------
map_to_magnetic (M_prime, ) list of int
Index map. Integers. ``M_prime = len(spinham.atoms.names)``
See also
--------
map_to_all
Examples
--------
See :ref:`user-guide_usage_spin-hamiltonian_magnetic-vs-non-magnetic` for more
details on how to use this property.
"""
if self._map_to_magnetic is None:
self._update_internals()
return self._map_to_magnetic
@property
def map_to_all(self):
r"""
Index map from magnetic atoms to all atoms.
Returns
-------
map_to_all (M, ) list of int
Index map. Integers. ``M = len(spinham.magnetic_atoms.names)``
See also
--------
map_to_magnetic
Examples
--------
See :ref:`user-guide_usage_spin-hamiltonian_magnetic-vs-non-magnetic` for more
details on how to use this property.
"""
if self._map_to_all is None:
self._update_internals()
return self._map_to_all
@property
def magnetic_atoms(self):
r"""
Magnetic atoms of the spin Hamiltonian.
Magnetic atom is defined as an atom with at least one parameter associated with
it.
This property is dynamically computed at every call.
Returns
-------
magnetic_atoms : dict
Dictionary of magnetic atoms. Have the same structure as
:py:attr:`.SpinHamiltonian.atoms`.
See Also
--------
M
Examples
--------
See :ref:`user-guide_usage_spin-hamiltonian_magnetic-vs-non-magnetic` for more
details on how to use this property.
"""
if self._magnetic_atoms is None:
self._update_internals()
return self._magnetic_atoms
@property
def M(self):
r"""
Amount of magnetic atoms in the unit cell.
Returns
-------
M : int
Amount of magnetic atoms in the unit cell.
See Also
--------
magnetic_atoms
"""
return len(self.magnetic_atoms.names)
############################################################################
# Distribution in equal sets #
############################################################################
[docs]
def restore_missing_parameters(self, strategy="zeros") -> None:
r"""
Checks that all interactions from the equivalent sets are present in the
Hamiltonian and adds the missing ones (if any).
.. versionadded:: 0.5.2
See :ref:`user-guide_theory-behind_equivalent-parameters` for more details on sets
of equivalent parameters.
Parameters
----------
strategy : str, default "zeros"
When some parameters from the equivalent set are missing, this argument
defines the value of the added parameters. Case-insensitive. Supported options
are
* "zeros" (default)
Values of missing parameters are set to zeros. This option does not change
the physics of the Hamiltonian.
* "mean"
Values of the missing parameters are set to the mean value of the parameters
from the equivalent set. This value might change the physics of the
Hamiltonian (as it adds new non-zero parameters).
Raises
------
ValueError
If ``strategy`` is not one of the supported options.
Notes
-----
If ``spinham.convention.multiple_counting`` is False, then this function does
nothing as only one parameter per set is stored and no parameters can be missing.
"""
if not self.convention._multiple_counting:
return
missing_parameters = _get_missing_parameters(
parameters=self._parameters, strategy=strategy.lower()
)
if len(missing_parameters) > 0:
self._parameters = self._parameters + missing_parameters
[docs]
def symmetrize(self) -> None:
r"""
Symmetrize interaction parameters as specified in the SI note 3 of |paper-2026|_.
Legacy method, equivalent to
.. code-block:: python
spinham.set_distribution(strategy="symmetrized")
Please use :py:meth:`.SpinHamiltonian.set_distribution` instead.
"""
self.set_distribution(strategy="symmetrized")
[docs]
def set_distribution(self, strategy="symmetrize") -> None:
"""
Enforces one of the supported distributions of parameters within the sets of
equivalent parameters.
.. versionadded:: 0.5.2
See :ref:`user-guide_theory-behind_equivalent-parameters` for more details.
Parameters
----------
strategy : str, default "symmetrize"
Strategy for distributing overall value between parameters of the equivalent
set. Case-insensitive. Supported options are
* "symmetrize" (default)
All parameters from the set are equal to each other.
* "one-for-all"
The sum is assigned to the single representative parameter. All other
parameters from the set are set to zeros.
Raises
------
ValueError
If ``strategy`` is not one of the supported options.
ValueError
If ``spinham.convention.multiple_counting`` is False. See Notes below.
See Also
--------
restore_missing_parameters
Notes
-----
If ``spinham.convention.multiple_counting`` is False, then this function raises
a ``ValueError`` as the distribution of parameters is fixed and can not be
changed.
On contrary to :py:meth:`.SpinHamiltonian.restore_missing_parameters`, this
method never changes the physics of the Hamiltonian.
Examples
--------
See :ref:`user-guide_usage_spin-hamiltonian_symmetrization` for more details on
how to use this function.
"""
# Note: self._zeeman_parameters never change, as there is always one
# parameter per set
if not self.convention._multiple_counting:
raise ValueError(
"When spinham.convention.multiple_counting is False, the distribution of parameters is fixed and can not be changed."
)
new_parameters = _set_distribution(
parameters=self._parameters, strategy=strategy.lower()
)
self._parameters = new_parameters
############################################################################
# Convention #
############################################################################
@property
def convention(self) -> Convention:
r"""
Convention of the spin Hamiltonian.
Returns
-------
convention : :py:class:`.Convention`
See Also
--------
Convention
Examples
--------
See :ref:`user-guide_usage_spin-hamiltonian_convention` for more details on how to
use this property.
"""
return self._convention
@convention.setter
def convention(self, new_convention: Convention):
self._set_multiple_counting(new_convention._multiple_counting)
self._set_spin_normalization(new_convention._spin_normalized)
self._set_convention_constant(
old_value=self.convention._c1, new_value=new_convention._c1, n=1, p_n=1
)
self._set_convention_constant(
old_value=self.convention._c21, new_value=new_convention._c21, n=2, p_n=1
)
self._set_convention_constant(
old_value=self.convention._c22, new_value=new_convention._c22, n=2, p_n=2
)
self._set_convention_constant(
old_value=self.convention._c31, new_value=new_convention._c31, n=3, p_n=1
)
self._set_convention_constant(
old_value=self.convention._c32, new_value=new_convention._c32, n=3, p_n=2
)
self._set_convention_constant(
old_value=self.convention._c33, new_value=new_convention._c33, n=3, p_n=3
)
self._set_convention_constant(
old_value=self.convention._c41, new_value=new_convention._c41, n=4, p_n=1
)
self._set_convention_constant(
old_value=self.convention._c42, new_value=new_convention._c42, n=4, p_n=2
)
self._set_convention_constant(
old_value=self.convention._c43, new_value=new_convention._c43, n=4, p_n=3
)
self._set_convention_constant(
old_value=self.convention._c44, new_value=new_convention._c44, n=4, p_n=4
)
self._set_convention_constant(
old_value=self.convention._c45, new_value=new_convention._c45, n=4, p_n=5
)
self._convention = new_convention
def _set_multiple_counting(self, multiple_counting: bool) -> None:
if multiple_counting is None or self.convention._multiple_counting is None:
return
multiple_counting = bool(multiple_counting)
if self.convention.multiple_counting == multiple_counting:
return
new_parameters = _InteractionParameters()
for (n, p_n, nus, alphas), parameter in self._parameters._container:
equivalent_parameters = _get_equivalent(
n=n, p_n=p_n, nus=nus, alphas=alphas, parameter=parameter
)
degeneracy = len(equivalent_parameters)
# It was absent before
if multiple_counting:
for nus, alphas, parameter in equivalent_parameters:
new_parameters.add(
specs=(n, p_n, nus, alphas), parameter=parameter / degeneracy
)
# It was present before
else:
nus, alphas, parameter = equivalent_parameters[0]
new_parameters.add(
specs=(n, p_n, nus, alphas), parameter=parameter, when_present="sum"
)
self._parameters = new_parameters
def _set_spin_normalization(self, spin_normalized: bool) -> None:
if spin_normalized is None or self.convention._spin_normalized is None:
return
spin_normalized = bool(spin_normalized)
if self.convention.spin_normalized == spin_normalized:
return
for i, (specs, _) in enumerate(self._parameters._container):
factor = 1.0
for alpha in specs[3]:
factor = factor * self.atoms.spins[alpha]
# Before it was not normalized
if spin_normalized:
self._parameters._container[i][1] *= factor
# Before it was normalized
else:
self._parameters._container[i][1] /= factor
def _set_convention_constant(self, old_value, new_value, n, p_n) -> None:
if new_value is None or old_value is None:
return
new_value = float(new_value)
if old_value == new_value:
return
# If factor is changing one has to scale parameters.
for i, (specs, _) in enumerate(self._parameters._container):
if specs[:2] == (n, p_n):
self._parameters._container[i][1] *= old_value / new_value
elif specs[:2] > (n, p_n):
break
############################################################################
# Units #
############################################################################
@property
def units(self) -> str:
r"""
Units of the Hamiltonian's interaction parameters.
.. versionadded:: 0.3.0
The parameters of the Hamiltonian are stored in some units of energy (or
energy-like).
When user adds a parameters to the Hamiltonian (:py:meth:`.SpinHamiltonian.add`)
the ``parameter`` argument is understood to be given in the units of
:py:attr:`.SpinHamiltonian.units`.
By default the Hamiltonian stores and expects parameters in "meV", but the user
can choose out of the supported units. See
:ref:`user-guide_usage_units_parameters` for the full list of supported units.
When Hamiltonian already has some parameters in it, then the change of
:py:attr:`.SpinHamiltonian.units` converts all parameter to the new units.
The parameters that the user tries to add afterwards are expected to be in the new
units.
Returns
-------
units : str
Notes
-----
List of supported units can be fount in :ref:`user-guide_usage_units` page.
Examples
--------
See :ref:`user-guide_usage_spin-hamiltonian_units` for more details on how to use
this property.
"""
return _PARAMETER_UNITS_MAKEUP[self._units]
@units.setter
def units(self, new_units: str):
new_units = _validated_units(units=new_units, supported_units=_PARAMETER_UNITS)
conversion_factor = _PARAMETER_UNITS[self._units] / _PARAMETER_UNITS[new_units]
for i in range(len(self._parameters._container)):
self._parameters._container[i][1] *= conversion_factor
self._units = new_units.lower()
############################################################################
# External magnetic field #
############################################################################
@property
def magnetic_field(self):
r"""
External magnetic field applied to the Hamiltonian.
Returns
-------
B : (3, ) :numpy:`ndarray`
Vector of magnetic field (magnetic flux density, B) in the units of
Tesla.
See Also
--------
set_magnetic_field
Notes
-----
See :py:meth:`.SpinHamiltonian.set_magnetic_field` for more details.
Examples
--------
See :ref:`user-guide_usage_spin-hamiltonian_magnetic-field` for more details on
how to use this property.
"""
if len(self._zeeman_parameters) == 0:
self._magnetic_field = np.zeros(3, dtype=float)
return self._magnetic_field
@magnetic_field.setter
def magnetic_field(self, new_value):
self.set_magnetic_field(B=new_value)
[docs]
def set_magnetic_field(self, B=None, alphas=None) -> None:
r"""
Sets a uniform external magnetic field applied to the Hamiltonian.
.. versionadded:: 0.5.2
Parameters
----------
B : (3, ) |array-like|_
Vector of magnetic field (magnetic flux density, B) given in the units of
Tesla.
alphas : list of int, optional
Indices of atoms, to which the magnetic field effect should be added. If
none give, then magnetic field is added only to the
:py:attr:`.SpinHamiltonian.magnetic_atoms`. See
:ref:`user-guide_usage_spin-hamiltonian_magnetic-vs-non-magnetic` for more
details on the difference between magnetic and non-magnetic atoms.
See Also
--------
magnetic_field
Notes
-----
The Zeeman term is defined as
.. math::
\sum_{\mu, \alpha}
\mu_B g_{\alpha} \boldsymbol{B}\cdot\boldsymbol{S}_{\mu,\alpha}
We map this term to the :ref:`ug_tb_sh_1-1`, which are written as
.. math::
C_1
\sum_{\mu, \alpha}
\boldsymbol{S}_{\mu,\alpha}
\cdot
\boldsymbol{J}^{Zeeman}_{\alpha}
where :math:`\boldsymbol{J}^{Zeeman}_{\alpha}` is defined as
.. math::
\boldsymbol{J}^{Zeeman}_{\alpha}
=
\dfrac{\mu_B g_{\alpha}}{C_1}\boldsymbol{B}
Zeeman energy is minimal when the magnetic moment is aligned with the direction of
the external field. Therefore, the spin vector shall be directed opposite to the
direction of the magnetic field. In other words, in the ground state
:math:`\ket{0}` of the Zeeman Hamiltonian, the eigenvalue of the spin operator
:math:`\boldsymbol{S}_{\mu,\alpha}^{||\boldsymbol{B}}` shall be equal to
:math:`-S_{\alpha}`. Therefore, the g-factors (``spinham.atoms.g_factors``) are
expected to be positive when the Zeeman term is written as we do in Magnopy.
Examples
--------
See :ref:`user-guide_usage_spin-hamiltonian_magnetic-field` for more details on
how to use this method.
"""
if B is None:
raise TypeError(
"SpinHamiltonian.set_magnetic_field() missing 1 required argument: 'B'"
)
B = np.array(B, dtype=float)
if B.shape != (3,):
raise ValueError(
f"Expected magnetic field to be a vector of shape (3,), got {B.shape}."
)
if self.convention._c1 is None:
self.convention._c1 = 1.0
mu_B = BOHR_MAGNETON / _PARAMETER_UNITS[self._units] # spinham.units / Tesla
if alphas is None:
alphas = self.map_to_all
else:
alphas = sorted(alphas)
self._magnetic_field = B
zeeman_parameters = [
mu_B * self.atoms.g_factors[alpha] * B / self.convention.c1
for alpha in alphas
]
zeeman_term = _InteractionParameters()
for alpha, parameter in zip(alphas, zeeman_parameters):
zeeman_term.add(specs=(1, 1, (), (alpha,)), parameter=parameter)
if len(self._zeeman_parameters) != 0:
self._parameters = self._parameters - self._zeeman_parameters
self._zeeman_parameters = zeeman_term
self._parameters = self._parameters + zeeman_term
self._reset_internals()
@property
def zeeman_parameters(self):
r"""
Returns an iterator over Zeeman parameters of the Hamiltonian.
.. versionadded:: 0.5.2
Returns
-------
parameters : iterator
Iterator over parameters of the Hamiltonian that originate from the Zeeman
interaction. See :py:attr:`.SpinHamiltonian.parameters` for more details on
the returned iterator.
See Also
--------
set_magnetic_field
magnetic_field
parameters
"""
return _InteractionParametersIterator(
parameters=self._zeeman_parameters, n=1, p_n=1
)
# ARGUMENT "h" DEPRECATED since 0.4.0
# Remove in May of 2026
[docs]
def add_magnetic_field(self, B=None, alphas=None, h=None) -> None:
r"""
Adds external magnetic field to the Hamiltonian.
Parameters
----------
B : (3, ) |array-like|_
See :py:attr:`.SpinHamiltonian.set_magnetic_field` for details.
alphas : list of int, optional
See :py:attr:`.SpinHamiltonian.set_magnetic_field` for details.
h : (3, ) |array-like|_
.. deprecated:: 0.4.0
The argument will be removed in May of 2026. Use ``B`` instead.
Notes
-----
The call ``spinham.add_magnetic_field(B = B, alphas = alphas)`` is equivalent to
the call
``spinham.set_magnetic_field(B = B + spinham.magnetic_field, alphas = alphas)``.
In other words, this method "adds" the magnetic field to the Hamiltonian whether
there was some magnetic field before or not.
On contrary, :py:attr:`.SpinHamiltonian.set_magnetic_field` "sets" the magnetic
field, i.e. replaces the previous magnetic field (if any).
"""
if h is not None:
import warnings
warnings.warn(
'Argument "h" is deprecated as of 0.4.0, use "B" instead. "h" will be removed in May of 2026.',
DeprecationWarning,
stacklevel=2,
)
B = h
self.set_magnetic_field(B=B + self.magnetic_field, alphas=alphas)
############################################################################
# Magnetic dipole-dipole interaction #
############################################################################
[docs]
def add_dipole_dipole(self, R_cut=None, E_cut=None, alphas=None):
r"""
Adds magnetic dipole dipole interaction to the Hamiltonian.
Magnetic dipole dipole interaction is added in the form of two-spin & two-sites
parameter
.. math::
C_{2,2}
\sum_{\mu,\nu,\alpha,\beta,i,j}
J_{dd}(\boldsymbol{r}_{\nu,\alpha\beta})^{ij}
S_{\mu,\alpha}^i
S_{\mu+\nu,\beta}^j
where the parameter is defined as
.. math::
J_{dd}(\boldsymbol{r}_{\nu,\alpha\beta})^{ij}
=
\dfrac{\mu_0\mu_B^2}{8\pi C_{2,2}}
\dfrac{g_{\alpha}g_{\beta}}{\vert\boldsymbol{r}_{\nu,\alpha\beta}\vert^3}
(\delta_{k,l} - 3\hat{r}_{\nu,\alpha\beta}^i\hat{r}_{\nu,\alpha\beta}^j)
if :py:attr:`.SpinHamiltonian.convention.multiple_counting` is ``True`` and as
.. math::
J_{dd}(\boldsymbol{r}_{\nu,\alpha\beta})^{ij}
=
\dfrac{\mu_0\mu_B^2}{4\pi C_{2,2}}
\dfrac{g_{\alpha}g_{\beta}}{\vert\boldsymbol{r}_{\nu,\alpha\beta}\vert^3}
(\delta_{k,l} - 3\hat{r}_{\nu,\alpha\beta}^i\hat{r}_{\nu,\alpha\beta}^j)
if :py:attr:`.SpinHamiltonian.convention.multiple_counting` is ``False``.
where :math:`g_{\alpha}` is a g-factor, :math:`\boldsymbol{\hat{r}}_{\nu,\alpha\beta}`
is a unit vector.
Parameters
----------
R_cut : float, optional
Cut off radius for the distance between a pair of atoms.
:math:`R_{cut} \ge 0`.
E_cut : float, optional
Cut off value for the maximum value of the parameter.
:math:`E_{cut} > 0`. Expected in the same units as
:py:attr:`.SpinHamiltonian.units`.
alphas : list of int, optional
Indices of atoms, to which the magnetic field effect should be added.
Raises
------
ValueError
* If none of the ``R_cut`` or ``E_cut`` are provided.
* If ``R_cut < 0``
* If ``E_cut <= 0``
Notes
-----
* If only ``R_cut`` is given, then the dipole dipole term between the pair of
spins :math:`S_{\mu,\alpha}^i` and :math:`S_{\mu+\nu,\beta}^j` is added if
:math:`\vert\boldsymbol{r}_{\nu,\alpha\beta}\vert <= R_{cut}`.
* If only ``E_cut`` is given, then the ``R_cut`` is estimated as
.. math::
R_{cut}
=
\left(
3\sqrt{2}
\dfrac{\mu_0\mu_B^2g_{max}^2}{8\pi C_{2,2}E_{cut}}
\right)^{\dfrac{1}{3}}
if :py:attr:`.SpinHamiltonian.convention.multiple_counting` is ``True`` and as
.. math::
R_{cut}
=
\left(
3\sqrt{2}
\dfrac{\mu_0\mu_B^2g_{max}^2}{4\pi C_{2,2}E_{cut}}
\right)^{\dfrac{1}{3}}
if :py:attr:`.SpinHamiltonian.convention.multiple_counting` is ``False``.
The dipole dipole term between the pair of spins :math:`S_{\mu,\alpha}^i` and
:math:`S_{\mu+\nu,\beta}^j` is added if
:math:`\vert\boldsymbol{r}_{\nu,\alpha\beta}\vert \le R_{cut}` and
:math:`\vert J_{dd}(\boldsymbol{r}_{\nu,\alpha\beta})^{ij}\vert\ge E_{cut}`
for some :math:`i, j`.
* If both ``R_cut`` and ``E_cut`` are provided, then the dipole dipole term
between the pair of spins :math:`S_{\mu,\alpha}^i` and
:math:`S_{\mu+\nu,\beta}^j` is added if
:math:`\vert\boldsymbol{r}_{\nu,\alpha\beta}\vert \le R_{cut}` and
:math:`\vert J_{dd}(\boldsymbol{r}_{\nu,\alpha\beta})^{ij}\vert \ge E_{cut}`
for some :math:`i, j`.
Magnetic dipole-dipole interaction is added either to magnetic atoms or
to the list of the atoms provided by user.
* If ``alphas is None``, then parameters of the magnetic field added
only to the magnetic atoms. In other words only to atoms that already have
at least one other parameter (any) associated with it.
* If ``alpha is not None``, then parameters of magnetic field are added
to the atoms with the provided indices (based on the order in
:py:attr:`.SpinHamiltonian.atoms`)
"""
# Constants
MU_0_MU_B = (
VACUUM_MAGNETIC_PERMEABILITY
* BOHR_MAGNETON**2
/ ANGSTROM**3
/ _PARAMETER_UNITS[self._units]
) # spinham.units * Angstrom^3
if E_cut is None and R_cut is None:
raise ValueError("Expected either E_cut or R_cut, got neither.")
if E_cut is not None:
if E_cut <= 0:
raise ValueError(f"Expected positive cut-off energy, got {E_cut}.")
R_cut = (
3
* np.sqrt(2)
* MU_0_MU_B
* max(self.atoms.g_factors) ** 2
/ 4
/ np.pi
/ self.convention.c22
/ E_cut
)
if self.convention.multiple_counting:
R_cut = R_cut / 2
R_cut = R_cut ** (1 / 3)
else:
R_cut = float(R_cut)
if R_cut < 0:
raise ValueError(f"Expected positive cut-off radius, got {R_cut}.")
# Get indices for unit cells of interest
a1, a2, a3 = self.cell
a_3_perp = abs(np.cross(a1, a2) @ a3 / np.linalg.norm(np.cross(a1, a2)))
m_3_max = ceil(R_cut / a_3_perp)
a_2_perp = np.cross(a2, a3) @ a1 / np.linalg.norm(np.cross(a2, a3))
m_2_max = ceil(R_cut / a_2_perp)
m_1_max = ceil(R_cut / np.linalg.norm(a1))
# Run over all pairs of atoms between (0, 0, 0) and all unit cells of
# interest
dd_parameters = _InteractionParameters()
if alphas is None:
alphas = self.map_to_all
for alpha in alphas:
for k in range(-m_3_max, m_3_max + 1):
for j in range(-m_2_max, m_2_max + 1):
for i in range(-m_1_max, m_1_max + 1):
for beta in alphas:
if ((i, j, k), beta) == ((0, 0, 0), alpha):
continue
vector = (
np.array([i, j, k])
+ self.atoms.positions[beta]
- self.atoms.positions[alpha]
) @ self.cell
distance = np.linalg.norm(vector)
if distance <= R_cut:
parameter = (
MU_0_MU_B
/ 4
/ np.pi
/ self.convention.c22
* self.atoms.g_factors[alpha]
* self.atoms.g_factors[beta]
/ distance**3
) * (
np.eye(3, dtype=float)
- 3 * np.outer(vector, vector) / distance**2
)
if self.convention.multiple_counting:
parameter = parameter / 2
if E_cut is None or (np.abs(parameter) >= E_cut).any():
if not self.convention.multiple_counting:
equivalent_parameters = _get_equivalent(
n=2,
p_n=2,
nus=((i, j, k),),
alphas=(alpha, beta),
parameter=parameter,
)
nu, alphas, parameter = equivalent_parameters[0]
else:
nu = (i, j, k)
alphas = (alpha, beta)
dd_parameters.add(
specs=(2, 2, (nu,), alphas),
parameter=parameter,
when_present="skip",
)
self._parameters = self._parameters + dd_parameters
############################################################################
# Copy getter #
############################################################################
[docs]
def copy(self):
R"""
Returns a new, independent copy of the same Hamiltonian.
Returns
-------
spinham : :py:class:`.SpinHamiltonian`
A new instance of the same Hamiltonian.
"""
return deepcopy(self)
[docs]
def get_empty(self):
r"""
Returns the Hamiltonian with the same cell, atoms, units and convention, but with no
parameters present.
Returns
-------
spinham : :py:class:`.SpinHamiltonian`
New instance of the spin Hamiltonian.
Notes
-----
Note that in the new Hamiltonian ``spinham.M == 0`` - as there is no parameters
present, meaning that no atoms are considered to be magnetic.
"""
return SpinHamiltonian(
cell=self.cell,
atoms=self.atoms,
convention=self.convention,
units=self.units,
)
############################################################################
# Arithmetic operations #
############################################################################
def __mul__(self, number):
if not isinstance(number, int) and not isinstance(number, float):
raise TypeError(
f"unsupported operand type(s) for *: '{type(number)}' and 'SpinHamiltonian'"
)
spinham = self.copy()
spinham._parameters = spinham._parameters * number
return spinham
def __rmul__(self, number):
return self.__mul__(number=number)
def __add__(self, other):
if not isinstance(other, SpinHamiltonian):
raise NotImplementedError
# Check that unit cells are the same
if not np.allclose(self.cell, other.cell):
raise ValueError(
"Unit cells of two Hamiltonians are different, summation is not supported"
)
# Check that atoms are the same
same_atoms = True
if len(self.atoms.names) != len(other.atoms.names):
same_atoms = False
else:
for i in range(len(self.atoms.names)):
if (
self.atoms.names[i] != other.atoms.names[i]
or not np.allclose(
self.atoms.positions[i], other.atoms.positions[i]
)
or abs(self.atoms.spins[i] - other.atoms.spins[i]) > 1e-8
or abs(self.atoms.g_factors[i] - other.atoms.g_factors[i]) > 1e-8
):
same_atoms = False
if not same_atoms:
raise ValueError(
"Atoms of two spin Hamiltonians are different, summation is not supported."
)
result = other.copy()
# Make sure that units are the same
result.units = self.units
# Make sure that conventions are the same
result.convention = self.convention
result._parameters = self._parameters + result._parameters
# Make sure that Zeeman parameters and magnetic field are correctly tracked.
result._magnetic_field = self._magnetic_field + result._magnetic_field
result._zeeman_parameters = self._zeeman_parameters + result._zeeman_parameters
return result
def __sub__(self, other):
return self + (-1) * other
############################################################################
# Interaction parameters #
############################################################################
[docs]
def add(
self,
nus,
alphas,
parameter,
units=None,
populate_equivalent=False,
when_present="raise error",
):
r"""
Adds any parameter with at most four components of spin operator to the
Hamiltonian.
.. versionadded:: 0.5.0
See :ref:`user-guide_theory-behind_spin-hamiltonian` for the definition of the
Hamiltonian.
Parameters
----------
nus : (n, 3) or (n-1, 3) list/tuple of tuple of int
List of unit cell indices associated with the parameter. Each unit cell index
is a tuple of three integers (t_1, t_2, t_3) corresponding to the translation
by :math:`t_1 \boldsymbol{a}_1 + t_2 \boldsymbol{a}_2 + t_3 \boldsymbol{a}_3`.
alphas : (n,) list/tuple of int
List of indices of atoms associated with the parameter. Based on the order in
:py:attr:`.SpinHamiltonian.atoms`.
units : str, optional
Units in which the ``parameter`` is given. Parameters have the the units of
energy. By default assumes :py:attr:`.SpinHamiltonian.units`. For the list of
the supported units see :ref:`user-guide_usage_units_parameters`. If
given ``units`` are different from :py:attr:`.SpinHamiltonian.units`, then
the parameter's value will be converted automatically from ``units`` to
:py:attr:`.SpinHamiltonian.units`.
populate_equivalent : bool, default False
Whether to automatically populate all equivalent parameters related by the
symmetrization procedure. Ignored if ``convention.multiple_counting`` is
``False``. See :ref:`user-guide_usage_spin-hamiltonian_equivalent-parameters`
for more details.
when_present : str, default "raise error"
Action to take if such parameter is already present in the Hamiltonian.
Case-insensitive. Supported values are:
- ``"raise error"`` (default): raises an error.
- ``"replace"``: replace existing value of the parameter with the new one.
- ``"sum"``: add the value of the parameter to the existing one.
- ``"mean"``: replace the value of the parameter with the arithmetic mean of
existing and new parameters.
- ``"skip"``: Leave existing parameter unchanged and continue without raising
an error.
Notes
-----
If ``len(nus) == len(alphas) - 1``, then
* ``alphas[0]`` always located in the unit cell (0, 0, 0).
* ``alphas[i]`` is located in the unit cell ``nus[i-1]`` for ``i >= 1``.
If ``len(nus) == len(alphas)``, then
* ``alphas[i]`` is located in the unit cell ``nus[i]`` for all ``i``.
Note that the translational symmetry is always enforced, so in the second case the
``nus`` are updated as ``nus[i] = nus[i] - nus[0]`` for all ``i`` before the
parameter is added to the Hamiltonian.
Examples
--------
See :ref:`user-guide_usage_spin-hamiltonian_adding-parameters` for more details
on how to use this method.
"""
self._reset_internals()
for alpha in alphas:
_validate_atom_index(index=alpha, atoms=self.atoms)
for nu in nus:
_validate_unit_cell_index(ijk=nu)
if not (len(alphas) - 1 == len(nus) or len(alphas) == len(nus)):
raise ValueError(
f"Expected number of unit cell indices to be equal to or one less than the number of atom indices, got {len(alphas)} atom indices and {len(nus)} unit cell indices."
)
if len(nus) == len(alphas) - 1:
nus = tuple([(0, 0, 0)] + list(nus))
parameter = np.array(parameter)
if not len(parameter.shape) == len(alphas) or any(
_ != 3 for _ in parameter.shape
):
raise ValueError(
f"Expected parameter to be a tensor with {len(alphas)} components of size 3, got {parameter.shape}."
)
if units is not None:
units = _validated_units(units=units, supported_units=_PARAMETER_UNITS)
parameter = (
parameter * _PARAMETER_UNITS[units] / _PARAMETER_UNITS[self._units]
)
specs = _get_specs(nus=nus, alphas=alphas)
if self.convention.multiple_counting:
if populate_equivalent:
equivalent_parameters = _get_equivalent(
n=specs[0],
p_n=specs[1],
nus=specs[2],
alphas=specs[3],
parameter=parameter,
)
for nus, alphas, parameter in equivalent_parameters:
self._parameters.add(
specs=(specs[0], specs[1], nus, alphas),
parameter=parameter,
when_present=when_present,
)
else:
self._parameters.add(
specs=specs, parameter=parameter, when_present=when_present
)
else:
equivalent_parameters = _get_equivalent(
n=specs[0],
p_n=specs[1],
nus=specs[2],
alphas=specs[3],
parameter=parameter,
)
nus, alphas, parameter = equivalent_parameters[0]
self._parameters.add(
specs=(specs[0], specs[1], nus, alphas),
parameter=parameter,
when_present=when_present,
)
[docs]
def remove(self, nus, alphas, remove_equivalent=False):
r"""
Removes any parameter with at most four components of spin operator from the
Hamiltonian.
.. versionadded:: 0.5.0
See :ref:`user-guide_theory-behind_spin-hamiltonian` for the definition of the
Hamiltonian.
Parameters
----------
nus : (n, 3) or (n-1, 3) list/tuple of tuple of int
List of unit cell indices associated with the parameter. Each unit cell index
is a tuple of three integers (t_1, t_2, t_3) corresponding to the translation
by :math:`t_1 \boldsymbol{a}_1 + t_2 \boldsymbol{a}_2 + t_3 \boldsymbol{a}_3`.
alphas : (n,) list/tuple of int
List of indices of atoms associated with the parameter. Based on the order in
:py:attr:`.SpinHamiltonian.atoms`.
remove_equivalent : bool, default False
Whether to automatically remove all equivalent parameters related by the
symmetrization procedure. Ignored if ``convention.multiple_counting`` is
``False``. See :ref:`user-guide_usage_spin-hamiltonian_equivalent-parameters`
for more details.
Notes
-----
See notes of :py:meth:`.SpinHamiltonian.add` for the details on ``nus`` and
``alphas``.
Be careful when removing :py:attr:`.SpinHamiltonian.p1` parameters when
:py:attr:`.SpinHamiltonian.magnetic_field` is not zero, as the Zeeman term is
stored as a :py:attr:`.SpinHamiltonian.p1` parameters.
Examples
--------
See :ref:`user-guide_usage_spin-hamiltonian_removing-parameters` for more details
on how to use this method.
"""
for alpha in alphas:
_validate_atom_index(index=alpha, atoms=self.atoms)
for nu in nus:
_validate_unit_cell_index(ijk=nu)
if not (len(alphas) - 1 == len(nus) or len(alphas) == len(nus)):
raise ValueError(
f"Expected number of unit cell indices to be equal to or one less than the number of atom indices, got {len(alphas)} atom indices and {len(nus)} unit cell indices."
)
if len(nus) == len(alphas) - 1:
nus = tuple([(0, 0, 0)] + list(nus))
specs = _get_specs(nus=nus, alphas=alphas)
if (
specs[0] == 1
and specs[1] == 1
and not np.allclose(self.magnetic_field, np.zeros(3))
):
import warnings
warnings.warn(
"Attempt to remove p1 parameter when magnetic field is not zero.",
UserWarning,
stacklevel=2,
)
# Need to remove from the Zeeman term as well, if it is present.
# Multiple counting does not affect the Zeeman term.
if specs[0] == 1 and specs[1] == 1 and len(self._zeeman_parameters) != 0:
self._zeeman_parameters.remove(specs=specs)
# Remove from the main container of parameters.
if self.convention.multiple_counting:
if remove_equivalent:
equivalent_parameters = _get_equivalent(
n=specs[0],
p_n=specs[1],
nus=specs[2],
alphas=specs[3],
parameter=None,
)
for nus, alphas, _ in equivalent_parameters:
self._parameters.remove(specs=(specs[0], specs[1], nus, alphas))
else:
self._parameters.remove(specs=specs)
else:
equivalent_parameters = _get_equivalent(
n=specs[0], p_n=specs[1], nus=specs[2], alphas=specs[3]
)
nus, alphas, _ = equivalent_parameters[0]
self._parameters.remove(specs=(specs[0], specs[1], nus, alphas))
[docs]
def parameters(self, n=None, p_n=None):
r"""
Returns an iterator over parameters of the Hamiltonian.
.. versionadded:: 0.5.0
See :ref:`user-guide_theory-behind_spin-hamiltonian` for the definition of the
Hamiltonian and the meaning of ``n`` and ``p_n``.
Parameters
----------
n : int, optional
Number of spins in the terms of the spin Hamiltonian. Expected to be between
1 and 4. If not given, then all parameters are returned and ``p_n`` is
ignored.
p_n : int, optional
Index of integer partition of ``n``. Expected to be between 1 and maximal
number of integer partitions for the given ``n``. If not given, then
parameters of all respective terms of the spin Hamiltonian with ``n`` spins
are returned. Ignored if ``n`` is not given.
Returns
-------
parameters : iterator
Iterator over parameters of the Hamiltonian. Each element of the iterator is
a tuple ``(nus, alphas, parameter)`` where ``nus`` is a
tuple of unit cell indices, ``alphas`` is a tuple of atom indices, and
``parameter`` is a vector/matrix/tensor of the interaction parameter.
- ``len(alphas) = n``
- ``len(nus) == len(alphas) - 1``
- ``len(parameter.shape) == len(alphas)``
- ``parameter.shape[i] == 3`` for all ``0 <= i < len(alphas)``
- ``alphas[0]`` is always located in the unit cell (0, 0, 0)
- ``alphas[i]`` is located in the unit cell ``nus[i-1]`` for
``1 <= i < len(alphas)``
See Also
--------
add
remove
p1
p21
p22
p31
p32
p33
p41
p42
p43
p44
p45
"""
return _InteractionParametersIterator(parameters=self._parameters, n=n, p_n=p_n)
[docs]
def purge(self, tolerance=None):
r"""
Removes parameters of the Hamiltonian that are smaller than the given tolerance.
.. versionadded:: 0.5.2
Parameters
----------
tolerance : float, default 1e-8 meV
Parameters with absolute value of all components smaller than the tolerance
are removed. Expected to be non-negative. Expected to be
in the same units as :py:attr:`.SpinHamiltonian.units`. Default
value is :math:`10^{-8}` meV (converted to the units of the Hamiltonian).
Examples
--------
First lets create a Hamiltonian
.. doctest::
>>> import numpy as np
>>> import magnopy
>>> cell = np.eye(3)
>>> atoms = dict(
... names=["Fe"], positions=[[0, 0, 0]], spins=[5 / 2], g_factors=[2]
... )
>>> convention = magnopy.Convention(
... multiple_counting=True, spin_normalized=False, c22=1
... )
>>> spinham = magnopy.SpinHamiltonian(
... cell=cell, atoms=atoms, convention=convention
... )
and add some parameters to it
.. doctest::
>>> spinham.add(
... nus=[(1, 0, 0)],
... alphas=[0, 0],
... parameter=np.eye(3) * 1e-9,
... populate_equivalent=True,
... )
>>> spinham.add(
... nus=[(0, 1, 0)],
... alphas=[0, 0],
... parameter=np.eye(3) * 1e-7,
... populate_equivalent=True,
... )
There are four interaction parameters in the Hamiltonian
.. doctest::
>>> len(spinham.parameters())
4
>>> for nus, alphas, parameter in spinham.parameters():
... print(nus, alphas)
((-1, 0, 0),) (0, 0)
((0, -1, 0),) (0, 0)
((0, 1, 0),) (0, 0)
((1, 0, 0),) (0, 0)
Now if we purge the Hamiltonian with the default tolerance of 1e-8 meV, then
only two parameters remain
.. doctest::
>>> spinham.purge()
>>> len(spinham.parameters())
2
>>> for nus, alphas, parameter in spinham.parameters():
... print(nus, alphas)
((0, -1, 0),) (0, 0)
((0, 1, 0),) (0, 0)
"""
if tolerance is None:
tolerance = 1e-8 * _PARAMETER_UNITS["mev"] / _PARAMETER_UNITS[self._units]
tolerance = abs(float(tolerance))
# Remove from main container
new_parameters = _InteractionParameters()
for specs, parameter in self._parameters._container:
if not np.all(np.abs(parameter) < tolerance):
new_parameters._container.append([specs, parameter])
self._parameters = new_parameters
# Remove from Zeeman parameters
new_zeeman_parameters = _InteractionParameters()
for specs, parameter in self._zeeman_parameters._container:
if not np.all(np.abs(parameter) < tolerance):
new_zeeman_parameters._container.append([specs, parameter])
self._zeeman_parameters = new_zeeman_parameters
############################################################################
# One spin & one site (1) #
############################################################################
@property
def p1(self):
r"""
Parameters of one spin & one site term of the Hamiltonian (:ref:`ug_tb_sh_1-1`).
See :ref:`user-guide_theory-behind_spin-hamiltonian` for more details.
Returns
-------
parameters : iterator
Iterator over parameters of the Hamiltonian. See notes of
:py:meth:`.SpinHamiltonian.parameters` for more details.
See Also
--------
add
remove
parameters
"""
return self.parameters(n=1, p_n=1)
############################################################################
# Two spins & one site (2 + 0) #
############################################################################
@property
def p21(self):
r"""
Parameters of two spins & one site term of the Hamiltonian (:ref:`ug_tb_sh_2-1`).
See :ref:`user-guide_theory-behind_spin-hamiltonian` for more details.
Returns
-------
parameters : iterator
Iterator over parameters of the Hamiltonian. See notes of
:py:meth:`.SpinHamiltonian.parameters` for more details.
See Also
--------
add
remove
parameters
"""
return self.parameters(n=2, p_n=1)
############################################################################
# Two spins & two sites (1+1) #
############################################################################
@property
def p22(self):
r"""
Parameters of two spins & two sites term of the Hamiltonian (:ref:`ug_tb_sh_2-2`).
See :ref:`user-guide_theory-behind_spin-hamiltonian` for more details.
Returns
-------
parameters : iterator
Iterator over parameters of the Hamiltonian. See notes of
:py:meth:`.SpinHamiltonian.parameters` for more details.
See Also
--------
add
remove
parameters
"""
return self.parameters(n=2, p_n=2)
############################################################################
# Three spins & one site (3+0+0) #
############################################################################
@property
def p31(self):
r"""
Parameters of three spins & one site term of the Hamiltonian
(:ref:`ug_tb_sh_3-1`).
See :ref:`user-guide_theory-behind_spin-hamiltonian` for more details.
Returns
-------
parameters : iterator
Iterator over parameters of the Hamiltonian. See notes of
:py:meth:`.SpinHamiltonian.parameters` for more details.
See Also
--------
add
remove
parameters
"""
return self.parameters(n=3, p_n=1)
############################################################################
# Three spins & two sites (2+1+0) #
############################################################################
@property
def p32(self):
r"""
Parameters of three spins & two sites term of the Hamiltonian
(:ref:`ug_tb_sh_3-2`).
See :ref:`user-guide_theory-behind_spin-hamiltonian` for more details.
Returns
-------
parameters : iterator
Iterator over parameters of the Hamiltonian. See notes of
:py:meth:`.SpinHamiltonian.parameters` for more details.
See Also
--------
add
remove
parameters
"""
return self.parameters(n=3, p_n=2)
############################################################################
# Three spins & three sites (1+1+1) #
############################################################################
@property
def p33(self):
r"""
Parameters of three spins & three sites term of the Hamiltonian
(:ref:`ug_tb_sh_3-3`).
See :ref:`user-guide_theory-behind_spin-hamiltonian` for more details.
Returns
-------
parameters : iterator
Iterator over parameters of the Hamiltonian. See notes of
:py:meth:`.SpinHamiltonian.parameters` for more details.
See Also
--------
add
remove
parameters
"""
return self.parameters(n=3, p_n=3)
############################################################################
# Four spins & one site (4+0+0+0) #
############################################################################
@property
def p41(self):
r"""
Parameters of four spins & one site term of the Hamiltonian (:ref:`ug_tb_sh_4-1`).
See :ref:`user-guide_theory-behind_spin-hamiltonian` for more details.
Returns
-------
parameters : iterator
Iterator over parameters of the Hamiltonian. See notes of
:py:meth:`.SpinHamiltonian.parameters` for more details.
See Also
--------
add
remove
parameters
"""
return self.parameters(n=4, p_n=1)
############################################################################
# Four spins & two sites (3+1+0+0) #
############################################################################
@property
def p42(self):
r"""
Parameters of four spins & two sites term of the Hamiltonian
(:ref:`ug_tb_sh_4-2`).
See :ref:`user-guide_theory-behind_spin-hamiltonian` for more details.
Returns
-------
parameters : iterator
Iterator over parameters of the Hamiltonian. See notes of
:py:meth:`.SpinHamiltonian.parameters` for more details.
See Also
--------
add
remove
parameters
"""
return self.parameters(n=4, p_n=2)
############################################################################
# Four spins & two sites (2+2+0+0) #
############################################################################
@property
def p43(self):
r"""
Parameters of four spins & two sites term of the Hamiltonian
(:ref:`ug_tb_sh_4-3`).
See :ref:`user-guide_theory-behind_spin-hamiltonian` for more details.
Returns
-------
parameters : iterator
Iterator over parameters of the Hamiltonian. See notes of
:py:meth:`.SpinHamiltonian.parameters` for more details.
See Also
--------
add
remove
parameters
"""
return self.parameters(n=4, p_n=3)
############################################################################
# Four spins & three sites (2+1+1+0) #
############################################################################
@property
def p44(self):
r"""
Parameters of four spins & three sites term of the Hamiltonian
(:ref:`ug_tb_sh_4-4`).
See :ref:`user-guide_theory-behind_spin-hamiltonian` for more details.
Returns
-------
parameters : iterator
Iterator over parameters of the Hamiltonian. See notes of
:py:meth:`.SpinHamiltonian.parameters` for more details.
See Also
--------
add
remove
parameters
"""
return self.parameters(n=4, p_n=4)
############################################################################
# Four spins & four sites (1+1+1+1) #
############################################################################
@property
def p45(self):
r"""
Parameters of four spins & four sites term of the Hamiltonian
(:ref:`ug_tb_sh_4-5`).
See :ref:`user-guide_theory-behind_spin-hamiltonian` for more details.
Returns
-------
parameters : iterator
Iterator over parameters of the Hamiltonian. See notes of
:py:meth:`.SpinHamiltonian.parameters` for more details.
See Also
--------
add
remove
parameters
"""
return self.parameters(n=4, p_n=5)
############################################################################
# Legacy methods #
############################################################################
add_1 = _add_1
remove_1 = _remove_1
add_21 = _add_21
remove_21 = _remove_21
add_22 = _add_22
remove_22 = _remove_22
add_31 = _add_31
remove_31 = _remove_31
add_41 = _add_41
remove_41 = _remove_41
# Populate __all__ with objects defined in this file
__all__ = list(set(dir()) - old_dir)
# Remove all semi-private objects
__all__ = [i for i in __all__ if not i.startswith("_")]
del old_dir