Module diffpy.mpdf.magstructure
classes to create magnetic structures for mPDF calculations.
Expand source code
#!/usr/bin/env python
##############################################################################
#
# diffpy.mpdf by Frandsen Group
# Benjamin A. Frandsen benfrandsen@byu.edu
# (c) 2022 Benjamin Allen Frandsen
# All rights reserved
#
# File coded by: Benjamin Frandsen
#
# See AUTHORS.txt for a list of people who contributed.
# See LICENSE.txt for license information.
#
##############################################################################
"""classes to create magnetic structures for mPDF calculations."""
import copy
import random
import numpy as np
from diffpy.srreal.bondcalculator import BondCalculator
from diffpy.mpdf.magutils import generateAtomsXYZ, generateFromUnitCell, \
generateSpinsXYZ, getFFparams, jCalc, spinsFromAtoms, atomsFromSpins, \
findAtomIndices, visualizeSpins
class MagSpecies:
"""Store information for a single species of magnetic atom.
This class takes a diffpy.Structure object and uses it to generate spins
based on a set of propagation vectors and basis vectors. For more info
about magnetic propagation vectors, visit e.g.
http://dx.doi.org/10.1051/jp4:2001906 or Appendix B of Andrew Steele's
dissertation at https://andrewsteele.co.uk/physics/thesis .
Args:
struc (diffpy.Structure object): provides lattice parameters and unit
cell of desired structure.
label (string): label for this particular magnetic species. Should be
different from the labels for any other magnetic species you make.
Default is a random 8-character hexadecimal string.
strucIdxs (python list): list of integers giving indices of magnetic
atoms in the unit cell
atoms (numpy array): list of atomic coordinates of all the magnetic
atoms in the structure; e.g. generated by generateAtomsXYZ()
spins (numpy array): triplets giving the spin vectors of all the
atoms, in the same order as the atoms array provided as input.
In units of hbar.
calcIdxs (python list): list giving the indices of the atoms array
specifying the atoms to be used as the origin when calculating
the mPDF. If given the string argument 'all', then every atom
will be used (potentially causing very long calculation times).
These indices are relative to the atoms array for this specific
MagSpecies, not relative to the atoms array for the
MagStructure as a whole.
rmaxAtoms (float): maximum distance from the origin of atomic
positions generated by the makeAtoms method.
avgmom (numpy array): three-vector giving the average magnetic moment
for this species of magnetic atom to generate the spins. Relevant
to incommensurate structures generated from an mcif.
basisvecs (numpy array): nested three-vector(s) giving the basis
vectors to generate the spins. e.g. np.array([[0, 0, 1]]). Any
phase factor should be included directly with the basisvecs.
kvecs (numpy array): nested three-vector(s) giving the propagation
vectors for the magnetic structure in r.l.u.,
e.g. np.array([[0.5, 0.5, 0.5]])
S (float): Spin angular momentum quantum number in units of hbar.
L (float): Orbital angular momentum quantum number in units of hbar.
J (float): Total angular momentum quantum number in units of hbar.
gS (float): spin component of the Lande g-factor (g = gS+gL).
Calculated automatically from S, L, and J if not specified.
gL (float): orbital component of the Lande g-factor. Calculated
automatically from S, L, and J if not specified.
g (float): Lande g-factor. Calculated automatically as gS+gL if not
specified.
j2type (string): Specifies the way the j2 integral is included in the
magnetic form factor. Must be either 'RE' for rare earth or 'TM' for
transition metal. If 'RE', the coefficient on the j2 integral is
gL/g; if 'TM', the coefficient is (g-2)/g. Default is 'RE'. Note
that for the default values of S, L, and J, we will have gS=2,
gL=0, and g=2, and there will be no difference in the form factor
calculation for 'RE' or 'TM'.
ffparamkey (string): gives the appropriate key for getFFparams()
ffqgrid (numpy array): grid of momentum transfer values used for
calculating the magnetic form factor.
ff (numpy array): magnetic form factor.
useDiffpyStruc (boolean): True if atoms/spins to be generated from
a diffpy structure object; False if a user-provided unit cell is
to be used. Note that there may be some problems with user-
provided unit cells with lattice angles strongly deviated from
90 degrees.
latVecs (numpy array): Provides the unit cell lattice vectors as
np.array([avec, bvec, cvec]). Only useful if useDiffpyStruc = False.
atomBasis (numpy array): Provides positions of the magnetic atoms
in fractional coordinates within the unit cell. Only useful if
useDiffpyStruc = False. Example: np.array([[0, 0, 0], [0.5, 0.5, 0.5]])
spinBasis (numpy array): Provides the orientations of the spins in
the unit cell, in the same order as atomBasis. Only useful if
useDiffpyStruc = False. Example: np.array([[0, 0, 1], [0, 0, -1]]
origin (numpy array): Cartesian coordinates of the position that will
be considered the origin when generating spin directions from basis
vectors and propagation vectors. Default is np.array([0,0,0]).
verbose (boolean): If True, will print messages relating to the structure.
Useful for troubleshooting. Default is False.
useOcc (boolean): If True, atomic site occupancies will be used to scale
the magnetic moment vector. Default is False.
occ (scalar): Occupancy of the magnetic atom associated with
this MagSpecies. Default is 1.
"""
def __init__(self, struc=None, label=None, strucIdxs=None, atoms=None, spins=None,
calcIdxs=[0], rmaxAtoms=30.0, avgmom=None, basisvecs=None, kvecs=None, S=0.5,
L=0.0, J=None, gS=None, gL=None, g=None, j2type=None, ffparamkey=None,
ffqgrid=None, ff=None, useDiffpyStruc=True, latVecs=None,
atomBasis=None, spinBasis=None, origin=None, verbose=False,
useOcc=False, occ=None):
if label is None:
hex_string = '0123456789abcdef'
token = ''.join([random.choice(hex_string) for i in range(8)])
self.label = token
else:
self.label = label
self.rmaxAtoms = rmaxAtoms
self.S = S
self.L = L
if J is None:
J = S + L
self.J = J
else:
self.J = J
if gS is None:
self.gS = 1.0 + 1.0*(S*(S+1)-L*(L+1))/(J*(J+1))
else:
self.gS = gS
if gL is None:
self.gL = 0.5 + 1.0*(L*(L+1)-S*(S+1))/(2*J*(J+1))
else:
self.gL = gL
if g is None:
self.g = self.gS + self.gL
else:
self.g = g
if j2type is None:
self.j2type = 'RE'
else:
self.j2type = j2type
self.ffparamkey = ffparamkey
self.useDiffpyStruc = useDiffpyStruc
if strucIdxs is None:
self.strucIdxs = [0]
else:
self.strucIdxs = strucIdxs
if calcIdxs is None:
self.calcIdxs = [0]
else:
self.calcIdxs = calcIdxs
if struc is None:
self.struc = []
else:
self.struc = struc
if atoms is None:
self.atoms = np.array([])
else:
self.atoms = atoms
if spins is None:
self.spins = np.array([])
else:
self.spins = spins
if avgmom is None:
self.avgmom = np.array([0, 0, 0])
else:
self.avgmom = avgmom
if basisvecs is None:
self.basisvecs = np.array([[0, 0, 1]])
else:
self.basisvecs = basisvecs
if kvecs is None:
self.kvecs = np.array([[0, 0, 0]])
else:
self.kvecs = kvecs
if ff is None:
self.ff = np.array([])
else:
self.ff = ff
if ffqgrid is None:
self.ffqgrid = np.arange(0, 10.0, 0.01)
else:
self.ffqgrid = ffqgrid
if latVecs is None:
self.latVecs = np.array([[4., 0, 0], [0, 4., 0], [0, 0, 4.]])
else:
self.ff = latVecs
if atomBasis is None:
self.atomBasis = np.array([[0, 0, 0]])
else:
self.atomBasis = atomBasis
if spinBasis is None:
self.spinBasis = np.array([[0, 0, 1]])
else:
self.spinBasis = spinBasis
if origin is None:
self.origin = np.array([[0, 0, 0]])
else:
self.origin = origin
self.verbose = verbose
self.useOcc = useOcc
if occ is None:
self.occ = 1.0
else:
self.occ = occ
def __repr__(self):
if self.label == '':
return 'MagSpecies() object'
else:
return self.label+': MagSpecies() object'
def makeAtoms(self):
"""Generate the Cartesian coordinates of the atoms for this species.
"""
if self.useDiffpyStruc:
self.atoms = generateAtomsXYZ(self.struc, self.rmaxAtoms, self.strucIdxs)
else:
try:
self.atoms, self.spins = generateFromUnitCell(self.latVecs,
self.atomBasis,
self.spinBasis,
self.rmaxAtoms)
except:
print('Please check latVecs, atomBasis, and spinBasis.')
def makeSpins(self):
"""Generate the Cartesian coordinates of the spin vectors in the
structure. Must provide propagation vector(s) and basis
vector(s).
"""
#self.setOcc()
if self.useDiffpyStruc:
self.spins = generateSpinsXYZ(self.struc, self.atoms, self.kvecs, self.basisvecs,
self.origin, self.avgmom)
if self.useOcc:
self.spins *= self.occ
else:
print('Since you are not using a diffpy Structure object,')
print('the spins are generated from the makeAtoms() method.')
print('Please call that method if you have not already.')
def makeFF(self):
"""Generate the magnetic form factor.
"""
# g = self.gS+self.gL # move this up to initialization of MagSpecies
if getFFparams(self.ffparamkey) != ['none']:
if self.j2type == 'RE':
self.ff = (jCalc(self.ffqgrid, getFFparams(self.ffparamkey))+
self.gL/self.g * jCalc(self.ffqgrid, getFFparams(self.ffparamkey), j2=True))
elif self.j2type == 'TM':
self.ff = (jCalc(self.ffqgrid, getFFparams(self.ffparamkey))+
(self.g-2)/self.g * jCalc(self.ffqgrid, getFFparams(self.ffparamkey), j2=True))
else:
print("j2type argument must be either 'RE' or 'TM'. Using generic magnetic form factor.")
self.ff = jCalc(self.ffqgrid)
else:
print('Using generic magnetic form factor.')
self.ff = jCalc(self.ffqgrid)
def spinsFromAtoms(self,positions,fractional=True,returnIdxs=False):
"""Return the spin vectors corresponding to specified atomic
positions.
This method calls the diffpy.mpdf.magutils.spinsFromAtoms() method.
Args:
magstruc: MagSpecies or MagStructure object containing atoms and spins
positions (list or array): atomic positions for which the
corresponding spins should be returned.
fractional (boolean): set as True if the atomic positions are in
fractional coordinates of the crystallographic lattice
vectors.
returnIdxs (boolean): if True, the indices of the spins will also be
returned.
Returns:
Array consisting of the spins corresponding to the atomic positions.
"""
return spinsFromAtoms(self,positions,fractional,returnIdxs)
def atomsFromSpins(self,spinvecs,fractional=True,returnIdxs=False):
"""Return the atomic positions corresponding to specified spins.
This method calls the diffpy.mpdf.magutils.atomsFromSpins() method.
Args:
magstruc: MagSpecies or MagStructure object containing atoms and spins
spinvecs (list or array): spin vectors for which the
corresponding atoms should be returned.
fractional (boolean): set as True if the atomic positions are to be
returned as fractional coordinates of the crystallographic lattice
vectors.
returnIdxs (boolean): if True, the indices of the atoms will also be
returned.
Returns:
List of arrays of atoms corresponding to the spins.
"""
return atomsFromSpins(self,spinvecs,fractional,returnIdxs)
def findAtomIndices(self,atomList):
"""Return list of indices corresponding to input list of atomic coordinates.
This method calls the diffpy.mpdf.findAtomIndices() method.
Args:
atomList (numpy array of atomic coordinates)
Returns:
List of indices corresponding to the atomList.
"""
return findAtomIndices(self,atomList)
def setOcc(self):
"""Set the MagSpecies.occ attribute according to the Diffpy Structure.
"""
if self.struc != []:
self.occ = np.mean(self.struc.occupancy[self.strucIdxs])
else:
self.occ *= 1
def runChecks(self):
"""Run some simple checks and raise a warning if a problem is found.
"""
if self.verbose:
print(('Running checks for '+self.label+' MagSpecies object...\n'))
flagCount = 0
flag = False
if self.useDiffpyStruc:
# check that basisvecs and kvecs have same shape
if self.kvecs.shape != self.basisvecs.shape:
flag = True
if flag:
flagCount += 1
if self.verbose:
print('kvecs and basisvecs must have the same dimensions.')
else:
# check for improperlatVecs array
if self.latVecs.shape != (3, 3):
flag = True
if flag:
flagCount += 1
if self.verbose:
print('latVecs array does not have the correct dimensions.')
print('It must be a 3 x 3 nested array.')
print('Example: np.array([[4, 0, 0], [0, 4, 0], [0, 0, 4]])')
flag = False
# check for mismatched number of atoms and spins in basis
if self.atomBasis.shape != self.spinBasis.shape:
flag = True
if flag:
flagCount += 1
if self.verbose:
print('atomBasis and spinBasis must have the same dimensions.')
# summarize results
if flagCount == 0:
if self.verbose:
print('All MagSpecies() checks passed. No obvious problems found.\n')
def copy(self):
"""Return a deep copy of the MagSpecies object.
"""
return copy.deepcopy(self)
class MagStructure:
"""Build on the diffpy.Structure class to include magnetic attributes.
This class takes a diffpy.Structure object and packages additional info
relating to magnetic structure, which can then be fed to an MPDFcalculator
object.
Args:
struc (diffpy.Structure object): provides lattice parameters and unit
cell of desired structure.
atomic_struc (diffpy.Structure object): provides atomic structure inferred
from the MCIF file. Only relevant if reading in MCIF file. If not initialized, it is
assumed that either the magnetic and atomic unit cells are equivalent
or spins were generated from the atomic unit cell using basis vectors.
transform (string): string describing the transformation from the atomic
cell basis vectors to the magnetic cell basis vector. Should be
present whenever atomic_struc is.
species (python dictionary): dictionary of magnetic species in the
structure. The values are MagSpecies objects.
atoms (numpy array): list of atomic coordinates of all the magnetic
atoms in the structure; e.g. generated by generateAtomsXYZ()
spins (numpy array): triplets giving the spin vectors of all the
atoms, in the same order as the atoms array provided as input.
gfactors (numpy array): Lande g-factors of the magnetic moments
rmaxAtoms (float): maximum distance from the origin of atomic
positions generated by the makeAtoms method.
ffqgrid (numpy array): grid of momentum transfer values used for
calculating the magnetic form factor.
ff (numpy array): magnetic form factor. Should be same shape as
ffqgrid.
label (string): Optional descriptive string for the MagStructure.
K1 (float): a constant used for calculating Dr; should be averaged
over all magnetic species. Important if physical information is
to be extracted from mPDF scale factors, e.g. moment size.
K2 (float): another constant used for calculating Dr.
fractions (python dictionary): Dictionary providing the fraction of
spins in the magnetic structure corresponding to each species.
verbose (boolean): If True, will print messages relating to the structure.
Useful for troubleshooting. Default is False.
calcIdxs (python list): list giving the indices of the atoms array
specifying the atoms to be used as the origin when calculating
the mPDF. If given the string argument 'all', then every atom
will be used (potentially causing very long calculation times).
corrLength (scalar): magnetic correlation length such that the
magnitude of the correlation between two spins separated by a
distance d is given by exp(-d/corrLength). If set to zero, the
correlation length is assumed to be infinite.
dampingMat (3x3 matrix): damping matrix that encodes anisotropic
correlation lengths. If nonzero, this supercedes the scalar
corrLength attribute (which can only be used for isotropic
correlation lengths).
rho0 (float): number of magnetic moments per cubic Angstrom in the
magnetic structure; default value is 0.
netMag (float): net magnetization in Bohr magnetons per magnetic moment
in the sample; default is 0. Only nonzero for ferro/ferrimagnets or
canted antiferromagnets.
magneticAtomRatio (float): ratio of magnetic atoms to total atoms.
"""
def __init__(self, struc=None, species=None, atoms=None, spins=None,
gfactors=None, rmaxAtoms=30.0, avgmom=None, ffqgrid=None, ff=None,
label='', K1=None, K2=None, fractions=None, Uiso=0.01,
calcIdxs=None, corrLength=0.0, dampingMat=0.0, verbose=False,
netMag=0, rho0=0, magneticAtomRatio=0, atomic_struc=None,
transform=''):
self.rmaxAtoms = rmaxAtoms
self.label = label
self.transform = transform
if struc is None:
self.struc = []
else:
self.struc = struc
if atomic_struc is None:
self.atomic_struc = []
else:
self.atomic_struc = atomic_struc
if atoms is None:
self.atoms = np.array([])
else:
self.atoms = atoms
if spins is None:
self.spins = np.array([])
else:
self.spins = spins
if gfactors is None:
self.gfactors = np.array([2.0])
else:
self.gfactors = gfactors
if species is None:
self.species = {}
else:
self.species = species
if ffqgrid is None:
self.ffqgrid = np.arange(0, 10.0, 0.01)
else:
self.ffqgrid = ffqgrid
if ff is None:
self.ff = jCalc(self.ffqgrid)
else:
self.ff = ff
if K1 is None:
self.K1 = 0.66667*(1.913*2.81794/2.0)**2*2.0**2*0.5*(0.5+1)
else:
self.K1 = K1
if K2 is None:
self.K2 = self.K1
else:
self.K2 = K2
if fractions is None:
self.fractions = {}
else:
self.fractions = fractions
if calcIdxs is None:
self.calcIdxs = [0]
else:
self.calcIdxs = calcIdxs
self.Uiso = Uiso
self.corrLength = corrLength
self.dampingMat = dampingMat
self.verbose = verbose
self.rho0 = rho0
self.netMag = netMag
self.magneticAtomRatio = magneticAtomRatio
def __repr__(self):
if self.label == '':
return 'MagStructure() object'
else:
return self.label+': MagStructure() object'
def makeSpecies(self, label, strucIdxs=None, atoms=None, spins=None,
basisvecs=None, kvecs=None, S=0.5, L=0.0, J=None, gS=None,
gL=None, g=None, j2type=None, ffparamkey=None,ffqgrid=None,
ff=None, occ=None):
"""Create a MagSpecies object and add it to the species dictionary.
Args:
label (string): label for this particular magnetic species. Should be
different from the labels for any other magnetic species you make.
strucIdxs (python list): list of integers giving indices of magnetic
atoms in the unit cell
atoms (numpy array): list of atomic coordinates of all the magnetic
atoms in the structure; e.g. generated by generateAtomsXYZ()
spins (numpy array): triplets giving the spin vectors of all the
atoms, in the same order as the atoms array provided as input.
basisvecs (numpy array): nested three-vector(s) giving the basis
vectors to generate the spins. e.g. np.array([[0, 0, 1]]). Any
phase factor should be included directly with the basisvecs.
kvecs (numpy array): nested three-vector(s) giving the propagation
vectors for the magnetic structure in r.l.u.,
e.g. np.array([[0.5, 0.5, 0.5]])
gS (float): spin component of the Lande g-factor (g = gS+gL).
Calculated automatically from S, L, and J if not specified.
gL (float): orbital component of the Lande g-factor. Calculated
automatically from S, L, and J if not specified.
g (float): Lande g-factor. Calculated automatically as gS+gL if not
specified.
j2type (string): Specifies the way the j2 integral is included in the
magnetic form factor. Must be either 'RE' for rare earth or 'TM' for
transition metal. If 'RE', the coefficient on the j2 integral is
gL/g; if 'TM', the coefficient is (g-2)/g. Default is 'RE'. Note
that for the default values of S, L, and J, we will have gS=2,
gL=0, and g=2, and there will be no difference in the form factor
calculation for 'RE' or 'TM'.
ffparamkey (string): gives the appropriate key for getFFparams()
ffqgrid (numpy array): grid of momentum transfer values used for
calculating the magnetic form factor.
ff (numpy array): magnetic form factor.
"""
# check that the label is not a duplicate with any other mag species.
duplicate = False
for name in list(self.species.keys()):
if name == label:
duplicate = True
if not duplicate:
if ffqgrid is None:
ffqgrid = np.arange(0, 10.0, 0.01)
self.species[label] = MagSpecies(self.struc, label, strucIdxs, atoms, spins,
self.rmaxAtoms, basisvecs, kvecs, S, L,
J, gS, gL, g, j2type, ffparamkey, ffqgrid, ff,
self.verbose, occ)
# update the list of fractions
totatoms = 0.0
for key in self.species:
totatoms += self.species[key].atoms.shape[0]
for key in self.species:
if totatoms == 0.0:
totatoms = 1.0 # prevent divide by zero problems
frac = float(self.species[key].atoms.shape[0])/totatoms
self.fractions[key] = frac
self.runChecks()
else:
print('This label has already been assigned to another species in')
print('the structure. Please choose a new label.')
def getCoordsFromSpecies(self):
"""Read in atomic positions and spins from magnetic species.
This differs from makeSpins() and makeAtoms() because it simply loads
the atoms and spins from the species without re-generating them from
the structure.
"""
tempA = np.array([[0, 0, 0]])
tempS = np.array([[0, 0, 0]])
for key in self.species:
na = self.species[key].atoms.shape[0]
ns = self.species[key].atoms.shape[0]
if (na > 0) and (na == ns):
tempA = np.concatenate((tempA, self.species[key].atoms))
tempS = np.concatenate((tempS, self.species[key].spins))
else:
if self.verbose:
print(('Coordinates of atoms and spins for ' + key))
print('have not been loaded because they have not yet been')
print('generated and/or do not match in shape.')
if tempA.shape != (1, 3):
self.atoms = tempA[1:]
self.spins = tempS[1:]
elif len(self.species) == 0:
self.atoms = np.array([])
self.spins = np.array([])
def loadSpecies(self, magSpec):
"""Load in an already-existing MagSpecies object
Args:
magSpec (MagSpecies object): The magnetic species to be imported
into the structure.
"""
# check that the label is not a duplicate with any other mag species.
duplicate = False
for name in list(self.species.keys()):
if name == magSpec.label:
duplicate = True
if not duplicate:
self.species[magSpec.label] = magSpec
self.struc = magSpec.struc
self.getCoordsFromSpecies()
# update the list of fractions
totatoms = 0.0
for key in self.species:
totatoms += self.species[key].atoms.shape[0]
for key in list(self.species.keys()):
if totatoms == 0.0:
totatoms = 1.0 # prevent divide by zero problems
frac = float(self.species[key].atoms.shape[0])/totatoms
self.fractions[key] = frac
self.runChecks()
else:
print('The label for this species has already been assigned to')
print('another species in the structure. Please choose a new label')
print('for this species.')
def removeSpecies(self, label, update=True):
"""Remove a magnetic species from the species dictionary.
Args:
label (string): key for the dictionary entry to be removed.
update (boolean): if True, the MagStructure will update its atoms
and spins with the removed species now excluded.
"""
try:
del self.species[label]
if update:
self.getCoordsFromSpecies()
# update the list of fractions
totatoms = 0.0
for key in self.species:
totatoms += self.species[key].atoms.shape[0]
for key in self.species:
if totatoms == 0.0:
totatoms = 1.0 # prevent divide by zero problems
frac = float(self.species[key].atoms.shape[0])/totatoms
self.fractions[key] = frac
except:
print('Species cannot be deleted. Check that you are using the')
print('correct species label.')
def makeAtoms(self):
"""Generate the Cartesian coordinates of the atoms for this species.
Args:
fromUnitCell (boolean): True if atoms/spins to be generated from
a unit cell provided by the user; False if the diffpy structure
object is to be used.
unitcell (numpy array): Provides the unit cell lattice vectors as
np.array((avec, bvec, cvec)).
atombasis (numpy array): Provides positions of the magnetic atoms
in fractional coordinates within the unit cell.
spin cell (numpy array): Provides the orientations of the spins in
the unit cell, in the same order as atombasis
"""
temp = np.array([[0, 0, 0]])
for key in self.species:
self.species[key].makeAtoms()
temp = np.concatenate((temp, self.species[key].atoms))
self.atoms = temp[1:]
def makeSpins(self):
"""Generate the Cartesian coordinates of the spin vectors in the
structure. Calls the makeSpins() method for each MagSpecies in
the species dictionary and concatenates them together.
"""
temp = np.array([[0, 0, 0]])
for key in self.species:
self.species[key].makeSpins()
temp = np.concatenate((temp, self.species[key].spins))
self.spins = temp[1:]
def makeGfactors(self):
"""Generate an array of Lande g-factors in the same order as the spins
in the MagStructure.
"""
temp = np.array([2.0])
for key in self.species:
temp = np.concatenate((temp,
(self.species[key].gS+self.species[key].gL)*np.ones(self.species[key].spins.shape[0])))
self.gfactors = temp[1:]
def makeFractions(self):
"""Generate the fractions dictionary.
"""
try:
totatoms = 0.0
for key in self.species:
totatoms += self.species[key].atoms.shape[0]
for key in self.species:
if totatoms == 0.0:
totatoms = 1.0 # prevent divide by zero problems
frac = float(self.species[key].atoms.shape[0])/totatoms
self.fractions[key] = frac
except:
if len(self.species) == 0:
self.fractions = {}
else:
print('Check MagStructure.fractions dictionary for problems.')
def makeKfactors(self):
"""Set the factors K1 and K2 used for unnormalized mPDF. The fractions
dictionary must be accurate before running this method.
"""
K1, K2 = 0, 0
for key in self.species:
ga = self.species[key].g
Ja = self.species[key].J
K1 += self.fractions[key]*ga*np.sqrt(Ja*(Ja+1))
K2 += self.fractions[key]*ga**2*Ja*(Ja+1)
K1 = K1**2
K1 *= (1.913*2.81794/2.0)**2*2.0/3.0
K2 *= (1.913*2.81794/2.0)**2*2.0/3.0
self.K1 = K1
self.K2 = K2
def makeFF(self):
"""Generate the properly weighted average magnetic form factor of all
the magnetic species in the structure.
"""
try:
self.ffqgrid = list(self.species.values())[0].ffqgrid
self.ff = np.zeros_like(self.ffqgrid)
totatoms = 0.0
for key in self.species:
totatoms += self.species[key].atoms.shape[0]
for key in self.species:
frac = float(self.species[key].atoms.shape[0])/totatoms
self.species[key].makeFF()
self.ff += frac*self.species[key].ff
except:
if len(self.species) == 0:
self.ff = jCalc(self.ffqgrid)
else:
print('Check that all mag species have same q-grid.')
def makeAll(self):
"""Shortcut method to generate atoms, spins, g-factors, and form
factor for the magnetic structure all in one go.
"""
self.makeAtoms()
self.makeSpins()
self.makeGfactors()
self.makeFractions()
self.makeKfactors()
self.makeFF()
self.makeCalcIdxs()
self.runChecks(doCalcIdxsCheck=True)
def spinsFromAtoms(self,positions,fractional=True,returnIdxs=False):
"""Return the spin vectors corresponding to specified atomic
positions.
This method calls the diffpy.mpdf.spinsFromAtoms() method.
Args:
magstruc: MagSpecies or MagStructure object containing atoms and spins
positions (list or array): atomic positions for which the
corresponding spins should be returned.
fractional (boolean): set as True if the atomic positions are in
fractional coordinates of the crystallographic lattice
vectors.
returnIdxs (boolean): if True, the indices of the spins will also be
returned.
Returns:
Array consisting of the spins corresponding to the atomic positions.
"""
return spinsFromAtoms(self,positions,fractional,returnIdxs)
def atomsFromSpins(self,spinvecs,fractional=True,returnIdxs=False):
"""Return the atomic positions corresponding to specified spins.
This method calls the diffpy.mpdf.atomsFromSpins() method.
Args:
magstruc: MagSpecies or MagStructure object containing atoms and spins
spinvecs (list or array): spin vectors for which the
corresponding atoms should be returned.
fractional (boolean): set as True if the atomic positions are to be
returned as fractional coordinates of the crystallographic lattice
vectors.
returnIdxs (boolean): if True, the indices of the atoms will also be
returned.
Returns:
List of arrays of atoms corresponding to the spins.
"""
return atomsFromSpins(self,spinvecs,fractional,returnIdxs)
def visualize(self,atoms,spins,showcrystalaxes=False,
axesorigin=np.array([0,0,0])):
"""Generate a crude 3-d plot to visualize the selected spins.
Args:
atoms (numpy array): array of atomic positions of spins to be
visualized.
spins (numpy array): array of spin vectors in same order as atoms.
showcrystalaxes (boolean): if True, will display the crystal axes
determined from the first magnetic species in the MagStructure
axesorigin (array): position at which the crystal axes should be
displayed
"""
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
fig = visualizeSpins(atoms,spins)
if showcrystalaxes:
ax3d = fig.axes[0]
try:
mspec=list(self.species.items())[0][1]
if mspec.useDiffpyStruc:
lat=mspec.struc.lattice
a, b, c = lat.stdbase
else:
a, b, c = mspec.latVecs
xo, yo, zo = axesorigin
ax3d.quiver(xo, yo, zo, a[0], a[1], a[2], pivot='tail', color='r')
ax3d.quiver(xo, yo, zo, b[0], b[1], b[2], pivot='tail', color='g')
ax3d.quiver(xo, yo, zo, c[0], c[1], c[2], pivot='tail', color='b')
except:
print('Please make sure your magnetic structure contains a')
print('magnetic species with MagSpecies.struc set to a diffpy')
print('structure or MagSpecies.latVecs provided and')
print('MagSpecies.useDiffpyStruc set to False.')
plt.show()
def findAtomIndices(self,atomList):
"""Return list of indices corresponding to input list of atomic coordinates.
This method calls the diffpy.mpdf.findAtomIndices() method.
Args:
atomList (numpy array of atomic coordinates)
Returns:
List of indices corresponding to the atomList.
"""
return findAtomIndices(self,atomList)
def runChecks(self, doCalcIdxsCheck=False):
"""Run some simple checks and raise a warning if a problem is found.
"""
# do the MagSpecies checks
for key in self.species:
self.species[key].runChecks()
if self.verbose:
print(('Running checks for '+self.label+' MagStructure object...\n'))
flag = False
flagCount = 0
# check for duplication among magnetic species
if len(self.species) > 0:
if list(self.species.values())[0].useDiffpyStruc:
idxs = []
for key in self.species:
idxs.append(self.species[key].strucIdxs)
idxs = [item for sublist in idxs for item in sublist] # flatten the list
for idx in idxs:
if idxs.count(idx) > 1:
flag = True
if flag:
flagCount += 1
if self.verbose:
print('Warning: Magnetic species may have overlapping atoms.')
print('Check the strucIdxs lists for your magnetic species.')
flag = False
# check that the fractions are consistent
totatoms = 0.0
for key in self.species:
totatoms += self.species[key].atoms.shape[0]
for key in self.species:
if totatoms == 0.0:
totatoms = 1.0 # prevent divide by zero problems
frac = float(self.species[key].atoms.shape[0])/totatoms
if (frac > 0) and (np.abs(frac - self.fractions[key])/frac > 0.1):
flag = True
if flag:
flagCount += 1
if self.verbose:
print('Species fractions do not correspond to actual number of')
print('spins of each species in the structure.')
flag = False
### check if calcIdxs may not be representative of all MagSpecies.
if doCalcIdxsCheck: # option to turn off this check e.g. when loading a MagSpecies
if len(self.calcIdxs) < len(self.species):
flag = True
if flag:
flagCount += 1
print('Warning: your calcIdxs may not be representative of all')
print('the magnetic species. calcIdxs should have the index of')
print('at least one spin from each species. Use')
print('magStruc.getSpeciesIdxs() to see starting indices for')
print('each species.\n')
flag = False
### check if calcIdxs has indices that exceed the spin array
if self.atoms.shape[0]>0:
if (np.array(self.calcIdxs).max()+1) > self.atoms.shape[0]:
flag = True
if flag:
flagCount += 1
print('calcIdxs contains indices that are too large for the')
print('arrays of atoms and spins contained in the MagStructure.')
flag = False
# summarize results
if flagCount == 0:
if self.verbose:
print('All MagStructure checks passed. No obvious problems found.')
def getSpeciesIdxs(self):
"""Return a dictionary with the starting index in the atoms and spins
arrays corresponding to each magnetic species.
"""
idxDict = {}
startIdx = 0
for key in self.species:
idxDict[key] = startIdx
startIdx += self.species[key].atoms.shape[0]
return idxDict
def makeCalcIdxs(self):
"""Generate the indices of the atoms to be used for the calculation.
"""
idxDict = self.getSpeciesIdxs()
calcIdxs = []
for key in self.species:
calcIdxs.append(np.array(self.species[key].calcIdxs) +
idxDict[key])
calcIdxs = [ci for sublist in calcIdxs for ci in sublist]
self.calcIdxs = calcIdxs
def generateScaledSpins(self, originIdx=0):
"""Apply a correlation length to the spin magnitudes.
Args:
originIdx (int): The index of the spin in magstructure.spins
that should be considered the origin.
Returns:
scaledSpins (np.array): An array with the same shape as the
self.spins array, where the magnitudes of the spins have
been scaled in accordance with the correlation length.
"""
scaledSpins = 1.0*self.spins
if type(self.dampingMat) != np.ndarray: # isotropic correlation length
xi = self.corrLength
if xi != 0.0:
distanceVecs = self.atoms - self.atoms[originIdx]
distances = np.apply_along_axis(np.linalg.norm, 1, distanceVecs)
rescale = np.exp(-distances/xi)[:,np.newaxis]
scaledSpins *= rescale
else:
dampingMat = self.dampingMat
# ensure that dampingMat is 3x3 symmetric matrix
if dampingMat.shape == (3, 3):
if np.allclose(dampingMat, dampingMat.T, rtol=1e-5, atol=1e-8):
distanceVecs = self.atoms - self.atoms[originIdx]
distances = np.apply_along_axis(np.linalg.norm, 1, distanceVecs)
distanceVecsN = distanceVecs/np.apply_along_axis(np.linalg.norm,1,distanceVecs)[:,np.newaxis]
mult1 = np.tensordot(dampingMat, distanceVecsN, axes=(0,1)).T
xi = 1.0/np.sqrt(np.apply_along_axis(np.sum, 1, distanceVecsN*mult1))
xi[originIdx] = 1.0 # avoid divide-by-zero problem
rescale = np.exp(-distances/xi)[:,np.newaxis]
scaledSpins *= rescale
else:
print('Damping matrix is not symmetric. Spins will not be modified.')
else:
print('Damping matrix is not a 3x3 matrix. Spins will not be modified.')
return scaledSpins
def calcAtomicDensity(self, volume=0, numSpins=0):
"""Determine the number density of magnetic moments.
Sets the calculated number density equal to self.rho0.
Args:
volume (scalar): Volume of the MagStructure. If equal to the
default value of 0, then the volume will be calculated
assuming a sphere of radius rmaxAtoms.
numSpins (integer): number of magnetic moments in the volume
being considered. If equal to the default value of 0, then
the numSpins will be set to the length of the spins array.
"""
if self.struc != []:
volume = np.sqrt(np.linalg.det(self.struc.lattice.metrics))
for key in self.species:
#strucIdxs = self.species[key].strucIdxs
#numSpins += self.species[key].struc.occupancy[strucIdxs].sum()
numSpins += len(self.species[key].strucIdxs)
self.rho0 = numSpins / volume
else:
print('Please create a diffpy Structure object to use this feature')
# if volume==0:
# radius = self.rmaxAtoms + \
# np.linalg.norm(self.struc.lattice.stdbase.sum(axis=1))
# volume = 1.33333*np.pi*radius**3
# if numSpins==0:
# numSpins = len(self.spins)
# self.rho0 = numSpins / volume
def calcMagneticAtomRatio(self):
"""Determine the ratio of magnetic atoms to total atoms.
Sets the calculated number equal to self.magneticAtomRatio.
"""
occ = self.struc.occupancy
totalOcc = occ.sum()
numMagAtoms = 0
if self.struc != []:
for key in self.species:
strucIdxs = self.species[key].strucIdxs
numMagAtoms += occ[strucIdxs].sum()
self.magneticAtomRatio = numMagAtoms / totalOcc
else:
print('Please create a diffpy Structure object to use this feature')
def calcNetMag(self,method='directCalculation'):
"""Determine the net magnetization per spin.
Sets the calculated value equal to self.netMag.
This method is only necessary for magnetic structures with a
net magnetic moment, such as a ferromagnet, ferrimagnet, or
canted antiferromagnet.
Args:
method: How the net magnetization should be calculated.
'directCalculation': the mean of the entire structure is
calculated. May have slight inaccuracies due to edge effects.
'speciesCalculation': use the individual MagSpecies to find
the average magnetization per spin.
"""
if method == 'directCalculation':
totalMag = np.sum(self.spins, axis=0) * np.mean(self.gfactors)
self.netMag = np.linalg.norm(totalMag) / self.spins.shape[0]
if method == 'speciesCalculation':
mags = []
weights = []
netMag = np.array([0.0,0.0,0.0])
if self.struc != []:
for key in self.species:
g = self.species[key].gS + self.species[key].gL
mag = g * np.linalg.norm(self.species[key].spins[0]) # assumes constant magnitude of spins
mags.append(mag)
weight = np.sum(self.struc.occupancy[self.species[key].strucIdxs])
weights.append(weight)
netMag += g * weight * self.species[key].spins[0] # assumes uniform spins within the species
mags = np.array(mags)
weights = np.array(weights)
#self.netMag = np.sum(mags * weights)/np.sum(weights)
self.netMag = np.linalg.norm(netMag/np.sum(weights))
else:
print('Please create a diffpy Structure object to use this feature')
def copy(self):
"""Return a deep copy of the MagStructure object."""
return copy.deepcopy(self)
Classes
class MagSpecies (struc=None, label=None, strucIdxs=None, atoms=None, spins=None, calcIdxs=[0], rmaxAtoms=30.0, avgmom=None, basisvecs=None, kvecs=None, S=0.5, L=0.0, J=None, gS=None, gL=None, g=None, j2type=None, ffparamkey=None, ffqgrid=None, ff=None, useDiffpyStruc=True, latVecs=None, atomBasis=None, spinBasis=None, origin=None, verbose=False, useOcc=False, occ=None)
-
Store information for a single species of magnetic atom.
This class takes a diffpy.Structure object and uses it to generate spins based on a set of propagation vectors and basis vectors. For more info about magnetic propagation vectors, visit e.g. http://dx.doi.org/10.1051/jp4:2001906 or Appendix B of Andrew Steele's dissertation at https://andrewsteele.co.uk/physics/thesis .
Args
struc
:diffpy.Structure object
- provides lattice parameters and unit cell of desired structure.
label
:string
- label for this particular magnetic species. Should be different from the labels for any other magnetic species you make. Default is a random 8-character hexadecimal string.
strucIdxs
:python list
- list of integers giving indices of magnetic atoms in the unit cell
atoms
:numpy array
- list of atomic coordinates of all the magnetic atoms in the structure; e.g. generated by generateAtomsXYZ()
spins
:numpy array
- triplets giving the spin vectors of all the atoms, in the same order as the atoms array provided as input. In units of hbar.
calcIdxs
:python list
- list giving the indices of the atoms array specifying the atoms to be used as the origin when calculating the mPDF. If given the string argument 'all', then every atom will be used (potentially causing very long calculation times). These indices are relative to the atoms array for this specific MagSpecies, not relative to the atoms array for the MagStructure as a whole.
rmaxAtoms
:float
- maximum distance from the origin of atomic positions generated by the makeAtoms method.
avgmom
:numpy array
- three-vector giving the average magnetic moment for this species of magnetic atom to generate the spins. Relevant to incommensurate structures generated from an mcif.
basisvecs
:numpy array
- nested three-vector(s) giving the basis vectors to generate the spins. e.g. np.array([[0, 0, 1]]). Any phase factor should be included directly with the basisvecs.
kvecs
:numpy array
- nested three-vector(s) giving the propagation vectors for the magnetic structure in r.l.u., e.g. np.array([[0.5, 0.5, 0.5]])
S
:float
- Spin angular momentum quantum number in units of hbar.
L
:float
- Orbital angular momentum quantum number in units of hbar.
J
:float
- Total angular momentum quantum number in units of hbar.
gS
:float
- spin component of the Lande g-factor (g = gS+gL). Calculated automatically from S, L, and J if not specified.
gL
:float
- orbital component of the Lande g-factor. Calculated automatically from S, L, and J if not specified.
g
:float
- Lande g-factor. Calculated automatically as gS+gL if not specified.
j2type
:string
- Specifies the way the j2 integral is included in the magnetic form factor. Must be either 'RE' for rare earth or 'TM' for transition metal. If 'RE', the coefficient on the j2 integral is gL/g; if 'TM', the coefficient is (g-2)/g. Default is 'RE'. Note that for the default values of S, L, and J, we will have gS=2, gL=0, and g=2, and there will be no difference in the form factor calculation for 'RE' or 'TM'.
ffparamkey
:string
- gives the appropriate key for getFFparams()
ffqgrid
:numpy array
- grid of momentum transfer values used for calculating the magnetic form factor.
ff
:numpy array
- magnetic form factor.
useDiffpyStruc
:boolean
- True if atoms/spins to be generated from a diffpy structure object; False if a user-provided unit cell is to be used. Note that there may be some problems with user- provided unit cells with lattice angles strongly deviated from 90 degrees.
latVecs
:numpy array
- Provides the unit cell lattice vectors as np.array([avec, bvec, cvec]). Only useful if useDiffpyStruc = False.
atomBasis
:numpy array
- Provides positions of the magnetic atoms in fractional coordinates within the unit cell. Only useful if useDiffpyStruc = False. Example: np.array([[0, 0, 0], [0.5, 0.5, 0.5]])
spinBasis
:numpy array
- Provides the orientations of the spins in the unit cell, in the same order as atomBasis. Only useful if useDiffpyStruc = False. Example: np.array([[0, 0, 1], [0, 0, -1]]
origin
:numpy array
- Cartesian coordinates of the position that will be considered the origin when generating spin directions from basis vectors and propagation vectors. Default is np.array([0,0,0]).
verbose
:boolean
- If True, will print messages relating to the structure. Useful for troubleshooting. Default is False.
useOcc
:boolean
- If True, atomic site occupancies will be used to scale the magnetic moment vector. Default is False.
occ
:scalar
- Occupancy of the magnetic atom associated with this MagSpecies. Default is 1.
Expand source code
class MagSpecies: """Store information for a single species of magnetic atom. This class takes a diffpy.Structure object and uses it to generate spins based on a set of propagation vectors and basis vectors. For more info about magnetic propagation vectors, visit e.g. http://dx.doi.org/10.1051/jp4:2001906 or Appendix B of Andrew Steele's dissertation at https://andrewsteele.co.uk/physics/thesis . Args: struc (diffpy.Structure object): provides lattice parameters and unit cell of desired structure. label (string): label for this particular magnetic species. Should be different from the labels for any other magnetic species you make. Default is a random 8-character hexadecimal string. strucIdxs (python list): list of integers giving indices of magnetic atoms in the unit cell atoms (numpy array): list of atomic coordinates of all the magnetic atoms in the structure; e.g. generated by generateAtomsXYZ() spins (numpy array): triplets giving the spin vectors of all the atoms, in the same order as the atoms array provided as input. In units of hbar. calcIdxs (python list): list giving the indices of the atoms array specifying the atoms to be used as the origin when calculating the mPDF. If given the string argument 'all', then every atom will be used (potentially causing very long calculation times). These indices are relative to the atoms array for this specific MagSpecies, not relative to the atoms array for the MagStructure as a whole. rmaxAtoms (float): maximum distance from the origin of atomic positions generated by the makeAtoms method. avgmom (numpy array): three-vector giving the average magnetic moment for this species of magnetic atom to generate the spins. Relevant to incommensurate structures generated from an mcif. basisvecs (numpy array): nested three-vector(s) giving the basis vectors to generate the spins. e.g. np.array([[0, 0, 1]]). Any phase factor should be included directly with the basisvecs. kvecs (numpy array): nested three-vector(s) giving the propagation vectors for the magnetic structure in r.l.u., e.g. np.array([[0.5, 0.5, 0.5]]) S (float): Spin angular momentum quantum number in units of hbar. L (float): Orbital angular momentum quantum number in units of hbar. J (float): Total angular momentum quantum number in units of hbar. gS (float): spin component of the Lande g-factor (g = gS+gL). Calculated automatically from S, L, and J if not specified. gL (float): orbital component of the Lande g-factor. Calculated automatically from S, L, and J if not specified. g (float): Lande g-factor. Calculated automatically as gS+gL if not specified. j2type (string): Specifies the way the j2 integral is included in the magnetic form factor. Must be either 'RE' for rare earth or 'TM' for transition metal. If 'RE', the coefficient on the j2 integral is gL/g; if 'TM', the coefficient is (g-2)/g. Default is 'RE'. Note that for the default values of S, L, and J, we will have gS=2, gL=0, and g=2, and there will be no difference in the form factor calculation for 'RE' or 'TM'. ffparamkey (string): gives the appropriate key for getFFparams() ffqgrid (numpy array): grid of momentum transfer values used for calculating the magnetic form factor. ff (numpy array): magnetic form factor. useDiffpyStruc (boolean): True if atoms/spins to be generated from a diffpy structure object; False if a user-provided unit cell is to be used. Note that there may be some problems with user- provided unit cells with lattice angles strongly deviated from 90 degrees. latVecs (numpy array): Provides the unit cell lattice vectors as np.array([avec, bvec, cvec]). Only useful if useDiffpyStruc = False. atomBasis (numpy array): Provides positions of the magnetic atoms in fractional coordinates within the unit cell. Only useful if useDiffpyStruc = False. Example: np.array([[0, 0, 0], [0.5, 0.5, 0.5]]) spinBasis (numpy array): Provides the orientations of the spins in the unit cell, in the same order as atomBasis. Only useful if useDiffpyStruc = False. Example: np.array([[0, 0, 1], [0, 0, -1]] origin (numpy array): Cartesian coordinates of the position that will be considered the origin when generating spin directions from basis vectors and propagation vectors. Default is np.array([0,0,0]). verbose (boolean): If True, will print messages relating to the structure. Useful for troubleshooting. Default is False. useOcc (boolean): If True, atomic site occupancies will be used to scale the magnetic moment vector. Default is False. occ (scalar): Occupancy of the magnetic atom associated with this MagSpecies. Default is 1. """ def __init__(self, struc=None, label=None, strucIdxs=None, atoms=None, spins=None, calcIdxs=[0], rmaxAtoms=30.0, avgmom=None, basisvecs=None, kvecs=None, S=0.5, L=0.0, J=None, gS=None, gL=None, g=None, j2type=None, ffparamkey=None, ffqgrid=None, ff=None, useDiffpyStruc=True, latVecs=None, atomBasis=None, spinBasis=None, origin=None, verbose=False, useOcc=False, occ=None): if label is None: hex_string = '0123456789abcdef' token = ''.join([random.choice(hex_string) for i in range(8)]) self.label = token else: self.label = label self.rmaxAtoms = rmaxAtoms self.S = S self.L = L if J is None: J = S + L self.J = J else: self.J = J if gS is None: self.gS = 1.0 + 1.0*(S*(S+1)-L*(L+1))/(J*(J+1)) else: self.gS = gS if gL is None: self.gL = 0.5 + 1.0*(L*(L+1)-S*(S+1))/(2*J*(J+1)) else: self.gL = gL if g is None: self.g = self.gS + self.gL else: self.g = g if j2type is None: self.j2type = 'RE' else: self.j2type = j2type self.ffparamkey = ffparamkey self.useDiffpyStruc = useDiffpyStruc if strucIdxs is None: self.strucIdxs = [0] else: self.strucIdxs = strucIdxs if calcIdxs is None: self.calcIdxs = [0] else: self.calcIdxs = calcIdxs if struc is None: self.struc = [] else: self.struc = struc if atoms is None: self.atoms = np.array([]) else: self.atoms = atoms if spins is None: self.spins = np.array([]) else: self.spins = spins if avgmom is None: self.avgmom = np.array([0, 0, 0]) else: self.avgmom = avgmom if basisvecs is None: self.basisvecs = np.array([[0, 0, 1]]) else: self.basisvecs = basisvecs if kvecs is None: self.kvecs = np.array([[0, 0, 0]]) else: self.kvecs = kvecs if ff is None: self.ff = np.array([]) else: self.ff = ff if ffqgrid is None: self.ffqgrid = np.arange(0, 10.0, 0.01) else: self.ffqgrid = ffqgrid if latVecs is None: self.latVecs = np.array([[4., 0, 0], [0, 4., 0], [0, 0, 4.]]) else: self.ff = latVecs if atomBasis is None: self.atomBasis = np.array([[0, 0, 0]]) else: self.atomBasis = atomBasis if spinBasis is None: self.spinBasis = np.array([[0, 0, 1]]) else: self.spinBasis = spinBasis if origin is None: self.origin = np.array([[0, 0, 0]]) else: self.origin = origin self.verbose = verbose self.useOcc = useOcc if occ is None: self.occ = 1.0 else: self.occ = occ def __repr__(self): if self.label == '': return 'MagSpecies() object' else: return self.label+': MagSpecies() object' def makeAtoms(self): """Generate the Cartesian coordinates of the atoms for this species. """ if self.useDiffpyStruc: self.atoms = generateAtomsXYZ(self.struc, self.rmaxAtoms, self.strucIdxs) else: try: self.atoms, self.spins = generateFromUnitCell(self.latVecs, self.atomBasis, self.spinBasis, self.rmaxAtoms) except: print('Please check latVecs, atomBasis, and spinBasis.') def makeSpins(self): """Generate the Cartesian coordinates of the spin vectors in the structure. Must provide propagation vector(s) and basis vector(s). """ #self.setOcc() if self.useDiffpyStruc: self.spins = generateSpinsXYZ(self.struc, self.atoms, self.kvecs, self.basisvecs, self.origin, self.avgmom) if self.useOcc: self.spins *= self.occ else: print('Since you are not using a diffpy Structure object,') print('the spins are generated from the makeAtoms() method.') print('Please call that method if you have not already.') def makeFF(self): """Generate the magnetic form factor. """ # g = self.gS+self.gL # move this up to initialization of MagSpecies if getFFparams(self.ffparamkey) != ['none']: if self.j2type == 'RE': self.ff = (jCalc(self.ffqgrid, getFFparams(self.ffparamkey))+ self.gL/self.g * jCalc(self.ffqgrid, getFFparams(self.ffparamkey), j2=True)) elif self.j2type == 'TM': self.ff = (jCalc(self.ffqgrid, getFFparams(self.ffparamkey))+ (self.g-2)/self.g * jCalc(self.ffqgrid, getFFparams(self.ffparamkey), j2=True)) else: print("j2type argument must be either 'RE' or 'TM'. Using generic magnetic form factor.") self.ff = jCalc(self.ffqgrid) else: print('Using generic magnetic form factor.') self.ff = jCalc(self.ffqgrid) def spinsFromAtoms(self,positions,fractional=True,returnIdxs=False): """Return the spin vectors corresponding to specified atomic positions. This method calls the diffpy.mpdf.magutils.spinsFromAtoms() method. Args: magstruc: MagSpecies or MagStructure object containing atoms and spins positions (list or array): atomic positions for which the corresponding spins should be returned. fractional (boolean): set as True if the atomic positions are in fractional coordinates of the crystallographic lattice vectors. returnIdxs (boolean): if True, the indices of the spins will also be returned. Returns: Array consisting of the spins corresponding to the atomic positions. """ return spinsFromAtoms(self,positions,fractional,returnIdxs) def atomsFromSpins(self,spinvecs,fractional=True,returnIdxs=False): """Return the atomic positions corresponding to specified spins. This method calls the diffpy.mpdf.magutils.atomsFromSpins() method. Args: magstruc: MagSpecies or MagStructure object containing atoms and spins spinvecs (list or array): spin vectors for which the corresponding atoms should be returned. fractional (boolean): set as True if the atomic positions are to be returned as fractional coordinates of the crystallographic lattice vectors. returnIdxs (boolean): if True, the indices of the atoms will also be returned. Returns: List of arrays of atoms corresponding to the spins. """ return atomsFromSpins(self,spinvecs,fractional,returnIdxs) def findAtomIndices(self,atomList): """Return list of indices corresponding to input list of atomic coordinates. This method calls the diffpy.mpdf.findAtomIndices() method. Args: atomList (numpy array of atomic coordinates) Returns: List of indices corresponding to the atomList. """ return findAtomIndices(self,atomList) def setOcc(self): """Set the MagSpecies.occ attribute according to the Diffpy Structure. """ if self.struc != []: self.occ = np.mean(self.struc.occupancy[self.strucIdxs]) else: self.occ *= 1 def runChecks(self): """Run some simple checks and raise a warning if a problem is found. """ if self.verbose: print(('Running checks for '+self.label+' MagSpecies object...\n')) flagCount = 0 flag = False if self.useDiffpyStruc: # check that basisvecs and kvecs have same shape if self.kvecs.shape != self.basisvecs.shape: flag = True if flag: flagCount += 1 if self.verbose: print('kvecs and basisvecs must have the same dimensions.') else: # check for improperlatVecs array if self.latVecs.shape != (3, 3): flag = True if flag: flagCount += 1 if self.verbose: print('latVecs array does not have the correct dimensions.') print('It must be a 3 x 3 nested array.') print('Example: np.array([[4, 0, 0], [0, 4, 0], [0, 0, 4]])') flag = False # check for mismatched number of atoms and spins in basis if self.atomBasis.shape != self.spinBasis.shape: flag = True if flag: flagCount += 1 if self.verbose: print('atomBasis and spinBasis must have the same dimensions.') # summarize results if flagCount == 0: if self.verbose: print('All MagSpecies() checks passed. No obvious problems found.\n') def copy(self): """Return a deep copy of the MagSpecies object. """ return copy.deepcopy(self)
Methods
def atomsFromSpins(self, spinvecs, fractional=True, returnIdxs=False)
-
Return the atomic positions corresponding to specified spins.
This method calls the diffpy.mpdf.magutils.atomsFromSpins() method.
Args
magstruc
- MagSpecies or MagStructure object containing atoms and spins
spinvecs
:list
orarray
- spin vectors for which the corresponding atoms should be returned.
fractional
:boolean
- set as True if the atomic positions are to be returned as fractional coordinates of the crystallographic lattice vectors.
returnIdxs
:boolean
- if True, the indices of the atoms will also be returned.
Returns
List of arrays of atoms corresponding to the spins.
Expand source code
def atomsFromSpins(self,spinvecs,fractional=True,returnIdxs=False): """Return the atomic positions corresponding to specified spins. This method calls the diffpy.mpdf.magutils.atomsFromSpins() method. Args: magstruc: MagSpecies or MagStructure object containing atoms and spins spinvecs (list or array): spin vectors for which the corresponding atoms should be returned. fractional (boolean): set as True if the atomic positions are to be returned as fractional coordinates of the crystallographic lattice vectors. returnIdxs (boolean): if True, the indices of the atoms will also be returned. Returns: List of arrays of atoms corresponding to the spins. """ return atomsFromSpins(self,spinvecs,fractional,returnIdxs)
def copy(self)
-
Return a deep copy of the MagSpecies object.
Expand source code
def copy(self): """Return a deep copy of the MagSpecies object. """ return copy.deepcopy(self)
def findAtomIndices(self, atomList)
-
Return list of indices corresponding to input list of atomic coordinates.
This method calls the diffpy.mpdf.findAtomIndices() method.
Args
atomList (numpy array of atomic coordinates)
Returns
List of indices corresponding to the atomList.
Expand source code
def findAtomIndices(self,atomList): """Return list of indices corresponding to input list of atomic coordinates. This method calls the diffpy.mpdf.findAtomIndices() method. Args: atomList (numpy array of atomic coordinates) Returns: List of indices corresponding to the atomList. """ return findAtomIndices(self,atomList)
def makeAtoms(self)
-
Generate the Cartesian coordinates of the atoms for this species.
Expand source code
def makeAtoms(self): """Generate the Cartesian coordinates of the atoms for this species. """ if self.useDiffpyStruc: self.atoms = generateAtomsXYZ(self.struc, self.rmaxAtoms, self.strucIdxs) else: try: self.atoms, self.spins = generateFromUnitCell(self.latVecs, self.atomBasis, self.spinBasis, self.rmaxAtoms) except: print('Please check latVecs, atomBasis, and spinBasis.')
def makeFF(self)
-
Generate the magnetic form factor.
Expand source code
def makeFF(self): """Generate the magnetic form factor. """ # g = self.gS+self.gL # move this up to initialization of MagSpecies if getFFparams(self.ffparamkey) != ['none']: if self.j2type == 'RE': self.ff = (jCalc(self.ffqgrid, getFFparams(self.ffparamkey))+ self.gL/self.g * jCalc(self.ffqgrid, getFFparams(self.ffparamkey), j2=True)) elif self.j2type == 'TM': self.ff = (jCalc(self.ffqgrid, getFFparams(self.ffparamkey))+ (self.g-2)/self.g * jCalc(self.ffqgrid, getFFparams(self.ffparamkey), j2=True)) else: print("j2type argument must be either 'RE' or 'TM'. Using generic magnetic form factor.") self.ff = jCalc(self.ffqgrid) else: print('Using generic magnetic form factor.') self.ff = jCalc(self.ffqgrid)
def makeSpins(self)
-
Generate the Cartesian coordinates of the spin vectors in the structure. Must provide propagation vector(s) and basis vector(s).
Expand source code
def makeSpins(self): """Generate the Cartesian coordinates of the spin vectors in the structure. Must provide propagation vector(s) and basis vector(s). """ #self.setOcc() if self.useDiffpyStruc: self.spins = generateSpinsXYZ(self.struc, self.atoms, self.kvecs, self.basisvecs, self.origin, self.avgmom) if self.useOcc: self.spins *= self.occ else: print('Since you are not using a diffpy Structure object,') print('the spins are generated from the makeAtoms() method.') print('Please call that method if you have not already.')
def runChecks(self)
-
Run some simple checks and raise a warning if a problem is found.
Expand source code
def runChecks(self): """Run some simple checks and raise a warning if a problem is found. """ if self.verbose: print(('Running checks for '+self.label+' MagSpecies object...\n')) flagCount = 0 flag = False if self.useDiffpyStruc: # check that basisvecs and kvecs have same shape if self.kvecs.shape != self.basisvecs.shape: flag = True if flag: flagCount += 1 if self.verbose: print('kvecs and basisvecs must have the same dimensions.') else: # check for improperlatVecs array if self.latVecs.shape != (3, 3): flag = True if flag: flagCount += 1 if self.verbose: print('latVecs array does not have the correct dimensions.') print('It must be a 3 x 3 nested array.') print('Example: np.array([[4, 0, 0], [0, 4, 0], [0, 0, 4]])') flag = False # check for mismatched number of atoms and spins in basis if self.atomBasis.shape != self.spinBasis.shape: flag = True if flag: flagCount += 1 if self.verbose: print('atomBasis and spinBasis must have the same dimensions.') # summarize results if flagCount == 0: if self.verbose: print('All MagSpecies() checks passed. No obvious problems found.\n')
def setOcc(self)
-
Set the MagSpecies.occ attribute according to the Diffpy Structure.
Expand source code
def setOcc(self): """Set the MagSpecies.occ attribute according to the Diffpy Structure. """ if self.struc != []: self.occ = np.mean(self.struc.occupancy[self.strucIdxs]) else: self.occ *= 1
def spinsFromAtoms(self, positions, fractional=True, returnIdxs=False)
-
Return the spin vectors corresponding to specified atomic positions.
This method calls the diffpy.mpdf.magutils.spinsFromAtoms() method.
Args
magstruc
- MagSpecies or MagStructure object containing atoms and spins
positions
:list
orarray
- atomic positions for which the corresponding spins should be returned.
fractional
:boolean
- set as True if the atomic positions are in fractional coordinates of the crystallographic lattice vectors.
returnIdxs
:boolean
- if True, the indices of the spins will also be returned.
Returns
Array consisting of the spins corresponding to the atomic positions.
Expand source code
def spinsFromAtoms(self,positions,fractional=True,returnIdxs=False): """Return the spin vectors corresponding to specified atomic positions. This method calls the diffpy.mpdf.magutils.spinsFromAtoms() method. Args: magstruc: MagSpecies or MagStructure object containing atoms and spins positions (list or array): atomic positions for which the corresponding spins should be returned. fractional (boolean): set as True if the atomic positions are in fractional coordinates of the crystallographic lattice vectors. returnIdxs (boolean): if True, the indices of the spins will also be returned. Returns: Array consisting of the spins corresponding to the atomic positions. """ return spinsFromAtoms(self,positions,fractional,returnIdxs)
class MagStructure (struc=None, species=None, atoms=None, spins=None, gfactors=None, rmaxAtoms=30.0, avgmom=None, ffqgrid=None, ff=None, label='', K1=None, K2=None, fractions=None, Uiso=0.01, calcIdxs=None, corrLength=0.0, dampingMat=0.0, verbose=False, netMag=0, rho0=0, magneticAtomRatio=0, atomic_struc=None, transform='')
-
Build on the diffpy.Structure class to include magnetic attributes.
This class takes a diffpy.Structure object and packages additional info relating to magnetic structure, which can then be fed to an MPDFcalculator object.
Args
struc
:diffpy.Structure object
- provides lattice parameters and unit cell of desired structure.
atomic_struc
:diffpy.Structure object
- provides atomic structure inferred from the MCIF file. Only relevant if reading in MCIF file. If not initialized, it is assumed that either the magnetic and atomic unit cells are equivalent or spins were generated from the atomic unit cell using basis vectors.
transform
:string
- string describing the transformation from the atomic cell basis vectors to the magnetic cell basis vector. Should be present whenever atomic_struc is.
species
:python dictionary
- dictionary of magnetic species in the structure. The values are MagSpecies objects.
atoms
:numpy array
- list of atomic coordinates of all the magnetic atoms in the structure; e.g. generated by generateAtomsXYZ()
spins
:numpy array
- triplets giving the spin vectors of all the atoms, in the same order as the atoms array provided as input.
gfactors
:numpy array
- Lande g-factors of the magnetic moments
rmaxAtoms
:float
- maximum distance from the origin of atomic positions generated by the makeAtoms method.
ffqgrid
:numpy array
- grid of momentum transfer values used for calculating the magnetic form factor.
ff
:numpy array
- magnetic form factor. Should be same shape as ffqgrid.
label
:string
- Optional descriptive string for the MagStructure.
K1
:float
- a constant used for calculating Dr; should be averaged over all magnetic species. Important if physical information is to be extracted from mPDF scale factors, e.g. moment size.
K2
:float
- another constant used for calculating Dr.
fractions
:python dictionary
- Dictionary providing the fraction of spins in the magnetic structure corresponding to each species.
verbose
:boolean
- If True, will print messages relating to the structure. Useful for troubleshooting. Default is False.
calcIdxs
:python list
- list giving the indices of the atoms array specifying the atoms to be used as the origin when calculating the mPDF. If given the string argument 'all', then every atom will be used (potentially causing very long calculation times).
corrLength
:scalar
- magnetic correlation length such that the magnitude of the correlation between two spins separated by a distance d is given by exp(-d/corrLength). If set to zero, the correlation length is assumed to be infinite.
dampingMat
:3x3 matrix
- damping matrix that encodes anisotropic correlation lengths. If nonzero, this supercedes the scalar corrLength attribute (which can only be used for isotropic correlation lengths).
rho0
:float
- number of magnetic moments per cubic Angstrom in the magnetic structure; default value is 0.
netMag
:float
- net magnetization in Bohr magnetons per magnetic moment in the sample; default is 0. Only nonzero for ferro/ferrimagnets or canted antiferromagnets.
magneticAtomRatio
:float
- ratio of magnetic atoms to total atoms.
Expand source code
class MagStructure: """Build on the diffpy.Structure class to include magnetic attributes. This class takes a diffpy.Structure object and packages additional info relating to magnetic structure, which can then be fed to an MPDFcalculator object. Args: struc (diffpy.Structure object): provides lattice parameters and unit cell of desired structure. atomic_struc (diffpy.Structure object): provides atomic structure inferred from the MCIF file. Only relevant if reading in MCIF file. If not initialized, it is assumed that either the magnetic and atomic unit cells are equivalent or spins were generated from the atomic unit cell using basis vectors. transform (string): string describing the transformation from the atomic cell basis vectors to the magnetic cell basis vector. Should be present whenever atomic_struc is. species (python dictionary): dictionary of magnetic species in the structure. The values are MagSpecies objects. atoms (numpy array): list of atomic coordinates of all the magnetic atoms in the structure; e.g. generated by generateAtomsXYZ() spins (numpy array): triplets giving the spin vectors of all the atoms, in the same order as the atoms array provided as input. gfactors (numpy array): Lande g-factors of the magnetic moments rmaxAtoms (float): maximum distance from the origin of atomic positions generated by the makeAtoms method. ffqgrid (numpy array): grid of momentum transfer values used for calculating the magnetic form factor. ff (numpy array): magnetic form factor. Should be same shape as ffqgrid. label (string): Optional descriptive string for the MagStructure. K1 (float): a constant used for calculating Dr; should be averaged over all magnetic species. Important if physical information is to be extracted from mPDF scale factors, e.g. moment size. K2 (float): another constant used for calculating Dr. fractions (python dictionary): Dictionary providing the fraction of spins in the magnetic structure corresponding to each species. verbose (boolean): If True, will print messages relating to the structure. Useful for troubleshooting. Default is False. calcIdxs (python list): list giving the indices of the atoms array specifying the atoms to be used as the origin when calculating the mPDF. If given the string argument 'all', then every atom will be used (potentially causing very long calculation times). corrLength (scalar): magnetic correlation length such that the magnitude of the correlation between two spins separated by a distance d is given by exp(-d/corrLength). If set to zero, the correlation length is assumed to be infinite. dampingMat (3x3 matrix): damping matrix that encodes anisotropic correlation lengths. If nonzero, this supercedes the scalar corrLength attribute (which can only be used for isotropic correlation lengths). rho0 (float): number of magnetic moments per cubic Angstrom in the magnetic structure; default value is 0. netMag (float): net magnetization in Bohr magnetons per magnetic moment in the sample; default is 0. Only nonzero for ferro/ferrimagnets or canted antiferromagnets. magneticAtomRatio (float): ratio of magnetic atoms to total atoms. """ def __init__(self, struc=None, species=None, atoms=None, spins=None, gfactors=None, rmaxAtoms=30.0, avgmom=None, ffqgrid=None, ff=None, label='', K1=None, K2=None, fractions=None, Uiso=0.01, calcIdxs=None, corrLength=0.0, dampingMat=0.0, verbose=False, netMag=0, rho0=0, magneticAtomRatio=0, atomic_struc=None, transform=''): self.rmaxAtoms = rmaxAtoms self.label = label self.transform = transform if struc is None: self.struc = [] else: self.struc = struc if atomic_struc is None: self.atomic_struc = [] else: self.atomic_struc = atomic_struc if atoms is None: self.atoms = np.array([]) else: self.atoms = atoms if spins is None: self.spins = np.array([]) else: self.spins = spins if gfactors is None: self.gfactors = np.array([2.0]) else: self.gfactors = gfactors if species is None: self.species = {} else: self.species = species if ffqgrid is None: self.ffqgrid = np.arange(0, 10.0, 0.01) else: self.ffqgrid = ffqgrid if ff is None: self.ff = jCalc(self.ffqgrid) else: self.ff = ff if K1 is None: self.K1 = 0.66667*(1.913*2.81794/2.0)**2*2.0**2*0.5*(0.5+1) else: self.K1 = K1 if K2 is None: self.K2 = self.K1 else: self.K2 = K2 if fractions is None: self.fractions = {} else: self.fractions = fractions if calcIdxs is None: self.calcIdxs = [0] else: self.calcIdxs = calcIdxs self.Uiso = Uiso self.corrLength = corrLength self.dampingMat = dampingMat self.verbose = verbose self.rho0 = rho0 self.netMag = netMag self.magneticAtomRatio = magneticAtomRatio def __repr__(self): if self.label == '': return 'MagStructure() object' else: return self.label+': MagStructure() object' def makeSpecies(self, label, strucIdxs=None, atoms=None, spins=None, basisvecs=None, kvecs=None, S=0.5, L=0.0, J=None, gS=None, gL=None, g=None, j2type=None, ffparamkey=None,ffqgrid=None, ff=None, occ=None): """Create a MagSpecies object and add it to the species dictionary. Args: label (string): label for this particular magnetic species. Should be different from the labels for any other magnetic species you make. strucIdxs (python list): list of integers giving indices of magnetic atoms in the unit cell atoms (numpy array): list of atomic coordinates of all the magnetic atoms in the structure; e.g. generated by generateAtomsXYZ() spins (numpy array): triplets giving the spin vectors of all the atoms, in the same order as the atoms array provided as input. basisvecs (numpy array): nested three-vector(s) giving the basis vectors to generate the spins. e.g. np.array([[0, 0, 1]]). Any phase factor should be included directly with the basisvecs. kvecs (numpy array): nested three-vector(s) giving the propagation vectors for the magnetic structure in r.l.u., e.g. np.array([[0.5, 0.5, 0.5]]) gS (float): spin component of the Lande g-factor (g = gS+gL). Calculated automatically from S, L, and J if not specified. gL (float): orbital component of the Lande g-factor. Calculated automatically from S, L, and J if not specified. g (float): Lande g-factor. Calculated automatically as gS+gL if not specified. j2type (string): Specifies the way the j2 integral is included in the magnetic form factor. Must be either 'RE' for rare earth or 'TM' for transition metal. If 'RE', the coefficient on the j2 integral is gL/g; if 'TM', the coefficient is (g-2)/g. Default is 'RE'. Note that for the default values of S, L, and J, we will have gS=2, gL=0, and g=2, and there will be no difference in the form factor calculation for 'RE' or 'TM'. ffparamkey (string): gives the appropriate key for getFFparams() ffqgrid (numpy array): grid of momentum transfer values used for calculating the magnetic form factor. ff (numpy array): magnetic form factor. """ # check that the label is not a duplicate with any other mag species. duplicate = False for name in list(self.species.keys()): if name == label: duplicate = True if not duplicate: if ffqgrid is None: ffqgrid = np.arange(0, 10.0, 0.01) self.species[label] = MagSpecies(self.struc, label, strucIdxs, atoms, spins, self.rmaxAtoms, basisvecs, kvecs, S, L, J, gS, gL, g, j2type, ffparamkey, ffqgrid, ff, self.verbose, occ) # update the list of fractions totatoms = 0.0 for key in self.species: totatoms += self.species[key].atoms.shape[0] for key in self.species: if totatoms == 0.0: totatoms = 1.0 # prevent divide by zero problems frac = float(self.species[key].atoms.shape[0])/totatoms self.fractions[key] = frac self.runChecks() else: print('This label has already been assigned to another species in') print('the structure. Please choose a new label.') def getCoordsFromSpecies(self): """Read in atomic positions and spins from magnetic species. This differs from makeSpins() and makeAtoms() because it simply loads the atoms and spins from the species without re-generating them from the structure. """ tempA = np.array([[0, 0, 0]]) tempS = np.array([[0, 0, 0]]) for key in self.species: na = self.species[key].atoms.shape[0] ns = self.species[key].atoms.shape[0] if (na > 0) and (na == ns): tempA = np.concatenate((tempA, self.species[key].atoms)) tempS = np.concatenate((tempS, self.species[key].spins)) else: if self.verbose: print(('Coordinates of atoms and spins for ' + key)) print('have not been loaded because they have not yet been') print('generated and/or do not match in shape.') if tempA.shape != (1, 3): self.atoms = tempA[1:] self.spins = tempS[1:] elif len(self.species) == 0: self.atoms = np.array([]) self.spins = np.array([]) def loadSpecies(self, magSpec): """Load in an already-existing MagSpecies object Args: magSpec (MagSpecies object): The magnetic species to be imported into the structure. """ # check that the label is not a duplicate with any other mag species. duplicate = False for name in list(self.species.keys()): if name == magSpec.label: duplicate = True if not duplicate: self.species[magSpec.label] = magSpec self.struc = magSpec.struc self.getCoordsFromSpecies() # update the list of fractions totatoms = 0.0 for key in self.species: totatoms += self.species[key].atoms.shape[0] for key in list(self.species.keys()): if totatoms == 0.0: totatoms = 1.0 # prevent divide by zero problems frac = float(self.species[key].atoms.shape[0])/totatoms self.fractions[key] = frac self.runChecks() else: print('The label for this species has already been assigned to') print('another species in the structure. Please choose a new label') print('for this species.') def removeSpecies(self, label, update=True): """Remove a magnetic species from the species dictionary. Args: label (string): key for the dictionary entry to be removed. update (boolean): if True, the MagStructure will update its atoms and spins with the removed species now excluded. """ try: del self.species[label] if update: self.getCoordsFromSpecies() # update the list of fractions totatoms = 0.0 for key in self.species: totatoms += self.species[key].atoms.shape[0] for key in self.species: if totatoms == 0.0: totatoms = 1.0 # prevent divide by zero problems frac = float(self.species[key].atoms.shape[0])/totatoms self.fractions[key] = frac except: print('Species cannot be deleted. Check that you are using the') print('correct species label.') def makeAtoms(self): """Generate the Cartesian coordinates of the atoms for this species. Args: fromUnitCell (boolean): True if atoms/spins to be generated from a unit cell provided by the user; False if the diffpy structure object is to be used. unitcell (numpy array): Provides the unit cell lattice vectors as np.array((avec, bvec, cvec)). atombasis (numpy array): Provides positions of the magnetic atoms in fractional coordinates within the unit cell. spin cell (numpy array): Provides the orientations of the spins in the unit cell, in the same order as atombasis """ temp = np.array([[0, 0, 0]]) for key in self.species: self.species[key].makeAtoms() temp = np.concatenate((temp, self.species[key].atoms)) self.atoms = temp[1:] def makeSpins(self): """Generate the Cartesian coordinates of the spin vectors in the structure. Calls the makeSpins() method for each MagSpecies in the species dictionary and concatenates them together. """ temp = np.array([[0, 0, 0]]) for key in self.species: self.species[key].makeSpins() temp = np.concatenate((temp, self.species[key].spins)) self.spins = temp[1:] def makeGfactors(self): """Generate an array of Lande g-factors in the same order as the spins in the MagStructure. """ temp = np.array([2.0]) for key in self.species: temp = np.concatenate((temp, (self.species[key].gS+self.species[key].gL)*np.ones(self.species[key].spins.shape[0]))) self.gfactors = temp[1:] def makeFractions(self): """Generate the fractions dictionary. """ try: totatoms = 0.0 for key in self.species: totatoms += self.species[key].atoms.shape[0] for key in self.species: if totatoms == 0.0: totatoms = 1.0 # prevent divide by zero problems frac = float(self.species[key].atoms.shape[0])/totatoms self.fractions[key] = frac except: if len(self.species) == 0: self.fractions = {} else: print('Check MagStructure.fractions dictionary for problems.') def makeKfactors(self): """Set the factors K1 and K2 used for unnormalized mPDF. The fractions dictionary must be accurate before running this method. """ K1, K2 = 0, 0 for key in self.species: ga = self.species[key].g Ja = self.species[key].J K1 += self.fractions[key]*ga*np.sqrt(Ja*(Ja+1)) K2 += self.fractions[key]*ga**2*Ja*(Ja+1) K1 = K1**2 K1 *= (1.913*2.81794/2.0)**2*2.0/3.0 K2 *= (1.913*2.81794/2.0)**2*2.0/3.0 self.K1 = K1 self.K2 = K2 def makeFF(self): """Generate the properly weighted average magnetic form factor of all the magnetic species in the structure. """ try: self.ffqgrid = list(self.species.values())[0].ffqgrid self.ff = np.zeros_like(self.ffqgrid) totatoms = 0.0 for key in self.species: totatoms += self.species[key].atoms.shape[0] for key in self.species: frac = float(self.species[key].atoms.shape[0])/totatoms self.species[key].makeFF() self.ff += frac*self.species[key].ff except: if len(self.species) == 0: self.ff = jCalc(self.ffqgrid) else: print('Check that all mag species have same q-grid.') def makeAll(self): """Shortcut method to generate atoms, spins, g-factors, and form factor for the magnetic structure all in one go. """ self.makeAtoms() self.makeSpins() self.makeGfactors() self.makeFractions() self.makeKfactors() self.makeFF() self.makeCalcIdxs() self.runChecks(doCalcIdxsCheck=True) def spinsFromAtoms(self,positions,fractional=True,returnIdxs=False): """Return the spin vectors corresponding to specified atomic positions. This method calls the diffpy.mpdf.spinsFromAtoms() method. Args: magstruc: MagSpecies or MagStructure object containing atoms and spins positions (list or array): atomic positions for which the corresponding spins should be returned. fractional (boolean): set as True if the atomic positions are in fractional coordinates of the crystallographic lattice vectors. returnIdxs (boolean): if True, the indices of the spins will also be returned. Returns: Array consisting of the spins corresponding to the atomic positions. """ return spinsFromAtoms(self,positions,fractional,returnIdxs) def atomsFromSpins(self,spinvecs,fractional=True,returnIdxs=False): """Return the atomic positions corresponding to specified spins. This method calls the diffpy.mpdf.atomsFromSpins() method. Args: magstruc: MagSpecies or MagStructure object containing atoms and spins spinvecs (list or array): spin vectors for which the corresponding atoms should be returned. fractional (boolean): set as True if the atomic positions are to be returned as fractional coordinates of the crystallographic lattice vectors. returnIdxs (boolean): if True, the indices of the atoms will also be returned. Returns: List of arrays of atoms corresponding to the spins. """ return atomsFromSpins(self,spinvecs,fractional,returnIdxs) def visualize(self,atoms,spins,showcrystalaxes=False, axesorigin=np.array([0,0,0])): """Generate a crude 3-d plot to visualize the selected spins. Args: atoms (numpy array): array of atomic positions of spins to be visualized. spins (numpy array): array of spin vectors in same order as atoms. showcrystalaxes (boolean): if True, will display the crystal axes determined from the first magnetic species in the MagStructure axesorigin (array): position at which the crystal axes should be displayed """ import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import axes3d fig = visualizeSpins(atoms,spins) if showcrystalaxes: ax3d = fig.axes[0] try: mspec=list(self.species.items())[0][1] if mspec.useDiffpyStruc: lat=mspec.struc.lattice a, b, c = lat.stdbase else: a, b, c = mspec.latVecs xo, yo, zo = axesorigin ax3d.quiver(xo, yo, zo, a[0], a[1], a[2], pivot='tail', color='r') ax3d.quiver(xo, yo, zo, b[0], b[1], b[2], pivot='tail', color='g') ax3d.quiver(xo, yo, zo, c[0], c[1], c[2], pivot='tail', color='b') except: print('Please make sure your magnetic structure contains a') print('magnetic species with MagSpecies.struc set to a diffpy') print('structure or MagSpecies.latVecs provided and') print('MagSpecies.useDiffpyStruc set to False.') plt.show() def findAtomIndices(self,atomList): """Return list of indices corresponding to input list of atomic coordinates. This method calls the diffpy.mpdf.findAtomIndices() method. Args: atomList (numpy array of atomic coordinates) Returns: List of indices corresponding to the atomList. """ return findAtomIndices(self,atomList) def runChecks(self, doCalcIdxsCheck=False): """Run some simple checks and raise a warning if a problem is found. """ # do the MagSpecies checks for key in self.species: self.species[key].runChecks() if self.verbose: print(('Running checks for '+self.label+' MagStructure object...\n')) flag = False flagCount = 0 # check for duplication among magnetic species if len(self.species) > 0: if list(self.species.values())[0].useDiffpyStruc: idxs = [] for key in self.species: idxs.append(self.species[key].strucIdxs) idxs = [item for sublist in idxs for item in sublist] # flatten the list for idx in idxs: if idxs.count(idx) > 1: flag = True if flag: flagCount += 1 if self.verbose: print('Warning: Magnetic species may have overlapping atoms.') print('Check the strucIdxs lists for your magnetic species.') flag = False # check that the fractions are consistent totatoms = 0.0 for key in self.species: totatoms += self.species[key].atoms.shape[0] for key in self.species: if totatoms == 0.0: totatoms = 1.0 # prevent divide by zero problems frac = float(self.species[key].atoms.shape[0])/totatoms if (frac > 0) and (np.abs(frac - self.fractions[key])/frac > 0.1): flag = True if flag: flagCount += 1 if self.verbose: print('Species fractions do not correspond to actual number of') print('spins of each species in the structure.') flag = False ### check if calcIdxs may not be representative of all MagSpecies. if doCalcIdxsCheck: # option to turn off this check e.g. when loading a MagSpecies if len(self.calcIdxs) < len(self.species): flag = True if flag: flagCount += 1 print('Warning: your calcIdxs may not be representative of all') print('the magnetic species. calcIdxs should have the index of') print('at least one spin from each species. Use') print('magStruc.getSpeciesIdxs() to see starting indices for') print('each species.\n') flag = False ### check if calcIdxs has indices that exceed the spin array if self.atoms.shape[0]>0: if (np.array(self.calcIdxs).max()+1) > self.atoms.shape[0]: flag = True if flag: flagCount += 1 print('calcIdxs contains indices that are too large for the') print('arrays of atoms and spins contained in the MagStructure.') flag = False # summarize results if flagCount == 0: if self.verbose: print('All MagStructure checks passed. No obvious problems found.') def getSpeciesIdxs(self): """Return a dictionary with the starting index in the atoms and spins arrays corresponding to each magnetic species. """ idxDict = {} startIdx = 0 for key in self.species: idxDict[key] = startIdx startIdx += self.species[key].atoms.shape[0] return idxDict def makeCalcIdxs(self): """Generate the indices of the atoms to be used for the calculation. """ idxDict = self.getSpeciesIdxs() calcIdxs = [] for key in self.species: calcIdxs.append(np.array(self.species[key].calcIdxs) + idxDict[key]) calcIdxs = [ci for sublist in calcIdxs for ci in sublist] self.calcIdxs = calcIdxs def generateScaledSpins(self, originIdx=0): """Apply a correlation length to the spin magnitudes. Args: originIdx (int): The index of the spin in magstructure.spins that should be considered the origin. Returns: scaledSpins (np.array): An array with the same shape as the self.spins array, where the magnitudes of the spins have been scaled in accordance with the correlation length. """ scaledSpins = 1.0*self.spins if type(self.dampingMat) != np.ndarray: # isotropic correlation length xi = self.corrLength if xi != 0.0: distanceVecs = self.atoms - self.atoms[originIdx] distances = np.apply_along_axis(np.linalg.norm, 1, distanceVecs) rescale = np.exp(-distances/xi)[:,np.newaxis] scaledSpins *= rescale else: dampingMat = self.dampingMat # ensure that dampingMat is 3x3 symmetric matrix if dampingMat.shape == (3, 3): if np.allclose(dampingMat, dampingMat.T, rtol=1e-5, atol=1e-8): distanceVecs = self.atoms - self.atoms[originIdx] distances = np.apply_along_axis(np.linalg.norm, 1, distanceVecs) distanceVecsN = distanceVecs/np.apply_along_axis(np.linalg.norm,1,distanceVecs)[:,np.newaxis] mult1 = np.tensordot(dampingMat, distanceVecsN, axes=(0,1)).T xi = 1.0/np.sqrt(np.apply_along_axis(np.sum, 1, distanceVecsN*mult1)) xi[originIdx] = 1.0 # avoid divide-by-zero problem rescale = np.exp(-distances/xi)[:,np.newaxis] scaledSpins *= rescale else: print('Damping matrix is not symmetric. Spins will not be modified.') else: print('Damping matrix is not a 3x3 matrix. Spins will not be modified.') return scaledSpins def calcAtomicDensity(self, volume=0, numSpins=0): """Determine the number density of magnetic moments. Sets the calculated number density equal to self.rho0. Args: volume (scalar): Volume of the MagStructure. If equal to the default value of 0, then the volume will be calculated assuming a sphere of radius rmaxAtoms. numSpins (integer): number of magnetic moments in the volume being considered. If equal to the default value of 0, then the numSpins will be set to the length of the spins array. """ if self.struc != []: volume = np.sqrt(np.linalg.det(self.struc.lattice.metrics)) for key in self.species: #strucIdxs = self.species[key].strucIdxs #numSpins += self.species[key].struc.occupancy[strucIdxs].sum() numSpins += len(self.species[key].strucIdxs) self.rho0 = numSpins / volume else: print('Please create a diffpy Structure object to use this feature') # if volume==0: # radius = self.rmaxAtoms + \ # np.linalg.norm(self.struc.lattice.stdbase.sum(axis=1)) # volume = 1.33333*np.pi*radius**3 # if numSpins==0: # numSpins = len(self.spins) # self.rho0 = numSpins / volume def calcMagneticAtomRatio(self): """Determine the ratio of magnetic atoms to total atoms. Sets the calculated number equal to self.magneticAtomRatio. """ occ = self.struc.occupancy totalOcc = occ.sum() numMagAtoms = 0 if self.struc != []: for key in self.species: strucIdxs = self.species[key].strucIdxs numMagAtoms += occ[strucIdxs].sum() self.magneticAtomRatio = numMagAtoms / totalOcc else: print('Please create a diffpy Structure object to use this feature') def calcNetMag(self,method='directCalculation'): """Determine the net magnetization per spin. Sets the calculated value equal to self.netMag. This method is only necessary for magnetic structures with a net magnetic moment, such as a ferromagnet, ferrimagnet, or canted antiferromagnet. Args: method: How the net magnetization should be calculated. 'directCalculation': the mean of the entire structure is calculated. May have slight inaccuracies due to edge effects. 'speciesCalculation': use the individual MagSpecies to find the average magnetization per spin. """ if method == 'directCalculation': totalMag = np.sum(self.spins, axis=0) * np.mean(self.gfactors) self.netMag = np.linalg.norm(totalMag) / self.spins.shape[0] if method == 'speciesCalculation': mags = [] weights = [] netMag = np.array([0.0,0.0,0.0]) if self.struc != []: for key in self.species: g = self.species[key].gS + self.species[key].gL mag = g * np.linalg.norm(self.species[key].spins[0]) # assumes constant magnitude of spins mags.append(mag) weight = np.sum(self.struc.occupancy[self.species[key].strucIdxs]) weights.append(weight) netMag += g * weight * self.species[key].spins[0] # assumes uniform spins within the species mags = np.array(mags) weights = np.array(weights) #self.netMag = np.sum(mags * weights)/np.sum(weights) self.netMag = np.linalg.norm(netMag/np.sum(weights)) else: print('Please create a diffpy Structure object to use this feature') def copy(self): """Return a deep copy of the MagStructure object.""" return copy.deepcopy(self)
Methods
def atomsFromSpins(self, spinvecs, fractional=True, returnIdxs=False)
-
Return the atomic positions corresponding to specified spins.
This method calls the diffpy.mpdf.atomsFromSpins() method.
Args
magstruc
- MagSpecies or MagStructure object containing atoms and spins
spinvecs
:list
orarray
- spin vectors for which the corresponding atoms should be returned.
fractional
:boolean
- set as True if the atomic positions are to be returned as fractional coordinates of the crystallographic lattice vectors.
returnIdxs
:boolean
- if True, the indices of the atoms will also be returned.
Returns
List of arrays of atoms corresponding to the spins.
Expand source code
def atomsFromSpins(self,spinvecs,fractional=True,returnIdxs=False): """Return the atomic positions corresponding to specified spins. This method calls the diffpy.mpdf.atomsFromSpins() method. Args: magstruc: MagSpecies or MagStructure object containing atoms and spins spinvecs (list or array): spin vectors for which the corresponding atoms should be returned. fractional (boolean): set as True if the atomic positions are to be returned as fractional coordinates of the crystallographic lattice vectors. returnIdxs (boolean): if True, the indices of the atoms will also be returned. Returns: List of arrays of atoms corresponding to the spins. """ return atomsFromSpins(self,spinvecs,fractional,returnIdxs)
def calcAtomicDensity(self, volume=0, numSpins=0)
-
Determine the number density of magnetic moments. Sets the calculated number density equal to self.rho0.
Args
volume
:scalar
- Volume of the MagStructure. If equal to the default value of 0, then the volume will be calculated assuming a sphere of radius rmaxAtoms.
numSpins
:integer
- number of magnetic moments in the volume being considered. If equal to the default value of 0, then the numSpins will be set to the length of the spins array.
Expand source code
def calcAtomicDensity(self, volume=0, numSpins=0): """Determine the number density of magnetic moments. Sets the calculated number density equal to self.rho0. Args: volume (scalar): Volume of the MagStructure. If equal to the default value of 0, then the volume will be calculated assuming a sphere of radius rmaxAtoms. numSpins (integer): number of magnetic moments in the volume being considered. If equal to the default value of 0, then the numSpins will be set to the length of the spins array. """ if self.struc != []: volume = np.sqrt(np.linalg.det(self.struc.lattice.metrics)) for key in self.species: #strucIdxs = self.species[key].strucIdxs #numSpins += self.species[key].struc.occupancy[strucIdxs].sum() numSpins += len(self.species[key].strucIdxs) self.rho0 = numSpins / volume else: print('Please create a diffpy Structure object to use this feature')
def calcMagneticAtomRatio(self)
-
Determine the ratio of magnetic atoms to total atoms. Sets the calculated number equal to self.magneticAtomRatio.
Expand source code
def calcMagneticAtomRatio(self): """Determine the ratio of magnetic atoms to total atoms. Sets the calculated number equal to self.magneticAtomRatio. """ occ = self.struc.occupancy totalOcc = occ.sum() numMagAtoms = 0 if self.struc != []: for key in self.species: strucIdxs = self.species[key].strucIdxs numMagAtoms += occ[strucIdxs].sum() self.magneticAtomRatio = numMagAtoms / totalOcc else: print('Please create a diffpy Structure object to use this feature')
def calcNetMag(self, method='directCalculation')
-
Determine the net magnetization per spin. Sets the calculated value equal to self.netMag.
This method is only necessary for magnetic structures with a net magnetic moment, such as a ferromagnet, ferrimagnet, or canted antiferromagnet.
Args
method
- How the net magnetization should be calculated. 'directCalculation': the mean of the entire structure is calculated. May have slight inaccuracies due to edge effects. 'speciesCalculation': use the individual MagSpecies to find the average magnetization per spin.
Expand source code
def calcNetMag(self,method='directCalculation'): """Determine the net magnetization per spin. Sets the calculated value equal to self.netMag. This method is only necessary for magnetic structures with a net magnetic moment, such as a ferromagnet, ferrimagnet, or canted antiferromagnet. Args: method: How the net magnetization should be calculated. 'directCalculation': the mean of the entire structure is calculated. May have slight inaccuracies due to edge effects. 'speciesCalculation': use the individual MagSpecies to find the average magnetization per spin. """ if method == 'directCalculation': totalMag = np.sum(self.spins, axis=0) * np.mean(self.gfactors) self.netMag = np.linalg.norm(totalMag) / self.spins.shape[0] if method == 'speciesCalculation': mags = [] weights = [] netMag = np.array([0.0,0.0,0.0]) if self.struc != []: for key in self.species: g = self.species[key].gS + self.species[key].gL mag = g * np.linalg.norm(self.species[key].spins[0]) # assumes constant magnitude of spins mags.append(mag) weight = np.sum(self.struc.occupancy[self.species[key].strucIdxs]) weights.append(weight) netMag += g * weight * self.species[key].spins[0] # assumes uniform spins within the species mags = np.array(mags) weights = np.array(weights) #self.netMag = np.sum(mags * weights)/np.sum(weights) self.netMag = np.linalg.norm(netMag/np.sum(weights)) else: print('Please create a diffpy Structure object to use this feature')
def copy(self)
-
Return a deep copy of the MagStructure object.
Expand source code
def copy(self): """Return a deep copy of the MagStructure object.""" return copy.deepcopy(self)
def findAtomIndices(self, atomList)
-
Return list of indices corresponding to input list of atomic coordinates.
This method calls the diffpy.mpdf.findAtomIndices() method.
Args
atomList (numpy array of atomic coordinates)
Returns
List of indices corresponding to the atomList.
Expand source code
def findAtomIndices(self,atomList): """Return list of indices corresponding to input list of atomic coordinates. This method calls the diffpy.mpdf.findAtomIndices() method. Args: atomList (numpy array of atomic coordinates) Returns: List of indices corresponding to the atomList. """ return findAtomIndices(self,atomList)
def generateScaledSpins(self, originIdx=0)
-
Apply a correlation length to the spin magnitudes.
Args
originIdx
:int
- The index of the spin in magstructure.spins that should be considered the origin.
Returns
scaledSpins (np.array): An array with the same shape as the self.spins array, where the magnitudes of the spins have been scaled in accordance with the correlation length.
Expand source code
def generateScaledSpins(self, originIdx=0): """Apply a correlation length to the spin magnitudes. Args: originIdx (int): The index of the spin in magstructure.spins that should be considered the origin. Returns: scaledSpins (np.array): An array with the same shape as the self.spins array, where the magnitudes of the spins have been scaled in accordance with the correlation length. """ scaledSpins = 1.0*self.spins if type(self.dampingMat) != np.ndarray: # isotropic correlation length xi = self.corrLength if xi != 0.0: distanceVecs = self.atoms - self.atoms[originIdx] distances = np.apply_along_axis(np.linalg.norm, 1, distanceVecs) rescale = np.exp(-distances/xi)[:,np.newaxis] scaledSpins *= rescale else: dampingMat = self.dampingMat # ensure that dampingMat is 3x3 symmetric matrix if dampingMat.shape == (3, 3): if np.allclose(dampingMat, dampingMat.T, rtol=1e-5, atol=1e-8): distanceVecs = self.atoms - self.atoms[originIdx] distances = np.apply_along_axis(np.linalg.norm, 1, distanceVecs) distanceVecsN = distanceVecs/np.apply_along_axis(np.linalg.norm,1,distanceVecs)[:,np.newaxis] mult1 = np.tensordot(dampingMat, distanceVecsN, axes=(0,1)).T xi = 1.0/np.sqrt(np.apply_along_axis(np.sum, 1, distanceVecsN*mult1)) xi[originIdx] = 1.0 # avoid divide-by-zero problem rescale = np.exp(-distances/xi)[:,np.newaxis] scaledSpins *= rescale else: print('Damping matrix is not symmetric. Spins will not be modified.') else: print('Damping matrix is not a 3x3 matrix. Spins will not be modified.') return scaledSpins
def getCoordsFromSpecies(self)
-
Read in atomic positions and spins from magnetic species.
This differs from makeSpins() and makeAtoms() because it simply loads the atoms and spins from the species without re-generating them from the structure.
Expand source code
def getCoordsFromSpecies(self): """Read in atomic positions and spins from magnetic species. This differs from makeSpins() and makeAtoms() because it simply loads the atoms and spins from the species without re-generating them from the structure. """ tempA = np.array([[0, 0, 0]]) tempS = np.array([[0, 0, 0]]) for key in self.species: na = self.species[key].atoms.shape[0] ns = self.species[key].atoms.shape[0] if (na > 0) and (na == ns): tempA = np.concatenate((tempA, self.species[key].atoms)) tempS = np.concatenate((tempS, self.species[key].spins)) else: if self.verbose: print(('Coordinates of atoms and spins for ' + key)) print('have not been loaded because they have not yet been') print('generated and/or do not match in shape.') if tempA.shape != (1, 3): self.atoms = tempA[1:] self.spins = tempS[1:] elif len(self.species) == 0: self.atoms = np.array([]) self.spins = np.array([])
def getSpeciesIdxs(self)
-
Return a dictionary with the starting index in the atoms and spins arrays corresponding to each magnetic species.
Expand source code
def getSpeciesIdxs(self): """Return a dictionary with the starting index in the atoms and spins arrays corresponding to each magnetic species. """ idxDict = {} startIdx = 0 for key in self.species: idxDict[key] = startIdx startIdx += self.species[key].atoms.shape[0] return idxDict
def loadSpecies(self, magSpec)
-
Load in an already-existing MagSpecies object
Args
magSpec
:MagSpecies object
- The magnetic species to be imported into the structure.
Expand source code
def loadSpecies(self, magSpec): """Load in an already-existing MagSpecies object Args: magSpec (MagSpecies object): The magnetic species to be imported into the structure. """ # check that the label is not a duplicate with any other mag species. duplicate = False for name in list(self.species.keys()): if name == magSpec.label: duplicate = True if not duplicate: self.species[magSpec.label] = magSpec self.struc = magSpec.struc self.getCoordsFromSpecies() # update the list of fractions totatoms = 0.0 for key in self.species: totatoms += self.species[key].atoms.shape[0] for key in list(self.species.keys()): if totatoms == 0.0: totatoms = 1.0 # prevent divide by zero problems frac = float(self.species[key].atoms.shape[0])/totatoms self.fractions[key] = frac self.runChecks() else: print('The label for this species has already been assigned to') print('another species in the structure. Please choose a new label') print('for this species.')
def makeAll(self)
-
Shortcut method to generate atoms, spins, g-factors, and form factor for the magnetic structure all in one go.
Expand source code
def makeAll(self): """Shortcut method to generate atoms, spins, g-factors, and form factor for the magnetic structure all in one go. """ self.makeAtoms() self.makeSpins() self.makeGfactors() self.makeFractions() self.makeKfactors() self.makeFF() self.makeCalcIdxs() self.runChecks(doCalcIdxsCheck=True)
def makeAtoms(self)
-
Generate the Cartesian coordinates of the atoms for this species.
Args
fromUnitCell
:boolean
- True if atoms/spins to be generated from a unit cell provided by the user; False if the diffpy structure object is to be used.
unitcell
:numpy array
- Provides the unit cell lattice vectors as np.array((avec, bvec, cvec)).
atombasis
:numpy array
- Provides positions of the magnetic atoms in fractional coordinates within the unit cell.
spin cell (numpy array): Provides the orientations of the spins in the unit cell, in the same order as atombasis
Expand source code
def makeAtoms(self): """Generate the Cartesian coordinates of the atoms for this species. Args: fromUnitCell (boolean): True if atoms/spins to be generated from a unit cell provided by the user; False if the diffpy structure object is to be used. unitcell (numpy array): Provides the unit cell lattice vectors as np.array((avec, bvec, cvec)). atombasis (numpy array): Provides positions of the magnetic atoms in fractional coordinates within the unit cell. spin cell (numpy array): Provides the orientations of the spins in the unit cell, in the same order as atombasis """ temp = np.array([[0, 0, 0]]) for key in self.species: self.species[key].makeAtoms() temp = np.concatenate((temp, self.species[key].atoms)) self.atoms = temp[1:]
def makeCalcIdxs(self)
-
Generate the indices of the atoms to be used for the calculation.
Expand source code
def makeCalcIdxs(self): """Generate the indices of the atoms to be used for the calculation. """ idxDict = self.getSpeciesIdxs() calcIdxs = [] for key in self.species: calcIdxs.append(np.array(self.species[key].calcIdxs) + idxDict[key]) calcIdxs = [ci for sublist in calcIdxs for ci in sublist] self.calcIdxs = calcIdxs
def makeFF(self)
-
Generate the properly weighted average magnetic form factor of all the magnetic species in the structure.
Expand source code
def makeFF(self): """Generate the properly weighted average magnetic form factor of all the magnetic species in the structure. """ try: self.ffqgrid = list(self.species.values())[0].ffqgrid self.ff = np.zeros_like(self.ffqgrid) totatoms = 0.0 for key in self.species: totatoms += self.species[key].atoms.shape[0] for key in self.species: frac = float(self.species[key].atoms.shape[0])/totatoms self.species[key].makeFF() self.ff += frac*self.species[key].ff except: if len(self.species) == 0: self.ff = jCalc(self.ffqgrid) else: print('Check that all mag species have same q-grid.')
def makeFractions(self)
-
Generate the fractions dictionary.
Expand source code
def makeFractions(self): """Generate the fractions dictionary. """ try: totatoms = 0.0 for key in self.species: totatoms += self.species[key].atoms.shape[0] for key in self.species: if totatoms == 0.0: totatoms = 1.0 # prevent divide by zero problems frac = float(self.species[key].atoms.shape[0])/totatoms self.fractions[key] = frac except: if len(self.species) == 0: self.fractions = {} else: print('Check MagStructure.fractions dictionary for problems.')
def makeGfactors(self)
-
Generate an array of Lande g-factors in the same order as the spins in the MagStructure.
Expand source code
def makeGfactors(self): """Generate an array of Lande g-factors in the same order as the spins in the MagStructure. """ temp = np.array([2.0]) for key in self.species: temp = np.concatenate((temp, (self.species[key].gS+self.species[key].gL)*np.ones(self.species[key].spins.shape[0]))) self.gfactors = temp[1:]
def makeKfactors(self)
-
Set the factors K1 and K2 used for unnormalized mPDF. The fractions dictionary must be accurate before running this method.
Expand source code
def makeKfactors(self): """Set the factors K1 and K2 used for unnormalized mPDF. The fractions dictionary must be accurate before running this method. """ K1, K2 = 0, 0 for key in self.species: ga = self.species[key].g Ja = self.species[key].J K1 += self.fractions[key]*ga*np.sqrt(Ja*(Ja+1)) K2 += self.fractions[key]*ga**2*Ja*(Ja+1) K1 = K1**2 K1 *= (1.913*2.81794/2.0)**2*2.0/3.0 K2 *= (1.913*2.81794/2.0)**2*2.0/3.0 self.K1 = K1 self.K2 = K2
def makeSpecies(self, label, strucIdxs=None, atoms=None, spins=None, basisvecs=None, kvecs=None, S=0.5, L=0.0, J=None, gS=None, gL=None, g=None, j2type=None, ffparamkey=None, ffqgrid=None, ff=None, occ=None)
-
Create a MagSpecies object and add it to the species dictionary.
Args
label
:string
- label for this particular magnetic species. Should be different from the labels for any other magnetic species you make.
strucIdxs
:python list
- list of integers giving indices of magnetic atoms in the unit cell
atoms
:numpy array
- list of atomic coordinates of all the magnetic atoms in the structure; e.g. generated by generateAtomsXYZ()
spins
:numpy array
- triplets giving the spin vectors of all the atoms, in the same order as the atoms array provided as input.
basisvecs
:numpy array
- nested three-vector(s) giving the basis vectors to generate the spins. e.g. np.array([[0, 0, 1]]). Any phase factor should be included directly with the basisvecs.
kvecs
:numpy array
- nested three-vector(s) giving the propagation vectors for the magnetic structure in r.l.u., e.g. np.array([[0.5, 0.5, 0.5]])
gS
:float
- spin component of the Lande g-factor (g = gS+gL). Calculated automatically from S, L, and J if not specified.
gL
:float
- orbital component of the Lande g-factor. Calculated automatically from S, L, and J if not specified.
g
:float
- Lande g-factor. Calculated automatically as gS+gL if not specified.
j2type
:string
- Specifies the way the j2 integral is included in the magnetic form factor. Must be either 'RE' for rare earth or 'TM' for transition metal. If 'RE', the coefficient on the j2 integral is gL/g; if 'TM', the coefficient is (g-2)/g. Default is 'RE'. Note that for the default values of S, L, and J, we will have gS=2, gL=0, and g=2, and there will be no difference in the form factor calculation for 'RE' or 'TM'.
ffparamkey
:string
- gives the appropriate key for getFFparams()
ffqgrid
:numpy array
- grid of momentum transfer values used for calculating the magnetic form factor.
ff
:numpy array
- magnetic form factor.
Expand source code
def makeSpecies(self, label, strucIdxs=None, atoms=None, spins=None, basisvecs=None, kvecs=None, S=0.5, L=0.0, J=None, gS=None, gL=None, g=None, j2type=None, ffparamkey=None,ffqgrid=None, ff=None, occ=None): """Create a MagSpecies object and add it to the species dictionary. Args: label (string): label for this particular magnetic species. Should be different from the labels for any other magnetic species you make. strucIdxs (python list): list of integers giving indices of magnetic atoms in the unit cell atoms (numpy array): list of atomic coordinates of all the magnetic atoms in the structure; e.g. generated by generateAtomsXYZ() spins (numpy array): triplets giving the spin vectors of all the atoms, in the same order as the atoms array provided as input. basisvecs (numpy array): nested three-vector(s) giving the basis vectors to generate the spins. e.g. np.array([[0, 0, 1]]). Any phase factor should be included directly with the basisvecs. kvecs (numpy array): nested three-vector(s) giving the propagation vectors for the magnetic structure in r.l.u., e.g. np.array([[0.5, 0.5, 0.5]]) gS (float): spin component of the Lande g-factor (g = gS+gL). Calculated automatically from S, L, and J if not specified. gL (float): orbital component of the Lande g-factor. Calculated automatically from S, L, and J if not specified. g (float): Lande g-factor. Calculated automatically as gS+gL if not specified. j2type (string): Specifies the way the j2 integral is included in the magnetic form factor. Must be either 'RE' for rare earth or 'TM' for transition metal. If 'RE', the coefficient on the j2 integral is gL/g; if 'TM', the coefficient is (g-2)/g. Default is 'RE'. Note that for the default values of S, L, and J, we will have gS=2, gL=0, and g=2, and there will be no difference in the form factor calculation for 'RE' or 'TM'. ffparamkey (string): gives the appropriate key for getFFparams() ffqgrid (numpy array): grid of momentum transfer values used for calculating the magnetic form factor. ff (numpy array): magnetic form factor. """ # check that the label is not a duplicate with any other mag species. duplicate = False for name in list(self.species.keys()): if name == label: duplicate = True if not duplicate: if ffqgrid is None: ffqgrid = np.arange(0, 10.0, 0.01) self.species[label] = MagSpecies(self.struc, label, strucIdxs, atoms, spins, self.rmaxAtoms, basisvecs, kvecs, S, L, J, gS, gL, g, j2type, ffparamkey, ffqgrid, ff, self.verbose, occ) # update the list of fractions totatoms = 0.0 for key in self.species: totatoms += self.species[key].atoms.shape[0] for key in self.species: if totatoms == 0.0: totatoms = 1.0 # prevent divide by zero problems frac = float(self.species[key].atoms.shape[0])/totatoms self.fractions[key] = frac self.runChecks() else: print('This label has already been assigned to another species in') print('the structure. Please choose a new label.')
def makeSpins(self)
-
Generate the Cartesian coordinates of the spin vectors in the structure. Calls the makeSpins() method for each MagSpecies in the species dictionary and concatenates them together.
Expand source code
def makeSpins(self): """Generate the Cartesian coordinates of the spin vectors in the structure. Calls the makeSpins() method for each MagSpecies in the species dictionary and concatenates them together. """ temp = np.array([[0, 0, 0]]) for key in self.species: self.species[key].makeSpins() temp = np.concatenate((temp, self.species[key].spins)) self.spins = temp[1:]
def removeSpecies(self, label, update=True)
-
Remove a magnetic species from the species dictionary.
Args
label
:string
- key for the dictionary entry to be removed.
update
:boolean
- if True, the MagStructure will update its atoms and spins with the removed species now excluded.
Expand source code
def removeSpecies(self, label, update=True): """Remove a magnetic species from the species dictionary. Args: label (string): key for the dictionary entry to be removed. update (boolean): if True, the MagStructure will update its atoms and spins with the removed species now excluded. """ try: del self.species[label] if update: self.getCoordsFromSpecies() # update the list of fractions totatoms = 0.0 for key in self.species: totatoms += self.species[key].atoms.shape[0] for key in self.species: if totatoms == 0.0: totatoms = 1.0 # prevent divide by zero problems frac = float(self.species[key].atoms.shape[0])/totatoms self.fractions[key] = frac except: print('Species cannot be deleted. Check that you are using the') print('correct species label.')
def runChecks(self, doCalcIdxsCheck=False)
-
Run some simple checks and raise a warning if a problem is found.
Expand source code
def runChecks(self, doCalcIdxsCheck=False): """Run some simple checks and raise a warning if a problem is found. """ # do the MagSpecies checks for key in self.species: self.species[key].runChecks() if self.verbose: print(('Running checks for '+self.label+' MagStructure object...\n')) flag = False flagCount = 0 # check for duplication among magnetic species if len(self.species) > 0: if list(self.species.values())[0].useDiffpyStruc: idxs = [] for key in self.species: idxs.append(self.species[key].strucIdxs) idxs = [item for sublist in idxs for item in sublist] # flatten the list for idx in idxs: if idxs.count(idx) > 1: flag = True if flag: flagCount += 1 if self.verbose: print('Warning: Magnetic species may have overlapping atoms.') print('Check the strucIdxs lists for your magnetic species.') flag = False # check that the fractions are consistent totatoms = 0.0 for key in self.species: totatoms += self.species[key].atoms.shape[0] for key in self.species: if totatoms == 0.0: totatoms = 1.0 # prevent divide by zero problems frac = float(self.species[key].atoms.shape[0])/totatoms if (frac > 0) and (np.abs(frac - self.fractions[key])/frac > 0.1): flag = True if flag: flagCount += 1 if self.verbose: print('Species fractions do not correspond to actual number of') print('spins of each species in the structure.') flag = False ### check if calcIdxs may not be representative of all MagSpecies. if doCalcIdxsCheck: # option to turn off this check e.g. when loading a MagSpecies if len(self.calcIdxs) < len(self.species): flag = True if flag: flagCount += 1 print('Warning: your calcIdxs may not be representative of all') print('the magnetic species. calcIdxs should have the index of') print('at least one spin from each species. Use') print('magStruc.getSpeciesIdxs() to see starting indices for') print('each species.\n') flag = False ### check if calcIdxs has indices that exceed the spin array if self.atoms.shape[0]>0: if (np.array(self.calcIdxs).max()+1) > self.atoms.shape[0]: flag = True if flag: flagCount += 1 print('calcIdxs contains indices that are too large for the') print('arrays of atoms and spins contained in the MagStructure.') flag = False # summarize results if flagCount == 0: if self.verbose: print('All MagStructure checks passed. No obvious problems found.')
def spinsFromAtoms(self, positions, fractional=True, returnIdxs=False)
-
Return the spin vectors corresponding to specified atomic positions.
This method calls the diffpy.mpdf.spinsFromAtoms() method.
Args
magstruc
- MagSpecies or MagStructure object containing atoms and spins
positions
:list
orarray
- atomic positions for which the corresponding spins should be returned.
fractional
:boolean
- set as True if the atomic positions are in fractional coordinates of the crystallographic lattice vectors.
returnIdxs
:boolean
- if True, the indices of the spins will also be returned.
Returns
Array consisting of the spins corresponding to the atomic positions.
Expand source code
def spinsFromAtoms(self,positions,fractional=True,returnIdxs=False): """Return the spin vectors corresponding to specified atomic positions. This method calls the diffpy.mpdf.spinsFromAtoms() method. Args: magstruc: MagSpecies or MagStructure object containing atoms and spins positions (list or array): atomic positions for which the corresponding spins should be returned. fractional (boolean): set as True if the atomic positions are in fractional coordinates of the crystallographic lattice vectors. returnIdxs (boolean): if True, the indices of the spins will also be returned. Returns: Array consisting of the spins corresponding to the atomic positions. """ return spinsFromAtoms(self,positions,fractional,returnIdxs)
def visualize(self, atoms, spins, showcrystalaxes=False, axesorigin=array([0, 0, 0]))
-
Generate a crude 3-d plot to visualize the selected spins.
Args
atoms
:numpy array
- array of atomic positions of spins to be visualized.
spins
:numpy array
- array of spin vectors in same order as atoms.
showcrystalaxes
:boolean
- if True, will display the crystal axes determined from the first magnetic species in the MagStructure
axesorigin
:array
- position at which the crystal axes should be displayed
Expand source code
def visualize(self,atoms,spins,showcrystalaxes=False, axesorigin=np.array([0,0,0])): """Generate a crude 3-d plot to visualize the selected spins. Args: atoms (numpy array): array of atomic positions of spins to be visualized. spins (numpy array): array of spin vectors in same order as atoms. showcrystalaxes (boolean): if True, will display the crystal axes determined from the first magnetic species in the MagStructure axesorigin (array): position at which the crystal axes should be displayed """ import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import axes3d fig = visualizeSpins(atoms,spins) if showcrystalaxes: ax3d = fig.axes[0] try: mspec=list(self.species.items())[0][1] if mspec.useDiffpyStruc: lat=mspec.struc.lattice a, b, c = lat.stdbase else: a, b, c = mspec.latVecs xo, yo, zo = axesorigin ax3d.quiver(xo, yo, zo, a[0], a[1], a[2], pivot='tail', color='r') ax3d.quiver(xo, yo, zo, b[0], b[1], b[2], pivot='tail', color='g') ax3d.quiver(xo, yo, zo, c[0], c[1], c[2], pivot='tail', color='b') except: print('Please make sure your magnetic structure contains a') print('magnetic species with MagSpecies.struc set to a diffpy') print('structure or MagSpecies.latVecs provided and') print('MagSpecies.useDiffpyStruc set to False.') plt.show()