Module isopytools.fitutils
Tools for adapting ISODISTORT outputs to DiffPy fits.
Expand source code
#!/usr/bin/env python
##############################################################################
#
# isopytools 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.
#
##############################################################################
"""Tools for adapting ISODISTORT outputs to DiffPy fits.
"""
from diffpy.srfit.fitbase.parameterset import ParameterSet
from collections import OrderedDict
import diffpy.structure.symmetryutilities as su
import diffpy.structure.spacegroups as sg
from isopytools.iso2diffpy import get_positions
import numpy as np
import sympy as sp
def getvars(fitnode):
"""Return dictionary of variable names and values in srfit fit object.
"""
state = {p.name: p.value for p in fitnode}
return state
def setvars(fitnode, state):
"""Set fitnode variables according to their state dictionary.
Skip any state items that are constrained.
"""
for n, v in dict(state).items():
p = fitnode.get(n)
p.setValue(v)
return
def generate_constraints(iso):
"""Generate general expressions for the position of each atom.
Args:
iso: IsoInfo instance from iso2diffpy
Returns:
Dictionary with coordinates of each atom expressed in terms of the
deltas (which can in turn be expressed in terms of the mode
amplitudes).
"""
names, coords = get_positions(iso, returnDict=False)
sgobj = sg.GetSpaceGroup(iso.spacegroupnum)
alltransformations = []
uniqueidxs = []
# go through list of symmetry operations once to sort out the duplicates
for idx, name in enumerate(names):
eqpos, symoplist, mult = su.expandPosition(sgobj, coords[idx])
allpositions = []
for sublist in symoplist:
for symop in sublist:
newposN = (np.dot(symop.R, coords[idx]) + symop.t) % 1
allpositions.append(newposN)
allpositions = np.round(np.array(allpositions), decimals=6)
unique, idxs = np.unique(np.round(allpositions, decimals=4), axis=0, return_index=True)
uniqueidxs.append(idxs)
# now go through it again to apply the symmetry transformations to the mode shifts
counter = 0
for idx, name in enumerate(names):
pos = sp.Matrix([iso.positions[name+'_x'][0],iso.positions[name+'_y'][0],iso.positions[name+'_z'][0]])
eqpos, symoplist, mult = su.expandPosition(sgobj, coords[idx])
partialtransformations = []
for sublist in symoplist:
for symop in sublist:
Rmat = sp.Matrix(symop.R)
tmat = sp.Matrix(symop.t)
newpos = Rmat*pos + tmat
partialtransformations.append(newpos)
subset = [partialtransformations[i] for i in uniqueidxs[counter]]
for vector in subset:
for idx, element in enumerate(vector):
try:
float(element)
vector[idx] = element % 1
except TypeError:
pass
alltransformations.append(subset)
counter += 1
# create a dictionary with the transformation constriants
counter = 0
transformed_positions = OrderedDict()
for tr in alltransformations:
for transformed_pos in tr:
counter += 1
transformed_positions['atom'+str(counter)+'_x'] = str(transformed_pos[0])
transformed_positions['atom'+str(counter)+'_y'] = str(transformed_pos[1])
transformed_positions['atom'+str(counter)+'_z'] = str(transformed_pos[2])
return transformed_positions
def loadmodes(fit,iso,phase):
"""Turns symmetry mode amplitudes into fit variables.
This function creates a ParameterSet consisting of the symmetry mode
amplitudes, constrains the xyz positions of the atoms in relation to
the mode amplitudes, and sets the amplitudes as fitting variables. The
mode amplitude fitting variables are given the tag 'smd'.
Args:
fit: diffpy.srfit FitRecipe instance
iso: IsoInfo instance from iso2diffpy
phase: diffpy.structure parameters generated from the structure
that was added to the PDF contribution.
"""
smd = ParameterSet('smd')
phase.addParameterSet(smd)
for n, v in iso.modepars.items():
smd.newParameter(n, v)
for n, ev in iso.deltas.items():
smd.newParameter(n, ev[-1])
smd.constrain(n, ev[0])
atoms = phase.getScatterers()
transform_dict = generate_constraints(iso)
for idx, (v, ev) in enumerate(transform_dict.items()):
atomIdx, mod = divmod(idx, 3)
if mod == 0:
smd.constrain(atoms[atomIdx].x,ev)
if mod == 1:
smd.constrain(atoms[atomIdx].y,ev)
if mod == 2:
smd.constrain(atoms[atomIdx].z,ev)
for pa in smd.iterPars('a\d+'):
fit.addVar(pa,tag='smd')
Functions
def generate_constraints(iso)
-
Generate general expressions for the position of each atom.
Args
iso
- IsoInfo instance from iso2diffpy
Returns
Dictionary with coordinates of each atom expressed in terms of the deltas (which can in turn be expressed in terms of the mode amplitudes).
Expand source code
def generate_constraints(iso): """Generate general expressions for the position of each atom. Args: iso: IsoInfo instance from iso2diffpy Returns: Dictionary with coordinates of each atom expressed in terms of the deltas (which can in turn be expressed in terms of the mode amplitudes). """ names, coords = get_positions(iso, returnDict=False) sgobj = sg.GetSpaceGroup(iso.spacegroupnum) alltransformations = [] uniqueidxs = [] # go through list of symmetry operations once to sort out the duplicates for idx, name in enumerate(names): eqpos, symoplist, mult = su.expandPosition(sgobj, coords[idx]) allpositions = [] for sublist in symoplist: for symop in sublist: newposN = (np.dot(symop.R, coords[idx]) + symop.t) % 1 allpositions.append(newposN) allpositions = np.round(np.array(allpositions), decimals=6) unique, idxs = np.unique(np.round(allpositions, decimals=4), axis=0, return_index=True) uniqueidxs.append(idxs) # now go through it again to apply the symmetry transformations to the mode shifts counter = 0 for idx, name in enumerate(names): pos = sp.Matrix([iso.positions[name+'_x'][0],iso.positions[name+'_y'][0],iso.positions[name+'_z'][0]]) eqpos, symoplist, mult = su.expandPosition(sgobj, coords[idx]) partialtransformations = [] for sublist in symoplist: for symop in sublist: Rmat = sp.Matrix(symop.R) tmat = sp.Matrix(symop.t) newpos = Rmat*pos + tmat partialtransformations.append(newpos) subset = [partialtransformations[i] for i in uniqueidxs[counter]] for vector in subset: for idx, element in enumerate(vector): try: float(element) vector[idx] = element % 1 except TypeError: pass alltransformations.append(subset) counter += 1 # create a dictionary with the transformation constriants counter = 0 transformed_positions = OrderedDict() for tr in alltransformations: for transformed_pos in tr: counter += 1 transformed_positions['atom'+str(counter)+'_x'] = str(transformed_pos[0]) transformed_positions['atom'+str(counter)+'_y'] = str(transformed_pos[1]) transformed_positions['atom'+str(counter)+'_z'] = str(transformed_pos[2]) return transformed_positions
def getvars(fitnode)
-
Return dictionary of variable names and values in srfit fit object.
Expand source code
def getvars(fitnode): """Return dictionary of variable names and values in srfit fit object. """ state = {p.name: p.value for p in fitnode} return state
def loadmodes(fit, iso, phase)
-
Turns symmetry mode amplitudes into fit variables.
This function creates a ParameterSet consisting of the symmetry mode amplitudes, constrains the xyz positions of the atoms in relation to the mode amplitudes, and sets the amplitudes as fitting variables. The mode amplitude fitting variables are given the tag 'smd'.
Args
fit
- diffpy.srfit FitRecipe instance
iso
- IsoInfo instance from iso2diffpy
phase
- diffpy.structure parameters generated from the structure that was added to the PDF contribution.
Expand source code
def loadmodes(fit,iso,phase): """Turns symmetry mode amplitudes into fit variables. This function creates a ParameterSet consisting of the symmetry mode amplitudes, constrains the xyz positions of the atoms in relation to the mode amplitudes, and sets the amplitudes as fitting variables. The mode amplitude fitting variables are given the tag 'smd'. Args: fit: diffpy.srfit FitRecipe instance iso: IsoInfo instance from iso2diffpy phase: diffpy.structure parameters generated from the structure that was added to the PDF contribution. """ smd = ParameterSet('smd') phase.addParameterSet(smd) for n, v in iso.modepars.items(): smd.newParameter(n, v) for n, ev in iso.deltas.items(): smd.newParameter(n, ev[-1]) smd.constrain(n, ev[0]) atoms = phase.getScatterers() transform_dict = generate_constraints(iso) for idx, (v, ev) in enumerate(transform_dict.items()): atomIdx, mod = divmod(idx, 3) if mod == 0: smd.constrain(atoms[atomIdx].x,ev) if mod == 1: smd.constrain(atoms[atomIdx].y,ev) if mod == 2: smd.constrain(atoms[atomIdx].z,ev) for pa in smd.iterPars('a\d+'): fit.addVar(pa,tag='smd')
def setvars(fitnode, state)
-
Set fitnode variables according to their state dictionary.
Skip any state items that are constrained.
Expand source code
def setvars(fitnode, state): """Set fitnode variables according to their state dictionary. Skip any state items that are constrained. """ for n, v in dict(state).items(): p = fitnode.get(n) p.setValue(v) return