def test_Uisotropy(self): """check isotropy value for ADP-s at specified sites. """ sg225 = GetSpaceGroup(225) corepos = [[0, 0, 0], [0.1, 0.13, 0.17]] eau = ExpandAsymmetricUnit(sg225, corepos) self.assertEqual([True, False], eau.Uisotropy) sc = SymmetryConstraints(sg225, eau.expandedpos) self.assertEqual(4 * [True] + 192 * [False], sc.Uisotropy) return
def test_UFormula_g186c_eqxyz(self): '''Check rotated U formulas at the symmetry positions of c-site in 186. ''' sg186 = GetSpaceGroup(186) crules = [ {'U11': 'A', 'U22': 'A', 'U33': 'C', 'U12': 'D', 'U13': 'E', 'U23': '-E'}, {'U11': 'A', 'U22': '2*A-2*D', 'U33': 'C', 'U12': 'A-D', 'U13': 'E', 'U23': '2*E'}, {'U11': '2*A-2*D', 'U22': 'A', 'U33': 'C', 'U12': 'A-D', 'U13': '-2*E', 'U23': '-E'}, {'U11': 'A', 'U22': 'A', 'U33': 'C', 'U12': 'D', 'U13': '-E', 'U23': 'E'}, {'U11': 'A', 'U22': '2*A-2*D', 'U33': 'C', 'U12': 'A-D', 'U13': '-E', 'U23': '-2*E'}, {'U11': '2*A-2*D', 'U22': 'A', 'U33': 'C', 'U12': 'A-D', 'U13': '2*E', 'U23': 'E'}, ] self.assertEqual(6, len(self.g186c.eqxyz)) gc = self.g186c for idx in range(6): self.assertEqual(crules[idx], gc.UFormula(gc.eqxyz[idx], 'ABCDEF')) uiso = numpy.array([[2, 1, 0], [1, 2, 0], [0, 0, 2]]) eau = ExpandAsymmetricUnit(sg186, [gc.xyz], [uiso]) for u in eau.expandedUijs: du = numpy.linalg.norm((uiso - u).flatten()) self.assertAlmostEqual(0.0, du, 8) symcon = SymmetryConstraints(sg186, sum(eau.expandedpos, []), sum(eau.expandedUijs, [])) upd = dict(symcon.Upars) self.assertEqual(2.0, upd['U110']) self.assertEqual(2.0, upd['U330']) self.assertEqual(1.0, upd['U120']) self.assertEqual(0.0, upd['U130']) uisod = {'U11' : 2.0, 'U22' : 2.0, 'U33' : 2.0, 'U12' : 1.0, 'U13' : 0.0, 'U23' : 0.0} for ufms in symcon.UFormulas(): for n, fm in ufms.items(): self.assertEqual(uisod[n], eval(fm, upd)) return
def _constrainXYZs(self, positions): """Constrain the positions. positions -- The coordinates of the scatterers. """ from diffpy.structure.symmetryutilities import SymmetryConstraints sg = self.sg sgoffset = self.sgoffset # We do this without ADPs here so we can skip much complication. See # the _constrainADPs method for details. g = SymmetryConstraints(sg, positions, sgoffset=sgoffset) scatterers = self.scatterers self._xyzpars = BaseSpaceGroupParameters("xyzpars") # Make proxies to the free xyz parameters xyznames = [name[:1]+"_"+name[1:] for name, val in g.pospars] for pname in xyznames: name, idx = pname.rsplit('_', 1) idx = int(idx) par = scatterers[idx].get(name) newpar = self.__addPar(pname, par) self._xyzpars.addParameter(newpar) # Constrain non-free xyz parameters fpos = g.positionFormulas(xyznames) for idx, tmp in enumerate(zip(scatterers, fpos)): scatterer, fp = tmp # Extract the constraint equation from the formula for parname, formula in fp.items(): _makeconstraint(parname, formula, scatterer, idx, self._parameters) return
def test___init__(self): """check SymmetryConstraints.__init__() """ sg225 = GetSpaceGroup(225) # initialize from nested lists and arrays from ExpandAsymmetricUnit eau = ExpandAsymmetricUnit(sg225, [[0, 0, 0]]) sc0 = SymmetryConstraints(sg225, eau.expandedpos) self.assertEqual(1, len(sc0.coremap)) # initialize from list of arrays of coordinates poslistarrays = [xyz for xyz in sc0.positions] sc1 = SymmetryConstraints(sg225, poslistarrays) self.assertEqual(1, len(sc1.coremap)) # initialize from list of lists of coordinates poslistlist = [list(xyz) for xyz in poslistarrays] sc2 = SymmetryConstraints(sg225, poslistlist) self.assertEqual(1, len(sc2.coremap)) # initialize from nx3 array posarray = numpy.array(poslistlist) sc3 = SymmetryConstraints(sg225, posarray) self.assertEqual(1, len(sc3.coremap)) # finally initialize from a single coordinate sc4 = SymmetryConstraints(sg225, [0, 0, 0]) self.assertEqual(1, len(sc4.coremap)) return
def test_corepos(self): """test_corepos - find positions in the asymmetric unit. """ sg225 = GetSpaceGroup(225) corepos = [[0, 0, 0], [0.1, 0.13, 0.17]] eau = ExpandAsymmetricUnit(sg225, corepos) sc = SymmetryConstraints(sg225, eau.expandedpos) self.assertEqual(2, len(sc.corepos)) self.assertTrue(numpy.all(corepos[0] == sc.corepos[0])) self.assertTrue(numpy.all(corepos[1] == sc.corepos[1])) self.assertEqual(2, len(sc.coremap)) mapped_count = sum(len(idcs) for idcs in sc.coremap.values()) self.assertEqual(len(sc.positions), mapped_count) self.assertTrue(sc.coremap[0] == list(range(4))) self.assertTrue(sc.coremap[4] == list(range(4, 4+192))) return
def test_UparValues(self): """check SymmetryConstraints.UparValues() """ places = 12 sg1 = GetSpaceGroup(1) sg225 = GetSpaceGroup(225) pos = [[0, 0, 0]] Uijs = [[[0.1, 0.4, 0.5], [0.4, 0.2, 0.6], [0.5, 0.6, 0.3]]] sc1 = SymmetryConstraints(sg1, pos, Uijs) duv = 0.1 * numpy.arange(1, 7) - sc1.UparValues() self.assertAlmostEqual(0, max(numpy.fabs(duv)), places) sc225 = SymmetryConstraints(sg225, pos, Uijs) self.assertEqual(1, len(sc225.UparValues())) self.assertAlmostEqual(0.2, sc225.UparValues()[0], places) return
def test_UparSymbols(self): """check SymmetryConstraints.UparSymbols() """ sg1 = GetSpaceGroup(1) sg225 = GetSpaceGroup(225) pos = [[0, 0, 0]] Uijs = numpy.zeros((1, 3, 3)) sc1 = SymmetryConstraints(sg1, pos, Uijs) self.assertEqual(6, len(sc1.UparSymbols())) sc225 = SymmetryConstraints(sg225, pos, Uijs) self.assertEqual(['U110'], sc225.UparSymbols()) return
def find_asymmetric_positions(positions, space_group): """Return the asymmetric atom positions among a set of positions when considering symmetry operations defined by a space group. Parameters ---------- positions : list A list of cartesian atom positions. space_group : diffpy.structure.spacegroupmod.SpaceGroup Space group describing the symmetry operations. Returns ------- numpy.ndarray Asymmetric atom positions. """ asymmetric_positions = SymmetryConstraints(space_group, positions).corepos return [ np.array([np.allclose(xyz, asym_xyz) for xyz in positions]) for asym_xyz in asymmetric_positions ][0]
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 = tobeconstrained.keys() uindices.sort() # remove old constraints pospat = re.compile(r'^([xyz])\((\d+)\)') Uijpat = re.compile(r'^(u11|u22|u33|u12|u13|u23)\((\d+)\)') for var in 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*(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.iteritems(): 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
def _constrainADPs(self, positions): """Constrain the ADPs. positions -- The coordinates of the scatterers. """ from diffpy.structure.symmetryutilities import stdUsymbols from diffpy.structure.symmetryutilities import SymmetryConstraints if not self.constrainadps: return sg = self.sg sgoffset = self.sgoffset scatterers = self.scatterers isosymbol = self.isosymbol adpsymbols = self.adpsymbols adpmap = dict(zip(stdUsymbols, adpsymbols)) self._adppars = BaseSpaceGroupParameters("adppars") # Prepare ADPs. Note that not all scatterers have constrainable ADPs. # For example, MoleculeParSet from objcryststructure does not. We # discard those. nonadps = [] Uijs = [] for sidx, scatterer in enumerate(scatterers): pars = [scatterer.get(symb) for symb in adpsymbols] if None in pars: nonadps.append(sidx) continue Uij = numpy.zeros((3,3), dtype=float) for idx, par in enumerate(pars): i, j = _idxtoij[idx] Uij[i,j] = Uij[j,i] = par.getValue() Uijs.append(Uij) # Discard any positions for the nonadps positions = list(positions) nonadps.reverse() [positions.pop(idx) for idx in nonadps] # Now we can create symmetry constraints without having to worry about # the nonadps g = SymmetryConstraints(sg, positions, Uijs, sgoffset=sgoffset) adpnames = [adpmap[name[:3]] + "_" + name[3:] for name, val in g.Upars] # Make proxies to the free adp parameters. We start by filtering out # the isotropic ones so we can use the isotropic parameter. isoidx = [] isonames = [] for pname in adpnames: name, idx = pname.rsplit('_', 1) idx = int(idx) # Check for isotropic ADPs scatterer = scatterers[idx] if isosymbol and g.Uisotropy[idx] and idx not in isoidx: isoidx.append(idx) par = scatterer.get(isosymbol) if par is not None: parname = "%s_%i" % (isosymbol, idx) newpar = self.__addPar(parname, par) self._adppars.addParameter(newpar) isonames.append(newpar.name) else: par = scatterer.get(name) if par is not None: newpar = self.__addPar(pname, par) self._adppars.addParameter(newpar) # Constrain dependent isotropics for idx, isoname in zip(isoidx[:], isonames): for j in g.coremap[idx]: if j == idx: continue isoidx.append(j) scatterer = scatterers[j] scatterer.constrain(isosymbol, isoname, ns = self._parameters) fadp = g.UFormulas(adpnames) # Constrain dependent anisotropics. We use the fact that an # anisotropic cannot be dependent on an isotropic. for idx, tmp in enumerate(zip(scatterers, fadp)): if idx in isoidx: continue scatterer, fa = tmp # Extract the constraint equation from the formula for stdparname, formula in fa.items(): pname = adpmap[stdparname] _makeconstraint(pname, formula, scatterer, idx, self._parameters)