Source code for radtools.io.tb2j

r"""
Input-output from |TB2J|_.
"""

import numpy as np

from radtools.crystal.atom import Atom
from radtools.exchange.hamiltonian import ExchangeHamiltonian
from radtools.exchange.parameter import ExchangeParameter


[docs] def read_tb2j_model(filename, quiet=True) -> ExchangeHamiltonian: r""" Read exchange Hamiltonian from |TB2J|_ output file. .. versionchanged:: 0.7 In |TB2J|_ exchange Hamiltonian is define in a following notation: .. math:: H = -\sum_{i,j} \hat{\boldsymbol{S}}_i \cdot \boldsymbol{J}_{i,j} \hat{\boldsymbol{S}}_j where spin vectors :math:`\boldsymbol{S}_i` are normalized to 1 and double counting is present (both :math:`ij` and :math:`ji` are in the sum). :py:class:`.ExchangeHamiltonian` can store exchange values in any notation. One could check the notation by calling the attribute :py:attr:`.ExchangeHamiltonian.notation`. This function reads and stores exchange parameters in the notation of Hamiltonian mentioned above. Distance between the atoms are not read from the atoms, but rather computed from the unit cell and atom positions (see :py:meth:`.Crystal.get_distance`). If the distance read from the file is different from the computed one and ``quiet=False``, the warning is printed. It is usual for the computed and read distances to differ in the last digits, since the unit cell is provided with the same precision as the distance in |TB2J|_ output file. Parameters ---------- filename : str Path to the |TB2J|_ output file. quiet : bool, default True Whether to suppress output. Returns ------- model : :py:class:`.ExchangeHamiltonian` Exchange Hamiltonian build from |TB2J|_ file. With notation set to "TB2J". """ major_sep = "=" * 90 minor_sep = "-" * 88 garbage = str.maketrans( {"(": None, ")": None, "[": None, "]": None, ",": None, "'": None} ) # Do not correct spelling, it is taken from TB2J. cell_flag = "Cell (Angstrom):" atoms_flag = "Atoms:" atom_end_flag = "Total" exchange_flag = "Exchange:" iso_flag = "J_iso:" aniso_flag = "J_ani:" dmi_flag = "DMI:" file = open(filename, "r") model = ExchangeHamiltonian(notation="TB2J") line = True # Read everything before exchange while line: line = file.readline() # Read cell if line and cell_flag in line: model.crystal.lattice.cell = np.array( [ list(map(float, file.readline().split())), list(map(float, file.readline().split())), list(map(float, file.readline().split())), ] ) # Read atoms if line and atoms_flag in line: line = file.readline() line = file.readline() line = file.readline().split() i = 1 while line and atom_end_flag not in line: try: # Slicing is not used intentionally. magmom = tuple(map(float, [line[5], line[6], line[7]])) except IndexError: magmom = None try: charge = float(line[4]) except IndexError: charge = None model.add_atom( Atom( name=line[0], position=tuple(map(float, line[1:4])), index=i, magmom=magmom, charge=charge, ) ) line = file.readline().split() i += 1 # Check if the exchange section is reached if line and exchange_flag in line: break # Identify lattice type model.crystal.identify() # Read exchange while line: while line and minor_sep not in line: line = file.readline() line = file.readline().translate(garbage).split() # atom1 = model.crystal.get_atom(line[0]) # atom2 = model.crystal.get_atom(line[1]) atom1 = line[0] atom2 = line[1] R = tuple(map(int, line[2:5])) distance = float(line[-1]) iso = None aniso = None dmi = None while line and minor_sep not in line: line = file.readline() # Read isotropic exchange if line and iso_flag in line: iso = float(line.split()[-1]) # Read anisotropic exchange if line and aniso_flag in line: aniso = np.array( [ list(map(float, file.readline().translate(garbage).split())), list(map(float, file.readline().translate(garbage).split())), list(map(float, file.readline().translate(garbage).split())), ] ) # Read DMI if line and dmi_flag in line: dmi = tuple(map(float, line.translate(garbage).split()[-3:])) # Adding info from the exchange block to the ExchangeHamiltonian structure J = ExchangeParameter(iso=iso, aniso=aniso, dmi=dmi) model.add_bond(J, atom1, atom2, R) computed_distance = model.get_distance(atom1, atom2, R) if abs(computed_distance - distance) > 0.001 and not quiet: print( f"\nComputed distance is a different from the read one:\n" + f" Computed: {computed_distance:.4f}\n " + f"Read: {distance:.4f}\n" ) return model