#!/usr/bin/env python
##############################################################################
#
# PDFgui by DANSE Diffraction group
# Simon J. L. Billinge
# (c) 2006 trustees of the Michigan State University.
# All rights reserved.
#
# File coded by: Pavol Juhas
#
# See AUTHORS.txt for a list of people who contributed.
# See LICENSE.txt for license information.
#
##############################################################################
"""Class FitStructure for storage of one phase and related fitting
parameters."""
import copy
import re
import numpy
from diffpy.pdfgui.control.constraint import Constraint
from diffpy.pdfgui.control.controlerrors import ControlTypeError, ControlValueError
from diffpy.pdfgui.control.parameter import Parameter
from diffpy.pdfgui.control.pdfstructure import PDFStructure
from diffpy.structure import Atom
[docs]
class FitStructure(PDFStructure):
"""FitStructure holds initial and refined structure and related fit
parameters. Inherited from PDFStructure.
Class data members:
symposeps -- tolerance for recognizing site as symmetry position
Data members (in addition to those in PDFStructure):
owner -- instance of parent Fitting (set in Organizer.add())
initial -- initial structure, same as self
refined -- refined structure when available or None
constraints -- dictionary of { refvar_string : Constraint_instance }
selected_pairs -- string of selected pairs, by default "all-all".
Use setSelectedPairs() and getSelectedPairs() methods
to access its value.
custom_spacegroup -- instance of SpaceGroup which has no equivalent
in diffpy.structure.spacegroups module. This can happen
after reading from a CIF file. When equivalent space
group exists, custom_spacegroup is None.
Refinable variables: pscale, spdiameter, delta1, delta2, sratio, lat(n),
where n=1..6, x(i), y(i), z(i), occ(i), u11(i), u22(i), u33(i),
u12(i), u13(i), u23(i), where i=1..Natoms
Non-refinable variable: rcut, stepcut
"""
# class data members:
symposeps = 0.001
# evaluation of sorted_standard_space_groups deferred when necessary
sorted_standard_space_groups = []
def __init__(self, name, *args, **kwargs):
"""Initialize FitDataSet.
name -- name of the data set. The same name is used for
self.initial and self.final.
args, kwargs -- optional arguments passed to parent PDFStructure
"""
PDFStructure.__init__(self, name)
self.owner = None
# self.initial deliberately not assigned,
# it gets mapped to self by __getattr__
self.refined = None
self.constraints = {}
self.selected_pairs = "all-all"
self.initial.pdffit["sgoffset"] = [0.0, 0.0, 0.0]
self.custom_spacegroup = None
return
def _update_custom_spacegroup(self, parser):
"""Helper method for read() and readStr(), which takes care of setting
custom_spacegroup after successful reading.
parser -- instance of StructureParser used in reading.
No return value.
"""
self.custom_spacegroup = None
self.initial.pdffit["sgoffset"] = [0.0, 0.0, 0.0]
if hasattr(parser, "spacegroup"):
sg = parser.spacegroup
# when sg.number is None or 0, we have a custom spacegroup
if not sg.number:
# overwrite sg.number with 0, an identifier for custom SG
sg.number = 0
self.custom_spacegroup = sg
# here sg.number is 0 or positive integer
self.initial.pdffit["spcgr"] = sg.short_name
return
[docs]
def read(self, filename, format="auto"):
"""Load structure from a file, raise ControlFileError for invalid or
unknown structure format. Overloads PDFStructure.read() to handle
custom_spacegroup attribute.
filename -- file to be loaded
format -- structure format such as 'pdffit', 'pdb', 'xyz'. When
'auto' all available formats are tried in a row.
Return instance of StructureParser used to load the data.
See Structure.read() for more info.
"""
p = PDFStructure.read(self, filename, format)
# update data only after successful reading
self._update_custom_spacegroup(p)
# FIXME Temporary workaround to prevent forced isotropy of the Uij
# values. To be removed and handled by atom isotropy column.
self.anisotropy = True
return p
[docs]
def readStr(self, s, format="auto"):
"""Same as PDFStructure.readStr, but handle the custom_spacegroup data.
Return instance of StructureParser used to load the data. See
Structure.readStr() for more info.
"""
p = PDFStructure.readStr(self, s, format)
# update data only after successful reading
self._update_custom_spacegroup(p)
# FIXME Temporary workaround to prevent forced isotropy of the Uij
# values. To be removed and handled by atom isotropy column.
self.anisotropy = True
return p
def __getattr__(self, name):
"""Map self.initial to self.
This is called only when normal attribute lookup fails.
"""
if name == "initial":
value = self
else:
emsg = "A instance has no attribute '%s'" % name
raise AttributeError(emsg)
return value
def _getStrId(self):
"""Make a string identifier.
return value: string id
"""
return "p_" + self.name
[docs]
def clearRefined(self):
"""Clear all refinement results."""
self.refined = None
return
[docs]
def obtainRefined(self, server, iphase):
"""Upload refined phase from PdfFit server instance.
server -- instance of PdfFit server
iphase -- index of this phase in server
"""
server.setphase(iphase)
if self.refined is None:
self.refined = PDFStructure(self.name)
self.refined.readStr(server.save_struct_string(iphase), "pdffit")
return
[docs]
def findParameters(self):
"""Obtain dictionary of parameters used by self.constraints. The keys
of returned dictionary are integer parameter indices, and the values
are Parameter instances, with guessed initial values.
returns dictionary of indices and Parameter instances
"""
foundpars = {}
for var, con in self.constraints.items():
con.guess(self.initial.getvar(var))
for pidx, pguess in con.parguess.items():
# skip if already found
if pidx in foundpars:
continue
# insert to foundpars otherwise
if pguess is not None:
foundpars[pidx] = Parameter(pidx, initial=pguess)
else:
foundpars[pidx] = Parameter(pidx, initial=0.0)
return foundpars
[docs]
def applyParameters(self, parameters):
"""Evaluate constraint formulas and adjust initial PDFStructure.
parameters -- dictionary of parameter indices with Parameter
instance values. Values may also be float type.
"""
# convert values to floats
parvalues = {}
for pidx, par in parameters.items():
if isinstance(par, Parameter):
parvalues[pidx] = par.initialValue()
else:
parvalues[pidx] = float(par)
# evaluate constraints
for var, con in self.constraints.items():
self.initial.setvar(var, con.evalFormula(parvalues))
return
[docs]
def changeParameterIndex(self, oldidx, newidx):
"""Change a parameter index to a new value.
This will replace all instances of one parameter name with
another in this fit.
"""
for var in self.constraints:
formula = self.constraints[var].formula
pat = r"@%i\b" % oldidx
newformula = re.sub(pat, "@%i" % newidx, formula)
self.constraints[var].formula = newformula
return
def _popAtomConstraints(self):
"""Take out atom-related items from the constraints dictionary.
This is useful when atom indices are going to change due to
insertion or removal of atoms. See also _restoreAtomConstraints().
Return a dictionary of atom instances vs dictionary of related
refinable variables (stripped of "(siteindex)") and Constraint
instances - for example {atom : {'u13' : constraint}}.
"""
rv = {}
# atom variable pattern
avpat = re.compile(r"^([xyz]|occ|u11|u22|u33|u12|u13|u23)\((\d+)\)")
for var in list(self.constraints.keys()):
m = avpat.match(var)
if not m:
continue
barevar = m.group(1)
atomidx = int(m.group(2)) - 1
cnts = rv.setdefault(self.initial[atomidx], {})
cnts[barevar] = self.constraints.pop(var)
return rv
def _restoreAtomConstraints(self, acd):
"""Restore self.constraints from atom constraints dictionary. This is
useful for getting correct atom indices into refvar strings. See also
_popAtomConstraints()
acd -- dictionary obtained from _popAtomConstraints()
"""
for i, a in enumerate(self.initial):
if a not in acd:
continue
# there are some constraints for atom a
siteindex = i + 1
cnts = acd[a]
for barevar, con in cnts.items():
var = barevar + "(%i)" % siteindex
self.constraints[var] = con
return
[docs]
def insertAtoms(self, index, atomlist):
"""Insert list of atoms before index and adjust self.constraints.
index -- position in the initial structure, atoms are appended
when larger than len(self.initial).
atomlist -- list of atom instances.
"""
acd = self._popAtomConstraints()
# FIXME Temporary workaround to prevent forced isotropy of the Uij
# values. To be removed and handled by atom isotropy column.
for a in atomlist:
a.anisotropy = True
# workaround ends here.
self.initial[index:index] = atomlist
self._restoreAtomConstraints(acd)
return
[docs]
def deleteAtoms(self, indices):
"""Removed atoms at given indices and adjust self.constraints.
indices -- list of integer indices of atoms to be deleted
"""
acd = self._popAtomConstraints()
# get unique, reverse sorted indices
ruindices = sorted(set(indices), reverse=True)
for i in ruindices:
self.initial.pop(i)
self._restoreAtomConstraints(acd)
return
[docs]
def expandSuperCell(self, mno):
"""Perform supercell expansion for this structure and adjust
constraints for positions and lattice parameters. New lattice
parameters are multiplied and fractional coordinates divided by
corresponding multiplier. New atoms are grouped with their source in
the original cell.
mno -- tuple or list of three positive integer cell multipliers along
the a, b, c axis
"""
# check argument
if tuple(mno) == (1, 1, 1):
return
if min(mno) < 1:
raise ControlValueError("mno must contain 3 positive integers")
# back to business
acd = self._popAtomConstraints()
mnofloats = numpy.array(mno[:3], dtype=float)
ijklist = [(i, j, k) for i in range(mno[0]) for j in range(mno[1]) for k in range(mno[2])]
# build a list of new atoms
newatoms = []
for a in self.initial:
for ijk in ijklist:
adup = Atom(a)
adup.xyz = (a.xyz + ijk) / mnofloats
newatoms.append(adup)
# does atom a have any constraint?
if a not in acd:
continue
# add empty constraint dictionary for duplicate atom
acd[adup] = {}
for barevar, con in acd[a].items():
formula = con.formula
if barevar in ("x", "y", "z"):
symidx = "xyz".index(barevar)
if ijk[symidx] != 0:
formula += " + %i" % ijk[symidx]
if mno[symidx] > 1:
formula = "(%s)/%.1f" % (formula, mno[symidx])
formula = re.sub(r"\((@\d+)\)", r"\1", formula)
# keep other formulas intact and add constraint
# for barevar of the duplicate atom
acd[adup][barevar] = Constraint(formula)
# replace original atoms with newatoms
self.initial[:] = newatoms
for ai, an in zip(self.initial, newatoms):
if an in acd:
acd[ai] = acd[an]
# and rebuild their constraints
self._restoreAtomConstraints(acd)
# take care of lattice parameters
self.initial.lattice.setLatPar(
a=mno[0] * self.initial.lattice.a,
b=mno[1] * self.initial.lattice.b,
c=mno[2] * self.initial.lattice.c,
)
# adjust lattice constraints if present
latvars = ("lat(1)", "lat(2)", "lat(3)")
for var, multiplier in zip(latvars, mno):
if var in self.constraints and multiplier > 1:
con = self.constraints[var]
formula = "%.0f*(%s)" % (multiplier, con.formula)
formula = re.sub(r"\((@\d+)\)", r"\1", formula)
con.formula = formula
return
[docs]
def isSpaceGroupPossible(self, spacegroup):
"""Check if space group is consistent with lattice parameters.
spacegroup -- instance of SpaceGroup
Return bool.
"""
from diffpy.structure.symmetryutilities import isSpaceGroupLatPar
return isSpaceGroupLatPar(spacegroup, *self.initial.lattice.abcABG())
[docs]
def getSpaceGroupList(self):
"""Return a list of SpaceGroup instances sorted by International Tables
number.
When custom_spacegroup is defined, the list starts with
custom_spacegroup.
"""
if not FitStructure.sorted_standard_space_groups:
import diffpy.structure.spacegroups as SG
existing_names = {}
unique_named_list = []
for sg in SG.SpaceGroupList:
if sg.short_name not in existing_names:
unique_named_list.append(sg)
existing_names[sg.short_name] = True
# sort by International Tables number, stay compatible with 2.3
n_sg = [(sg.number % 1000, sg) for sg in unique_named_list]
n_sg = sorted(n_sg, key=lambda x: x[0]) # sort by the first element of tuple.
FitStructure.sorted_standard_space_groups = [sg for n, sg in n_sg]
sglist = list(FitStructure.sorted_standard_space_groups)
if self.custom_spacegroup:
sglist.insert(0, self.custom_spacegroup)
return sglist
[docs]
def getSpaceGroup(self, sgname):
"""Find space group in getSpaceGroupList() by short_name or number.
sgname can be non-standard in case of CIF file defined space group.
Return instance of SpaceGroup. Raise ValueError if sgname cannot
be found or when it is not present in getSpaceGroupList().
"""
import diffpy.structure.spacegroups as SG
# this should match the "CIF data" sgname
sgmatch = [sg for sg in self.getSpaceGroupList() if sg.short_name == sgname]
# use standard lookup function when not matched by short_name
if not sgmatch:
sgmatch.append(SG.GetSpaceGroup(sgname))
if not sgmatch:
emsg = "Unknown space group %r" % sgname
raise ValueError(emsg)
sgfound = sgmatch[0]
return sgfound
[docs]
def expandAsymmetricUnit(self, spacegroup, indices, sgoffset=[0, 0, 0]):
"""Perform symmetry expansion for atoms at given indices. Temperature
factors may be corrected to reflect the symmetry. All constraints for
expanded atoms are erased with the exception of the occupancy("occ".
Constraints of unaffected atoms are adjusted for new positions
self.initial.
spacegroup -- instance of SpaceGroup from diffpy.structure
indices -- list of integer indices of atoms to be expanded
sgoffset -- optional offset of space group origin [0,0,0]
"""
from diffpy.structure.symmetryutilities import ExpandAsymmetricUnit
acd = self._popAtomConstraints()
# get unique, reverse sorted indices
ruindices = sorted(set(indices), reverse=True)
coreatoms = [self.initial[i] for i in ruindices]
corepos = [a.xyz for a in coreatoms]
coreUijs = [a.U for a in coreatoms]
eau = ExpandAsymmetricUnit(spacegroup, corepos, coreUijs, sgoffset=sgoffset, eps=self.symposeps)
# build a nested list of new atoms:
newatoms = []
for i in range(len(coreatoms)):
ca = coreatoms[i]
caocc_con = None
if ca in acd and "occ" in acd[ca]:
caocc_con = acd[ca]["occ"]
eca = [] # expanded core atom
for j in range(eau.multiplicity[i]):
a = Atom(ca)
a.xyz = eau.expandedpos[i][j]
a.U = eau.expandedUijs[i][j]
eca.append(a)
if caocc_con is None:
continue
# make a copy of occupancy constraint
acd[a] = {"occ": copy.copy(caocc_con)}
newatoms.append(eca)
# insert new atoms where they belong
for i, atomlist in zip(ruindices, newatoms):
self.initial[i : i + 1] = atomlist
# remember this spacegroup as the last one used
self.initial.pdffit["spcgr"] = spacegroup.short_name
self.initial.pdffit["sgoffset"] = list(sgoffset)
# tidy constraints
self._restoreAtomConstraints(acd)
return
[docs]
def applySymmetryConstraints(self, spacegroup, indices, posflag, Uijflag, sgoffset=[0, 0, 0]):
"""Generate symmetry constraints for positions and thermal factors.
Both positions and thermal factors may get corrected to reflect space
group symmetry. Old positional and thermal constraints get erased. New
parameter indices start at fist decade after the last used parameter.
spacegroup -- instance of SpaceGroup from diffpy.structure
indices -- list of integer indices of atoms to be expanded
posflag -- required bool flag for constraining positions
Uijflag -- required bool flag for Uij constrainment
sgoffset -- optional offset of space group origin [0,0,0]
"""
if not posflag and not Uijflag:
return
# need to do something
from diffpy.structure.symmetryutilities import SymmetryConstraints
# get unique sorted indices
tobeconstrained = dict.fromkeys(indices)
uindices = sorted(tobeconstrained.keys())
# remove old constraints
pospat = re.compile(r"^([xyz])\((\d+)\)")
Uijpat = re.compile(r"^(u11|u22|u33|u12|u13|u23)\((\d+)\)")
for var in list(self.constraints.keys()):
mpos = posflag and pospat.match(var)
mUij = Uijflag and Uijpat.match(var)
if mpos and (int(mpos.group(2)) - 1) in tobeconstrained:
del self.constraints[var]
elif mUij and (int(mUij.group(2)) - 1) in tobeconstrained:
del self.constraints[var]
# find the largest used parameter index; pidxused must have an element
pidxused = [i for i in self.owner.updateParameters()] + [0]
# new parameters will start at the next decade
parzeroidx = 10 * (int(max(pidxused) / 10)) + 10
# dictionary of parameter indices and their values
newparvalues = {}
selatoms = [self.initial[i] for i in uindices]
selpos = [a.xyz for a in selatoms]
selUijs = [a.U for a in selatoms]
symcon = SymmetryConstraints(spacegroup, selpos, selUijs, sgoffset=sgoffset, eps=self.symposeps)
# deal with positions
if posflag:
# fix positions:
for a, xyz in zip(selatoms, symcon.positions):
a.xyz = xyz
possymbols, parvalues = _makeParNames(symcon.pospars, parzeroidx)
newparvalues.update(parvalues)
eqns = symcon.positionFormulasPruned(possymbols)
for aidx, eq in zip(uindices, eqns):
siteidx = aidx + 1
for barevar, formula in eq.items():
var = barevar + "(%i)" % siteidx
self.constraints[var] = Constraint(formula)
# deal with temperature factors
if Uijflag:
# fix thermals
for a, Uij in zip(selatoms, symcon.Uijs):
a.U = Uij
Usymbols, parvalues = _makeParNames(symcon.Upars, parzeroidx)
newparvalues.update(parvalues)
eqns = symcon.UFormulasPruned(Usymbols)
for aidx, eq in zip(uindices, eqns):
siteidx = aidx + 1
for barevar, formula in eq.items():
# keys in formula dictionary are uppercase
var = barevar.lower() + "(%i)" % siteidx
self.constraints[var] = Constraint(formula)
# update parameter values in parent Fitting
self.owner.updateParameters()
for pidx, pvalue in newparvalues.items():
parobj = self.owner.parameters[pidx]
parobj.setInitial(pvalue)
# and finally remember this space group
self.initial.pdffit["spcgr"] = spacegroup.short_name
self.initial.pdffit["sgoffset"] = list(sgoffset)
return
[docs]
def setSelectedPairs(self, s):
"""Set the value of selected_pairs to s, raise ControlValueError when s
has invalid syntax. The selected_pairs is a comma separated list of
words formatted as.
[!]{element|indexOrRange|all}-[!]{element|indexOrRange|all}
where '!' excludes the given atoms from first or second pair.
Examples:
all-all all possible pairs
Na-Na only Na-Na pairs
all-all, !Na- all pairs except Na-Na (first index skips Na)
all-all, -!Na same as previous (second index skips Na)
Na-1:4 pairs of Na and first 4 atoms
all-all, !Cl-!Cl exclude any pairs containing Cl
all-all, !Cl-, -!Cl same as previous
1-all only pairs including the first atom
Use getPairSelectionFlags() method to get a list of included values
for first and second pair index.
"""
# check syntax of s
psf = self.getPairSelectionFlags(s)
self.selected_pairs = psf["fixed_pair_string"]
return
[docs]
def getSelectedPairs(self):
return self.selected_pairs
[docs]
def getPairSelectionFlags(self, s=None):
"""Translate string s to a list of allowed values for first and second
pair index. Raise ControlValueError for invalid syntax of s. See
setSelectedPairs() docstring for a definition of pair selection syntax.
s -- string describing selected pairs (default: self.selected_pairs)
Return a dictionary with following keys:
firstflags -- list of selection flags for first indices
secondflags -- list of selection flags for second indices
fixed_pair_string -- argument corrected to standard syntax
"""
if s is None:
s = self.selected_pairs
Natoms = len(self.initial)
# sets of first and second indices
firstflags = Natoms * [False]
secondflags = Natoms * [False]
# words of fixed_pair_string
words_fixed = []
s1 = s.strip(" \t,")
words = re.split(r" *, *", s1)
for w in words:
wparts = w.split("-")
if len(wparts) != 2:
emsg = "Selection word '%s' must contain one dash '-'." % w
raise ControlValueError(emsg)
sel0 = self._parseAtomSelectionString(wparts[0])
sel1 = self._parseAtomSelectionString(wparts[1])
wfixed = sel0["fixedstring"] + "-" + sel1["fixedstring"]
words_fixed.append(wfixed)
for idx, flg in sel0["flags"].items():
firstflags[idx] = flg
for idx, flg in sel1["flags"].items():
secondflags[idx] = flg
# build returned dictionary
rv = {
"firstflags": firstflags,
"secondflags": secondflags,
"fixed_pair_string": ", ".join(words_fixed),
}
return rv
[docs]
def applyPairSelection(self, server, phaseidx):
"""Apply pair selection for calculations of partial PDF.
server -- instance of PdfFit engine
phaseidx -- phase index in PdfFit engine starting from 1
"""
psf = self.getPairSelectionFlags()
idx = 0
for iflag, jflag in zip(psf["firstflags"], psf["secondflags"]):
idx += 1
server.selectAtomIndex(phaseidx, "i", idx, iflag)
server.selectAtomIndex(phaseidx, "j", idx, jflag)
return
[docs]
def getSelectedIndices(self, s):
"""Indices of the atoms that match the specified selection string.
s -- selection string consisting of one or more atom selection
words formatted as [!]{element|indexOrRange|all}
Example: "1:4, 7, Cl".
Return a list of integers.
Raise ControlValueError for invalid selection string format.
"""
s1 = "".join(c for c in s if not c.isspace())
words = s1.split(",")
indices = set()
for w in words:
asd = self._parseAtomSelectionString(w)
for idx, flg in asd["flags"].items():
if flg:
indices.add(idx)
else:
indices.discard(idx)
rv = sorted(indices)
return rv
# Regular expression object for matching atom selection strings.
# Will be assign with the first call to _parseAtomSelectionString.
_rxatomselection = None
def _parseAtomSelectionString(self, s):
"""Process string that describes a set of atoms in the structure.
s -- selection string formatted as [!]{element|indexOrRange|all}
"!" negates the selection, indexOrRange can be 1, 1:4,
where atom indices starts from 1, and "all" matches all atoms.
Return a dictionary with following keys:
'fixedstring' -- selection string adjusted to standard formatting
'flags' -- dictionary of atom indices and boolean flags for
normal or negated selection.
Raise ControlValueError for invalid string format.
"""
# delayed initialization of the class variable
if self._rxatomselection is None:
FitStructure._rxatomselection = re.compile(
r"""
(?P<negate>!?) # exclamation point
(?:(?P<element>[a-zA-Z]+)$| # element|all or
(?P<start>\d+)(?P<stop>:\d+)?$ # number range
)""",
re.VERBOSE,
)
assert self._rxatomselection
Natoms = len(self.initial)
flags = {}
rv = {"fixedstring": "", "flags": flags}
# allow empty string and return an empty flags dictionary
s1 = s.replace(" ", "")
if not s1:
return rv
mx = self._rxatomselection.match(s1)
if not mx:
emsg = "Invalid selection syntax in '%s'" % s
raise ControlValueError(emsg)
if mx.group("negate"):
rv["fixedstring"] = "!"
flg = not mx.group("negate")
# process atom type
if mx.group("element"):
elfixed = mx.group("element")
elfixed = elfixed[0:1].upper() + elfixed[1:].lower()
if elfixed == "All":
flags.update(dict.fromkeys(range(Natoms), flg))
rv["fixedstring"] += elfixed.lower()
else:
for idx in range(Natoms):
if self.initial[idx].element == elfixed:
flags[idx] = flg
rv["fixedstring"] += elfixed
# process range
else:
lo = max(int(mx.group("start")) - 1, 0)
rv["fixedstring"] += mx.group("start")
hi = lo + 1
if mx.group("stop"):
hi = int(mx.group("stop")[1:])
rv["fixedstring"] += mx.group("stop")
hi = min(hi, Natoms)
flags.update(dict.fromkeys(range(lo, hi), flg))
return rv
[docs]
def copy(self, other=None):
"""Copy self to other. if other is None, create new instance.
other -- reference to other object
returns reference to copied object
"""
# check arguments
if other is None:
other = FitStructure(self.name)
elif not isinstance(other, PDFStructure):
emsg = "other must be PDFStructure or FitStructure"
raise ControlTypeError(emsg)
# copy initial structure (self) to other
PDFStructure.copy(self, other)
# copy refined structure to other when it is FitStructure
if isinstance(other, FitStructure):
if self.refined is None:
other.refined = None
else:
other.refined = self.refined.copy(other.refined)
# copy constraints
other.constraints = copy.deepcopy(self.constraints)
other.selected_pairs = self.selected_pairs
return other
[docs]
def load(self, z, subpath):
"""Load structure from a zipped project file.
z -- zipped project file
subpath -- path to its own storage within project file
"""
# subpath = projname/fitname/structure/myname/
from diffpy.pdfgui.control.pdfguicontrol import CtrlUnpickler
from diffpy.pdfgui.utils import asunicode
subs = subpath.split("/")
rootDict = z.fileTree[subs[0]][subs[1]][subs[2]][subs[3]]
strudata = asunicode(z.read(subpath + "initial"))
self.initial.readStr(strudata, "pdffit")
# refined
if "refined" in rootDict:
self.refined = PDFStructure(self.name)
refdata = asunicode(z.read(subpath + "refined"))
self.refined.readStr(refdata, "pdffit")
# constraints
if "constraints" in rootDict:
self.constraints = CtrlUnpickler.loads(z.read(subpath + "constraints"))
translate = {"gamma": "delta1", "delta": "delta2", "srat": "sratio"}
for old, new in translate.items():
if old in self.constraints:
self.constraints[new] = self.constraints.pop(old)
# selected_pairs
if "selected_pairs" in rootDict:
self.selected_pairs = asunicode(z.read(subpath + "selected_pairs"))
# sgoffset
if "sgoffset" in rootDict:
sgoffsetstr = asunicode(z.read(subpath + "sgoffset"))
sgoffset = [float(w) for w in sgoffsetstr.split()]
self.initial.pdffit["sgoffset"] = sgoffset
# custom_spacegroup
if "custom_spacegroup" in rootDict:
spkl = z.read(subpath + "custom_spacegroup")
self.custom_spacegroup = CtrlUnpickler.loads(spkl)
return
[docs]
def save(self, z, subpath):
"""Save structure to a zipped project file.
z -- zipped project file
subpath -- path to its own storage within project file
"""
from diffpy.pdfgui.utils import safeCPickleDumps
z.writestr(subpath + "initial", self.initial.writeStr("pdffit"))
if self.refined:
z.writestr(subpath + "refined", self.refined.writeStr("pdffit"))
if self.constraints:
spkl = safeCPickleDumps(self.constraints)
z.writestr(subpath + "constraints", spkl)
z.writestr(subpath + "selected_pairs", self.selected_pairs)
# sgoffset
sgoffset = self.initial.pdffit.get("sgoffset", [0.0, 0.0, 0.0])
sgoffsetstr = "%g %g %g" % tuple(sgoffset)
z.writestr(subpath + "sgoffset", sgoffsetstr)
if self.custom_spacegroup:
spkl = safeCPickleDumps(self.custom_spacegroup)
z.writestr(subpath + "custom_spacegroup", spkl)
return
[docs]
def getYNames(self):
"""Get names of data item which can be plotted as y.
returns a name str list
"""
return list(self.constraints.keys())
[docs]
def getXNames(self):
"""Get names of data item which can be plotted as x.
returns a name str list
"""
# in fact nothing
return []
[docs]
def getData(self, name, step=-1):
"""Get self's data member.
name -- data item name
step -- step info, it can be:
(1) a number ( -1 means latest step ): for single step
(2) a list of numbers: for multiple steps
(3) None: for all steps
returns data object, be it a single number, a list, or a list of list
"""
# FIXME: for next plot interface, we need find how many steps the
# plotter is requiring for and make exact same number of copies of
# data by name
data = self.owner.getMetaData(name)
if data is not None:
return data
return self.owner._getData(self, name, step)
# End of class FitStructure
# Local helper functions -----------------------------------------------------
def _makeParNames(sympars, parzeroindex):
"""Return a tuple of (symbols, parvalues), where symbols is a list of
unique PDFFit parameter strings in "@%i" format and parvalues is a
dictionary of parameter indices and their values. The symbols have indices
10n + (1, 2, 3) when referring to x, y, z, or 10n + (4, 5, 6, 7, 8, 9) when
referring to Uij.
sympars -- pospars or Upars attribute of a SymmetryConstraints object
Must be a sequence of symbols and values.
parzeroindex -- the offset of all parameter indices.
Must be a multiple of 10.
Return a tuple of (possymbols, Usymbols, parvalues).
This function is only used in FitStructure.applySymmetryConstraints method.
"""
if parzeroindex % 10:
raise ValueError("parzeroindex must be a multiple of 10.")
smbindex = {
"x": 1,
"y": 2,
"z": 3,
"U11": 4,
"U22": 5,
"U33": 6,
"U12": 7,
"U13": 8,
"U23": 9,
}
symbols = []
parvalues = {}
for smb, value in sympars:
if smb[:1] == "U":
nsite = 10 * int(smb[3:])
nvar = smbindex[smb[:3]]
else:
nsite = 10 * int(smb[1:])
nvar = smbindex[smb[:1]]
pidx = parzeroindex + nsite + nvar
symbols.append("@%i" % pidx)
parvalues[pidx] = value
assert len(symbols) == len(parvalues)
rv = (symbols, parvalues)
return rv
# End of file