Spin Hamiltonian#

For the theoretical background on the spin Hamiltonian see Spin Hamiltonian.

SpinHamiltonian class is at the heart of magnopy. Every calculation starts with the definition of some spin Hamiltonian. This class stores the crystal structure, convention and all parameters in it.

Note

It stores values of spins, but not the spin direction. The motivation behind is that for any spin Hamiltonian the direction of spin vectors would be defined by its parameters (it can be done by finding the ground state of the Hamiltonian). Nevertheless, a calculation is possible for an arbitrary set of spin directions, that are not necessary describe the ground state of the Hamiltonian, therefore, we decided not to include them in this class to avoid confusion and unnecessary bookkeeping.

Three objects are required to create spin Hamiltonian Cell, Atoms and Convention.

>>> import numpy as np
>>> import magnopy
>>> cell = np.eye(3)
>>> atoms = {
...     "names" : ["Fe1", "Fe2"],
...     "species" : ["Fe", "Fe"],
...     "spglib_types" : [1, 1],
...     "positions" : [[0.0, 0.0, 0.0], [0.5, 0.5, 0.5]],
...     "spins" : [5/2, 5/2],
...     "g_factors" : [2, 2]
... }
>>> convention = magnopy.Convention(
...     multiple_counting=True, spin_normalized=False, c1=1, c21=1, c22=1 / 2, c31=1, c41=1
... )
>>> spinham = magnopy.SpinHamiltonian(cell=cell, atoms=atoms, convention=convention)

Adding and removing parameters#

Spin Hamiltonian stores parameters of the Expanded form. There are eleven groups of parameters. A property that starts with p (i.e. SpinHamiltonian.p1) provides access to the parameters of each group. Two functions that start with add_ (i.e. SpinHamiltonian.add_1()) or remove_ (i.e. SpinHamiltonian.remove_1()) add or remove a parameter from the Hamiltonian.

>>> import numpy as np
>>> # Add on-site anisotropy (two spins & one site)
>>> # Atoms are identified by their index in the spinham.atoms: 0 for Fe1
>>> spinham.add_21(alpha=0, parameter=np.diag([2, -1, -1]))
>>> # Add nearest-neighbor bilinear exchange (two spins & two sites)
>>> spinham.add_22(alpha = 0, beta = 0, nu = (1, 0, 0), parameter = np.eye(3))
>>> spinham.add_22(alpha = 0, beta = 0, nu = (0, 1, 0), parameter = np.eye(3))
>>> spinham.add_22(alpha = 0, beta = 0, nu = (0, 0, 1), parameter = np.eye(3))

Properties that give access to the parameters behave as lists (technically it is either a list or an iterator)

>>> for alpha, parameter in spinham.p21:
...     print(spinham.atoms.names[alpha], parameter, sep="\n")
...
Fe1
[[ 2  0  0]
 [ 0 -1  0]
 [ 0  0 -1]]

Note that there are 6 parameters in the p22, as multiple_counting is True

>>> for alpha, beta, nu, parameter in spinham.p22:
...     print(spinham.atoms.names[alpha], spinham.atoms.names[beta], nu)
...     print(parameter)
...
Fe1 Fe1 (0, 0, 1)
[[1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]]
Fe1 Fe1 (0, 1, 0)
[[1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]]
Fe1 Fe1 (1, 0, 0)
[[1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]]
Fe1 Fe1 (0, 0, -1)
[[1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]]
Fe1 Fe1 (0, -1, 0)
[[1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]]
Fe1 Fe1 (-1, 0, 0)
[[1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]]

Property

Loop variables

SpinHamiltonian.p1

for alpha, parameter in spinham.p1:

SpinHamiltonian.p21

for alpha, parameter in spinham.p21:

SpinHamiltonian.p22

for alpha, beta, nu, parameter in spinham.p22:

SpinHamiltonian.p31

for alpha, parameter in spinham.p31:

SpinHamiltonian.p32

for alpha, beta, nu, parameter in spinham.p32:

SpinHamiltonian.p33

for alpha, beta, gamma, nu, _lambda, parameter in spinham.p33:

SpinHamiltonian.p41

for alpha, parameter in spinham.p41:

SpinHamiltonian.p421

for alpha, beta, nu, parameter in spinham.p421:

SpinHamiltonian.p422

for alpha, beta, nu, parameter in spinham.p422:

SpinHamiltonian.p43

