From d65dce1374f0754d8d19df6d0985cf1814011aea Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sun, 8 Dec 2024 16:03:00 -0500 Subject: [PATCH 1/7] initial commit, tests need discussion --- src/diffpy/utils/diffraction_objects.py | 7 +- tests/test_diffraction_objects.py | 93 +++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 3 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 6ce965d0..39f6d15c 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -366,7 +366,7 @@ def on_d(self): def scale_to(self, target_diff_object, xtype=None, xvalue=None): f""" - returns a new diffraction object which is the current object but recaled in y to the target + returns a new diffraction object which is the current object but rescaled in y to the target Parameters ---------- @@ -390,14 +390,15 @@ def scale_to(self, target_diff_object, xtype=None, xvalue=None): data = self.on_xtype(xtype) target = target_diff_object.on_xtype(xtype) + if len(data[0]) == 0 or len(target[0]) == 0 or len(data[0]) != len(target[0]): + raise ValueError("I cannot scale two diffraction objects with empty or different lengths.") if xvalue is None: xvalue = data[0][0] + (data[0][-1] - data[0][0]) / 2.0 xindex = (np.abs(data[0] - xvalue)).argmin() ytarget = target[1][xindex] yself = data[1][xindex] - scaled.on_tth[1] = data[1] * ytarget / yself - scaled.on_q[1] = data[1] * ytarget / yself + scaled._all_arrays[:, 0] = data[1] * ytarget / yself return scaled def on_xtype(self, xtype): diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index aeba1522..e386c7cc 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -223,6 +223,99 @@ def test_on_xtype_bad(): test.on_xtype("invalid") +params_scale_to = [ + # UC1: xvalue exact match + ( + [ + np.array([10, 15, 25, 30, 60, 140]), + np.array([10, 20, 25, 30, 60, 100]), + "tth", + 2 * np.pi, + np.array([10, 15, 25, 30, 60, 140]), + np.array([2, 3, 4, 5, 6, 7]), + "tth", + 2 * np.pi, + "tth", + 60, + ], + [np.array([1, 2, 2.5, 3, 6, 10])], + ), + # UC2: xvalue approximate match + ( + [ + np.array([0.11, 0.24, 0.31, 0.4]), + np.array([10, 20, 40, 60]), + "q", + 2 * np.pi, + np.array([0.11, 0.24, 0.31, 0.4]), + np.array([1, 3, 4, 5]), + "q", + 2 * np.pi, + "q", + 0.1, + ], + [np.array([1, 2, 4, 6])], + ), +] + + +@pytest.mark.parametrize("inputs, expected", params_scale_to) +def test_scale_to(inputs, expected): + orig_diff_object = DiffractionObject(xarray=inputs[0], yarray=inputs[1], xtype=inputs[2], wavelength=inputs[3]) + target_diff_object = DiffractionObject( + xarray=inputs[4], yarray=inputs[5], xtype=inputs[6], wavelength=inputs[7] + ) + scaled_diff_object = orig_diff_object.scale_to(target_diff_object, xtype=inputs[8], xvalue=inputs[9]) + # Check the intensity data is same as expected + assert np.allclose(scaled_diff_object.on_xtype(inputs[8])[1], expected[0]) + + +params_scale_to_bad = [ + # UC1: at least one of the y-arrays is empty + ( + [ + np.array([]), + np.array([]), + "tth", + 2 * np.pi, + np.array([11, 14, 16, 20, 25, 30]), + np.array([2, 3, 4, 5, 6, 7]), + "tth", + 2 * np.pi, + "tth", + 60, + ] + ), + # UC2: diffraction objects with different array lengths + ( + [ + np.array([0.11, 0.24, 0.31, 0.4, 0.5]), + np.array([10, 20, 40, 50, 60]), + "q", + 2 * np.pi, + np.array([0.1, 0.15, 0.3, 0.4]), + np.array([1, 3, 4, 5]), + "q", + 2 * np.pi, + "q", + 0.1, + ] + ), +] + + +@pytest.mark.parametrize("inputs", params_scale_to_bad) +def test_scale_to_bad(inputs): + orig_diff_object = DiffractionObject(xarray=inputs[0], yarray=inputs[1], xtype=inputs[2], wavelength=inputs[3]) + target_diff_object = DiffractionObject( + xarray=inputs[4], yarray=inputs[5], xtype=inputs[6], wavelength=inputs[7] + ) + with pytest.raises( + ValueError, match="I cannot scale two diffraction objects with empty or different lengths." + ): + orig_diff_object.scale_to(target_diff_object, xtype=inputs[8], xvalue=inputs[9]) + + params_index = [ # UC1: exact match ([4 * np.pi, np.array([30.005, 60]), np.array([1, 2]), "tth", "tth", 30.005], [0]), From 5d07b9a2ba015dd52c8ce96afc4d72606835a373 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Mon, 9 Dec 2024 17:11:53 -0500 Subject: [PATCH 2/7] add news and more tests --- news/scaleto.rst | 23 +++++ src/diffpy/utils/diffraction_objects.py | 44 +++++---- tests/test_diffraction_objects.py | 122 +++++++++++++++--------- 3 files changed, 122 insertions(+), 67 deletions(-) create mode 100644 news/scaleto.rst diff --git a/news/scaleto.rst b/news/scaleto.rst new file mode 100644 index 00000000..6f6b0635 --- /dev/null +++ b/news/scaleto.rst @@ -0,0 +1,23 @@ +**Added:** + +* functionality to rescale diffraction objects, placing one on top of another at a specified point + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 39f6d15c..0fd77746 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -364,41 +364,43 @@ def on_tth(self): def on_d(self): return [self.all_arrays[:, 3], self.all_arrays[:, 0]] - def scale_to(self, target_diff_object, xtype=None, xvalue=None): - f""" + def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=0): + """ returns a new diffraction object which is the current object but rescaled in y to the target + The y-value in the target at the closest specified x-value will be used as the factor to scale to. + The entire array is scaled by this factor so that one object places on top of the other at that point. + If multiple values of `q`, `tth`, or `d` are provided, the priority is `q` > `tth` > `d`. + If none are provided, the midpoint of the current object's `q`-array will be used. + Parameters ---------- target_diff_object: DiffractionObject - the diffraction object you want to scale the current one on to - xtype: string, optional. Default is Q - the xtype, from {XQUANTITIES}, that you will specify a point from to scale to - xvalue: float. Default is the midpoint of the array - the y-value in the target at this x-value will be used as the factor to scale to. - The entire array is scaled be the factor that places on on top of the other at that point. - xvalue does not have to be in the x-array, the point closest to this point will be used for the scaling. + the diffraction object you want to scale the current one onto + + q, tth, d : float, optional, default is the midpoint of the current object's `q`-array + the xvalue (in `q`, `tth`, or `d` space) to align the current and target objects + + offset : float, optional, default is 0 + an offset to add to the scaled y-values Returns ------- the rescaled DiffractionObject as a new object - """ scaled = deepcopy(self) - if xtype is None: - xtype = "q" + xtype = "q" if q is not None else "tth" if tth is not None else "d" if d is not None else "q" + data, target = self.on_xtype(xtype), target_diff_object.on_xtype(xtype) + if len(data[0]) == 0 or len(target[0]) == 0: + raise ValueError("I cannot scale diffraction objects with empty arrays.") - data = self.on_xtype(xtype) - target = target_diff_object.on_xtype(xtype) - if len(data[0]) == 0 or len(target[0]) == 0 or len(data[0]) != len(target[0]): - raise ValueError("I cannot scale two diffraction objects with empty or different lengths.") + xvalue = q if xtype == "q" else tth if xtype == "tth" else d if xvalue is None: - xvalue = data[0][0] + (data[0][-1] - data[0][0]) / 2.0 + xvalue = (data[0][0] + data[0][-1]) / 2.0 - xindex = (np.abs(data[0] - xvalue)).argmin() - ytarget = target[1][xindex] - yself = data[1][xindex] - scaled._all_arrays[:, 0] = data[1] * ytarget / yself + x_data, x_target = (np.abs(data[0] - xvalue)).argmin(), (np.abs(target[0] - xvalue)).argmin() + y_data, y_target = data[1][x_data], target[1][x_target] + scaled._all_arrays[:, 0] = data[1] * y_target / y_data + offset return scaled def on_xtype(self, xtype): diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index e386c7cc..df8f14d3 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -224,96 +224,126 @@ def test_on_xtype_bad(): params_scale_to = [ - # UC1: xvalue exact match + # UC1: same x-array and y-array, check offset ( [ np.array([10, 15, 25, 30, 60, 140]), - np.array([10, 20, 25, 30, 60, 100]), + np.array([2, 3, 4, 5, 6, 7]), "tth", 2 * np.pi, np.array([10, 15, 25, 30, 60, 140]), np.array([2, 3, 4, 5, 6, 7]), "tth", 2 * np.pi, + None, + 60, + None, + 2.1, + ], + ["tth", np.array([4.1, 5.1, 6.1, 7.1, 8.1, 9.1])], + ), + # UC2: same length x-arrays with exact x-value match + ( + [ + np.array([10, 15, 25, 30, 60, 140]), + np.array([10, 20, 25, 30, 60, 100]), + "tth", + 2 * np.pi, + np.array([10, 20, 25, 30, 60, 140]), + np.array([2, 3, 4, 5, 6, 7]), "tth", + 2 * np.pi, + None, 60, + None, + 0, ], - [np.array([1, 2, 2.5, 3, 6, 10])], + ["tth", np.array([1, 2, 2.5, 3, 6, 10])], ), - # UC2: xvalue approximate match + # UC3: same length x-arrays with approximate x-value match ( [ - np.array([0.11, 0.24, 0.31, 0.4]), + np.array([0.12, 0.24, 0.31, 0.4]), np.array([10, 20, 40, 60]), "q", 2 * np.pi, - np.array([0.11, 0.24, 0.31, 0.4]), + np.array([0.14, 0.24, 0.31, 0.4]), np.array([1, 3, 4, 5]), "q", 2 * np.pi, - "q", 0.1, + None, + None, + 0, ], - [np.array([1, 2, 4, 6])], + ["q", np.array([1, 2, 4, 6])], ), -] - - -@pytest.mark.parametrize("inputs, expected", params_scale_to) -def test_scale_to(inputs, expected): - orig_diff_object = DiffractionObject(xarray=inputs[0], yarray=inputs[1], xtype=inputs[2], wavelength=inputs[3]) - target_diff_object = DiffractionObject( - xarray=inputs[4], yarray=inputs[5], xtype=inputs[6], wavelength=inputs[7] - ) - scaled_diff_object = orig_diff_object.scale_to(target_diff_object, xtype=inputs[8], xvalue=inputs[9]) - # Check the intensity data is same as expected - assert np.allclose(scaled_diff_object.on_xtype(inputs[8])[1], expected[0]) - - -params_scale_to_bad = [ - # UC1: at least one of the y-arrays is empty + # UC4: different x-array lengths with approximate x-value match ( [ - np.array([]), - np.array([]), + np.array([10, 25, 30.1, 40.2, 61, 120, 140]), + np.array([10, 20, 30, 40, 50, 60, 100]), "tth", 2 * np.pi, - np.array([11, 14, 16, 20, 25, 30]), - np.array([2, 3, 4, 5, 6, 7]), + np.array([20, 25.5, 32, 45, 50, 62, 100, 125, 140]), + np.array([1.1, 2, 3, 3.5, 4, 5, 10, 12, 13]), "tth", 2 * np.pi, - "tth", + None, 60, - ] + None, + 0, + ], + # scaling factor is calculated at index = 5 for self and index = 6 for target + ["tth", np.array([1, 2, 3, 4, 5, 6, 10])], ), - # UC2: diffraction objects with different array lengths + # UC5: user specified multiple x-values, prioritize q > tth > d ( [ - np.array([0.11, 0.24, 0.31, 0.4, 0.5]), - np.array([10, 20, 40, 50, 60]), - "q", + np.array([10, 25, 30.1, 40.2, 61, 120, 140]), + np.array([10, 20, 30, 40, 50, 60, 100]), + "tth", 2 * np.pi, - np.array([0.1, 0.15, 0.3, 0.4]), - np.array([1, 3, 4, 5]), - "q", + np.array([20, 25.5, 32, 45, 50, 62, 100, 125, 140]), + np.array([1.1, 2, 3, 3.5, 4, 5, 10, 12, 13]), + "tth", 2 * np.pi, - "q", - 0.1, - ] + None, + 60, + 10, + 0, + ], + ["tth", np.array([1, 2, 3, 4, 5, 6, 10])], ), ] -@pytest.mark.parametrize("inputs", params_scale_to_bad) -def test_scale_to_bad(inputs): +@pytest.mark.parametrize("inputs, expected", params_scale_to) +def test_scale_to(inputs, expected): orig_diff_object = DiffractionObject(xarray=inputs[0], yarray=inputs[1], xtype=inputs[2], wavelength=inputs[3]) target_diff_object = DiffractionObject( xarray=inputs[4], yarray=inputs[5], xtype=inputs[6], wavelength=inputs[7] ) - with pytest.raises( - ValueError, match="I cannot scale two diffraction objects with empty or different lengths." - ): - orig_diff_object.scale_to(target_diff_object, xtype=inputs[8], xvalue=inputs[9]) + scaled_diff_object = orig_diff_object.scale_to( + target_diff_object, q=inputs[8], tth=inputs[9], d=inputs[10], offset=inputs[11] + ) + # Check the intensity data is same as expected + assert np.allclose(scaled_diff_object.on_xtype(expected[0])[1], expected[1]) + + +def test_scale_to_bad(): + # UC1: at least one of the y-arrays is empty + orig_diff_object = DiffractionObject( + xarray=np.array([]), yarray=np.array([]), xtype="tth", wavelength=2 * np.pi + ) + target_diff_object = DiffractionObject( + xarray=np.array([11, 14, 16, 20, 25, 30]), + yarray=np.array([2, 3, 4, 5, 6, 7]), + xtype="tth", + wavelength=2 * np.pi, + ) + with pytest.raises(ValueError, match="I cannot scale diffraction objects with empty arrays."): + orig_diff_object.scale_to(target_diff_object, tth=20) params_index = [ From 3c5a426929fc8eb935375befa4841badfbd0202a Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Tue, 10 Dec 2024 11:40:27 -0500 Subject: [PATCH 3/7] remove error msg, change copy method --- src/diffpy/utils/diffraction_objects.py | 4 +--- tests/test_diffraction_objects.py | 15 --------------- 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 1e7ee05d..e1b45cd1 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -388,11 +388,9 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=0): ------- the rescaled DiffractionObject as a new object """ - scaled = deepcopy(self) + scaled = self.copy() xtype = "q" if q is not None else "tth" if tth is not None else "d" if d is not None else "q" data, target = self.on_xtype(xtype), target_diff_object.on_xtype(xtype) - if len(data[0]) == 0 or len(target[0]) == 0: - raise ValueError("I cannot scale diffraction objects with empty arrays.") xvalue = q if xtype == "q" else tth if xtype == "tth" else d if xvalue is None: diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index e066c025..2903bb49 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -331,21 +331,6 @@ def test_scale_to(inputs, expected): assert np.allclose(scaled_diff_object.on_xtype(expected[0])[1], expected[1]) -def test_scale_to_bad(): - # UC1: at least one of the y-arrays is empty - orig_diff_object = DiffractionObject( - xarray=np.array([]), yarray=np.array([]), xtype="tth", wavelength=2 * np.pi - ) - target_diff_object = DiffractionObject( - xarray=np.array([11, 14, 16, 20, 25, 30]), - yarray=np.array([2, 3, 4, 5, 6, 7]), - xtype="tth", - wavelength=2 * np.pi, - ) - with pytest.raises(ValueError, match="I cannot scale diffraction objects with empty arrays."): - orig_diff_object.scale_to(target_diff_object, tth=20) - - params_index = [ # UC1: exact match ([4 * np.pi, np.array([30.005, 60]), np.array([1, 2]), "tth", "tth", 30.005], [0]), From 79161f177ec2463be321fd6a47fe28ca63f27328 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Thu, 12 Dec 2024 15:04:49 -0500 Subject: [PATCH 4/7] remove expected parameters in bad tests --- tests/test_diffraction_objects.py | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 68dd0c01..043da5c0 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -290,27 +290,24 @@ def test_scale_to(inputs, expected): params_scale_to_bad = [ ( - [ - np.array([10, 25, 30.1, 40.2, 61, 120, 140]), - np.array([10, 20, 30, 40, 50, 60, 100]), - "tth", - 2 * np.pi, - np.array([20, 25.5, 32, 45, 50, 62, 100, 125, 140]), - np.array([1.1, 2, 3, 3.5, 4, 5, 10, 12, 13]), - "tth", - 2 * np.pi, - None, - 60, - 10, - 0, - ], - ["tth", np.array([1, 2, 3, 4, 5, 6, 10])], + np.array([10, 25, 30.1, 40.2, 61, 120, 140]), + np.array([10, 20, 30, 40, 50, 60, 100]), + "tth", + 2 * np.pi, + np.array([20, 25.5, 32, 45, 50, 62, 100, 125, 140]), + np.array([1.1, 2, 3, 3.5, 4, 5, 10, 12, 13]), + "tth", + 2 * np.pi, + None, + 60, + 10, + 0, ), ] -@pytest.mark.parametrize("inputs, expected", params_scale_to_bad) -def test_scale_to_bad(inputs, expected): +@pytest.mark.parametrize("inputs", params_scale_to_bad) +def test_scale_to_bad(inputs): orig_diff_object = DiffractionObject(xarray=inputs[0], yarray=inputs[1], xtype=inputs[2], wavelength=inputs[3]) target_diff_object = DiffractionObject( xarray=inputs[4], yarray=inputs[5], xtype=inputs[6], wavelength=inputs[7] From 1b7aa14903814ba6df2c235f240c4724966db308 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Thu, 12 Dec 2024 15:07:40 -0500 Subject: [PATCH 5/7] edit docstring --- src/diffpy/utils/diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 936bb25c..a8f0bbaa 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -397,7 +397,7 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=0): The y-value in the target at the closest specified x-value will be used as the factor to scale to. The entire array is scaled by this factor so that one object places on top of the other at that point. - If multiple values of `q`, `tth`, or `d` are provided, the priority is `q` > `tth` > `d`. + If multiple values of `q`, `tth`, or `d` are provided, an error will be raised. If none are provided, the midpoint of the current object's `q`-array will be used. Parameters From c22a95bc9a03231fb812c29bbf2ffded06d17355 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Fri, 13 Dec 2024 12:26:50 -0500 Subject: [PATCH 6/7] remove default value in the function --- src/diffpy/utils/diffraction_objects.py | 19 +++++++------ tests/test_diffraction_objects.py | 36 ++++++++++++------------- 2 files changed, 26 insertions(+), 29 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index a8f0bbaa..0813ef9b 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -397,15 +397,14 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=0): The y-value in the target at the closest specified x-value will be used as the factor to scale to. The entire array is scaled by this factor so that one object places on top of the other at that point. - If multiple values of `q`, `tth`, or `d` are provided, an error will be raised. - If none are provided, the midpoint of the current object's `q`-array will be used. + If multiple values of `q`, `tth`, or `d` are provided, or none are provided, an error will be raised. Parameters ---------- target_diff_object: DiffractionObject the diffraction object you want to scale the current one onto - q, tth, d : float, optional, default is the midpoint of the current object's `q`-array + q, tth, d : float, optional, must specify exactly one of them the xvalue (in `q`, `tth`, or `d` space) to align the current and target objects offset : float, optional, default is 0 @@ -417,19 +416,19 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=0): """ scaled = self.copy() count = sum([q is not None, tth is not None, d is not None]) - if count > 1: - raise ValueError("You can only specify one of 'q', 'tth', or 'd'. Please rerun specifying only one.") + if count != 1: + raise ValueError( + "You must specify exactly one of 'q', 'tth', or 'd'. Please rerun specifying only one." + ) xtype = "q" if q is not None else "tth" if tth is not None else "d" if d is not None else "q" data, target = self.on_xtype(xtype), target_diff_object.on_xtype(xtype) xvalue = q if xtype == "q" else tth if xtype == "tth" else d - if xvalue is None: - xvalue = (data[0][0] + data[0][-1]) / 2.0 - x_data, x_target = (np.abs(data[0] - xvalue)).argmin(), (np.abs(target[0] - xvalue)).argmin() - y_data, y_target = data[1][x_data], target[1][x_target] - scaled._all_arrays[:, 0] = data[1] * y_target / y_data + offset + xindex_data = (np.abs(data[0] - xvalue)).argmin() + xindex_target = (np.abs(target[0] - xvalue)).argmin() + scaled._all_arrays[:, 0] = data[1] * target[1][xindex_target] / data[1][xindex_data] + offset return scaled def on_xtype(self, xtype): diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 043da5c0..bd8c6c6e 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -254,24 +254,6 @@ def test_init_invalid_xtype(): # scaling factor is calculated at index = 5 for self and index = 6 for target ["tth", np.array([1, 2, 3, 4, 5, 6, 10])], ), - # UC5: user did not specify anything, use the midpoint of the current object's q-array - ( - [ - np.array([0.1, 0.2, 0.3]), - np.array([1, 2, 3]), - "q", - 2 * np.pi, - np.array([0.05, 0.1, 0.2, 0.3]), - np.array([5, 10, 20, 30]), - "q", - 2 * np.pi, - None, - None, - None, - 0, - ], - ["q", np.array([10, 20, 30])], - ), ] @@ -289,6 +271,22 @@ def test_scale_to(inputs, expected): params_scale_to_bad = [ + # UC1: user did not specify anything + ( + np.array([0.1, 0.2, 0.3]), + np.array([1, 2, 3]), + "q", + 2 * np.pi, + np.array([0.05, 0.1, 0.2, 0.3]), + np.array([5, 10, 20, 30]), + "q", + 2 * np.pi, + None, + None, + None, + 0, + ), + # UC2: user specified more than one of q, tth, and d ( np.array([10, 25, 30.1, 40.2, 61, 120, 140]), np.array([10, 20, 30, 40, 50, 60, 100]), @@ -313,7 +311,7 @@ def test_scale_to_bad(inputs): xarray=inputs[4], yarray=inputs[5], xtype=inputs[6], wavelength=inputs[7] ) with pytest.raises( - ValueError, match="You can only specify one of 'q', 'tth', or 'd'. Please rerun specifying only one." + ValueError, match="You must specify exactly one of 'q', 'tth', or 'd'. Please rerun specifying only one." ): orig_diff_object.scale_to(target_diff_object, q=inputs[8], tth=inputs[9], d=inputs[10], offset=inputs[11]) From 53413fb9038e00e5dae3b206a0a5742ec8dbd0a4 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sat, 14 Dec 2024 11:51:20 -0500 Subject: [PATCH 7/7] make tests more readable --- src/diffpy/utils/diffraction_objects.py | 8 +- tests/test_diffraction_objects.py | 202 +++++++++++++----------- 2 files changed, 114 insertions(+), 96 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 0813ef9b..7c8e3aee 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -405,7 +405,8 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=0): the diffraction object you want to scale the current one onto q, tth, d : float, optional, must specify exactly one of them - the xvalue (in `q`, `tth`, or `d` space) to align the current and target objects + The value of the x-array where you want the curves to line up vertically. + Specify a value on one of the allowed grids, q, tth, or d), e.g., q=10. offset : float, optional, default is 0 an offset to add to the scaled y-values @@ -421,8 +422,9 @@ def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=0): "You must specify exactly one of 'q', 'tth', or 'd'. Please rerun specifying only one." ) - xtype = "q" if q is not None else "tth" if tth is not None else "d" if d is not None else "q" - data, target = self.on_xtype(xtype), target_diff_object.on_xtype(xtype) + xtype = "q" if q is not None else "tth" if tth is not None else "d" + data = self.on_xtype(xtype) + target = target_diff_object.on_xtype(xtype) xvalue = q if xtype == "q" else tth if xtype == "tth" else d diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index bd8c6c6e..8a2ed670 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -183,137 +183,153 @@ def test_init_invalid_xtype(): params_scale_to = [ # UC1: same x-array and y-array, check offset ( - [ - np.array([10, 15, 25, 30, 60, 140]), - np.array([2, 3, 4, 5, 6, 7]), - "tth", - 2 * np.pi, - np.array([10, 15, 25, 30, 60, 140]), - np.array([2, 3, 4, 5, 6, 7]), - "tth", - 2 * np.pi, - None, - 60, - None, - 2.1, - ], - ["tth", np.array([4.1, 5.1, 6.1, 7.1, 8.1, 9.1])], + { + "xarray": np.array([10, 15, 25, 30, 60, 140]), + "yarray": np.array([2, 3, 4, 5, 6, 7]), + "xtype": "tth", + "wavelength": 2 * np.pi, + "target_xarray": np.array([10, 15, 25, 30, 60, 140]), + "target_yarray": np.array([2, 3, 4, 5, 6, 7]), + "target_xtype": "tth", + "target_wavelength": 2 * np.pi, + "q": None, + "tth": 60, + "d": None, + "offset": 2.1, + }, + {"xtype": "tth", "yarray": np.array([4.1, 5.1, 6.1, 7.1, 8.1, 9.1])}, ), # UC2: same length x-arrays with exact x-value match ( - [ - np.array([10, 15, 25, 30, 60, 140]), - np.array([10, 20, 25, 30, 60, 100]), - "tth", - 2 * np.pi, - np.array([10, 20, 25, 30, 60, 140]), - np.array([2, 3, 4, 5, 6, 7]), - "tth", - 2 * np.pi, - None, - 60, - None, - 0, - ], - ["tth", np.array([1, 2, 2.5, 3, 6, 10])], + { + "xarray": np.array([10, 15, 25, 30, 60, 140]), + "yarray": np.array([10, 20, 25, 30, 60, 100]), + "xtype": "tth", + "wavelength": 2 * np.pi, + "target_xarray": np.array([10, 20, 25, 30, 60, 140]), + "target_yarray": np.array([2, 3, 4, 5, 6, 7]), + "target_xtype": "tth", + "target_wavelength": 2 * np.pi, + "q": None, + "tth": 60, + "d": None, + "offset": 0, + }, + {"xtype": "tth", "yarray": np.array([1, 2, 2.5, 3, 6, 10])}, ), # UC3: same length x-arrays with approximate x-value match ( - [ - np.array([0.12, 0.24, 0.31, 0.4]), - np.array([10, 20, 40, 60]), - "q", - 2 * np.pi, - np.array([0.14, 0.24, 0.31, 0.4]), - np.array([1, 3, 4, 5]), - "q", - 2 * np.pi, - 0.1, - None, - None, - 0, - ], - ["q", np.array([1, 2, 4, 6])], + { + "xarray": np.array([0.12, 0.24, 0.31, 0.4]), + "yarray": np.array([10, 20, 40, 60]), + "xtype": "q", + "wavelength": 2 * np.pi, + "target_xarray": np.array([0.14, 0.24, 0.31, 0.4]), + "target_yarray": np.array([1, 3, 4, 5]), + "target_xtype": "q", + "target_wavelength": 2 * np.pi, + "q": 0.1, + "tth": None, + "d": None, + "offset": 0, + }, + {"xtype": "q", "yarray": np.array([1, 2, 4, 6])}, ), # UC4: different x-array lengths with approximate x-value match ( - [ - np.array([10, 25, 30.1, 40.2, 61, 120, 140]), - np.array([10, 20, 30, 40, 50, 60, 100]), - "tth", - 2 * np.pi, - np.array([20, 25.5, 32, 45, 50, 62, 100, 125, 140]), - np.array([1.1, 2, 3, 3.5, 4, 5, 10, 12, 13]), - "tth", - 2 * np.pi, - None, - 60, - None, - 0, - ], - # scaling factor is calculated at index = 5 for self and index = 6 for target - ["tth", np.array([1, 2, 3, 4, 5, 6, 10])], + { + "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), + "yarray": np.array([10, 20, 30, 40, 50, 60, 100]), + "xtype": "tth", + "wavelength": 2 * np.pi, + "target_xarray": np.array([20, 25.5, 32, 45, 50, 62, 100, 125, 140]), + "target_yarray": np.array([1.1, 2, 3, 3.5, 4, 5, 10, 12, 13]), + "target_xtype": "tth", + "target_wavelength": 2 * np.pi, + "q": None, + "tth": 60, + "d": None, + "offset": 0, + }, + # scaling factor is calculated at index = 4 (tth=61) for self and index = 5 for target (tth=62) + {"xtype": "tth", "yarray": np.array([1, 2, 3, 4, 5, 6, 10])}, ), ] @pytest.mark.parametrize("inputs, expected", params_scale_to) def test_scale_to(inputs, expected): - orig_diff_object = DiffractionObject(xarray=inputs[0], yarray=inputs[1], xtype=inputs[2], wavelength=inputs[3]) + orig_diff_object = DiffractionObject( + xarray=inputs["xarray"], yarray=inputs["yarray"], xtype=inputs["xtype"], wavelength=inputs["wavelength"] + ) target_diff_object = DiffractionObject( - xarray=inputs[4], yarray=inputs[5], xtype=inputs[6], wavelength=inputs[7] + xarray=inputs["target_xarray"], + yarray=inputs["target_yarray"], + xtype=inputs["target_xtype"], + wavelength=inputs["target_wavelength"], ) scaled_diff_object = orig_diff_object.scale_to( - target_diff_object, q=inputs[8], tth=inputs[9], d=inputs[10], offset=inputs[11] + target_diff_object, q=inputs["q"], tth=inputs["tth"], d=inputs["d"], offset=inputs["offset"] ) - # Check the intensity data is same as expected - assert np.allclose(scaled_diff_object.on_xtype(expected[0])[1], expected[1]) + # Check the intensity data is the same as expected + assert np.allclose(scaled_diff_object.on_xtype(expected["xtype"])[1], expected["yarray"]) params_scale_to_bad = [ # UC1: user did not specify anything ( - np.array([0.1, 0.2, 0.3]), - np.array([1, 2, 3]), - "q", - 2 * np.pi, - np.array([0.05, 0.1, 0.2, 0.3]), - np.array([5, 10, 20, 30]), - "q", - 2 * np.pi, - None, - None, - None, - 0, + { + "xarray": np.array([0.1, 0.2, 0.3]), + "yarray": np.array([1, 2, 3]), + "xtype": "q", + "wavelength": 2 * np.pi, + "target_xarray": np.array([0.05, 0.1, 0.2, 0.3]), + "target_yarray": np.array([5, 10, 20, 30]), + "target_xtype": "q", + "target_wavelength": 2 * np.pi, + "q": None, + "tth": None, + "d": None, + "offset": 0, + } ), # UC2: user specified more than one of q, tth, and d ( - np.array([10, 25, 30.1, 40.2, 61, 120, 140]), - np.array([10, 20, 30, 40, 50, 60, 100]), - "tth", - 2 * np.pi, - np.array([20, 25.5, 32, 45, 50, 62, 100, 125, 140]), - np.array([1.1, 2, 3, 3.5, 4, 5, 10, 12, 13]), - "tth", - 2 * np.pi, - None, - 60, - 10, - 0, + { + "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), + "yarray": np.array([10, 20, 30, 40, 50, 60, 100]), + "xtype": "tth", + "wavelength": 2 * np.pi, + "target_xarray": np.array([20, 25.5, 32, 45, 50, 62, 100, 125, 140]), + "target_yarray": np.array([1.1, 2, 3, 3.5, 4, 5, 10, 12, 13]), + "target_xtype": "tth", + "target_wavelength": 2 * np.pi, + "q": None, + "tth": 60, + "d": 10, + "offset": 0, + } ), ] @pytest.mark.parametrize("inputs", params_scale_to_bad) def test_scale_to_bad(inputs): - orig_diff_object = DiffractionObject(xarray=inputs[0], yarray=inputs[1], xtype=inputs[2], wavelength=inputs[3]) + orig_diff_object = DiffractionObject( + xarray=inputs["xarray"], yarray=inputs["yarray"], xtype=inputs["xtype"], wavelength=inputs["wavelength"] + ) target_diff_object = DiffractionObject( - xarray=inputs[4], yarray=inputs[5], xtype=inputs[6], wavelength=inputs[7] + xarray=inputs["target_xarray"], + yarray=inputs["target_yarray"], + xtype=inputs["target_xtype"], + wavelength=inputs["target_wavelength"], ) with pytest.raises( ValueError, match="You must specify exactly one of 'q', 'tth', or 'd'. Please rerun specifying only one." ): - orig_diff_object.scale_to(target_diff_object, q=inputs[8], tth=inputs[9], d=inputs[10], offset=inputs[11]) + orig_diff_object.scale_to( + target_diff_object, q=inputs["q"], tth=inputs["tth"], d=inputs["d"], offset=inputs["offset"] + ) params_index = [