diff --git a/news/fix-deprecation-camel-case-2.rst b/news/fix-deprecation-camel-case-2.rst new file mode 100644 index 0000000..266e90c --- /dev/null +++ b/news/fix-deprecation-camel-case-2.rst @@ -0,0 +1,23 @@ +**Added:** + +* No News Added: deprecate CamelCase function and add tests for snake-case + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/structure/symmetryutilities.py b/src/diffpy/structure/symmetryutilities.py index d255666..77cac5e 100644 --- a/src/diffpy/structure/symmetryutilities.py +++ b/src/diffpy/structure/symmetryutilities.py @@ -32,6 +32,7 @@ import numpy from diffpy.structure.structureerrors import SymmetryError +from diffpy.utils._deprecator import build_deprecation_message, deprecated # Constants ------------------------------------------------------------------ @@ -41,8 +42,61 @@ # ---------------------------------------------------------------------------- - -def isSpaceGroupLatPar(spacegroup, a, b, c, alpha, beta, gamma): +base = "diffpy.symmetryutilities" +removal_version = "4.0.0" + +isSpaceGroupLatPar_deprecation_msg = build_deprecation_message( + base, + "isSpaceGroupLatPar", + "is_space_group_lat_par", + removal_version, +) + +is_constantFormula_deprecation_msg = build_deprecation_message( + base, + "isconstantFormula", + "is_constant_formula", + removal_version, +) +positionDifference_deprecation_msg = build_deprecation_message( + base, + "positionDifference", + "position_difference", + removal_version, +) +nearestSiteIndex_deprecation_msg = build_deprecation_message( + base, + "nearestSiteIndex", + "nearest_site_index", + removal_version, +) +equalPositions_deprecation_msg = build_deprecation_message( + base, + "equalPositions", + "equal_positions", + removal_version, +) +expandPosition_deprecation_msg = build_deprecation_message( + base, + "expandPosition", + "expand_position", + removal_version, +) +nullSpace_deprecation_msg = build_deprecation_message( + base, + "nullSpace", + "null_space", + removal_version, +) +findInvariants_deprecation_msg = build_deprecation_message( + base, + "_findInvariants", + "_find_invariants", + removal_version, +) + + +def is_space_group_lat_par(spacegroup, a, b, c, alpha, beta, gamma): """Check if space group allows passed lattice parameters. Parameters @@ -103,6 +157,53 @@ def check_cubic(): return rule() +@deprecated(isSpaceGroupLatPar_deprecation_msg) +def isSpaceGroupLatPar(spacegroup, a, b, c, alpha, beta, gamma): + """This function has been deprecated and will be removed in version + 4.0.0. + + Please use diffpy.symmetryutilities.is_space_group_lat_par instead. + """ + + # crystal system rules + # ref: Benjamin, W. A., Introduction to crystallography, + # New York (1969), p.60 + def check_triclinic(): + return True + + def check_monoclinic(): + rv = (alpha == gamma == 90) or (alpha == beta == 90) + return rv + + def check_orthorhombic(): + return alpha == beta == gamma == 90 + + def check_tetragonal(): + return a == b and alpha == beta == gamma == 90 + + def check_trigonal(): + rv = (a == b == c and alpha == beta == gamma) or (a == b and alpha == beta == 90 and gamma == 120) + return rv + + def check_hexagonal(): + return a == b and alpha == beta == 90 and gamma == 120 + + def check_cubic(): + return a == b == c and alpha == beta == gamma == 90 + + crystal_system_rules = { + "TRICLINIC": check_triclinic, + "MONOCLINIC": check_monoclinic, + "ORTHORHOMBIC": check_orthorhombic, + "TETRAGONAL": check_tetragonal, + "TRIGONAL": check_trigonal, + "HEXAGONAL": check_hexagonal, + "CUBIC": check_cubic, + } + rule = crystal_system_rules[spacegroup.crystal_system] + return rule() + + # Constant regular expression used in isconstantFormula(). # isconstantFormula runs faster when regular expression is not # compiled per every single call. @@ -110,7 +211,7 @@ def check_cubic(): _rx_constant_formula = re.compile(r"[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)??(/[-+]?\d+)?$") -def isconstantFormula(s): +def is_constant_formula(s): """Check if formula string is constant. Parameters @@ -127,6 +228,17 @@ def isconstantFormula(s): return bool(res) +@deprecated(is_constantFormula_deprecation_msg) +def isconstantFormula(s): + """This function has been deprecated and will be removed in version + 4.0.0. + + Please use diffpy.symmetryutilities.is_constant_formula instead. + """ + res = _rx_constant_formula.match(s.replace(" ", "")) + return bool(res) + + # Helper class intended for this module only: @@ -187,7 +299,22 @@ def __call__(self, xyz): # End of class _Position2Tuple +@deprecated(positionDifference_deprecation_msg) def positionDifference(xyz0, xyz1): + """This function has been deprecated and will be removed in version + 4.0.0. + + Please use diffpy.symmetryutilities.position_difference instead. + """ + dxyz = numpy.asarray(xyz0) - xyz1 + # map differences to [0,0.5] + dxyz = dxyz - numpy.floor(dxyz) + mask = dxyz > 0.5 + dxyz[mask] = 1.0 - dxyz[mask] + return dxyz + + +def position_difference(xyz0, xyz1): """Smallest difference between two coordinates in periodic lattice. Parameters @@ -209,7 +336,7 @@ def positionDifference(xyz0, xyz1): return dxyz -def nearestSiteIndex(sites, xyz): +def nearest_site_index(sites, xyz): """Index of the nearest site to a specified position. Parameters @@ -225,12 +352,37 @@ def nearestSiteIndex(sites, xyz): Index of the nearest site. """ # we use box distance to be consistent with _Position2Tuple conversion - dbox = positionDifference(sites, xyz).max(axis=1) + dbox = position_difference(sites, xyz).max(axis=1) + nearindex = numpy.argmin(dbox) + return nearindex + + +@deprecated(nearestSiteIndex_deprecation_msg) +def nearestSiteIndex(sites, xyz): + """This function has been deprecated and will be removed in version + 4.0.0. + + Please use diffpy.symmetryutilities.nearest_site_index instead. + """ + # we use box distance to be consistent with _Position2Tuple conversion + dbox = position_difference(sites, xyz).max(axis=1) nearindex = numpy.argmin(dbox) return nearindex +@deprecated(equalPositions_deprecation_msg) def equalPositions(xyz0, xyz1, eps): + """This function has been deprecated and will be removed in version + 4.0.0. + + Please use diffpy.symmetryutilities.equal_positions instead. + """ + # we use box distance to be consistent with _Position2Tuple conversion + dxyz = position_difference(xyz0, xyz1) + return numpy.all(dxyz <= eps) + + +def equal_positions(xyz0, xyz1, eps): """Equality of two coordinates with optional tolerance. Parameters @@ -246,11 +398,11 @@ def equalPositions(xyz0, xyz1, eps): ``True`` when two coordinates are closer than `eps`. """ # we use box distance to be consistent with _Position2Tuple conversion - dxyz = positionDifference(xyz0, xyz1) + dxyz = position_difference(xyz0, xyz1) return numpy.all(dxyz <= eps) -def expandPosition(spacegroup, xyz, sgoffset=[0, 0, 0], eps=None): +def expand_position(spacegroup, xyz, sgoffset=[0, 0, 0], eps=None): """Obtain unique equivalent positions and corresponding operations. Parameters @@ -287,9 +439,49 @@ def expandPosition(spacegroup, xyz, sgoffset=[0, 0, 0], eps=None): site_symops[tpl] = [] # double check if there is any position nearby if positions: - nearpos = positions[nearestSiteIndex(positions, pos)] + nearpos = positions[nearest_site_index(positions, pos)] + # is it an equivalent position? + if equal_positions(nearpos, pos, eps): + # tpl should map to the same list as nearpos + site_symops[tpl] = site_symops[pos2tuple(nearpos)] + pos_is_new = False + if pos_is_new: + positions.append(pos) + # here tpl is inside site_symops + site_symops[tpl].append(symop) + # pos_symops is nested list of symops associated with each position + pos_symops = [site_symops[pos2tuple(p)] for p in positions] + multiplicity = len(positions) + return positions, pos_symops, multiplicity + + +@deprecated(expandPosition_deprecation_msg) +def expandPosition(spacegroup, xyz, sgoffset=[0, 0, 0], eps=None): + """This function has been deprecated and will be removed in version + 4.0.0. + + Please use diffpy.symmetryutilities.expand_position instead. + """ + sgoffset = numpy.asarray(sgoffset, dtype=float) + if eps is None: + eps = epsilon + pos2tuple = _Position2Tuple(eps) + positions = [] + site_symops = {} # position tuples with [related symops] + for symop in spacegroup.iter_symops(): + # operate on coordinates in non-shifted spacegroup + pos = symop(xyz + sgoffset) - sgoffset + mask = numpy.logical_or(pos < 0.0, pos >= 1.0) + pos[mask] -= numpy.floor(pos[mask]) + tpl = pos2tuple(pos) + if tpl not in site_symops: + pos_is_new = True + site_symops[tpl] = [] + # double check if there is any position nearby + if positions: + nearpos = positions[nearest_site_index(positions, pos)] # is it an equivalent position? - if equalPositions(nearpos, pos, eps): + if equal_positions(nearpos, pos, eps): # tpl should map to the same list as nearpos site_symops[tpl] = site_symops[pos2tuple(nearpos)] pos_is_new = False @@ -303,7 +495,25 @@ def expandPosition(spacegroup, xyz, sgoffset=[0, 0, 0], eps=None): return positions, pos_symops, multiplicity +@deprecated(nullSpace_deprecation_msg) def nullSpace(A): + """This function has been deprecated and will be removed in version + 4.0.0. + + Please use diffpy.symmetryutilities.null_space instead. + """ + from numpy import linalg + + u, s, v = linalg.svd(A) + # s may have smaller dimension than v + vnrows = numpy.shape(v)[0] + mask = numpy.ones(vnrows, dtype=bool) + mask[s > epsilon] = False + null_space = numpy.compress(mask, v, axis=0) + return null_space + + +def null_space(A): """Null space of matrix A.""" from numpy import linalg @@ -316,7 +526,30 @@ def nullSpace(A): return null_space +@deprecated(findInvariants_deprecation_msg) def _findInvariants(symops): + """This function has been deprecated and will be removed in version + 4.0.0. + + Please use diffpy.symmetryutilities._find_invariants instead. + """ + invrnts = None + R0 = numpy.identity(3, dtype=float) + t0 = numpy.zeros(3, dtype=float) + for ops in symops: + for op in ops: + if numpy.all(op.R == R0) and numpy.all(op.t == t0): + invrnts = ops + break + if invrnts: + break + if invrnts is None: + emsg = "Could not find identity operation." + raise ValueError(emsg) + return invrnts + + +def _find_invariants(symops): """Find a list of symmetry operations which contains identity. Parameters @@ -352,6 +585,62 @@ def _findInvariants(symops): # ---------------------------------------------------------------------------- +generator_site = "diffpy.symmetryutilities.GeneratorSite" +signedRatStr_deprecation_msg = build_deprecation_message( + generator_site, + "signedRatStr", + "signed_rat_str", + removal_version, +) +findNullSpace_deprecation_msg = build_deprecation_message( + generator_site, + "_findNullSpace", + "_find_nullspace", + removal_version, +) +findPosParameters_deprecation_msg = build_deprecation_message( + generator_site, + "_findPosParameters", + "_find_pos_parameters", + removal_version, +) +findUSpace_deprecation_msg = build_deprecation_message( + generator_site, + "_findUSpace", + "_find_uspace", + removal_version, +) +findUParameters_deprecation_msg = build_deprecation_message( + generator_site, + "_findUParameters", + "_find_uparamters", + removal_version, +) +findeqUij_deprecation_msg = build_deprecation_message( + generator_site, + "_findeqUij", + "_find_eq_uij", + removal_version, +) +positionFormula_deprecation_msg = build_deprecation_message( + generator_site, + "positionFormula", + "position_formula", + removal_version, +) +UFormula_deprecation_msg = build_deprecation_message( + generator_site, + "UFormula", + "u_formula", + removal_version, +) +eqIndex_deprecation_msg = build_deprecation_message( + generator_site, + "eqIndex", + "eq_index", + removal_version, +) + class GeneratorSite(object): """Storage of data related to a generator positions. @@ -458,8 +747,8 @@ def __init__( self.pparameters = [] self.Uparameters = [] # fill in the values - sites, ops, mult = expandPosition(spacegroup, xyz, sgoffset, eps) - invariants = _findInvariants(ops) + sites, ops, mult = expand_position(spacegroup, xyz, sgoffset, eps) + invariants = _find_invariants(ops) # shift self.xyz exactly to the special position if mult > 1: xyzdups = numpy.array([op(xyz + self.sgoffset) - self.sgoffset for op in invariants]) @@ -469,21 +758,39 @@ def __init__( if numpy.any(dxyz != 0.0): self.xyz = xyz + dxyz self.xyz[numpy.fabs(self.xyz) < self.eps] = 0.0 - sites, ops, mult = expandPosition(spacegroup, self.xyz, self.sgoffset, eps) - invariants = _findInvariants(ops) + sites, ops, mult = expand_position(spacegroup, self.xyz, self.sgoffset, eps) + invariants = _find_invariants(ops) # self.xyz, sites, ops are all adjusted here self.eqxyz = sites self.symops = ops self.multiplicity = mult self.invariants = invariants - self._findNullSpace() - self._findPosParameters() - self._findUSpace() - self._findUParameters() - self._findeqUij() + self._find_nullspace() + self._find_pos_parameters() + self._find_uspace() + self._find_uparameters() + self._find_eq_uij() return + @deprecated(signedRatStr_deprecation_msg) def signedRatStr(self, x): + """This function has been deprecated and will be removed in version + 4.0.0. + + Please use diffpy.symmetryutilities.GeneratorSite.signed_rat_str instead. + """ + s = "{:.8g}".format(x) + if len(s) < 6: + return "%+g" % x + den = numpy.array([3.0, 6.0, 7.0, 9.0]) + nom = x * den + idx = numpy.where(numpy.fabs(nom - nom.round()) < self.eps)[0] + if idx.size == 0: + return "%+g" % x + # here we have fraction + return "%+.0f/%.0f" % (nom[idx[0]], den[idx[0]]) + + def signed_rat_str(self, x): """Convert floating point number to signed rational representation. @@ -511,7 +818,41 @@ def signedRatStr(self, x): # here we have fraction return "%+.0f/%.0f" % (nom[idx[0]], den[idx[0]]) + @deprecated(findNullSpace_deprecation_msg) def _findNullSpace(self): + """This function has been deprecated and will be removed in version + 4.0.0. + + Please use diffpy.symmetryutilities.GeneratorSite._find_nullspace instead. + """ + R0 = self.invariants[0].R + Rdiff = [(symop.R - R0) for symop in self.invariants] + Rdiff = numpy.concatenate(Rdiff, axis=0) + self.null_space = null_space(Rdiff) + if self.null_space.size == 0: + return + # reverse sort rows of null_space rows by absolute value + key = tuple(numpy.fabs(numpy.transpose(self.null_space))[::-1]) + order = numpy.lexsort(key) + self.null_space = self.null_space[order[::-1]] + # rationalize by the smallest element larger than cutoff + cutoff = 1.0 / 32 + for row in self.null_space: + abrow = numpy.abs(row) + sgrow = numpy.sign(row) + # equalize items with round-off-equal absolute value + ii = abrow.argsort() + delta = 1e-8 * abrow[ii[-1]] + for k in ii[1:]: + if abrow[k] - abrow[k - 1] < delta: + abrow[k] = abrow[k - 1] + # find the smallest nonzero absolute element + jnz = numpy.flatnonzero(abrow > cutoff) + idx = jnz[abrow[jnz].argmin()] + row[:] = (sgrow * abrow) / sgrow[idx] / abrow[idx] + return + + def _find_nullspace(self): """Calculate `self.null_space` from `self.invariants`. Try to represent `self.null_space` using small integers. @@ -519,7 +860,7 @@ def _findNullSpace(self): R0 = self.invariants[0].R Rdiff = [(symop.R - R0) for symop in self.invariants] Rdiff = numpy.concatenate(Rdiff, axis=0) - self.null_space = nullSpace(Rdiff) + self.null_space = null_space(Rdiff) if self.null_space.size == 0: return # reverse sort rows of null_space rows by absolute value @@ -543,7 +884,28 @@ def _findNullSpace(self): row[:] = (sgrow * abrow) / sgrow[idx] / abrow[idx] return + @deprecated(findPosParameters_deprecation_msg) def _findPosParameters(self): + """This function has been deprecated and will be removed in version + 4.0.0. + + Please use diffpy.symmetryutilities.GeneratorSite._find_pos_parameters instead. + """ + usedsymbol = {} + # parameter values depend on offset of self.xyz + txyz = self.xyz + # define txyz such that most of its elements are zero + for nvec in self.null_space: + idx = numpy.where(numpy.fabs(nvec) >= epsilon)[0][0] + varvalue = txyz[idx] / nvec[idx] + txyz = txyz - varvalue * nvec + # determine standard parameter name + vname = [s for s in "xyz"[idx:] if s not in usedsymbol][0] + self.pparameters.append((vname, varvalue)) + usedsymbol[vname] = True + return + + def _find_pos_parameters(self): """Find pparameters and their values for expressing `self.xyz`.""" usedsymbol = {} @@ -560,7 +922,44 @@ def _findPosParameters(self): usedsymbol[vname] = True return + @deprecated(findUSpace_deprecation_msg) def _findUSpace(self): + """This function has been deprecated and will be removed in version + 4.0.0. + + Please use diffpy.symmetryutilities.GeneratorSite._findUSpace instead. + """ + n = len(self.invariants) + R6zall = numpy.tile(-numpy.identity(6, dtype=float), (n, 1)) + R6zall_iter = numpy.split(R6zall, n, axis=0) + i6kl = ( + (0, (0, 0)), + (1, (1, 1)), + (2, (2, 2)), + (3, (0, 1)), + (4, (0, 2)), + (5, (1, 2)), + ) + for op, R6z in zip(self.invariants, R6zall_iter): + R = op.R + for j, Ucj in enumerate(self.Ucomponents): + Ucj2 = numpy.dot(R, numpy.dot(Ucj, R.T)) + for i, kl in i6kl: + R6z[i, j] += Ucj2[kl] + Usp6 = null_space(R6zall) + # normalize Usp6 by its maximum component + mxcols = numpy.argmax(numpy.fabs(Usp6), axis=1) + mxrows = numpy.arange(len(mxcols)) + Usp6 /= Usp6[mxrows, mxcols].reshape(-1, 1) + Usp6 = numpy.around(Usp6, 2) + # normalize again after rounding to get correct signs + mxcols = numpy.argmax(numpy.fabs(Usp6), axis=1) + Usp6 /= Usp6[mxrows, mxcols].reshape(-1, 1) + self.Uspace = numpy.tensordot(Usp6, self.Ucomponents, axes=(1, 0)) + self.Uisotropy = len(self.Uspace) == 1 + return + + def _find_uspace(self): """Find independent U components with respect to invariant rotations.""" n = len(self.invariants) @@ -580,7 +979,7 @@ def _findUSpace(self): Ucj2 = numpy.dot(R, numpy.dot(Ucj, R.T)) for i, kl in i6kl: R6z[i, j] += Ucj2[kl] - Usp6 = nullSpace(R6zall) + Usp6 = null_space(R6zall) # normalize Usp6 by its maximum component mxcols = numpy.argmax(numpy.fabs(Usp6), axis=1) mxrows = numpy.arange(len(mxcols)) @@ -593,7 +992,27 @@ def _findUSpace(self): self.Uisotropy = len(self.Uspace) == 1 return + @deprecated(findUParameters_deprecation_msg) def _findUParameters(self): + """This function has been deprecated and will be removed in version + 4.0.0. + + Please use diffpy.symmetryutilities.GeneratorSite._findUParameters instead. + """ + # permute indices as 00 11 22 01 02 12 10 20 21 + diagorder = numpy.array((0, 4, 8, 1, 2, 5, 3, 6, 7)) + Uijflat = self.Uij.flatten() + for Usp in self.Uspace: + Uspflat = Usp.flatten() + Uspnorm2 = numpy.dot(Uspflat, Uspflat) + permidx = next(i for i, x in enumerate(Uspflat[diagorder]) if x == 1) + idx = diagorder[permidx] + vname = self.idx2Usymbol[idx] + varvalue = numpy.dot(Uijflat, Uspflat) / Uspnorm2 + self.Uparameters.append((vname, varvalue)) + return + + def _find_uparameters(self): """Find Uparameters and their values for expressing `self.Uij`.""" # permute indices as 00 11 22 01 02 12 10 20 21 @@ -609,7 +1028,27 @@ def _findUParameters(self): self.Uparameters.append((vname, varvalue)) return + @deprecated(findeqUij_deprecation_msg) def _findeqUij(self): + """This function has been deprecated and will be removed in version + 4.0.0. + + Please use diffpy.symmetryutilities.GeneratorSite._find_eq_uij instead. + """ + self.Uij = numpy.zeros((3, 3), dtype=float) + for i in range(len(self.Uparameters)): + Usp = self.Uspace[i] + varvalue = self.Uparameters[i][1] + self.Uij += varvalue * Usp + # now determine eqUij + for ops in self.symops: + # take first rotation matrix + R = ops[0].R + Rt = R.transpose() + self.eqUij.append(numpy.dot(R, numpy.dot(self.Uij, Rt))) + return + + def _find_eq_uij(self): """Adjust `self.Uij` and `self.eqUij` to be consistent with spacegroup.""" self.Uij = numpy.zeros((3, 3), dtype=float) @@ -625,7 +1064,47 @@ def _findeqUij(self): self.eqUij.append(numpy.dot(R, numpy.dot(self.Uij, Rt))) return + @deprecated(positionFormula_deprecation_msg) def positionFormula(self, pos, xyzsymbols=("x", "y", "z")): + """This function has been deprecated and will be removed in version + 4.0.0. + + Please use diffpy.symmetryutilities.GeneratorSite.positionFormula instead. + """ + # find pos in eqxyz + idx = nearest_site_index(self.eqxyz, pos) + eqpos = self.eqxyz[idx] + if not equal_positions(eqpos, pos, self.eps): + return {} + # any rotation matrix should do fine + R = self.symops[idx][0].R + nsrotated = numpy.dot(self.null_space, numpy.transpose(R)) + # build formulas using eqpos + # find offset + teqpos = numpy.array(eqpos) + for nvec, (vname, varvalue) in zip(nsrotated, self.pparameters): + teqpos -= nvec * varvalue + # map varnames to xyzsymbols + name2sym = dict(zip(("x", "y", "z"), xyzsymbols)) + xyzformula = 3 * [""] + for nvec, (vname, ignore) in zip(nsrotated, self.pparameters): + for i in range(3): + if abs(nvec[i]) < epsilon: + continue + xyzformula[i] += "%s*%s " % ( + self.signed_rat_str(nvec[i]), + name2sym[vname], + ) + # add constant offset teqpos to all formulas + for i in range(3): + if xyzformula[i] and abs(teqpos[i]) < epsilon: + continue + xyzformula[i] += self.signed_rat_str(teqpos[i]) + # reduce unnecessary +1* and -1* + xyzformula = [re.sub("^[+]1[*]|(?<=[+-])1[*]", "", f).strip() for f in xyzformula] + return dict(zip(("x", "y", "z"), xyzformula)) + + def position_formula(self, pos, xyzsymbols=("x", "y", "z")): """Formula of equivalent position with respect to generator site. @@ -645,9 +1124,9 @@ def positionFormula(self, pos, xyzsymbols=("x", "y", "z")): ``-x``, ``z +0.5``, ``0.25``. """ # find pos in eqxyz - idx = nearestSiteIndex(self.eqxyz, pos) + idx = nearest_site_index(self.eqxyz, pos) eqpos = self.eqxyz[idx] - if not equalPositions(eqpos, pos, self.eps): + if not equal_positions(eqpos, pos, self.eps): return {} # any rotation matrix should do fine R = self.symops[idx][0].R @@ -665,19 +1144,53 @@ def positionFormula(self, pos, xyzsymbols=("x", "y", "z")): if abs(nvec[i]) < epsilon: continue xyzformula[i] += "%s*%s " % ( - self.signedRatStr(nvec[i]), + self.signed_rat_str(nvec[i]), name2sym[vname], ) # add constant offset teqpos to all formulas for i in range(3): if xyzformula[i] and abs(teqpos[i]) < epsilon: continue - xyzformula[i] += self.signedRatStr(teqpos[i]) + xyzformula[i] += self.signed_rat_str(teqpos[i]) # reduce unnecessary +1* and -1* xyzformula = [re.sub("^[+]1[*]|(?<=[+-])1[*]", "", f).strip() for f in xyzformula] return dict(zip(("x", "y", "z"), xyzformula)) + @deprecated(UFormula_deprecation_msg) def UFormula(self, pos, Usymbols=stdUsymbols): + """This function has been deprecated and will be removed in version + 4.0.0. + + Please use diffpy.symmetryutilities.GeneratorSite.u_formula instead. + """ + # find pos in eqxyz + idx = nearest_site_index(self.eqxyz, pos) + eqpos = self.eqxyz[idx] + if not equal_positions(eqpos, pos, self.eps): + return {} + # any rotation matrix should do fine + R = self.symops[idx][0].R + Rt = R.transpose() + Usrotated = [numpy.dot(R, numpy.dot(Us, Rt)) for Us in self.Uspace] + Uformula = dict.fromkeys(stdUsymbols, "") + name2sym = dict(zip(stdUsymbols, Usymbols)) + for Usr, (vname, ignore) in zip(Usrotated, self.Uparameters): + # avoid adding off-diagonal elements twice + assert numpy.all(Usr == Usr.T) + Usr -= numpy.tril(Usr, -1) + Usrflat = Usr.flatten() + for i in numpy.where(Usrflat)[0]: + f = "%+g*%s" % (Usrflat[i], name2sym[vname]) + smbl = self.idx2Usymbol[i] + Uformula[smbl] += f + for smbl, f in Uformula.items(): + if not f: + f = "0" + f = re.sub(r"^[+]?1[*]|^[+](?=\d)|(?<=[+-])1[*]", "", f).strip() + Uformula[smbl] = f + return Uformula + + def u_formula(self, pos, Usymbols=stdUsymbols): """List of atom displacement formulas with custom parameter symbols. @@ -697,9 +1210,9 @@ def UFormula(self, pos, Usymbols=stdUsymbols): pos is not equivalent to generator. """ # find pos in eqxyz - idx = nearestSiteIndex(self.eqxyz, pos) + idx = nearest_site_index(self.eqxyz, pos) eqpos = self.eqxyz[idx] - if not equalPositions(eqpos, pos, self.eps): + if not equal_positions(eqpos, pos, self.eps): return {} # any rotation matrix should do fine R = self.symops[idx][0].R @@ -723,7 +1236,16 @@ def UFormula(self, pos, Usymbols=stdUsymbols): Uformula[smbl] = f return Uformula + @deprecated(eqIndex_deprecation_msg) def eqIndex(self, pos): + """This function has been deprecated and will be removed in version + 4.0.0. + + Please use diffpy.symmetryutilities.GeneratorSite.eq_index instead. + """ + return nearest_site_index(self.eqxyz, pos) + + def eq_index(self, pos): """Index of the nearest generator equivalent site. Parameters @@ -736,7 +1258,7 @@ def eqIndex(self, pos): int Index of the nearest generator equivalent site. """ - return nearestSiteIndex(self.eqxyz, pos) + return nearest_site_index(self.eqxyz, pos) # End of class GeneratorSite @@ -820,8 +1342,29 @@ def __init__(self, spacegroup, corepos, coreUijs=None, sgoffset=[0, 0, 0], eps=N # Helper function for SymmetryConstraints class. It may be useful # elsewhere therefore its name does not start with underscore. +pruneFormulaDictionary_deprecation_msg = build_deprecation_message( + base, + "pruneFormulaDictionary", + "prune_formula_dictionary", + removal_version, +) + +@deprecated(pruneFormulaDictionary_deprecation_msg) def pruneFormulaDictionary(eqdict): + """This function has been deprecated and will be removed in version + 4.0.0. + + Please use diffpy.symmetryutilities.prune_formula_dictionary instead. + """ + pruned = {} + for smb, eq in eqdict.items(): + if not is_constant_formula(eq): + pruned[smb] = eq + return pruned + + +def prune_formula_dictionary(eqdict): """Remove constant items from formula dictionary. Parameters @@ -837,11 +1380,68 @@ def pruneFormulaDictionary(eqdict): """ pruned = {} for smb, eq in eqdict.items(): - if not isconstantFormula(eq): + if not is_constant_formula(eq): pruned[smb] = eq return pruned +symmetry_constraints = "diffpy.symmetryutilities.SymmetryConstraints" +findConstraints_deprecation_msg = build_deprecation_message( + symmetry_constraints, + "_findConstraints", + "_find_constraints", + removal_version, +) +posparSymbols_deprecation_msg = build_deprecation_message( + symmetry_constraints, + "posparSymbols", + "pospar_symbols", + removal_version, +) +posparValues_deprecation_msg = build_deprecation_message( + symmetry_constraints, + "posparValues", + "pospar_values", + removal_version, +) +positionFormulas_deprecation_msg = build_deprecation_message( + symmetry_constraints, + "positionFormulas", + "position_formulas", + removal_version, +) +positionFormulasPruned_deprecation_msg = build_deprecation_message( + symmetry_constraints, + "positionFormulasPruned", + "position_formulas_pruned", + removal_version, +) +UparSymbols_deprecation_msg = build_deprecation_message( + symmetry_constraints, + "UparSymbols", + "upar_symbols", + removal_version, +) +UparValues_deprecation_msg = build_deprecation_message( + symmetry_constraints, + "UparValues", + "upar_values", + removal_version, +) +UFormulas_deprecation_msg = build_deprecation_message( + symmetry_constraints, + "UFormulas", + "u_formulas", + removal_version, +) +UFormulasPruned_deprecation_msg = build_deprecation_message( + symmetry_constraints, + "UFormulasPruned", + "u_formulas_pruned", + removal_version, +) + + class SymmetryConstraints(object): """Generate symmetry constraints for specified positions. @@ -931,10 +1531,62 @@ def __init__(self, spacegroup, positions, Uijs=None, sgoffset=[0, 0, 0], eps=Non self.Ueqns = numpos * [None] self.Uisotropy = numpos * [False] # all members should be initialized here - self._findConstraints() + self._find_constraints() return def _findConstraints(self): + """This function has been deprecated and will be removed in version + 4.0.0. + + Please use diffpy.symmetryutilities.SymmetryConstraints._findConstraints instead. + """ + numpos = len(self.positions) + # canonical xyzsymbols and Usymbols + xyzsymbols = [smbl + str(i) for i in range(numpos) for smbl in "xyz"] + Usymbols = [smbl + str(i) for i in range(numpos) for smbl in stdUsymbols] + independent = set(range(numpos)) + for genidx in range(numpos): + if genidx not in independent: + continue + # it is a generator + self.coremap[genidx] = [] + genpos = self.positions[genidx] + genUij = self.Uijs[genidx] + gen = GeneratorSite(self.spacegroup, genpos, genUij, self.sgoffset, self.eps) + # append new pparameters if there are any + gxyzsymbols = xyzsymbols[3 * genidx : 3 * (genidx + 1)] + for k, v in gen.pparameters: + smbl = gxyzsymbols["xyz".index(k)] + self.pospars.append((smbl, v)) + gUsymbols = Usymbols[6 * genidx : 6 * (genidx + 1)] + for k, v in gen.Uparameters: + smbl = gUsymbols[stdUsymbols.index(k)] + self.Upars.append((smbl, v)) + # search for equivalents inside indies + indies = sorted(independent) + for indidx in indies: + indpos = self.positions[indidx] + formula = gen.position_formula(indpos, gxyzsymbols) + # formula is empty when indidx is independent + if not formula: + continue + # indidx is dependent here + independent.remove(indidx) + self.coremap[genidx].append(indidx) + self.poseqns[indidx] = formula + self.Ueqns[indidx] = gen.u_formula(indpos, gUsymbols) + # make sure positions and Uijs are consistent with spacegroup + eqidx = gen.eq_index(indpos) + dxyz = gen.eqxyz[eqidx] - indpos + self.positions[indidx] += dxyz - dxyz.round() + self.Uijs[indidx] = gen.eqUij[eqidx] + self.Uisotropy[indidx] = gen.Uisotropy + # all done here + coreidx = sorted(self.coremap.keys()) + self.corepos = [self.positions[i] for i in coreidx] + return + + def _find_constraints(self): """Find constraints for positions and anisotropic displacements `Uij`.""" numpos = len(self.positions) @@ -963,7 +1615,7 @@ def _findConstraints(self): indies = sorted(independent) for indidx in indies: indpos = self.positions[indidx] - formula = gen.positionFormula(indpos, gxyzsymbols) + formula = gen.position_formula(indpos, gxyzsymbols) # formula is empty when indidx is independent if not formula: continue @@ -971,9 +1623,9 @@ def _findConstraints(self): independent.remove(indidx) self.coremap[genidx].append(indidx) self.poseqns[indidx] = formula - self.Ueqns[indidx] = gen.UFormula(indpos, gUsymbols) + self.Ueqns[indidx] = gen.u_formula(indpos, gUsymbols) # make sure positions and Uijs are consistent with spacegroup - eqidx = gen.eqIndex(indpos) + eqidx = gen.eq_index(indpos) dxyz = gen.eqxyz[eqidx] - indpos self.positions[indidx] += dxyz - dxyz.round() self.Uijs[indidx] = gen.eqUij[eqidx] @@ -983,15 +1635,61 @@ def _findConstraints(self): self.corepos = [self.positions[i] for i in coreidx] return + @deprecated(posparSymbols_deprecation_msg) def posparSymbols(self): + """This function has been deprecated and will be removed in version + 4.0.0. + + Please use diffpy.symmetryutilities.SymmetryConstraints.pospare_symbols instead. + """ + return [n for n, v in self.pospars] + + def pospar_symbols(self): """Return list of standard position parameter symbols.""" return [n for n, v in self.pospars] + @deprecated(posparValues_deprecation_msg) def posparValues(self): + """This function has been deprecated and will be removed in version + 4.0.0. + + Please use diffpy.symmetryutilities.SymmetryConstraints.pospare_values instead. + """ + return [v for n, v in self.pospars] + + def pospar_values(self): """Return list of position parameters values.""" return [v for n, v in self.pospars] + @deprecated(positionFormulas_deprecation_msg) def positionFormulas(self, xyzsymbols=None): + """This function has been deprecated and will be removed in version + 4.0.0. + + Please use diffpy.symmetryutilities.SymmetryConstraints.position_formulas instead. + """ + if not xyzsymbols: + return list(self.poseqns) + # check xyzsymbols + if len(xyzsymbols) < len(self.pospars): + emsg = "Not enough symbols for %i position parameters" % len(self.pospars) + raise SymmetryError(emsg) + # build translation dictionary + trsmbl = dict(zip(self.pospar_symbols(), xyzsymbols)) + + def translatesymbol(matchobj): + return trsmbl[matchobj.group(0)] + + pat = re.compile(r"\b[xyz]\d+") + rv = [] + for eqns in self.poseqns: + treqns = {} + for smbl, eq in eqns.items(): + treqns[smbl] = re.sub(pat, translatesymbol, eq) + rv.append(treqns) + return rv + + def position_formulas(self, xyzsymbols=None): """List of position formulas with custom parameter symbols. Parameters @@ -1013,7 +1711,7 @@ def positionFormulas(self, xyzsymbols=None): emsg = "Not enough symbols for %i position parameters" % len(self.pospars) raise SymmetryError(emsg) # build translation dictionary - trsmbl = dict(zip(self.posparSymbols(), xyzsymbols)) + trsmbl = dict(zip(self.pospar_symbols(), xyzsymbols)) def translatesymbol(matchobj): return trsmbl[matchobj.group(0)] @@ -1028,6 +1726,16 @@ def translatesymbol(matchobj): return rv def positionFormulasPruned(self, xyzsymbols=None): + """This function has been deprecated and will be removed in version + 4.0.0. + + Please use diffpy.symmetryutilities.SymmetryConstraints.position_formulas_pruned instead. + """ + rv = [prune_formula_dictionary(eqns) for eqns in self.position_formulas(xyzsymbols)] + return rv + + @deprecated(positionFormulasPruned_deprecation_msg) + def position_formulas_pruned(self, xyzsymbols=None): """List of position formula dictionaries with constant items removed. @@ -1045,19 +1753,65 @@ def positionFormulasPruned(self, xyzsymbols=None): list List of coordinate formula dictionaries. """ - rv = [pruneFormulaDictionary(eqns) for eqns in self.positionFormulas(xyzsymbols)] + rv = [prune_formula_dictionary(eqns) for eqns in self.position_formulas(xyzsymbols)] return rv + @deprecated(UparSymbols_deprecation_msg) def UparSymbols(self): + """This function has been deprecated and will be removed in version + 4.0.0. + + Please use diffpy.symmetryutilities.SymmetryConstraints.upar_symbols instead. + """ + return [n for n, v in self.Upars] + + def upar_symbols(self): """Return list of standard atom displacement parameter symbols.""" return [n for n, v in self.Upars] + @deprecated(UparValues_deprecation_msg) def UparValues(self): + """This function has been deprecated and will be removed in version + 4.0.0. + + Please use diffpy.symmetryutilities.SymmetryConstraints.upar_values instead. + """ + return [v for n, v in self.Upars] + + def upar_values(self): """Return list of atom displacement parameters values.""" return [v for n, v in self.Upars] + @deprecated(UFormulas_deprecation_msg) def UFormulas(self, Usymbols=None): + """This function has been deprecated and will be removed in version + 4.0.0. + + Please use diffpy.symmetryutilities.SymmetryConstraints.u_formulas instead. + """ + if not Usymbols: + return list(self.Ueqns) + # check Usymbols + if len(Usymbols) < len(self.Upars): + emsg = "Not enough symbols for %i U parameters" % len(self.Upars) + raise SymmetryError(emsg) + # build translation dictionary + trsmbl = dict(zip(self.upar_symbols(), Usymbols)) + + def translatesymbol(matchobj): + return trsmbl[matchobj.group(0)] + + pat = re.compile(r"\bU\d\d\d+") + rv = [] + for eqns in self.Ueqns: + treqns = {} + for smbl, eq in eqns.items(): + treqns[smbl] = re.sub(pat, translatesymbol, eq) + rv.append(treqns) + return rv + + def u_formulas(self, Usymbols=None): """List of atom displacement formulas with custom parameter symbols. @@ -1081,7 +1835,7 @@ def UFormulas(self, Usymbols=None): emsg = "Not enough symbols for %i U parameters" % len(self.Upars) raise SymmetryError(emsg) # build translation dictionary - trsmbl = dict(zip(self.UparSymbols(), Usymbols)) + trsmbl = dict(zip(self.upar_symbols(), Usymbols)) def translatesymbol(matchobj): return trsmbl[matchobj.group(0)] @@ -1095,7 +1849,17 @@ def translatesymbol(matchobj): rv.append(treqns) return rv + @deprecated(UFormulasPruned_deprecation_msg) def UFormulasPruned(self, Usymbols=None): + """This function has been deprecated and will be removed in version + 4.0.0. + + Please use diffpy.symmetryutilities.SymmetryConstraints.u_formulas_pruned instead. + """ + rv = [prune_formula_dictionary(eqns) for eqns in self.u_formulas(Usymbols)] + return rv + + def u_formulas_pruned(self, Usymbols=None): """List of atom displacement formula dictionaries with constant items removed. @@ -1114,7 +1878,7 @@ def UFormulasPruned(self, Usymbols=None): List of atom displacement formulas in tuples of ``(U11, U22, U33, U12, U13, U23)``. """ - rv = [pruneFormulaDictionary(eqns) for eqns in self.UFormulas(Usymbols)] + rv = [prune_formula_dictionary(eqns) for eqns in self.u_formulas(Usymbols)] return rv @@ -1129,9 +1893,9 @@ def UFormulasPruned(self, Usymbols=None): site = [0.125, 0.625, 0.13] Uij = [[1, 2, 3], [2, 4, 5], [3, 5, 6]] g = GeneratorSite(sg100, site, Uij=Uij) - fm100 = g.positionFormula(site) + fm100 = g.position_formula(site) print("g = GeneratorSite(sg100, %r)" % site) print("g.positionFormula(%r) = %s" % (site, fm100)) print("g.pparameters =", g.pparameters) print("g.Uparameters =", g.Uparameters) - print("g.UFormula(%r) =" % site, g.UFormula(site)) + print("g.UFormula(%r) =" % site, g.u_formula(site)) diff --git a/tests/test_symmetryutilities.py b/tests/test_symmetryutilities.py index 5002a29..89dc03d 100644 --- a/tests/test_symmetryutilities.py +++ b/tests/test_symmetryutilities.py @@ -26,9 +26,13 @@ GeneratorSite, SymmetryConstraints, _Position2Tuple, + expand_position, expandPosition, + is_constant_formula, + is_space_group_lat_par, isconstantFormula, isSpaceGroupLatPar, + prune_formula_dictionary, pruneFormulaDictionary, ) @@ -67,6 +71,30 @@ def test_isSpaceGroupLatPar(self): self.assertTrue(isSpaceGroupLatPar(cubic, 3, 3, 3, 90, 90, 90)) return + def test_is_space_group_lat_par(self): + """Check isSpaceGroupLatPar()""" + triclinic = GetSpaceGroup("P1") + monoclinic = GetSpaceGroup("P2") + orthorhombic = GetSpaceGroup("P222") + tetragonal = GetSpaceGroup("P4") + trigonal = GetSpaceGroup("P3") + hexagonal = GetSpaceGroup("P6") + cubic = GetSpaceGroup("P23") + self.assertTrue((triclinic, 1, 2, 3, 40, 50, 60)) + self.assertFalse(is_space_group_lat_par(monoclinic, 1, 2, 3, 40, 50, 60)) + self.assertTrue(is_space_group_lat_par(monoclinic, 1, 2, 3, 90, 50, 90)) + self.assertFalse(is_space_group_lat_par(orthorhombic, 1, 2, 3, 90, 50, 90)) + self.assertTrue(is_space_group_lat_par(orthorhombic, 1, 2, 3, 90, 90, 90)) + self.assertFalse(is_space_group_lat_par(tetragonal, 1, 2, 3, 90, 90, 90)) + self.assertTrue(is_space_group_lat_par(tetragonal, 2, 2, 3, 90, 90, 90)) + self.assertFalse(is_space_group_lat_par(trigonal, 2, 2, 3, 90, 90, 90)) + self.assertTrue(is_space_group_lat_par(trigonal, 2, 2, 2, 80, 80, 80)) + self.assertFalse(is_space_group_lat_par(hexagonal, 2, 2, 2, 80, 80, 80)) + self.assertTrue(is_space_group_lat_par(hexagonal, 2, 2, 3, 90, 90, 120)) + self.assertFalse(is_space_group_lat_par(cubic, 2, 2, 3, 90, 90, 120)) + self.assertTrue(is_space_group_lat_par(cubic, 3, 3, 3, 90, 90, 90)) + return + def test_sgtbx_spacegroup_aliases(self): """Check GetSpaceGroup for non-standard aliases from sgtbx.""" self.assertIs(GetSpaceGroup("Fm3m"), GetSpaceGroup(225)) @@ -84,6 +112,17 @@ def test_expandPosition(self): self.assertEqual(4, pmult) return + def test_expand_position(self): + """Check expandPosition()""" + # ok again Ni example + fcc = GetSpaceGroup(225) + pos, pops, pmult = expand_position(fcc, [0, 0, 0]) + self.assertTrue(numpy.all(pos[0] == 0.0)) + self.assertEqual(4, len(pos)) + self.assertEqual(192, sum([len(line) for line in pops])) + self.assertEqual(4, pmult) + return + def test_pruneFormulaDictionary(self): """Check pruneFormulaDictionary()""" fmdict = {"x": "3*y-0.17", "y": "0", "z": "0.13"} @@ -91,6 +130,13 @@ def test_pruneFormulaDictionary(self): self.assertEqual({"x": "3*y-0.17"}, pruned) return + def test_prune_formula_dictionary(self): + """Check pruneFormulaDictionary()""" + fmdict = {"x": "3*y-0.17", "y": "0", "z": "0.13"} + pruned = prune_formula_dictionary(fmdict) + self.assertEqual({"x": "3*y-0.17"}, pruned) + return + def test_isconstantFormula(self): """Check isconstantFormula()""" self.assertFalse(isconstantFormula("x-y+z")) @@ -100,6 +146,15 @@ def test_isconstantFormula(self): self.assertTrue(isconstantFormula("+13/ 9")) return + def test_is_constant_formula(self): + """Check isconstantFormula()""" + self.assertFalse(is_constant_formula("x-y+z")) + self.assertTrue(is_constant_formula("6.023e23")) + self.assertTrue(is_constant_formula("22/7")) + self.assertTrue(is_constant_formula("- 22/7")) + self.assertTrue(is_constant_formula("+13/ 9")) + return + # End of class TestRoutines @@ -228,6 +283,13 @@ def test_signedRatStr(self): self.assertEqual("+1", g.signedRatStr(1.00000000000002)) return + def test_signed_rat_str(self): + "check GeneratorSite.signedRatStr()" + g = self.g117c + self.assertEqual("-1", g.signed_rat_str(-1.00000000000002)) + self.assertEqual("+1", g.signed_rat_str(1.00000000000002)) + return + def test_positionFormula(self): """Check GeneratorSite.positionFormula()""" # 117c @@ -252,6 +314,30 @@ def test_positionFormula(self): self.assertEqual([], self.g227oc.pparameters) return + def test_position_formula(self): + """Check GeneratorSite.positionFormula()""" + # 117c + self.assertEqual([], self.g117c.pparameters) + self.assertEqual([("x", self.x)], self.g117h.pparameters) + # 143c + pfm143c = self.g143c.position_formula(self.g143c.xyz) + self.assertEqual("+2/3", pfm143c["x"]) + self.assertEqual("+1/3", pfm143c["y"]) + self.assertEqual("z", pfm143c["z"]) + # 143d + x, y, z = self.x, self.y, self.z + pfm143d = self.g143d.position_formula([-x + y, -x, z]) + self.assertEqual("-x+y", pfm143d["x"].replace(" ", "")) + self.assertEqual("-x+1", pfm143d["y"].replace(" ", "")) + self.assertTrue(re.match("[+]?z", pfm143d["z"].strip())) + # 227a + self.assertEqual([], self.g227a.pparameters) + self.assertEqual([], self.g227oa.pparameters) + # 227c + self.assertEqual([], self.g227c.pparameters) + self.assertEqual([], self.g227oc.pparameters) + return + def test_positionFormula_sg209(self): "check positionFormula at [x, 1-x, -x] site of the F432 space group." sg209 = GetSpaceGroup("F 4 3 2") @@ -263,6 +349,17 @@ def test_positionFormula_sg209(self): self.assertEqual("-x+1", pfm["z"].replace(" ", "")) return + def test_position_formula_sg209(self): + "check positionFormula at [x, 1-x, -x] site of the F432 space group." + sg209 = GetSpaceGroup("F 4 3 2") + xyz = [0.05198, 0.94802, -0.05198] + g209e = GeneratorSite(sg209, xyz) + pfm = g209e.position_formula(xyz) + self.assertEqual("x", pfm["x"]) + self.assertEqual("-x+1", pfm["y"].replace(" ", "")) + self.assertEqual("-x+1", pfm["z"].replace(" ", "")) + return + def test_UFormula(self): """Check GeneratorSite.UFormula()""" # Ref: Willis and Pryor, Thermal Vibrations in Crystallography, @@ -369,6 +466,112 @@ def test_UFormula(self): self.assertEqual(rule06, ufm) return + def test_u_formula(self): + """Check GeneratorSite.u_formula()""" + # Ref: Willis and Pryor, Thermal Vibrations in Crystallography, + # Cambridge University Press 1975, p. 104-110 + smbl = ("A", "B", "C", "D", "E", "F") + norule = { + "U11": "A", + "U22": "B", + "U33": "C", + "U12": "D", + "U13": "E", + "U23": "F", + } + rule05 = { + "U11": "A", + "U22": "A", + "U33": "C", + "U12": "D", + "U13": "0", + "U23": "0", + } + rule06 = { + "U11": "A", + "U22": "A", + "U33": "C", + "U12": "D", + "U13": "E", + "U23": "E", + } + rule07 = { + "U11": "A", + "U22": "A", + "U33": "C", + "U12": "D", + "U13": "E", + "U23": "-E", + } + rule15 = { + "U11": "A", + "U22": "B", + "U33": "C", + "U12": "0.5*B", + "U13": "0.5*F", + "U23": "F", + } + rule16 = { + "U11": "A", + "U22": "A", + "U33": "C", + "U12": "0.5*A", + "U13": "0", + "U23": "0", + } + rule17 = { + "U11": "A", + "U22": "A", + "U33": "A", + "U12": "0", + "U13": "0", + "U23": "0", + } + rule18 = { + "U11": "A", + "U22": "A", + "U33": "A", + "U12": "D", + "U13": "D", + "U23": "D", + } + ufm = self.g117c.u_formula(self.g117c.xyz, smbl) + self.assertEqual(rule05, ufm) + ufm = self.g117h.u_formula(self.g117h.xyz, smbl) + self.assertEqual(rule07, ufm) + ufm = self.g143a.u_formula(self.g143a.xyz, smbl) + self.assertEqual(rule16, ufm) + ufm = self.g143b.u_formula(self.g143b.xyz, smbl) + self.assertEqual(rule16, ufm) + ufm = self.g143c.u_formula(self.g143c.xyz, smbl) + self.assertEqual(rule16, ufm) + ufm = self.g143d.u_formula(self.g143d.xyz, smbl) + self.assertEqual(norule, ufm) + ufm = self.g164e.u_formula(self.g164e.xyz, smbl) + self.assertEqual(rule15, ufm) + ufm = self.g164f.u_formula(self.g164f.xyz, smbl) + self.assertEqual(rule15, ufm) + ufm = self.g164g.u_formula(self.g164g.xyz, smbl) + self.assertEqual(rule15, ufm) + ufm = self.g164h.u_formula(self.g164h.xyz, smbl) + self.assertEqual(rule15, ufm) + ufm = self.g186c.u_formula(self.g186c.xyz, smbl) + self.assertEqual(rule07, ufm) + ufm = self.g227a.u_formula(self.g227a.xyz, smbl) + self.assertEqual(rule17, ufm) + ufm = self.g227c.u_formula(self.g227c.xyz, smbl) + self.assertEqual(rule18, ufm) + ufm = self.g227oa.u_formula(self.g227oa.xyz, smbl) + self.assertEqual(rule17, ufm) + ufm = self.g227oc.u_formula(self.g227oc.xyz, smbl) + self.assertEqual(rule18, ufm) + # SG 167 in hexagonal and rhombohedral setting + ufm = self.gh167e.u_formula(self.gh167e.xyz, smbl) + self.assertEqual(rule15, ufm) + ufm = self.gr167e.u_formula(self.gr167e.xyz, smbl) + self.assertEqual(rule06, ufm) + return + def test_UFormula_g186c_eqxyz(self): """Check rotated U formulas at the symmetry positions of c-site in 186.""" @@ -451,6 +654,88 @@ def test_UFormula_g186c_eqxyz(self): self.assertEqual(uisod[n], eval(fm, upd)) return + def test_u_formula_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.u_formula(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.u_formulas(): + for n, fm in ufms.items(): + self.assertEqual(uisod[n], eval(fm, upd)) + return + def test_UFormula_self_reference(self): "Ensure U formulas have no self reference such as U13=0.5*U13." for g in self.generators.values(): @@ -458,6 +743,13 @@ def test_UFormula_self_reference(self): self.assertEqual([], badformulas) return + def test_u_formula_self_reference(self): + "Ensure U formulas have no self reference such as U13=0.5*U13." + for g in self.generators.values(): + badformulas = [(n, fm) for n, fm in g.u_formula(g.xyz).items() if n in fm and n != fm] + self.assertEqual([], badformulas) + return + def test__findUParameters(self): """Check GeneratorSite._findUParameters()""" # by default all Uparameters equal zero, this would fail for NaNs @@ -480,6 +772,11 @@ def test_eqIndex(self): self.assertEqual(13, self.g227oc.eqIndex(self.g227oc.eqxyz[13])) return + def test_eq_index(self): + """Check GeneratorSite.eqIndex()""" + self.assertEqual(13, self.g227oc.eq_index(self.g227oc.eqxyz[13])) + return + # End of class TestGeneratorSite @@ -581,6 +878,18 @@ def test_UparSymbols(self): self.assertEqual(["U110"], sc225.UparSymbols()) return + def test_upar_symbols(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.upar_symbols())) + sc225 = SymmetryConstraints(sg225, pos, Uijs) + self.assertEqual(["U110"], sc225.upar_symbols()) + return + def test_UparValues(self): """Check SymmetryConstraints.UparValues()""" places = 12 @@ -596,6 +905,21 @@ def test_UparValues(self): self.assertAlmostEqual(0.2, sc225.UparValues()[0], places) return + def test_upar_values(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.upar_values() + self.assertAlmostEqual(0, max(numpy.fabs(duv)), places) + sc225 = SymmetryConstraints(sg225, pos, Uijs) + self.assertEqual(1, len(sc225.upar_values())) + self.assertAlmostEqual(0.2, sc225.upar_values()[0], places) + return + # def test_UFormulas(self): # """check SymmetryConstraints.UFormulas()