From 7db3a4f44f444c4e6d84c2da799b7de94af9567d Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 27 Dec 2024 14:40:34 -0500 Subject: [PATCH 01/10] Refactor __eq__ function for DO --- src/diffpy/utils/diffraction_objects.py | 94 +++++++++++++------------ 1 file changed, 50 insertions(+), 44 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 25e3e28c..316758d0 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -14,9 +14,15 @@ XQUANTITIES = ANGLEQUANTITIES + DQUANTITIES + QQUANTITIES XUNITS = ["degrees", "radians", "rad", "deg", "inv_angs", "inv_nm", "nm-1", "A-1"] -x_grid_emsg = ( - "objects are not on the same x-grid. You may add them using the self.add method " - "and specifying how to handle the mismatch." +x_grid_length_mismatch_emsg = ( + "The two objects have different x-array lengths. " + "Please ensure the length of the x-value during initialization is identical." +) + +invalid_add_type_emsg = ( + "You may only add a DiffractionObject with another DiffractionObject or a scalar value. " + "Please rerun by adding another DiffractionObject instance or a scalar value. " + "e.g., my_do_1 + my_do_2 or my_do + 10" ) @@ -169,32 +175,44 @@ def __eq__(self, other): return True def __add__(self, other): - summed = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): - summed.on_tth[1] = self.on_tth[1] + other - summed.on_q[1] = self.on_q[1] + other - elif not isinstance(other, DiffractionObject): - raise TypeError("I only know how to sum two DiffractionObject objects") - elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_emsg) - else: - summed.on_tth[1] = self.on_tth[1] + other.on_tth[1] - summed.on_q[1] = self.on_q[1] + other.on_q[1] - return summed + """Add a scalar value or another DiffractionObject to the xarrays of + the DiffractionObject. - def __radd__(self, other): - summed = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): - summed.on_tth[1] = self.on_tth[1] + other - summed.on_q[1] = self.on_q[1] + other - elif not isinstance(other, DiffractionObject): - raise TypeError("I only know how to sum two Scattering_object objects") - elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_emsg) + Parameters + ---------- + other : DiffractionObject or int or float + The object to add to the current DiffractionObject. If `other` is a scalar value, + it will be added to all xarrays. The length of the xarrays must match if `other` is + an instance of DiffractionObject. + + Returns + ------- + DiffractionObject + The new and deep-copied DiffractionObject instance after adding values to the xarrays. + + Raises + ------ + ValueError + Raised when the length of the xarrays of the two DiffractionObject instances do not match. + TypeError + Raised when the type of `other` is not an instance of DiffractionObject, int, or float. + """ + summed_do = deepcopy(self) + # Add scalar value to all xarrays by broadcasting + if isinstance(other, (int, float)): + summed_do._all_arrays[:, 1] += other + summed_do._all_arrays[:, 2] += other + summed_do._all_arrays[:, 3] += other + # Add xarrays of two DiffractionObject instances + elif isinstance(other, DiffractionObject): + if len(self.on_tth()[0]) != len(other.on_tth()[0]): + raise ValueError(x_grid_length_mismatch_emsg) + summed_do._all_arrays[:, 1] += other.on_q()[0] + summed_do._all_arrays[:, 2] += other.on_tth()[0] + summed_do._all_arrays[:, 3] += other.on_d()[0] else: - summed.on_tth[1] = self.on_tth[1] + other.on_tth[1] - summed.on_q[1] = self.on_q[1] + other.on_q[1] - return summed + raise TypeError(invalid_add_type_emsg) + return summed_do def __sub__(self, other): subtracted = deepcopy(self) @@ -204,7 +222,7 @@ def __sub__(self, other): elif not isinstance(other, DiffractionObject): raise TypeError("I only know how to subtract two Scattering_object objects") elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_emsg) + raise RuntimeError(x_grid_length_mismatch_emsg) else: subtracted.on_tth[1] = self.on_tth[1] - other.on_tth[1] subtracted.on_q[1] = self.on_q[1] - other.on_q[1] @@ -218,7 +236,7 @@ def __rsub__(self, other): elif not isinstance(other, DiffractionObject): raise TypeError("I only know how to subtract two Scattering_object objects") elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_emsg) + raise RuntimeError(x_grid_length_mismatch_emsg) else: subtracted.on_tth[1] = other.on_tth[1] - self.on_tth[1] subtracted.on_q[1] = other.on_q[1] - self.on_q[1] @@ -232,19 +250,7 @@ def __mul__(self, other): elif not isinstance(other, DiffractionObject): raise TypeError("I only know how to multiply two Scattering_object objects") elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_emsg) - else: - multiplied.on_tth[1] = self.on_tth[1] * other.on_tth[1] - multiplied.on_q[1] = self.on_q[1] * other.on_q[1] - return multiplied - - def __rmul__(self, other): - multiplied = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): - multiplied.on_tth[1] = other * self.on_tth[1] - multiplied.on_q[1] = other * self.on_q[1] - elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_emsg) + raise RuntimeError(x_grid_length_mismatch_emsg) else: multiplied.on_tth[1] = self.on_tth[1] * other.on_tth[1] multiplied.on_q[1] = self.on_q[1] * other.on_q[1] @@ -258,7 +264,7 @@ def __truediv__(self, other): elif not isinstance(other, DiffractionObject): raise TypeError("I only know how to multiply two Scattering_object objects") elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_emsg) + raise RuntimeError(x_grid_length_mismatch_emsg) else: divided.on_tth[1] = self.on_tth[1] / other.on_tth[1] divided.on_q[1] = self.on_q[1] / other.on_q[1] @@ -270,7 +276,7 @@ def __rtruediv__(self, other): divided.on_tth[1] = other / self.on_tth[1] divided.on_q[1] = other / self.on_q[1] elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_emsg) + raise RuntimeError(x_grid_length_mismatch_emsg) else: divided.on_tth[1] = other.on_tth[1] / self.on_tth[1] divided.on_q[1] = other.on_q[1] / self.on_q[1] From 3d4841b75bd85767678cfdb19320bd8ada213558 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 27 Dec 2024 14:41:17 -0500 Subject: [PATCH 02/10] Add __eq__ news and test --- news/add-operations-tests.rst | 23 +++++++++++ tests/conftest.py | 17 ++++++++ tests/test_diffraction_objects.py | 65 +++++++++++++++++++++++++++++-- 3 files changed, 102 insertions(+), 3 deletions(-) create mode 100644 news/add-operations-tests.rst diff --git a/news/add-operations-tests.rst b/news/add-operations-tests.rst new file mode 100644 index 00000000..e59edbd5 --- /dev/null +++ b/news/add-operations-tests.rst @@ -0,0 +1,23 @@ +**Added:** + +* unit tests for __add__ operation for DiffractionObject + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/tests/conftest.py b/tests/conftest.py index 7f8de460..5eaaa902 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -63,3 +63,20 @@ def invalid_q_or_d_or_wavelength_error_msg(): "The supplied input array and wavelength will result in an impossible two-theta. " "Please check these values and re-instantiate the DiffractionObject with correct values." ) + + +@pytest.fixture +def invalid_add_type_error_msg(): + return ( + "You may only add a DiffractionObject with another DiffractionObject or a scalar value. " + "Please rerun by adding another DiffractionObject instance or a scalar value. " + "e.g., my_do_1 + my_do_2 or my_do + 10" + ) + + +@pytest.fixture +def x_grid_size_mismatch_error_msg(): + return ( + "The two objects have different x-array lengths. " + "Please ensure the length of the x-value during initialization is identical." + ) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 71271931..9675ad7f 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -155,7 +155,7 @@ def test_diffraction_objects_equality( @pytest.mark.parametrize( - "xtype, expected_xarray", + "xtype, expected_all_arrays", [ # Test whether on_xtype returns the correct xarray values. # C1: tth to tth, expect no change in xarray value @@ -169,10 +169,10 @@ def test_diffraction_objects_equality( ("d", np.array([12.13818, 6.28319])), ], ) -def test_on_xtype(xtype, expected_xarray, do_minimal_tth): +def test_on_xtype(xtype, expected_all_arrays, do_minimal_tth): do = do_minimal_tth actual_xrray, actual_yarray = do.on_xtype(xtype) - assert np.allclose(actual_xrray, expected_xarray) + assert np.allclose(actual_xrray, expected_all_arrays) assert np.allclose(actual_yarray, np.array([1, 2])) @@ -702,3 +702,62 @@ def test_copy_object(do_minimal): do_copy = do.copy() assert do == do_copy assert id(do) != id(do_copy) + + +@pytest.mark.parametrize( + "starting_all_arrays, scalar_value, expected_all_arrays", + [ + # Test scalar addition to xarray values (q, tth, d) and expect no change to yarray values + ( # C1: Add integer of 5, expect xarray to increase by by 5 + np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), + 5, + np.array([[1.0, 5.51763809, 35.0, 17.13818192], [2.0, 6.0, 65.0, 11.28318531]]), + ), + ( # C2: Add float of 5.1, expect xarray to be added by 5.1 + np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), + 5.1, + np.array([[1.0, 5.61763809, 35.1, 17.23818192], [2.0, 6.1, 65.1, 11.38318531]]), + ), + ], +) +def test_addition_operator_by_scalar(starting_all_arrays, scalar_value, expected_all_arrays, do_minimal_tth): + do = do_minimal_tth + assert np.allclose(do.all_arrays, starting_all_arrays) + do_sum = do + scalar_value + assert np.allclose(do_sum.all_arrays, expected_all_arrays) + + +@pytest.mark.parametrize( + "LHS_all_arrays, RHS_all_arrays, expected_all_arrays_sum", + [ + # Test addition of two DO objects, expect combined xarray values (q, tth, d) and no change to yarray + ( # C1: Add two DO objects with identical xarray values, expect sum of xarray values + (np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]),), + (np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]),), + np.array([[1.0, 1.03527618, 60.0, 24.27636384], [2.0, 2.0, 120.0, 12.56637061]]), + ), + ], +) +def test_addition_operator_by_another_do(LHS_all_arrays, RHS_all_arrays, expected_all_arrays_sum, do_minimal_tth): + assert np.allclose(do_minimal_tth.all_arrays, LHS_all_arrays) + do_LHS = do_minimal_tth + do_RHS = do_minimal_tth + do_sum = do_LHS + do_RHS + assert np.allclose(do_LHS.all_arrays, LHS_all_arrays) + assert np.allclose(do_RHS.all_arrays, RHS_all_arrays) + assert np.allclose(do_sum.all_arrays, expected_all_arrays_sum) + + +def test_addition_operator_invalid_type(do_minimal_tth, invalid_add_type_error_msg): + # Add a string to a DO object, expect TypeError, only scalar (int, float) allowed for addition + do_LHS = do_minimal_tth + with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): + do_LHS + "string_value" + + +def test_addition_operator_invalid_xarray_length(do_minimal, do_minimal_tth, x_grid_size_mismatch_error_msg): + # Combine two DO objects, one with empty xarrays (do_minimal) and the other with non-empty xarrays + do_LHS = do_minimal + do_RHS = do_minimal_tth + with pytest.raises(ValueError, match=re.escape(x_grid_size_mismatch_error_msg)): + do_LHS + do_RHS From 5d0ebcc62552af1df5953d2fa75fe0f7efdfaa34 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 27 Dec 2024 14:44:34 -0500 Subject: [PATCH 03/10] Add example to __eq__ --- src/diffpy/utils/diffraction_objects.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 316758d0..f2a0ce68 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -196,7 +196,16 @@ def __add__(self, other): Raised when the length of the xarrays of the two DiffractionObject instances do not match. TypeError Raised when the type of `other` is not an instance of DiffractionObject, int, or float. + + Examples + -------- + Add a scalar value to the xarrays of the DiffractionObject instance: + >>> new_do = my_do + 10.1 + + Add the xarrays of two DiffractionObject instances: + >>> new_do = my_do_1 + my_do_2 """ + summed_do = deepcopy(self) # Add scalar value to all xarrays by broadcasting if isinstance(other, (int, float)): From 9741a8ed56acc859770cb12e19d7d4d712cd6bb5 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 27 Dec 2024 14:46:08 -0500 Subject: [PATCH 04/10] Revert not needed change to naming in a test func --- tests/test_diffraction_objects.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 9675ad7f..0555dc37 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -155,7 +155,7 @@ def test_diffraction_objects_equality( @pytest.mark.parametrize( - "xtype, expected_all_arrays", + "xtype, expected_xarray", [ # Test whether on_xtype returns the correct xarray values. # C1: tth to tth, expect no change in xarray value @@ -169,10 +169,10 @@ def test_diffraction_objects_equality( ("d", np.array([12.13818, 6.28319])), ], ) -def test_on_xtype(xtype, expected_all_arrays, do_minimal_tth): +def test_on_xtype(xtype, expected_xarray, do_minimal_tth): do = do_minimal_tth actual_xrray, actual_yarray = do.on_xtype(xtype) - assert np.allclose(actual_xrray, expected_all_arrays) + assert np.allclose(actual_xrray, expected_xarray) assert np.allclose(actual_yarray, np.array([1, 2])) From 3f577d7e558cd6f346c01ea3c71c1063f0d91be5 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 27 Dec 2024 14:46:40 -0500 Subject: [PATCH 05/10] Apply pre-commit --- src/diffpy/utils/diffraction_objects.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index f2a0ce68..097f1d9d 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -196,16 +196,16 @@ def __add__(self, other): Raised when the length of the xarrays of the two DiffractionObject instances do not match. TypeError Raised when the type of `other` is not an instance of DiffractionObject, int, or float. - + Examples -------- Add a scalar value to the xarrays of the DiffractionObject instance: >>> new_do = my_do + 10.1 - + Add the xarrays of two DiffractionObject instances: >>> new_do = my_do_1 + my_do_2 """ - + summed_do = deepcopy(self) # Add scalar value to all xarrays by broadcasting if isinstance(other, (int, float)): From d56158344269410a37f36ea0e27c18778168eaec Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 27 Dec 2024 23:42:14 -0500 Subject: [PATCH 06/10] Add radd and rmul back and add tests --- src/diffpy/utils/diffraction_objects.py | 14 ++++++++++++++ tests/test_diffraction_objects.py | 18 +++++++++++------- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 097f1d9d..e741d94a 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -223,6 +223,8 @@ def __add__(self, other): raise TypeError(invalid_add_type_emsg) return summed_do + __radd__ = __add__ + def __sub__(self, other): subtracted = deepcopy(self) if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): @@ -265,6 +267,18 @@ def __mul__(self, other): multiplied.on_q[1] = self.on_q[1] * other.on_q[1] return multiplied + def __rmul__(self, other): + multiplied = deepcopy(self) + if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): + multiplied.on_tth[1] = other * self.on_tth[1] + multiplied.on_q[1] = other * self.on_q[1] + elif self.on_tth[0].all() != other.on_tth[0].all(): + raise RuntimeError(x_grid_length_mismatch_emsg) + else: + multiplied.on_tth[1] = self.on_tth[1] * other.on_tth[1] + multiplied.on_q[1] = self.on_q[1] * other.on_q[1] + return multiplied + def __truediv__(self, other): divided = deepcopy(self) if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 0555dc37..30dffb25 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -705,7 +705,7 @@ def test_copy_object(do_minimal): @pytest.mark.parametrize( - "starting_all_arrays, scalar_value, expected_all_arrays", + "starting_all_arrays, scalar_to_add, expected_all_arrays", [ # Test scalar addition to xarray values (q, tth, d) and expect no change to yarray values ( # C1: Add integer of 5, expect xarray to increase by by 5 @@ -720,11 +720,13 @@ def test_copy_object(do_minimal): ), ], ) -def test_addition_operator_by_scalar(starting_all_arrays, scalar_value, expected_all_arrays, do_minimal_tth): +def test_addition_operator_by_scalar(starting_all_arrays, scalar_to_add, expected_all_arrays, do_minimal_tth): do = do_minimal_tth assert np.allclose(do.all_arrays, starting_all_arrays) - do_sum = do + scalar_value - assert np.allclose(do_sum.all_arrays, expected_all_arrays) + do_sum_RHS = do + scalar_to_add + do_sum_LHS = scalar_to_add + do + assert np.allclose(do_sum_RHS.all_arrays, expected_all_arrays) + assert np.allclose(do_sum_LHS.all_arrays, expected_all_arrays) @pytest.mark.parametrize( @@ -750,10 +752,12 @@ def test_addition_operator_by_another_do(LHS_all_arrays, RHS_all_arrays, expecte def test_addition_operator_invalid_type(do_minimal_tth, invalid_add_type_error_msg): # Add a string to a DO object, expect TypeError, only scalar (int, float) allowed for addition - do_LHS = do_minimal_tth + do = do_minimal_tth with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): - do_LHS + "string_value" - + do + "string_value" + with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): + "string_value" + do + def test_addition_operator_invalid_xarray_length(do_minimal, do_minimal_tth, x_grid_size_mismatch_error_msg): # Combine two DO objects, one with empty xarrays (do_minimal) and the other with non-empty xarrays From ac5a2f3b608b53128a6944bc2919ca902d6d3849 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 28 Dec 2024 04:42:24 +0000 Subject: [PATCH 07/10] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/test_diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 30dffb25..0f4cc08e 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -757,7 +757,7 @@ def test_addition_operator_invalid_type(do_minimal_tth, invalid_add_type_error_m do + "string_value" with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): "string_value" + do - + def test_addition_operator_invalid_xarray_length(do_minimal, do_minimal_tth, x_grid_size_mismatch_error_msg): # Combine two DO objects, one with empty xarrays (do_minimal) and the other with non-empty xarrays From 11c41669b5c5cc7fc1d8340ac0011dfdce6f0782 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 28 Dec 2024 23:57:16 -0500 Subject: [PATCH 08/10] refactor: add yarrays together instead of xarrays for __add__ --- src/diffpy/utils/diffraction_objects.py | 61 +++++++++++++------------ 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index e741d94a..25e09381 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -14,15 +14,15 @@ XQUANTITIES = ANGLEQUANTITIES + DQUANTITIES + QQUANTITIES XUNITS = ["degrees", "radians", "rad", "deg", "inv_angs", "inv_nm", "nm-1", "A-1"] -x_grid_length_mismatch_emsg = ( - "The two objects have different x-array lengths. " - "Please ensure the length of the x-value during initialization is identical." +y_grid_length_mismatch_emsg = ( + "The two objects have different y-array lengths. " + "Please ensure the length of the y-value during initialization is identical." ) invalid_add_type_emsg = ( "You may only add a DiffractionObject with another DiffractionObject or a scalar value. " "Please rerun by adding another DiffractionObject instance or a scalar value. " - "e.g., my_do_1 + my_do_2 or my_do + 10" + "e.g., my_do_1 + my_do_2 or my_do + 10 or 10 + my_do" ) @@ -175,56 +175,57 @@ def __eq__(self, other): return True def __add__(self, other): - """Add a scalar value or another DiffractionObject to the xarrays of - the DiffractionObject. + """Add a scalar value or another DiffractionObject to the yarray of the + DiffractionObject. Parameters ---------- other : DiffractionObject or int or float The object to add to the current DiffractionObject. If `other` is a scalar value, - it will be added to all xarrays. The length of the xarrays must match if `other` is + it will be added to all yarray. The length of the yarray must match if `other` is an instance of DiffractionObject. Returns ------- DiffractionObject - The new and deep-copied DiffractionObject instance after adding values to the xarrays. + The new and deep-copied DiffractionObject instance after adding values to the yarray. Raises ------ ValueError - Raised when the length of the xarrays of the two DiffractionObject instances do not match. + Raised when the length of the yarray of the two DiffractionObject instances do not match. TypeError Raised when the type of `other` is not an instance of DiffractionObject, int, or float. Examples -------- - Add a scalar value to the xarrays of the DiffractionObject instance: + Add a scalar value to the yarray of the DiffractionObject instance: >>> new_do = my_do + 10.1 + >>> new_do = 10.1 + my_do - Add the xarrays of two DiffractionObject instances: + Add the yarray of two DiffractionObject instances: >>> new_do = my_do_1 + my_do_2 """ + self._check_operation_compatibility(other) summed_do = deepcopy(self) - # Add scalar value to all xarrays by broadcasting if isinstance(other, (int, float)): - summed_do._all_arrays[:, 1] += other - summed_do._all_arrays[:, 2] += other - summed_do._all_arrays[:, 3] += other - # Add xarrays of two DiffractionObject instances - elif isinstance(other, DiffractionObject): - if len(self.on_tth()[0]) != len(other.on_tth()[0]): - raise ValueError(x_grid_length_mismatch_emsg) - summed_do._all_arrays[:, 1] += other.on_q()[0] - summed_do._all_arrays[:, 2] += other.on_tth()[0] - summed_do._all_arrays[:, 3] += other.on_d()[0] - else: - raise TypeError(invalid_add_type_emsg) + summed_do._all_arrays[:, 0] += other + if isinstance(other, DiffractionObject): + summed_do._all_arrays[:, 0] += other.all_arrays[:, 0] return summed_do __radd__ = __add__ + def _check_operation_compatibility(self, other): + if not isinstance(other, (DiffractionObject, int, float)): + raise TypeError(invalid_add_type_emsg) + if isinstance(other, DiffractionObject): + self_yarray = self.all_arrays[:, 0] + other_yarray = other.all_arrays[:, 0] + if len(self_yarray) != len(other_yarray): + raise ValueError(y_grid_length_mismatch_emsg) + def __sub__(self, other): subtracted = deepcopy(self) if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): @@ -233,7 +234,7 @@ def __sub__(self, other): elif not isinstance(other, DiffractionObject): raise TypeError("I only know how to subtract two Scattering_object objects") elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_length_mismatch_emsg) + raise RuntimeError(y_grid_length_mismatch_emsg) else: subtracted.on_tth[1] = self.on_tth[1] - other.on_tth[1] subtracted.on_q[1] = self.on_q[1] - other.on_q[1] @@ -247,7 +248,7 @@ def __rsub__(self, other): elif not isinstance(other, DiffractionObject): raise TypeError("I only know how to subtract two Scattering_object objects") elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_length_mismatch_emsg) + raise RuntimeError(y_grid_length_mismatch_emsg) else: subtracted.on_tth[1] = other.on_tth[1] - self.on_tth[1] subtracted.on_q[1] = other.on_q[1] - self.on_q[1] @@ -261,7 +262,7 @@ def __mul__(self, other): elif not isinstance(other, DiffractionObject): raise TypeError("I only know how to multiply two Scattering_object objects") elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_length_mismatch_emsg) + raise RuntimeError(y_grid_length_mismatch_emsg) else: multiplied.on_tth[1] = self.on_tth[1] * other.on_tth[1] multiplied.on_q[1] = self.on_q[1] * other.on_q[1] @@ -273,7 +274,7 @@ def __rmul__(self, other): multiplied.on_tth[1] = other * self.on_tth[1] multiplied.on_q[1] = other * self.on_q[1] elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_length_mismatch_emsg) + raise RuntimeError(y_grid_length_mismatch_emsg) else: multiplied.on_tth[1] = self.on_tth[1] * other.on_tth[1] multiplied.on_q[1] = self.on_q[1] * other.on_q[1] @@ -287,7 +288,7 @@ def __truediv__(self, other): elif not isinstance(other, DiffractionObject): raise TypeError("I only know how to multiply two Scattering_object objects") elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_length_mismatch_emsg) + raise RuntimeError(y_grid_length_mismatch_emsg) else: divided.on_tth[1] = self.on_tth[1] / other.on_tth[1] divided.on_q[1] = self.on_q[1] / other.on_q[1] @@ -299,7 +300,7 @@ def __rtruediv__(self, other): divided.on_tth[1] = other / self.on_tth[1] divided.on_q[1] = other / self.on_q[1] elif self.on_tth[0].all() != other.on_tth[0].all(): - raise RuntimeError(x_grid_length_mismatch_emsg) + raise RuntimeError(y_grid_length_mismatch_emsg) else: divided.on_tth[1] = other.on_tth[1] / self.on_tth[1] divided.on_q[1] = other.on_q[1] / self.on_q[1] From 846c72ae76f29426ae009ed257c17226484a6a8d Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 28 Dec 2024 23:57:55 -0500 Subject: [PATCH 09/10] test: combine do with another do or scalar value tests --- tests/conftest.py | 14 +++++-- tests/test_diffraction_objects.py | 64 ++++++++++++++++++------------- 2 files changed, 48 insertions(+), 30 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 5eaaa902..9e5f1e60 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -47,6 +47,12 @@ def do_minimal_tth(): return DiffractionObject(wavelength=2 * np.pi, xarray=np.array([30, 60]), yarray=np.array([1, 2]), xtype="tth") +@pytest.fixture +def do_minimal_d(): + # Create an instance of DiffractionObject with non-empty xarray, yarray, and wavelength values + return DiffractionObject(wavelength=1.54, xarray=np.array([1, 2]), yarray=np.array([1, 2]), xtype="d") + + @pytest.fixture def wavelength_warning_msg(): return ( @@ -70,13 +76,13 @@ def invalid_add_type_error_msg(): return ( "You may only add a DiffractionObject with another DiffractionObject or a scalar value. " "Please rerun by adding another DiffractionObject instance or a scalar value. " - "e.g., my_do_1 + my_do_2 or my_do + 10" + "e.g., my_do_1 + my_do_2 or my_do + 10 or 10 + my_do" ) @pytest.fixture -def x_grid_size_mismatch_error_msg(): +def y_grid_size_mismatch_error_msg(): return ( - "The two objects have different x-array lengths. " - "Please ensure the length of the x-value during initialization is identical." + "The two objects have different y-array lengths. " + "Please ensure the length of the y-value during initialization is identical." ) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index ab7eaf26..649de01b 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -715,47 +715,57 @@ def test_copy_object(do_minimal): @pytest.mark.parametrize( "starting_all_arrays, scalar_to_add, expected_all_arrays", [ - # Test scalar addition to xarray values (q, tth, d) and expect no change to yarray values - ( # C1: Add integer of 5, expect xarray to increase by by 5 + # Test scalar addition to yarray values (intensity) and expect no change to xarrays (q, tth, d) + ( # C1: Add integer of 5, expect yarray to increase by by 5 np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), 5, - np.array([[1.0, 5.51763809, 35.0, 17.13818192], [2.0, 6.0, 65.0, 11.28318531]]), + np.array([[6.0, 0.51763809, 30.0, 12.13818192], [7.0, 1.0, 60.0, 6.28318531]]), ), - ( # C2: Add float of 5.1, expect xarray to be added by 5.1 + ( # C2: Add float of 5.1, expect yarray to be added by 5.1 np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), 5.1, - np.array([[1.0, 5.61763809, 35.1, 17.23818192], [2.0, 6.1, 65.1, 11.38318531]]), + np.array([[6.1, 0.51763809, 30.0, 12.13818192], [7.1, 1.0, 60.0, 6.28318531]]), ), ], ) def test_addition_operator_by_scalar(starting_all_arrays, scalar_to_add, expected_all_arrays, do_minimal_tth): do = do_minimal_tth assert np.allclose(do.all_arrays, starting_all_arrays) - do_sum_RHS = do + scalar_to_add - do_sum_LHS = scalar_to_add + do - assert np.allclose(do_sum_RHS.all_arrays, expected_all_arrays) - assert np.allclose(do_sum_LHS.all_arrays, expected_all_arrays) + do_scalar_right_sum = do + scalar_to_add + assert np.allclose(do_scalar_right_sum.all_arrays, expected_all_arrays) + do_scalar_left_sum = scalar_to_add + do + assert np.allclose(do_scalar_left_sum.all_arrays, expected_all_arrays) @pytest.mark.parametrize( - "LHS_all_arrays, RHS_all_arrays, expected_all_arrays_sum", + "do_1_all_arrays, " + "do_2_all_arrays, " + "expected_do_1_all_arrays_with_y_summed, " + "expected_do_2_all_arrays_with_y_summed", [ # Test addition of two DO objects, expect combined xarray values (q, tth, d) and no change to yarray - ( # C1: Add two DO objects with identical xarray values, expect sum of xarray values + ( # C1: Add two DO objects, expect sum of yarray values (np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]),), - (np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]),), - np.array([[1.0, 1.03527618, 60.0, 24.27636384], [2.0, 2.0, 120.0, 12.56637061]]), + (np.array([[1.0, 6.28318531, 100.70777771, 1], [2.0, 3.14159265, 45.28748053, 2.0]]),), + (np.array([[2.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]),), + (np.array([[2.0, 6.28318531, 100.70777771, 1], [4.0, 3.14159265, 45.28748053, 2.0]]),), ), ], ) -def test_addition_operator_by_another_do(LHS_all_arrays, RHS_all_arrays, expected_all_arrays_sum, do_minimal_tth): - assert np.allclose(do_minimal_tth.all_arrays, LHS_all_arrays) - do_LHS = do_minimal_tth - do_RHS = do_minimal_tth - do_sum = do_LHS + do_RHS - assert np.allclose(do_LHS.all_arrays, LHS_all_arrays) - assert np.allclose(do_RHS.all_arrays, RHS_all_arrays) - assert np.allclose(do_sum.all_arrays, expected_all_arrays_sum) +def test_addition_operator_by_another_do( + do_1_all_arrays, + do_2_all_arrays, + expected_do_1_all_arrays_with_y_summed, + expected_do_2_all_arrays_with_y_summed, + do_minimal_tth, + do_minimal_d, +): + do_1 = do_minimal_tth + assert np.allclose(do_1.all_arrays, do_1_all_arrays) + do_2 = do_minimal_d + assert np.allclose(do_2.all_arrays, do_2_all_arrays) + assert np.allclose((do_1 + do_2).all_arrays, expected_do_1_all_arrays_with_y_summed) + assert np.allclose((do_2 + do_1).all_arrays, expected_do_2_all_arrays_with_y_summed) def test_addition_operator_invalid_type(do_minimal_tth, invalid_add_type_error_msg): @@ -767,9 +777,11 @@ def test_addition_operator_invalid_type(do_minimal_tth, invalid_add_type_error_m "string_value" + do -def test_addition_operator_invalid_xarray_length(do_minimal, do_minimal_tth, x_grid_size_mismatch_error_msg): +def test_addition_operator_invalid_yarray_length(do_minimal, do_minimal_tth, y_grid_size_mismatch_error_msg): # Combine two DO objects, one with empty xarrays (do_minimal) and the other with non-empty xarrays - do_LHS = do_minimal - do_RHS = do_minimal_tth - with pytest.raises(ValueError, match=re.escape(x_grid_size_mismatch_error_msg)): - do_LHS + do_RHS + do_1 = do_minimal + do_2 = do_minimal_tth + assert len(do_1.all_arrays[:, 0]) == 0 + assert len(do_2.all_arrays[:, 0]) == 2 + with pytest.raises(ValueError, match=re.escape(y_grid_size_mismatch_error_msg)): + do_1 + do_2 From da70bd6629ab7589bd628feca625f106c5b515a0 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 29 Dec 2024 01:00:04 -0500 Subject: [PATCH 10/10] test: changed xarray to yarray in one of the test cases --- tests/test_diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 649de01b..ccab71fe 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -743,7 +743,7 @@ def test_addition_operator_by_scalar(starting_all_arrays, scalar_to_add, expecte "expected_do_1_all_arrays_with_y_summed, " "expected_do_2_all_arrays_with_y_summed", [ - # Test addition of two DO objects, expect combined xarray values (q, tth, d) and no change to yarray + # Test addition of two DO objects, expect combined yarray values and no change to xarrays ((q, tth, d) ( # C1: Add two DO objects, expect sum of yarray values (np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]),), (np.array([[1.0, 6.28318531, 100.70777771, 1], [2.0, 3.14159265, 45.28748053, 2.0]]),),