From cf8d9060b2526e198f4f00c3ab99e9d5216f761b Mon Sep 17 00:00:00 2001 From: Luis Kitsu Iglesias Date: Fri, 28 Mar 2025 12:00:10 -0600 Subject: [PATCH 1/4] test: created a test for squeeze morph using sine wave. Created function to test --- src/diffpy/morph/morphs/morphsqueeze.py | 52 +++++++++++++++++++++++ tests/test_morphsqueeze.py | 56 +++++++++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 src/diffpy/morph/morphs/morphsqueeze.py create mode 100644 tests/test_morphsqueeze.py diff --git a/src/diffpy/morph/morphs/morphsqueeze.py b/src/diffpy/morph/morphs/morphsqueeze.py new file mode 100644 index 0000000..58a1412 --- /dev/null +++ b/src/diffpy/morph/morphs/morphsqueeze.py @@ -0,0 +1,52 @@ +"""class MorphSqueeze -- apply a non-linear squeeze to the morph. +This morph non-linearly adjusts the x-coordinates. +The y-values are then recomputed by interpolating the original data. +""" + +import numpy as np + +from diffpy.morph.morphs.morph import LABEL_GR, LABEL_RA, Morph + + +class MorphSqueeze(Morph): + summary = "Squeeze morph by desired amount (non-linear transformation)" + xinlabel = LABEL_RA # This label need to change to be more generic + yinlabel = LABEL_GR # This label need to change to be more generic + xoutlabel = LABEL_RA # This label need to change to be more generic + youtlabel = LABEL_GR # This label need to change to be more generic + parnames = ["squeeze"] + + def morph(self, x_morph, y_morph, x_target, y_target): + """Resample arrays onto the specified grid using a non-linear squeeze. + + Parameters + ---------- + x_morph : array-like + The input x-values to be transformed. + y_morph : array-like + The input y-values. + x_target, y_target : array-like + The target grid arrays (left unchanged by this morph). + + Returns + ------- + (x_morph_out, y_morph_out, x_target, y_target) + """ + # Initialize the parent class to set up attributes + Morph.morph(self, x_morph, y_morph, x_target, y_target) + + # If squeeze is zero, return original output + if self.squeeze == 0: + return self.xyallout + + # Compute new x positions using the non-linear squeeze transformation: + new_x = self.x_morph_in + self.squeeze * np.sin(self.x_morph_in) + self.x_morph_out = new_x + + # Interpolate the y-values at the new x positions. + self.y_morph_out = np.interp(new_x, self.x_morph_in, self.y_morph_in) + + return self.xyallout + + +# End of class MorphSqueeze diff --git a/tests/test_morphsqueeze.py b/tests/test_morphsqueeze.py new file mode 100644 index 0000000..2950c4f --- /dev/null +++ b/tests/test_morphsqueeze.py @@ -0,0 +1,56 @@ +import numpy as np +import pytest + +from diffpy.morph.morphs.morphsqueeze import MorphSqueeze + + +class TestMorphSqueeze: + @pytest.fixture + def setup(self): + # Create a sine-wave for testing + self.x_morph = np.linspace(0, 2 * np.pi, 1000) + self.y_morph = np.sin(self.x_morph) + self.x_target = self.x_morph.copy() + self.y_target = self.y_morph.copy() + return + + def test_no_squeeze(self, setup): + """When squeeze is zero, the input should be unchanged.""" + morph = MorphSqueeze() + morph.squeeze = 0.0 + + x_morph, y_morph, x_target, y_target = morph( + self.x_morph, self.y_morph, self.x_target, self.y_target + ) + + # Verify that the morph output matches the original input + assert np.allclose(x_morph, self.x_morph) + assert np.allclose(y_morph, self.y_morph) + # And the target arrays remain unchanged + assert np.allclose(x_target, self.x_target) + assert np.allclose(y_target, self.y_target) + + def test_morph_with_squeeze(self, setup): + """Test that with a non-zero squeeze, + x_morph is transformed non-linearly.""" + morph = MorphSqueeze() + morph.squeeze = 0.7 + x_new, y_new, x_target, y_target = morph( + self.x_morph, self.y_morph, self.x_target, self.y_target + ) + + # Check that target arrays remain unchanged + assert np.allclose(self.y_target, y_target) + + # For this test, we expect: + # x_new = x_morph + squeeze_factor * sin(x_morph) + expected_x = self.x_morph + morph.squeeze * np.sin(self.x_morph) + expected_y = np.sin(expected_x) + + # Allow for some tolerance because of numerical interpolation if used + res = sum(np.fabs(expected_y - y_new)) + assert res < 1 + + +if __name__ == "__main__": + TestMorphSqueeze() From 089fd3883271a1009bbd64bfcc4c0af579beb306 Mon Sep 17 00:00:00 2001 From: Luis Kitsu Iglesias Date: Fri, 28 Mar 2025 12:14:48 -0600 Subject: [PATCH 2/4] news: adding news for squeeze morph --- news/morphsqueeze.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/morphsqueeze.rst diff --git a/news/morphsqueeze.rst b/news/morphsqueeze.rst new file mode 100644 index 0000000..4fca6b2 --- /dev/null +++ b/news/morphsqueeze.rst @@ -0,0 +1,23 @@ +**Added:** + +* Squeeze morph, test with sine wave + +**Changed:** + +* Nothing changed + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From b9d73d5e0efe07cffa6ce8fc19ac1db91b834af9 Mon Sep 17 00:00:00 2001 From: Luis Kitsu Iglesias Date: Fri, 28 Mar 2025 16:08:08 -0600 Subject: [PATCH 3/4] func/test: adding function and test for squeeze morph --- news/morphsqueeze.rst | 3 ++- src/diffpy/morph/morphs/morphsqueeze.py | 13 +++++++++---- tests/test_morphsqueeze.py | 24 ++++++++++++++++-------- 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/news/morphsqueeze.rst b/news/morphsqueeze.rst index 4fca6b2..67c81d0 100644 --- a/news/morphsqueeze.rst +++ b/news/morphsqueeze.rst @@ -4,7 +4,8 @@ **Changed:** -* Nothing changed +* Added cubic and quadratic terms, as well as +* squeeze_1 and squeeze_2 parameters **Deprecated:** diff --git a/src/diffpy/morph/morphs/morphsqueeze.py b/src/diffpy/morph/morphs/morphsqueeze.py index 58a1412..0e5d509 100644 --- a/src/diffpy/morph/morphs/morphsqueeze.py +++ b/src/diffpy/morph/morphs/morphsqueeze.py @@ -14,7 +14,7 @@ class MorphSqueeze(Morph): yinlabel = LABEL_GR # This label need to change to be more generic xoutlabel = LABEL_RA # This label need to change to be more generic youtlabel = LABEL_GR # This label need to change to be more generic - parnames = ["squeeze"] + parnames = ["squeeze_1", "squeeze_2"] def morph(self, x_morph, y_morph, x_target, y_target): """Resample arrays onto the specified grid using a non-linear squeeze. @@ -35,12 +35,17 @@ def morph(self, x_morph, y_morph, x_target, y_target): # Initialize the parent class to set up attributes Morph.morph(self, x_morph, y_morph, x_target, y_target) - # If squeeze is zero, return original output - if self.squeeze == 0: + # If squeeze_1 and squeeze_2 are zero, return original output + if self.squeeze_1 == 0 and self.squeeze_2 == 0: return self.xyallout # Compute new x positions using the non-linear squeeze transformation: - new_x = self.x_morph_in + self.squeeze * np.sin(self.x_morph_in) + new_x = ( + self.x_morph_in + + self.squeeze_1 * self.x_morph_in**2 + + self.squeeze_2 * self.x_morph_in**3 + ) + self.x_morph_out = new_x # Interpolate the y-values at the new x positions. diff --git a/tests/test_morphsqueeze.py b/tests/test_morphsqueeze.py index 2950c4f..c007903 100644 --- a/tests/test_morphsqueeze.py +++ b/tests/test_morphsqueeze.py @@ -7,17 +7,18 @@ class TestMorphSqueeze: @pytest.fixture def setup(self): - # Create a sine-wave for testing - self.x_morph = np.linspace(0, 2 * np.pi, 1000) + # Create data for testing + self.x_morph = np.linspace(0, 10, 1000) self.y_morph = np.sin(self.x_morph) self.x_target = self.x_morph.copy() self.y_target = self.y_morph.copy() return def test_no_squeeze(self, setup): - """When squeeze is zero, the input should be unchanged.""" + """When both squeeze are zero, the input should be unchanged.""" morph = MorphSqueeze() - morph.squeeze = 0.0 + morph.squeeze_1 = 0.0 + morph.squeeze_2 = 0.0 x_morph, y_morph, x_target, y_target = morph( self.x_morph, self.y_morph, self.x_target, self.y_target @@ -34,7 +35,9 @@ def test_morph_with_squeeze(self, setup): """Test that with a non-zero squeeze, x_morph is transformed non-linearly.""" morph = MorphSqueeze() - morph.squeeze = 0.7 + morph.squeeze_1 = -0.07 + morph.squeeze_2 = 0.1 + x_new, y_new, x_target, y_target = morph( self.x_morph, self.y_morph, self.x_target, self.y_target ) @@ -43,9 +46,14 @@ def test_morph_with_squeeze(self, setup): assert np.allclose(self.y_target, y_target) # For this test, we expect: - # x_new = x_morph + squeeze_factor * sin(x_morph) - expected_x = self.x_morph + morph.squeeze * np.sin(self.x_morph) - expected_y = np.sin(expected_x) + # x_new = x_morph + squeeze_1 * x_morph ** 2 + + # squeeze_2 * x_morph ** 3 + expected_x = ( + self.x_morph + + morph.squeeze_1 * self.x_morph**2 + + morph.squeeze_2 * self.x_morph**3 + ) + expected_y = np.interp(expected_x, self.x_morph, self.y_morph) # Allow for some tolerance because of numerical interpolation if used res = sum(np.fabs(expected_y - y_new)) From c2e49a02ad53e590be93a4b8249495b7d3785fdd Mon Sep 17 00:00:00 2001 From: Luis Kitsu Iglesias Date: Fri, 28 Mar 2025 17:13:53 -0600 Subject: [PATCH 4/4] funct/test: fixing squeeze function and test --- news/morphsqueeze.rst | 6 ++++-- src/diffpy/morph/morphs/morphsqueeze.py | 8 +++----- tests/test_morphsqueeze.py | 7 ++++--- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/news/morphsqueeze.rst b/news/morphsqueeze.rst index 67c81d0..6651944 100644 --- a/news/morphsqueeze.rst +++ b/news/morphsqueeze.rst @@ -5,7 +5,9 @@ **Changed:** * Added cubic and quadratic terms, as well as -* squeeze_1 and squeeze_2 parameters +* squeeze_1 and squeeze_2 parameters. +* I was not keeping the y-axis constant and +* corrected for it **Deprecated:** @@ -17,7 +19,7 @@ **Fixed:** -* +* I was not keeping the y-values constant, fixed now **Security:** diff --git a/src/diffpy/morph/morphs/morphsqueeze.py b/src/diffpy/morph/morphs/morphsqueeze.py index 0e5d509..42f37f9 100644 --- a/src/diffpy/morph/morphs/morphsqueeze.py +++ b/src/diffpy/morph/morphs/morphsqueeze.py @@ -1,10 +1,8 @@ """class MorphSqueeze -- apply a non-linear squeeze to the morph. This morph non-linearly adjusts the x-coordinates. -The y-values are then recomputed by interpolating the original data. +The y-values are the same as initial, just on a new grid. """ -import numpy as np - from diffpy.morph.morphs.morph import LABEL_GR, LABEL_RA, Morph @@ -48,8 +46,8 @@ def morph(self, x_morph, y_morph, x_target, y_target): self.x_morph_out = new_x - # Interpolate the y-values at the new x positions. - self.y_morph_out = np.interp(new_x, self.x_morph_in, self.y_morph_in) + # The y-axis should be the same + self.y_morph_out = self.y_morph_in return self.xyallout diff --git a/tests/test_morphsqueeze.py b/tests/test_morphsqueeze.py index c007903..27b0209 100644 --- a/tests/test_morphsqueeze.py +++ b/tests/test_morphsqueeze.py @@ -53,11 +53,12 @@ def test_morph_with_squeeze(self, setup): + morph.squeeze_1 * self.x_morph**2 + morph.squeeze_2 * self.x_morph**3 ) - expected_y = np.interp(expected_x, self.x_morph, self.y_morph) + expected_y = np.sin(self.x_morph) - # Allow for some tolerance because of numerical interpolation if used res = sum(np.fabs(expected_y - y_new)) - assert res < 1 + res2 = sum(np.fabs(expected_x - x_new)) + assert res < 0.1 + assert res2 < 0.1 if __name__ == "__main__":