for alpha, beta, gamma, nu, _lambda, parameter in spinham.p43:

SpinHamiltonian.p44

for alpha, beta, gamma, epsilon, nu, _lambda, rho, parameter in spinham.p44:

Hint

Unit cell indices nu, _lambda and rho are tuples of three integers, i.e. (nu_1, nu_2, nu_3). They describe translation of the unit cell by nu_1*a1 + nu_2*a2 + nu_3*a3, where a1, a2 and a3 are the lattice vectors of the cell.

Indices of the atoms within the unit cell (alpha, beta, gamma, epsilon) are integers starting from 0. They correspond to the order of atoms in the SpinHamiltonian.atoms dictionary.

All indices directly correspond to the indices in the mathematical form of the spin Hamiltonian in the Expanded form.

Cell and atoms#

Spin Hamiltonian class stores cell SpinHamiltonian.cell and atoms SpinHamiltonian.atoms as attributes

>>> spinham.cell
array([[1., 0., 0.],
       [0., 1., 0.],
       [0., 0., 1.]])
>>> spinham.atoms
{'names': ['Fe1', 'Fe2'], 'species': ['Fe', 'Fe'], 'spglib_types': [1, 1], 'positions': [[0.0, 0.0, 0.0], [0.5, 0.5, 0.5]], 'spins': [2.5, 2.5], 'g_factors': [2, 2]}
>>> # Magnopy adds syntactic sugar to the atoms dictionary inside the SpinHamiltonian class:
>>> # a command
>>> spinham.atoms.names
['Fe1', 'Fe2']
>>> # is equivalent to
>>> spinham.atoms["names"]
['Fe1', 'Fe2']
>>> # It works with any key of atoms dictionary
>>> spinham.atoms.spins
[2.5, 2.5]

Cell and atoms are not meant to be changed once the Hamiltonian is created

>>> spinham.cell = 2 * np.eye(3)
Traceback (most recent call last):
...
AttributeError: Change of the cell attribute is not supported after the creation of SpinHamiltonian instance. If you need to modify cell, then use pre-defined methods of SpinHamiltonian or create a new one.
>>> spinham.atoms = {}
Traceback (most recent call last):
...
AttributeError: Change of the atoms dictionary is not supported after the creation of SpinHamiltonian instance. If you need to modify atoms, then use pre-defined methods of SpinHamiltonian or create a new one.

Convention#

Convention of the Hamiltonian is stored as its attribute (SpinHamiltonian.convention).

>>> print(spinham.convention)
"custom" convention where
  * Bonds are counted multiple times in the sum;
  * Spin vectors are not normalized;
  * c1 = 1.0;
  * c21 = 1.0;
  * c22 = 0.5;
  * c31 = 1.0;
  * Undefined c32 factor;
  * Undefined c33 factor;
  * c41 = 1.0;
  * Undefined c421 factor;
  * Undefined c422 factor;
  * Undefined c43 factor;
  * Undefined c44 factor.

The convention of the Hamiltonian can be changed. If the convention is being changed, then the parameters will be adjusted accordingly. For example, if we change the numerical factor before the two spins & one site term or remove multiple counting

>>> new_convention = spinham.convention.get_modified(multiple_counting=False)
>>> spinham.convention = new_convention
>>> for alpha, parameter in spinham.p21:
...     print(spinham.atoms.names[alpha], parameter, sep="\n")
...
Fe1
[[ 2  0  0]
 [ 0 -1  0]
 [ 0  0 -1]]
>>> for alpha, beta, nu, parameter in spinham.p22:
...     print(spinham.atoms.names[alpha], spinham.atoms.names[beta], nu)
...     print(parameter)
...
Fe1 Fe1 (0, 0, 1)
[[2. 0. 0.]
 [0. 2. 0.]
 [0. 0. 2.]]
Fe1 Fe1 (0, 1, 0)
[[2. 0. 0.]
 [0. 2. 0.]
 [0. 0. 2.]]
Fe1 Fe1 (1, 0, 0)
[[2. 0. 0.]
 [0. 2. 0.]
 [0. 0. 2.]]

Hint

The main principle of changing convention can be formulated as "Physical properties of the Hamiltonian do not depend on its convention".

Units#

Spin Hamiltonian supports a number of units for its parameters. See Units of Hamiltonian's parameters for the full list of supported units.

By default the units are set to meV. You can consult or change the units of the parameters at any moment using SpinHamiltonian.units

