def checkConfiguration(self): """Verify that the dopant and base are elements. More detailed checking is done in the control method. """ from diffpy.pdfgui.control.controlerrors import ControlValueError from diffpy.pdffit2 import is_element base = self.textCtrlBaseElement.GetValue() dopant = self.textCtrlDopant.GetValue() # Make sure that the base and dopant are actual elements base = base.title() dopant = dopant.title() if not is_element(base): raise ControlValueError("'%s' is not an element!" % base) if not is_element(dopant): raise ControlValueError("'%s' is not an element!" % dopant) return
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
def setRGrid(self, rmin=None, rstep=None, rmax=None): """Change specified r-grid parameters (rmin, rstep, rmax). Adjust rmax for integer number of steps. rmin -- new low rcalc boundary rstep -- new r-grid step rmax -- new maximum rcalc, slightly adjusted to accommodate rstep No return value. Raise ControlValueError for invalid range specification. """ if rmin is None: rmin = self.rmin if rstep is None: rstep = self.rstep if rmax is None: rmax = self.rmax rstep = float(rstep) # check if arguments are valid if not rmin > 0: emsg = "Low range boundary must be positive." raise ControlValueError(emsg) if not rmin < rmax: emsg = "Invalid range boundaries." raise ControlValueError(emsg) if rstep <= 0.0: emsg = "Invalid value of rstep, rstep must be positive." raise ControlValueError(emsg) # find number of r bins nbins = int(math.ceil((rmax - rmin) / rstep)) # check for overshot due to round-off epsilonr = 1.0e-8 * rstep deltarmax = abs(rmin + (nbins - 1) * rstep - rmax) if nbins > 1 and deltarmax < epsilonr: nbins -= 1 # All went well, let us go ahead and set the attributes. self.rmin = rmin self.rstep = rstep self.rmax = rmin + nbins * rstep self.rlen = nbins + 1 return
def refresh(self): """Refresh wigets on the panel.""" if self.structure is None: raise ValueError("structure is not defined.") self.refreshTextCtrls() ### update the grid ### natoms = len(self.structure) nrows = self.gridAtoms.GetNumberRows() self.gridAtoms.BeginBatch() # make sure grid has correct number of rows if natoms > nrows: self.gridAtoms.InsertRows(numRows=natoms - nrows) elif natoms < nrows: self.gridAtoms.DeleteRows(numRows=nrows - natoms) # start with clean grid self.gridAtoms.ClearGrid() # fill the first 'elem' column with element symbols for row, atom in zip(range(natoms), self.structure): self.gridAtoms.SetCellValue(row, 0, atom.element) # update constraints bareAtomVarColumn = dict( zip(self.lAtomConstraints, range(1, 1 + len(self.lAtomConstraints)))) avpat = re.compile(r'(\w+)\((\d+)\)$') for var, con in self.constraints.iteritems(): m = avpat.match(var) if not m: continue barevar = m.group(1) if not barevar in bareAtomVarColumn: continue column = bareAtomVarColumn[barevar] row = int(m.group(2)) - 1 if not 0 <= row < natoms: emsg = "Invalid variable index for %r" % var raise ControlValueError(emsg) self.gridAtoms.SetCellValue(row, column, con.formula) barevar = re.sub(r'\(\d+\)$', '', var) if not barevar in bareAtomVarColumn: continue self.gridAtoms.AutosizeLabels() self.gridAtoms.AutoSizeColumns() self.gridAtoms.EndBatch() self.gridAtoms.AdjustScrollbars() self.gridAtoms.ForceRefresh() return
def updateWidgets(self): """Update the widgets.""" # Update space group sgname = self.sgComboBox.GetValue() try: self.spacegroup = self.structure.getSpaceGroup(sgname) error = None except ValueError: error = "Space group %s does not exist." % sgname # This changes list box value to the short_name of the new spacegroup # or to the name of previous spacegroup when getSpaceGroup failed. self.sgComboBox.SetValue(self.spacegroup.short_name) # Update offset for i in range(3): textctrl = self.textCtrls[i] val = textctrl.GetValue() # make sure the value is meaningful try: val = float(eval("1.0*" + val, dict(math.__dict__))) except (NameError, TypeError, SyntaxError): val = 0.0 textctrl.SetValue("%s" % val) self.offset[i] = val # find how many new atoms would be generated from diffpy.Structure.SymmetryUtilities import ExpandAsymmetricUnit corepos = [self.structure[i].xyz for i in self.indices] symposeps = self.structure.symposeps eau = ExpandAsymmetricUnit(self.spacegroup, corepos, sgoffset=self.offset, eps=symposeps) newsize = sum(eau.multiplicity) s = "" if len(self.indices) != 1: s = "s" message = "%i atom%s selected. Expanding to %i positions." %\ (len(self.indices), s, newsize) self.numConstrainedLabel.SetLabel(message) # Raise an error if we had to change the space group if error: raise ControlValueError(error) return
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
def updateWidgets(self): """Update the widgets.""" # Update space group sgname = self.sgComboBox.GetValue() try: self.spacegroup = self.structure.getSpaceGroup(sgname) error = None except ValueError: error = "Space group %s does not exist." % sgname # This changes list box value to the short_name of the new spacegroup # or to the name of previous spacegroup when getSpaceGroup failed. self.sgComboBox.SetValue(self.spacegroup.short_name) # Update offset for i in range(3): textctrl = self.textCtrls[i] val = textctrl.GetValue() # make sure the value is meaningful try: val = float(eval("1.0*"+val, dict(math.__dict__))) except (NameError, TypeError, SyntaxError): val = 0 textctrl.SetValue("%s"%val) self.offset[i] = val #newatoms = len(stemp) - len(self.structure) s = "" if len(self.indices) != 1: s = "s" message = "%i atom%s selected." %\ (len(self.indices), s) self.numConstrainedLabel.SetLabel(message) # Raise an error if we had to change the space group if error: raise ControlValueError(error); return
def makeDopingSeries(control, fit, base, dopant, paths, doping): """Make a temperature series. control -- pdguicontrol instance fit -- The template fit base -- Name of the base element dopant -- Name of the dopant element paths -- list of path names of new datasets doping -- list of doping values corresponding to the datasets returns a list of the new fit organization objects """ from diffpy.pdffit2 import is_element # Make sure that base and dopant are elements base = base.title() dopant = dopant.title() if not is_element(base): raise ControlValueError("'%s' is not an element!" % base) if not is_element(dopant): raise ControlValueError("'%s' is not an element!" % dopant) # Make sure that base and dopant are in the structure file(s) hasBase = False hasDopant = False for S in fit.strucs: for atom in S: if atom.element == base: hasBase = True if atom.element == dopant: hasDopant = True if hasBase and hasDopant: break if not hasBase: message = "The template structure does not contain the base atom." raise ControlValueError(message) if not hasDopant: message = "The template structure does not contain the dopant atom." raise ControlValueError(message) # Make sure we're only replacing a single dataset if len(fit.datasets) != 1: message = "Can't apply macro to fits with multiple datasets." raise ControlValueError(message) fits = [] # holds all of the other information about the dataset fitbasename = fit.name fitnewname = fit.name fitlastname = fit.name dataset = fit.datasets[0] for i in range(len(paths)): filename = paths[i] fitlastname = fitnewname fitcopy = control.copy(fit) # Get rid of the old dataset temp = fitcopy.datasets[0] fitcopy.remove(temp) # Configure the new dataset dsname = os.path.basename(filename) newdataset = FitDataSet(dsname) newdataset.readObs(filename) newdataset.qdamp = dataset.qdamp newdataset.qbroad = dataset.qbroad newdataset.dscale = dataset.dscale newdataset.fitrmin = dataset.fitrmin newdataset.fitrmax = dataset.fitrmax rstep = dataset.fitrstep st = dataset.getFitSamplingType() newdataset.setFitSamplingType(st, rstep) temperature = dataset.metadata.get("temperature") if temperature is None: temperature = 300.0 newdataset.metadata["temperature"] = temperature newdataset.constraints = copy.deepcopy(dataset.constraints) # Set the chosen temperature newdataset.metadata["doping"] = doping[i] # Add the dataset to the fitcopy fitcopy.add(newdataset, None) # Update the doping information in the structures for S in fitcopy.strucs: for A in S: if A.element == dopant: A.occupancy = doping[i] if A.element == base: A.occupancy = 1 - doping[i] # Set the parameters to the previous fit's name, if one exists. if fitlastname: parval = "=%s" % fitlastname for par in fitcopy.parameters.values(): par.setInitial(parval) # Now paste the copy into the control. fitnewname = "%s-%1.4f" % (fitbasename, doping[i]) o = control.paste(fitcopy, new_name=fitnewname) fits.append(o) return [f.organization() for f in fits]
def makeTemperatureSeries(control, fit, paths, temperatures): """Make a temperature series. control -- pdguicontrol instance fit -- The template fit paths -- list of path names of new datasets temperatures -- list of temperatures corresponding to the datasets returns a list of the new fit organization objects """ if len(fit.datasets) != 1: message = "Can't apply macro to fits with multiple datasets." raise ControlValueError(message) fits = [] # holds all of the other information about the dataset fitbasename = fit.name fitnewname = fit.name fitlastname = fit.name dataset = fit.datasets[0] for i in range(len(paths)): filename = paths[i] fitlastname = fitnewname fitcopy = control.copy(fit) # Get rid of the old dataset temp = fitcopy.datasets[0] fitcopy.remove(temp) # Configure the new dataset dsname = os.path.basename(filename) newdataset = FitDataSet(dsname) newdataset.readObs(filename) newdataset.qdamp = dataset.qdamp newdataset.qbroad = dataset.qbroad newdataset.dscale = dataset.dscale newdataset.fitrmin = dataset.fitrmin newdataset.fitrmax = dataset.fitrmax rstep = dataset.fitrstep st = dataset.getFitSamplingType() newdataset.setFitSamplingType(st, rstep) doping = dataset.metadata.get("doping") if doping is None: doping = 0.0 newdataset.metadata["doping"] = doping newdataset.constraints = copy.deepcopy(dataset.constraints) # Set the chosen temperature newdataset.metadata["temperature"] = temperatures[i] # Add the dataset to the fitcopy fitcopy.add(newdataset, None) # Set the parameters to the previous fit's name, if one exists. if fitlastname: parval = "=%s" % fitlastname for par in fitcopy.parameters.values(): par.setInitial(parval) # Now paste the copy into the control. fitnewname = "%s-T%i=%g" % (fitbasename, i + 1, temperatures[i]) o = control.paste(fitcopy, new_name=fitnewname) fits.append(o) return [f.organization() for f in fits]
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].iteritems(): 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