diff --git a/news/deprecate-symmetryutilities-3.rst b/news/deprecate-symmetryutilities-3.rst new file mode 100644 index 0000000..0527f8b --- /dev/null +++ b/news/deprecate-symmetryutilities-3.rst @@ -0,0 +1,27 @@ +**Added:** + +* Added ``equal_positions`` method in ``symmetryutilities.py`` +* Added ``expand_position`` method in ``symmetryutilities.py`` +* Added ``null_space`` method in ``symmetryutilities.py`` + +**Changed:** + +* + +**Deprecated:** + +* Deprecated ``equalPositions`` method in ``symmetryutilities.py`` for removal in version 4.0.0 +* Deprecated ``expandPosition`` method in ``symmetryutilities.py`` for removal in version 4.0.0 +* Deprecated ``nullSpace`` method in ``symmetryutilities.py`` for removal in version 4.0.0 + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/structure/symmetryutilities.py b/src/diffpy/structure/symmetryutilities.py index edb04de..63a3bc3 100644 --- a/src/diffpy/structure/symmetryutilities.py +++ b/src/diffpy/structure/symmetryutilities.py @@ -60,6 +60,24 @@ "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, +) # Constants ------------------------------------------------------------------ @@ -299,7 +317,17 @@ def nearest_site_index(sites, xyz): return nearindex +@deprecated(equalPositions_deprecation_msg) def equalPositions(xyz0, xyz1, eps): + """'diffpy.structure.equalPositions' is deprecated and will be + removed in version 4.0.0. + + Please use 'diffpy.structure.equal_positions' instead. + """ + return equal_positions(xyz0, xyz1, eps) + + +def equal_positions(xyz0, xyz1, eps): """Equality of two coordinates with optional tolerance. Parameters @@ -319,7 +347,17 @@ def equalPositions(xyz0, xyz1, eps): return numpy.all(dxyz <= eps) +@deprecated(expandPosition_deprecation_msg) def expandPosition(spacegroup, xyz, sgoffset=[0, 0, 0], eps=None): + """'diffpy.structure.expandPosition' is deprecated and will be + removed in version 4.0.0. + + Please use 'diffpy.structure.expand_position' instead. + """ + return expand_position(spacegroup, xyz, sgoffset, eps) + + +def expand_position(spacegroup, xyz, sgoffset=[0, 0, 0], eps=None): """Obtain unique equivalent positions and corresponding operations. Parameters @@ -358,7 +396,7 @@ def expandPosition(spacegroup, xyz, sgoffset=[0, 0, 0], eps=None): 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 @@ -372,7 +410,17 @@ def expandPosition(spacegroup, xyz, sgoffset=[0, 0, 0], eps=None): return positions, pos_symops, multiplicity +@deprecated(nullSpace_deprecation_msg) def nullSpace(A): + """'diffpy.structure.nullSpace' is deprecated and will be removed in + version 4.0.0. + + Please use 'diffpy.structure.null_space' instead. + """ + return null_space(A) + + +def null_space(A): """Null space of matrix A.""" from numpy import linalg @@ -588,7 +636,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 @@ -649,7 +697,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)) @@ -716,7 +764,7 @@ def positionFormula(self, pos, xyzsymbols=("x", "y", "z")): # find pos in eqxyz 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 @@ -768,7 +816,7 @@ def UFormula(self, pos, Usymbols=stdUsymbols): # find pos in eqxyz 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 diff --git a/tests/test_symmetryutilities.py b/tests/test_symmetryutilities.py index 289c22b..e618c5f 100644 --- a/tests/test_symmetryutilities.py +++ b/tests/test_symmetryutilities.py @@ -27,6 +27,9 @@ GeneratorSite, SymmetryConstraints, _Position2Tuple, + equal_positions, + equalPositions, + expand_position, expandPosition, is_constant_formula, is_space_group_latt_parms, @@ -34,6 +37,8 @@ isSpaceGroupLatPar, nearest_site_index, nearestSiteIndex, + null_space, + nullSpace, position_difference, positionDifference, pruneFormulaDictionary, @@ -127,6 +132,17 @@ def test_expandPosition(self): self.assertEqual(4, pmult) return + def test_expand_position(self): + """Check expand_position()""" + # 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"} @@ -152,6 +168,12 @@ def test_is_constant_formula(self): self.assertTrue(is_constant_formula("+13/ 9")) return + def test_equalPositions(self): + """Check equalPositions()""" + self.assertTrue(equalPositions([0.1, 0.2, 0.3], [0.1, 0.2, 0.3], 1.0e-5)) + self.assertTrue(equalPositions([0.1 + 0.5e-5, 0.2 + 0.5e-5, 0.3 + 0.5e-5], [0.1, 0.2, 0.3], 1.0e-5)) + self.assertFalse(equalPositions([0.2, 0.2, 0.3], [0.1, 0.2, 0.3], 1.0e-5)) + # End of class TestRoutines @@ -704,5 +726,85 @@ def test_nearest_site_index(sites, xyz, expected): assert actual == expected +@pytest.mark.parametrize( + "xyz0, xyz1, eps, expected", + [ + pytest.param([0.1, 0.2, 0.3], [0.1, 0.2, 0.3], 1.0e-5, True), # C1: same position + pytest.param( + [0.1 + 0.5e-5, 0.2 + 0.5e-5, 0.3 + 0.5e-5], [0.1, 0.2, 0.3], 1.0e-5, True + ), # C2: same position with some tolerance + pytest.param([0.2, 0.2, 0.3], [0.1, 0.2, 0.3], 1.0e-5, False), # C3: different positions + ], +) +def test_equal_positions(xyz0, xyz1, eps, expected): + """Check equalPositions.""" + actual = equal_positions(xyz0, xyz1, eps) + assert actual == expected + + +@pytest.mark.parametrize( + "A, expected_dim", + [ + pytest.param( # C1: full-rank 2x2 matrix + [[1.0, 0.0], [0.0, 1.0]], + 0, + ), + pytest.param( # C2: Nullspace has dim 1 + [[1.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 2.0]], + 1, + ), + pytest.param( # C3: Nullspace has dim 2 + [[1.0, 2.0, 3.0], [2.0, 4.0, 6.0], [0.0, 0.0, 0.0]], + 2, + ), + pytest.param( # C4: Nullspace has dim 2 + [[0.0, 0.0], [0.0, 0.0]], + 2, + ), + ], +) +def test_nullSpace(A, expected_dim): + """Check nullSpace returns an orthonormal basis on supported square + matrices.""" + A = numpy.asarray(A, dtype=float) + actual = nullSpace(A) + + assert actual.shape == (expected_dim, A.shape[1]) + assert numpy.allclose(A @ actual.T, numpy.zeros((A.shape[0], expected_dim)), atol=1e-12) + assert numpy.allclose(actual @ actual.T, numpy.eye(expected_dim), atol=1e-12) + + +@pytest.mark.parametrize( + "A, expected_dim", + [ + pytest.param( # C1: full-rank 2x2 matrix + [[1.0, 0.0], [0.0, 1.0]], + 0, + ), + pytest.param( # C2: Nullspace has dim 1 + [[1.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 2.0]], + 1, + ), + pytest.param( # C3: Nullspace has dim 2 + [[1.0, 2.0, 3.0], [2.0, 4.0, 6.0], [0.0, 0.0, 0.0]], + 2, + ), + pytest.param( # C4: Nullspace has dim 2 + [[0.0, 0.0], [0.0, 0.0]], + 2, + ), + ], +) +def test_null_space(A, expected_dim): + """Check null_space returns an orthonormal basis on supported square + matrices.""" + A = numpy.asarray(A, dtype=float) + actual = null_space(A) + + assert actual.shape == (expected_dim, A.shape[1]) + assert numpy.allclose(A @ actual.T, numpy.zeros((A.shape[0], expected_dim)), atol=1e-12) + assert numpy.allclose(actual @ actual.T, numpy.eye(expected_dim), atol=1e-12) + + if __name__ == "__main__": unittest.main()