>>> spinham.units
'meV'
>>> spinham.units = 'K'
>>> spinham.units
'Kelvin'
>>> for alpha, parameter in spinham.p21:
...     print(spinham.atoms.names[alpha], parameter, sep="\n")
...
Fe1
[[ 23.20903624   0.           0.        ]
 [  0.         -11.60451812   0.        ]
 [  0.           0.         -11.60451812]]

Note

Change of units automatically rescales all parameters of the Hamiltonian. The principle is the same as for change of convention: "Physical properties of the Hamiltonian do not depend on its units".

Methods that add parameters to the Hamiltonian (i.e. SpinHamiltonian.add_1(), SpinHamiltonian.add_21(), etc) interpret the input parameters in the units of SpinHamiltonian.units at the moment of the call.

Magnetic vs non-magnetic atoms#

Magnopy defines magnetic atom as an atom that has at least one parameter of the spin Hamiltonian associated with it. Each spin Hamiltonian contains \(M\) magnetic atoms (SpinHamiltonian.M). However, the crystal (cell & atoms) that are used for the definition of the spin Hamiltonian can contain \(M^{\prime} \ne M\) atoms.

Attribute SpinHamiltonian.atoms returns a dictionary with all atoms of the crystal, while SpinHamiltonian.magnetic_atoms returns dictionary with only magnetic atoms. The order of atoms is the same in both.

The indices in the specification of parameters correspond to the SpinHamiltonian.atoms. If you need to convert an index of SpinHamiltonian.atoms to an index of SpinHamiltonian.magnetic_atoms use the property SpinHamiltonian.map_to_magnetic

>>> index_in_atoms = 0
>>> index_in_magnetic_atoms = spinham.map_to_magnetic[index_in_atoms]

To convert from an index of SpinHamiltonian.magnetic_atoms to the index of SpinHamiltonian.atoms use

>>> index_in_magnetic_atoms = 0
>>> index_in_atoms = spinham.map_to_all[index_in_magnetic_atoms]

Here is an example that illustrates the difference between all atoms and magnetic atoms

>>> # Create a Hamiltonian with three atoms
>>> atoms = dict(
...     names=["Cr1", "Cr2", "Cr3"],
...     spins = [3/2, 3/2, 3/2],
...     g_factors=[2, 2, 2],
...     positions=[[0, 0, 0],[0.5, 0, 0],[0, 0.5, 0]]
... )
>>> conv = magnopy.Convention(
...     multiple_counting=True,
...     spin_normalized=False,
...     c21=1
... )
>>> spinham = magnopy.SpinHamiltonian(
...     cell=np.eye(3),
...     atoms=atoms,
...     convention=conv
... )

At this moment there is no magnetic atoms in the Hamiltonian (in the magnopy's context), even though all atoms of the crystal have non-zero spin value.

>>> spinham.M
0
>>> spinham.magnetic_atoms
{'names': [], 'spins': [], 'g_factors': [], 'positions': []}
>>> spinham.map_to_magnetic
[None, None, None]
>>> spinham.map_to_all
[]

Lets add an on-site quadratic anisotropy to the second atom

>>> spinham.add_21(alpha=1, parameter = np.diag([1, 2, 3]))

Now second atom has a parameter associated with it, hence it is considered magnetic.

>>> spinham.M
1
>>> spinham.magnetic_atoms
{'names': ['Cr2'], 'spins': [1.5], 'g_factors': [2], 'positions': [[0.5, 0, 0]]}
>>> spinham.map_to_magnetic
[None, 0, None]
>>> spinham.map_to_all
[1]
>>> # Note that in the specification of the parameter the index
>>> # of spinham.atoms is used
>>> print(spinham.p21[0][0])
1

Two mapping lists can be used to safely convert from one to another

>>> # From index of magnetic atom to the index of non-magnetic atom
>>> for i in range(spinham.M):
...     print(
...         spinham.magnetic_atoms.names[i],
...         spinham.atoms.names[spinham.map_to_all[i]]
...     )
...
Cr2 Cr2
>>> # From index of non-magnetic atom to the index of magnetic atom
>>> for i in range(len(spinham.atoms.names)):
...     if spinham.map_to_magnetic[i] is not None:
...         print(
...             spinham.magnetic_atoms.names[spinham.map_to_magnetic[i]],
...             spinham.atoms.names[i]
...         )
...
Cr2 Cr2