Module diffpy.mpdf.mpdfcalculator
class to perform 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.
#
##############################################################################
"""class to perform mPDF calculations"""
import copy
import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import convolve, fftconvolve
from diffpy.mpdf.magutils import calculatemPDF, calculateDr, estimate_effective_xi
class MPDFcalculator:
"""Create an MPDFcalculator object to help calculate mPDF functions.
This class is loosely modelled after the PDFcalculator class in diffpy.
At minimum, it requires a magnetic structure with atoms and spins, and
it calculates the mPDF from that. Various other options can be specified
for the calculated mPDF.
Args:
magstruc (MagStructure object): provides information about the
magnetic structure. Must have arrays of atoms and spins.
extendedrmin (float): extension of the r-grid on which the mPDF is
calculated to properly account for contribution of pairs just
before the boundary. 4 A by default.
extendedrmax (float): extension of the r-grid on which the mPDF is
calculated to properly account for contribution of pairs just
outside the boundary. 4 A by default.
qdamp (float): usual PDF qdamp parameter. Turned off if set to zero.
qmin (float): minimum experimentally accessible q-value (to be used
for simulating termination ripples). If <0, no termination effects
are included.
qmax (float): maximum experimentally accessible q-value (to be used
for simulating termination ripples). If <0, no termination effects
are included.
rmin (float): minimum value of r for which mPDF should be calculated.
rmax (float): maximum value of r for which mPDF should be calculated.
rstep (float): step size for r-grid of calculated mPDF.
ordScale (float): overall scale factor for the mPDF function f(r).
paraScale (float): scale factor for the paramagnetic part of the
unnormalized mPDF function D(r).
rmintr (float): minimum value of r for the Fourier transform of the
magnetic form factor required for unnormalized mPDF.
rmaxtr (float): maximum value of r for the Fourier transform of the
magnetic form factor required for unnormalized mPDF.
drtr (float): step size for r-grid used for calculating Fourier
transform of magnetic form mactor.
label (string): Optional descriptive string for the MPDFcalculator.
qwindow (numpy array): Q-space window function applied to the data
prior to Fourier transformation. Not used by default.
qgrid (numpy array): Q-space grid on which the window function is
defined.
"""
def __init__(self, magstruc=None, extendedrmax=4.0,
extendedrmin=4.0, qdamp=0.0, qmin=0.0,
qmax=-1.0, rmin=0.0, rmax=20.0, rstep=0.01,
ordScale=1.0, paraScale=1.0, rmintr=-5.0,
rmaxtr=5.0, label='', qwindow=None, qgrid=None):
if magstruc is None:
self.magstruc = []
else:
self.magstruc = magstruc
if magstruc.rmaxAtoms < rmax:
print('Warning: Your structure may not be big enough for your')
print('desired calculation range.')
self.extendedrmin = extendedrmin
self.extendedrmax = extendedrmax
self.qdamp = qdamp
self.qmin = qmin
self.qmax = qmax
self.rmin = rmin
self.rmax = rmax
self.rstep = rstep
self.ordScale = ordScale
self.paraScale = paraScale
self.rmintr = rmintr
self.rmaxtr = rmaxtr
self.label = label
if qwindow is None:
self.qwindow = np.array([0])
else:
self.qwindow = qwindow
if qgrid is None:
self.qgrid = np.array([0])
else:
self.qgrid = qgrid
def __repr__(self):
if self.label == '':
return 'MPDFcalculator() object'
else:
return self.label+': MPDFcalculator() object'
def calc(self, normalized=True, both=False, correlationMethod='simple',
linearTermMethod='exact'):
"""Calculate the magnetic PDF.
Args:
normalized (boolean): indicates whether or not the normalized mPDF
should be returned.
both (boolean): indicates whether or not both normalized and
unnormalized mPDF quantities should be returned.
correlationMethod (string): determines how the calculation should
be done if the correlation length is finite. Options are:
'simple'; exponential envelope is applied to the mPDF
'full'; actual spin magnitudes are adjusted according to the
correlation length; more accurate (especially for very
short correlation lengths) but slower (especially if
rmax is beyond ~30 A)
'auto'; simple method is chosen if xi <= 5 A, full otherwise
Note that any other option will be converted to 'simple'
linearTermMethod (string): determines how the calculation will
handle the linear term present for magnetic structures with a net
magnetization. Options are:
'exact'; slope will be calculated from the values of
MagStructure.rho0 and MagStructure.netMag, damping set by
MagStructure.corrLength.
'autoslope': slope will be determined by least-squares
minimization of the calculated mPDF, thereby ensuring that
the mPDF oscillates around zero, as it is supposed to.
Damping set by MagStructure.corrLength.
'fullauto': slope and damping set by least-squares minimization.
This should only be used in the case of an anisotropic
correlation length.
Note that any other option will be converted to 'exact'.
Returns: numpy array giving the r grid of the calculation, as well as
one or both of the mPDF quantities.
"""
peakWidth = np.sqrt(self.magstruc.Uiso)
if correlationMethod not in ['simple', 'full', 'auto']:
correlationMethod = 'simple' # convert non-standard inputs to simple
if linearTermMethod not in ['exact', 'autoslope', 'fullauto']:
linearTermMethod = 'exact' # convert non-standard inputs to simple
dampingMat = self.magstruc.dampingMat
xi = self.magstruc.corrLength
if correlationMethod == 'auto': # convert to full or simple
if xi <= 5.0:
correlationMethod = 'full'
else:
correlationMethod = 'simple'
if type(dampingMat) == np.ndarray and correlationMethod != 'full':
print("Warning: correlationMethod should be set to 'full' when using")
print("the damping matrix instead of a scalar correlation length.")
if type(dampingMat) != np.ndarray and linearTermMethod == 'fullauto':
print("Warning: 'fullauto' should only be used with an anisotropic")
print("correlation length as encoded in the damping matrix.")
if (xi==0) and (type(dampingMat) != np.ndarray):
calcIdxs = self.magstruc.calcIdxs
rcalc, frcalc = calculatemPDF(self.magstruc.atoms, self.magstruc.spins,
self.magstruc.gfactors, calcIdxs,
self.rstep, self.rmin, self.rmax,
peakWidth, self.qmin, self.qmax,
self.qdamp, self.extendedrmin,
self.extendedrmax, self.ordScale,
self.magstruc.K1, self.magstruc.rho0,
self.magstruc.netMag, xi,
linearTermMethod, False, self.qwindow,
self.qgrid)
elif correlationMethod == 'full': # change magnitudes of the spins
originalSpins = 1.0*self.magstruc.spins
for i, currentIdx in enumerate(self.magstruc.calcIdxs):
self.magstruc.spins = self.magstruc.generateScaledSpins(currentIdx)
rcalc, frtemp = calculatemPDF(self.magstruc.atoms, self.magstruc.spins,
self.magstruc.gfactors, [currentIdx],
self.rstep, self.rmin, self.rmax,
peakWidth, self.qmin, self.qmax,
self.qdamp, self.extendedrmin,
self.extendedrmax, self.ordScale,
self.magstruc.K1, self.magstruc.rho0,
self.magstruc.netMag, xi,
linearTermMethod, False, self.qwindow,
self.qgrid)
if i==0:
frcalc = 1.0*frtemp
else:
frcalc += frtemp
self.magstruc.spins = 1.0*originalSpins
frcalc /= len(self.magstruc.calcIdxs)
else: # simple method: apply exponential envelope
calcIdxs = self.magstruc.calcIdxs
rcalc, frcalc = calculatemPDF(self.magstruc.atoms, self.magstruc.spins,
self.magstruc.gfactors, calcIdxs,
self.rstep, self.rmin, self.rmax,
peakWidth, self.qmin, self.qmax,
self.qdamp, self.extendedrmin,
self.extendedrmax, self.ordScale,
self.magstruc.K1, self.magstruc.rho0,
self.magstruc.netMag, xi,
linearTermMethod, True, self.qwindow,
self.qgrid)
# create a mask to put the calculation on the desired grid
mask = np.logical_and(rcalc > self.rmin - 0.5*self.rstep,
rcalc < self.rmax + 0.5*self.rstep)
if normalized and not both:
return rcalc[mask], frcalc[mask]
elif not normalized and not both:
Drcalc = calculateDr(rcalc, frcalc, self.magstruc.ffqgrid,
self.magstruc.ff, self.paraScale, self.rmintr,
self.rmaxtr, self.rstep, self.qmin, self.qmax,
self.magstruc.K1, self.magstruc.K2)
return rcalc[mask], Drcalc[mask]
else:
Drcalc = calculateDr(rcalc, frcalc, self.magstruc.ffqgrid,
self.magstruc.ff, self.paraScale, self.rmintr,
self.rmaxtr, self.rstep, self.qmin, self.qmax,
self.magstruc.K1, self.magstruc.K2)
return rcalc[mask], frcalc[mask], Drcalc[mask]
def plot(self, normalized=True, both=False, scaled=True,
correlationMethod='simple', linearTermMethod='exact'):
"""Plot the magnetic PDF.
Args:
normalized (boolean): indicates whether or not the normalized mPDF
should be plotted.
both (boolean): indicates whether or not both normalized and
unnormalized mPDF quantities should be plotted.
correlationMethod (string): determines how the calculation should
be done if the correlation length is finite. Options are:
'simple'; exponential envelope is applied to the mPDF
'full'; actual spin magnitudes are adjusted according to the
correlation length; more accurate (especially for very
short correlation lengths) but slower (especially if
rmax is beyond ~30 A)
'auto'; simple method is chosen if xi <= 5 A, full otherwise
Note that any other option will be converted to 'simple'
linearTermMethod (string): determines how the calculation will
handle the linear term present for magnetic structures with a net
magnetization. Options are:
'exact'; slope will be calculated from the values of
MagStructure.rho0 and MagStructure.netMag, damping set by
MagStructure.corrLength.
'autoslope': slope will be determined by least-squares
minimization of the calculated mPDF, thereby ensuring that
the mPDF oscillates around zero, as it is supposed to.
Damping set by MagStructure.corrLength.
'fullauto': slope and damping set by least-squares minimization.
This should only be used in the case of an anisotropic
correlation length.
Note that any other option will be converted to 'exact'.
"""
fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_xlabel(r'r ($\mathdefault{\AA}$)')
ax.set_xlim(xmin=self.rmin, xmax=self.rmax)
if normalized and not both:
rcalc, frcalc = self.calc(normalized, both, correlationMethod, linearTermMethod)
ax.plot(rcalc, frcalc)
ax.set_ylabel(r'f ($\mathdefault{\AA^{-2}}$)')
elif not normalized and not both:
rcalc, Drcalc = self.calc(normalized, both, correlationMethod, linearTermMethod)
ax.plot(rcalc, Drcalc)
ax.set_ylabel(r'd ($\mathdefault{\AA^{-2}}$)')
else:
rcalc, frcalc, Drcalc = self.calc(normalized, both, correlationMethod, linearTermMethod)
if scaled:
frscl = np.max(np.abs(frcalc))
drscl = np.max(np.abs(Drcalc[rcalc>1.5]))
scl = frscl / drscl
else:
scl = 1.0
ax.plot(rcalc, frcalc, 'b-', label='f(r)')
ax.plot(rcalc, scl * Drcalc, 'r-', label='d(r)')
ax.set_ylabel(r'f, d ($\mathdefault{\AA^{-2}}$)')
plt.legend(loc='best')
plt.show()
def runChecks(self):
"""Run some quick checks to help with troubleshooting.
"""
print('Running checks on MPDFcalculator...\n')
flagCount = 0
flag = False
### check if number of spins and atoms do not match
if self.magstruc.atoms.shape[0] != self.magstruc.spins.shape[0]:
flag = True
if flag:
flagCount += 1
print('Number of atoms and spins do not match; try calling')
print('makeAtoms() and makeSpins() again on your MagStructure.\n')
flag = False
### check for nan values in spin array
if np.any(np.isnan(self.magstruc.spins)):
flag = True
if flag:
flagCount += 1
print('Spin array contains nan values ("not a number").\n')
flag = False
### check if rmax is too big for rmaxAtoms in structure
for key in self.magstruc.species:
if self.magstruc.species[key].rmaxAtoms < self.rmax:
flag = True
if flag:
flagCount += 1
print('Warning: the atoms in your MagStructure may not fill a')
print('volume large enough for the desired rmax for the mPDF')
print('calculation. Adjust rmax and/or rmaxAtoms in the')
print('MagSpecies or MagStructure objects.\n')
flag = False
### check for unphysical parameters like negative scale factors
if np.min((self.paraScale, self.ordScale)) < 0:
flag = True
if flag:
flagCount += 1
print('Warning: you have a negative scale factor.')
print(('Paramagnetic scale = ', self.paraScale))
print(('Ordered scale = ', self.ordScale))
flag = False
if flagCount == 0:
print('All checks passed. No obvious problems found.\n')
def rgrid(self):
"""Return the current r grid of the mPDF calculator."""
r = np.arange(0,self.rmax+10,self.rstep)
mask = np.logical_and(r > self.rmin - 0.5*self.rstep,
r < self.rmax + 0.5*self.rstep)
return r[mask]
def copy(self):
"""Return a deep copy of the MPDFcalculator object."""
return copy.deepcopy(self)
Classes
class MPDFcalculator (magstruc=None, extendedrmax=4.0, extendedrmin=4.0, qdamp=0.0, qmin=0.0, qmax=-1.0, rmin=0.0, rmax=20.0, rstep=0.01, ordScale=1.0, paraScale=1.0, rmintr=-5.0, rmaxtr=5.0, label='', qwindow=None, qgrid=None)
-
Create an MPDFcalculator object to help calculate mPDF functions.
This class is loosely modelled after the PDFcalculator class in diffpy. At minimum, it requires a magnetic structure with atoms and spins, and it calculates the mPDF from that. Various other options can be specified for the calculated mPDF.
Args
magstruc
:MagStructure object
- provides information about the magnetic structure. Must have arrays of atoms and spins.
extendedrmin
:float
- extension of the r-grid on which the mPDF is calculated to properly account for contribution of pairs just before the boundary. 4 A by default.
extendedrmax
:float
- extension of the r-grid on which the mPDF is calculated to properly account for contribution of pairs just outside the boundary. 4 A by default.
qdamp
:float
- usual PDF qdamp parameter. Turned off if set to zero.
qmin
:float
- minimum experimentally accessible q-value (to be used for simulating termination ripples). If <0, no termination effects are included.
qmax
:float
- maximum experimentally accessible q-value (to be used for simulating termination ripples). If <0, no termination effects are included.
rmin
:float
- minimum value of r for which mPDF should be calculated.
rmax
:float
- maximum value of r for which mPDF should be calculated.
rstep
:float
- step size for r-grid of calculated mPDF.
ordScale
:float
- overall scale factor for the mPDF function f(r).
paraScale
:float
- scale factor for the paramagnetic part of the unnormalized mPDF function D(r).
rmintr
:float
- minimum value of r for the Fourier transform of the magnetic form factor required for unnormalized mPDF.
rmaxtr
:float
- maximum value of r for the Fourier transform of the magnetic form factor required for unnormalized mPDF.
drtr
:float
- step size for r-grid used for calculating Fourier transform of magnetic form mactor.
label
:string
- Optional descriptive string for the MPDFcalculator.
qwindow
:numpy array
- Q-space window function applied to the data prior to Fourier transformation. Not used by default.
qgrid
:numpy array
- Q-space grid on which the window function is defined.
Expand source code
class MPDFcalculator: """Create an MPDFcalculator object to help calculate mPDF functions. This class is loosely modelled after the PDFcalculator class in diffpy. At minimum, it requires a magnetic structure with atoms and spins, and it calculates the mPDF from that. Various other options can be specified for the calculated mPDF. Args: magstruc (MagStructure object): provides information about the magnetic structure. Must have arrays of atoms and spins. extendedrmin (float): extension of the r-grid on which the mPDF is calculated to properly account for contribution of pairs just before the boundary. 4 A by default. extendedrmax (float): extension of the r-grid on which the mPDF is calculated to properly account for contribution of pairs just outside the boundary. 4 A by default. qdamp (float): usual PDF qdamp parameter. Turned off if set to zero. qmin (float): minimum experimentally accessible q-value (to be used for simulating termination ripples). If <0, no termination effects are included. qmax (float): maximum experimentally accessible q-value (to be used for simulating termination ripples). If <0, no termination effects are included. rmin (float): minimum value of r for which mPDF should be calculated. rmax (float): maximum value of r for which mPDF should be calculated. rstep (float): step size for r-grid of calculated mPDF. ordScale (float): overall scale factor for the mPDF function f(r). paraScale (float): scale factor for the paramagnetic part of the unnormalized mPDF function D(r). rmintr (float): minimum value of r for the Fourier transform of the magnetic form factor required for unnormalized mPDF. rmaxtr (float): maximum value of r for the Fourier transform of the magnetic form factor required for unnormalized mPDF. drtr (float): step size for r-grid used for calculating Fourier transform of magnetic form mactor. label (string): Optional descriptive string for the MPDFcalculator. qwindow (numpy array): Q-space window function applied to the data prior to Fourier transformation. Not used by default. qgrid (numpy array): Q-space grid on which the window function is defined. """ def __init__(self, magstruc=None, extendedrmax=4.0, extendedrmin=4.0, qdamp=0.0, qmin=0.0, qmax=-1.0, rmin=0.0, rmax=20.0, rstep=0.01, ordScale=1.0, paraScale=1.0, rmintr=-5.0, rmaxtr=5.0, label='', qwindow=None, qgrid=None): if magstruc is None: self.magstruc = [] else: self.magstruc = magstruc if magstruc.rmaxAtoms < rmax: print('Warning: Your structure may not be big enough for your') print('desired calculation range.') self.extendedrmin = extendedrmin self.extendedrmax = extendedrmax self.qdamp = qdamp self.qmin = qmin self.qmax = qmax self.rmin = rmin self.rmax = rmax self.rstep = rstep self.ordScale = ordScale self.paraScale = paraScale self.rmintr = rmintr self.rmaxtr = rmaxtr self.label = label if qwindow is None: self.qwindow = np.array([0]) else: self.qwindow = qwindow if qgrid is None: self.qgrid = np.array([0]) else: self.qgrid = qgrid def __repr__(self): if self.label == '': return 'MPDFcalculator() object' else: return self.label+': MPDFcalculator() object' def calc(self, normalized=True, both=False, correlationMethod='simple', linearTermMethod='exact'): """Calculate the magnetic PDF. Args: normalized (boolean): indicates whether or not the normalized mPDF should be returned. both (boolean): indicates whether or not both normalized and unnormalized mPDF quantities should be returned. correlationMethod (string): determines how the calculation should be done if the correlation length is finite. Options are: 'simple'; exponential envelope is applied to the mPDF 'full'; actual spin magnitudes are adjusted according to the correlation length; more accurate (especially for very short correlation lengths) but slower (especially if rmax is beyond ~30 A) 'auto'; simple method is chosen if xi <= 5 A, full otherwise Note that any other option will be converted to 'simple' linearTermMethod (string): determines how the calculation will handle the linear term present for magnetic structures with a net magnetization. Options are: 'exact'; slope will be calculated from the values of MagStructure.rho0 and MagStructure.netMag, damping set by MagStructure.corrLength. 'autoslope': slope will be determined by least-squares minimization of the calculated mPDF, thereby ensuring that the mPDF oscillates around zero, as it is supposed to. Damping set by MagStructure.corrLength. 'fullauto': slope and damping set by least-squares minimization. This should only be used in the case of an anisotropic correlation length. Note that any other option will be converted to 'exact'. Returns: numpy array giving the r grid of the calculation, as well as one or both of the mPDF quantities. """ peakWidth = np.sqrt(self.magstruc.Uiso) if correlationMethod not in ['simple', 'full', 'auto']: correlationMethod = 'simple' # convert non-standard inputs to simple if linearTermMethod not in ['exact', 'autoslope', 'fullauto']: linearTermMethod = 'exact' # convert non-standard inputs to simple dampingMat = self.magstruc.dampingMat xi = self.magstruc.corrLength if correlationMethod == 'auto': # convert to full or simple if xi <= 5.0: correlationMethod = 'full' else: correlationMethod = 'simple' if type(dampingMat) == np.ndarray and correlationMethod != 'full': print("Warning: correlationMethod should be set to 'full' when using") print("the damping matrix instead of a scalar correlation length.") if type(dampingMat) != np.ndarray and linearTermMethod == 'fullauto': print("Warning: 'fullauto' should only be used with an anisotropic") print("correlation length as encoded in the damping matrix.") if (xi==0) and (type(dampingMat) != np.ndarray): calcIdxs = self.magstruc.calcIdxs rcalc, frcalc = calculatemPDF(self.magstruc.atoms, self.magstruc.spins, self.magstruc.gfactors, calcIdxs, self.rstep, self.rmin, self.rmax, peakWidth, self.qmin, self.qmax, self.qdamp, self.extendedrmin, self.extendedrmax, self.ordScale, self.magstruc.K1, self.magstruc.rho0, self.magstruc.netMag, xi, linearTermMethod, False, self.qwindow, self.qgrid) elif correlationMethod == 'full': # change magnitudes of the spins originalSpins = 1.0*self.magstruc.spins for i, currentIdx in enumerate(self.magstruc.calcIdxs): self.magstruc.spins = self.magstruc.generateScaledSpins(currentIdx) rcalc, frtemp = calculatemPDF(self.magstruc.atoms, self.magstruc.spins, self.magstruc.gfactors, [currentIdx], self.rstep, self.rmin, self.rmax, peakWidth, self.qmin, self.qmax, self.qdamp, self.extendedrmin, self.extendedrmax, self.ordScale, self.magstruc.K1, self.magstruc.rho0, self.magstruc.netMag, xi, linearTermMethod, False, self.qwindow, self.qgrid) if i==0: frcalc = 1.0*frtemp else: frcalc += frtemp self.magstruc.spins = 1.0*originalSpins frcalc /= len(self.magstruc.calcIdxs) else: # simple method: apply exponential envelope calcIdxs = self.magstruc.calcIdxs rcalc, frcalc = calculatemPDF(self.magstruc.atoms, self.magstruc.spins, self.magstruc.gfactors, calcIdxs, self.rstep, self.rmin, self.rmax, peakWidth, self.qmin, self.qmax, self.qdamp, self.extendedrmin, self.extendedrmax, self.ordScale, self.magstruc.K1, self.magstruc.rho0, self.magstruc.netMag, xi, linearTermMethod, True, self.qwindow, self.qgrid) # create a mask to put the calculation on the desired grid mask = np.logical_and(rcalc > self.rmin - 0.5*self.rstep, rcalc < self.rmax + 0.5*self.rstep) if normalized and not both: return rcalc[mask], frcalc[mask] elif not normalized and not both: Drcalc = calculateDr(rcalc, frcalc, self.magstruc.ffqgrid, self.magstruc.ff, self.paraScale, self.rmintr, self.rmaxtr, self.rstep, self.qmin, self.qmax, self.magstruc.K1, self.magstruc.K2) return rcalc[mask], Drcalc[mask] else: Drcalc = calculateDr(rcalc, frcalc, self.magstruc.ffqgrid, self.magstruc.ff, self.paraScale, self.rmintr, self.rmaxtr, self.rstep, self.qmin, self.qmax, self.magstruc.K1, self.magstruc.K2) return rcalc[mask], frcalc[mask], Drcalc[mask] def plot(self, normalized=True, both=False, scaled=True, correlationMethod='simple', linearTermMethod='exact'): """Plot the magnetic PDF. Args: normalized (boolean): indicates whether or not the normalized mPDF should be plotted. both (boolean): indicates whether or not both normalized and unnormalized mPDF quantities should be plotted. correlationMethod (string): determines how the calculation should be done if the correlation length is finite. Options are: 'simple'; exponential envelope is applied to the mPDF 'full'; actual spin magnitudes are adjusted according to the correlation length; more accurate (especially for very short correlation lengths) but slower (especially if rmax is beyond ~30 A) 'auto'; simple method is chosen if xi <= 5 A, full otherwise Note that any other option will be converted to 'simple' linearTermMethod (string): determines how the calculation will handle the linear term present for magnetic structures with a net magnetization. Options are: 'exact'; slope will be calculated from the values of MagStructure.rho0 and MagStructure.netMag, damping set by MagStructure.corrLength. 'autoslope': slope will be determined by least-squares minimization of the calculated mPDF, thereby ensuring that the mPDF oscillates around zero, as it is supposed to. Damping set by MagStructure.corrLength. 'fullauto': slope and damping set by least-squares minimization. This should only be used in the case of an anisotropic correlation length. Note that any other option will be converted to 'exact'. """ fig = plt.figure() ax = fig.add_subplot(111) ax.set_xlabel(r'r ($\mathdefault{\AA}$)') ax.set_xlim(xmin=self.rmin, xmax=self.rmax) if normalized and not both: rcalc, frcalc = self.calc(normalized, both, correlationMethod, linearTermMethod) ax.plot(rcalc, frcalc) ax.set_ylabel(r'f ($\mathdefault{\AA^{-2}}$)') elif not normalized and not both: rcalc, Drcalc = self.calc(normalized, both, correlationMethod, linearTermMethod) ax.plot(rcalc, Drcalc) ax.set_ylabel(r'd ($\mathdefault{\AA^{-2}}$)') else: rcalc, frcalc, Drcalc = self.calc(normalized, both, correlationMethod, linearTermMethod) if scaled: frscl = np.max(np.abs(frcalc)) drscl = np.max(np.abs(Drcalc[rcalc>1.5])) scl = frscl / drscl else: scl = 1.0 ax.plot(rcalc, frcalc, 'b-', label='f(r)') ax.plot(rcalc, scl * Drcalc, 'r-', label='d(r)') ax.set_ylabel(r'f, d ($\mathdefault{\AA^{-2}}$)') plt.legend(loc='best') plt.show() def runChecks(self): """Run some quick checks to help with troubleshooting. """ print('Running checks on MPDFcalculator...\n') flagCount = 0 flag = False ### check if number of spins and atoms do not match if self.magstruc.atoms.shape[0] != self.magstruc.spins.shape[0]: flag = True if flag: flagCount += 1 print('Number of atoms and spins do not match; try calling') print('makeAtoms() and makeSpins() again on your MagStructure.\n') flag = False ### check for nan values in spin array if np.any(np.isnan(self.magstruc.spins)): flag = True if flag: flagCount += 1 print('Spin array contains nan values ("not a number").\n') flag = False ### check if rmax is too big for rmaxAtoms in structure for key in self.magstruc.species: if self.magstruc.species[key].rmaxAtoms < self.rmax: flag = True if flag: flagCount += 1 print('Warning: the atoms in your MagStructure may not fill a') print('volume large enough for the desired rmax for the mPDF') print('calculation. Adjust rmax and/or rmaxAtoms in the') print('MagSpecies or MagStructure objects.\n') flag = False ### check for unphysical parameters like negative scale factors if np.min((self.paraScale, self.ordScale)) < 0: flag = True if flag: flagCount += 1 print('Warning: you have a negative scale factor.') print(('Paramagnetic scale = ', self.paraScale)) print(('Ordered scale = ', self.ordScale)) flag = False if flagCount == 0: print('All checks passed. No obvious problems found.\n') def rgrid(self): """Return the current r grid of the mPDF calculator.""" r = np.arange(0,self.rmax+10,self.rstep) mask = np.logical_and(r > self.rmin - 0.5*self.rstep, r < self.rmax + 0.5*self.rstep) return r[mask] def copy(self): """Return a deep copy of the MPDFcalculator object.""" return copy.deepcopy(self)
Methods
def calc(self, normalized=True, both=False, correlationMethod='simple', linearTermMethod='exact')
-
Calculate the magnetic PDF.
Args
normalized
:boolean
- indicates whether or not the normalized mPDF should be returned.
both
:boolean
- indicates whether or not both normalized and unnormalized mPDF quantities should be returned.
correlationMethod
:string
- determines how the calculation should be done if the correlation length is finite. Options are: 'simple'; exponential envelope is applied to the mPDF 'full'; actual spin magnitudes are adjusted according to the correlation length; more accurate (especially for very short correlation lengths) but slower (especially if rmax is beyond ~30 A) 'auto'; simple method is chosen if xi <= 5 A, full otherwise Note that any other option will be converted to 'simple'
linearTermMethod
:string
- determines how the calculation will handle the linear term present for magnetic structures with a net magnetization. Options are: 'exact'; slope will be calculated from the values of MagStructure.rho0 and MagStructure.netMag, damping set by MagStructure.corrLength. 'autoslope': slope will be determined by least-squares minimization of the calculated mPDF, thereby ensuring that the mPDF oscillates around zero, as it is supposed to. Damping set by MagStructure.corrLength. 'fullauto': slope and damping set by least-squares minimization. This should only be used in the case of an anisotropic correlation length. Note that any other option will be converted to 'exact'.
Returns: numpy array giving the r grid of the calculation, as well as one or both of the mPDF quantities.
Expand source code
def calc(self, normalized=True, both=False, correlationMethod='simple', linearTermMethod='exact'): """Calculate the magnetic PDF. Args: normalized (boolean): indicates whether or not the normalized mPDF should be returned. both (boolean): indicates whether or not both normalized and unnormalized mPDF quantities should be returned. correlationMethod (string): determines how the calculation should be done if the correlation length is finite. Options are: 'simple'; exponential envelope is applied to the mPDF 'full'; actual spin magnitudes are adjusted according to the correlation length; more accurate (especially for very short correlation lengths) but slower (especially if rmax is beyond ~30 A) 'auto'; simple method is chosen if xi <= 5 A, full otherwise Note that any other option will be converted to 'simple' linearTermMethod (string): determines how the calculation will handle the linear term present for magnetic structures with a net magnetization. Options are: 'exact'; slope will be calculated from the values of MagStructure.rho0 and MagStructure.netMag, damping set by MagStructure.corrLength. 'autoslope': slope will be determined by least-squares minimization of the calculated mPDF, thereby ensuring that the mPDF oscillates around zero, as it is supposed to. Damping set by MagStructure.corrLength. 'fullauto': slope and damping set by least-squares minimization. This should only be used in the case of an anisotropic correlation length. Note that any other option will be converted to 'exact'. Returns: numpy array giving the r grid of the calculation, as well as one or both of the mPDF quantities. """ peakWidth = np.sqrt(self.magstruc.Uiso) if correlationMethod not in ['simple', 'full', 'auto']: correlationMethod = 'simple' # convert non-standard inputs to simple if linearTermMethod not in ['exact', 'autoslope', 'fullauto']: linearTermMethod = 'exact' # convert non-standard inputs to simple dampingMat = self.magstruc.dampingMat xi = self.magstruc.corrLength if correlationMethod == 'auto': # convert to full or simple if xi <= 5.0: correlationMethod = 'full' else: correlationMethod = 'simple' if type(dampingMat) == np.ndarray and correlationMethod != 'full': print("Warning: correlationMethod should be set to 'full' when using") print("the damping matrix instead of a scalar correlation length.") if type(dampingMat) != np.ndarray and linearTermMethod == 'fullauto': print("Warning: 'fullauto' should only be used with an anisotropic") print("correlation length as encoded in the damping matrix.") if (xi==0) and (type(dampingMat) != np.ndarray): calcIdxs = self.magstruc.calcIdxs rcalc, frcalc = calculatemPDF(self.magstruc.atoms, self.magstruc.spins, self.magstruc.gfactors, calcIdxs, self.rstep, self.rmin, self.rmax, peakWidth, self.qmin, self.qmax, self.qdamp, self.extendedrmin, self.extendedrmax, self.ordScale, self.magstruc.K1, self.magstruc.rho0, self.magstruc.netMag, xi, linearTermMethod, False, self.qwindow, self.qgrid) elif correlationMethod == 'full': # change magnitudes of the spins originalSpins = 1.0*self.magstruc.spins for i, currentIdx in enumerate(self.magstruc.calcIdxs): self.magstruc.spins = self.magstruc.generateScaledSpins(currentIdx) rcalc, frtemp = calculatemPDF(self.magstruc.atoms, self.magstruc.spins, self.magstruc.gfactors, [currentIdx], self.rstep, self.rmin, self.rmax, peakWidth, self.qmin, self.qmax, self.qdamp, self.extendedrmin, self.extendedrmax, self.ordScale, self.magstruc.K1, self.magstruc.rho0, self.magstruc.netMag, xi, linearTermMethod, False, self.qwindow, self.qgrid) if i==0: frcalc = 1.0*frtemp else: frcalc += frtemp self.magstruc.spins = 1.0*originalSpins frcalc /= len(self.magstruc.calcIdxs) else: # simple method: apply exponential envelope calcIdxs = self.magstruc.calcIdxs rcalc, frcalc = calculatemPDF(self.magstruc.atoms, self.magstruc.spins, self.magstruc.gfactors, calcIdxs, self.rstep, self.rmin, self.rmax, peakWidth, self.qmin, self.qmax, self.qdamp, self.extendedrmin, self.extendedrmax, self.ordScale, self.magstruc.K1, self.magstruc.rho0, self.magstruc.netMag, xi, linearTermMethod, True, self.qwindow, self.qgrid) # create a mask to put the calculation on the desired grid mask = np.logical_and(rcalc > self.rmin - 0.5*self.rstep, rcalc < self.rmax + 0.5*self.rstep) if normalized and not both: return rcalc[mask], frcalc[mask] elif not normalized and not both: Drcalc = calculateDr(rcalc, frcalc, self.magstruc.ffqgrid, self.magstruc.ff, self.paraScale, self.rmintr, self.rmaxtr, self.rstep, self.qmin, self.qmax, self.magstruc.K1, self.magstruc.K2) return rcalc[mask], Drcalc[mask] else: Drcalc = calculateDr(rcalc, frcalc, self.magstruc.ffqgrid, self.magstruc.ff, self.paraScale, self.rmintr, self.rmaxtr, self.rstep, self.qmin, self.qmax, self.magstruc.K1, self.magstruc.K2) return rcalc[mask], frcalc[mask], Drcalc[mask]
def copy(self)
-
Return a deep copy of the MPDFcalculator object.
Expand source code
def copy(self): """Return a deep copy of the MPDFcalculator object.""" return copy.deepcopy(self)
def plot(self, normalized=True, both=False, scaled=True, correlationMethod='simple', linearTermMethod='exact')
-
Plot the magnetic PDF.
Args
normalized
:boolean
- indicates whether or not the normalized mPDF should be plotted.
both
:boolean
- indicates whether or not both normalized and unnormalized mPDF quantities should be plotted.
correlationMethod
:string
- determines how the calculation should be done if the correlation length is finite. Options are: 'simple'; exponential envelope is applied to the mPDF 'full'; actual spin magnitudes are adjusted according to the correlation length; more accurate (especially for very short correlation lengths) but slower (especially if rmax is beyond ~30 A) 'auto'; simple method is chosen if xi <= 5 A, full otherwise Note that any other option will be converted to 'simple'
linearTermMethod
:string
- determines how the calculation will handle the linear term present for magnetic structures with a net magnetization. Options are: 'exact'; slope will be calculated from the values of MagStructure.rho0 and MagStructure.netMag, damping set by MagStructure.corrLength. 'autoslope': slope will be determined by least-squares minimization of the calculated mPDF, thereby ensuring that the mPDF oscillates around zero, as it is supposed to. Damping set by MagStructure.corrLength. 'fullauto': slope and damping set by least-squares minimization. This should only be used in the case of an anisotropic correlation length. Note that any other option will be converted to 'exact'.
Expand source code
def plot(self, normalized=True, both=False, scaled=True, correlationMethod='simple', linearTermMethod='exact'): """Plot the magnetic PDF. Args: normalized (boolean): indicates whether or not the normalized mPDF should be plotted. both (boolean): indicates whether or not both normalized and unnormalized mPDF quantities should be plotted. correlationMethod (string): determines how the calculation should be done if the correlation length is finite. Options are: 'simple'; exponential envelope is applied to the mPDF 'full'; actual spin magnitudes are adjusted according to the correlation length; more accurate (especially for very short correlation lengths) but slower (especially if rmax is beyond ~30 A) 'auto'; simple method is chosen if xi <= 5 A, full otherwise Note that any other option will be converted to 'simple' linearTermMethod (string): determines how the calculation will handle the linear term present for magnetic structures with a net magnetization. Options are: 'exact'; slope will be calculated from the values of MagStructure.rho0 and MagStructure.netMag, damping set by MagStructure.corrLength. 'autoslope': slope will be determined by least-squares minimization of the calculated mPDF, thereby ensuring that the mPDF oscillates around zero, as it is supposed to. Damping set by MagStructure.corrLength. 'fullauto': slope and damping set by least-squares minimization. This should only be used in the case of an anisotropic correlation length. Note that any other option will be converted to 'exact'. """ fig = plt.figure() ax = fig.add_subplot(111) ax.set_xlabel(r'r ($\mathdefault{\AA}$)') ax.set_xlim(xmin=self.rmin, xmax=self.rmax) if normalized and not both: rcalc, frcalc = self.calc(normalized, both, correlationMethod, linearTermMethod) ax.plot(rcalc, frcalc) ax.set_ylabel(r'f ($\mathdefault{\AA^{-2}}$)') elif not normalized and not both: rcalc, Drcalc = self.calc(normalized, both, correlationMethod, linearTermMethod) ax.plot(rcalc, Drcalc) ax.set_ylabel(r'd ($\mathdefault{\AA^{-2}}$)') else: rcalc, frcalc, Drcalc = self.calc(normalized, both, correlationMethod, linearTermMethod) if scaled: frscl = np.max(np.abs(frcalc)) drscl = np.max(np.abs(Drcalc[rcalc>1.5])) scl = frscl / drscl else: scl = 1.0 ax.plot(rcalc, frcalc, 'b-', label='f(r)') ax.plot(rcalc, scl * Drcalc, 'r-', label='d(r)') ax.set_ylabel(r'f, d ($\mathdefault{\AA^{-2}}$)') plt.legend(loc='best') plt.show()
def rgrid(self)
-
Return the current r grid of the mPDF calculator.
Expand source code
def rgrid(self): """Return the current r grid of the mPDF calculator.""" r = np.arange(0,self.rmax+10,self.rstep) mask = np.logical_and(r > self.rmin - 0.5*self.rstep, r < self.rmax + 0.5*self.rstep) return r[mask]
def runChecks(self)
-
Run some quick checks to help with troubleshooting.
Expand source code
def runChecks(self): """Run some quick checks to help with troubleshooting. """ print('Running checks on MPDFcalculator...\n') flagCount = 0 flag = False ### check if number of spins and atoms do not match if self.magstruc.atoms.shape[0] != self.magstruc.spins.shape[0]: flag = True if flag: flagCount += 1 print('Number of atoms and spins do not match; try calling') print('makeAtoms() and makeSpins() again on your MagStructure.\n') flag = False ### check for nan values in spin array if np.any(np.isnan(self.magstruc.spins)): flag = True if flag: flagCount += 1 print('Spin array contains nan values ("not a number").\n') flag = False ### check if rmax is too big for rmaxAtoms in structure for key in self.magstruc.species: if self.magstruc.species[key].rmaxAtoms < self.rmax: flag = True if flag: flagCount += 1 print('Warning: the atoms in your MagStructure may not fill a') print('volume large enough for the desired rmax for the mPDF') print('calculation. Adjust rmax and/or rmaxAtoms in the') print('MagSpecies or MagStructure objects.\n') flag = False ### check for unphysical parameters like negative scale factors if np.min((self.paraScale, self.ordScale)) < 0: flag = True if flag: flagCount += 1 print('Warning: you have a negative scale factor.') print(('Paramagnetic scale = ', self.paraScale)) print(('Ordered scale = ', self.ordScale)) flag = False if flagCount == 0: print('All checks passed. No obvious problems found.\n')