From 630f00e7cd1898eab370f1a79006a65acc188936 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 29 Dec 2024 18:11:53 -0500 Subject: [PATCH 1/8] feat: implement __sub__ feature for DiffractionObject --- src/diffpy/utils/diffraction_objects.py | 76 ++++++-------------- tests/test_diffraction_objects.py | 96 +++++++++++++++++-------- 2 files changed, 88 insertions(+), 84 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 25e09381..58393ef4 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -217,42 +217,16 @@ def __add__(self, other): __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): - subtracted.on_tth[1] = self.on_tth[1] - other - subtracted.on_q[1] = self.on_q[1] - 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(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] - return subtracted + self._check_operation_compatibility(other) + subtracted_do = deepcopy(self) + if isinstance(other, (int, float)): + subtracted_do._all_arrays[:, 0] -= other + if isinstance(other, DiffractionObject): + subtracted_do._all_arrays[:, 0] -= other.all_arrays[:, 0] + return subtracted_do - def __rsub__(self, other): - subtracted = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): - subtracted.on_tth[1] = other - self.on_tth[1] - subtracted.on_q[1] = other - self.on_q[1] - 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(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] - return subtracted + __rsub__ = __sub__ def __mul__(self, other): multiplied = deepcopy(self) @@ -268,19 +242,10 @@ 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(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] - return multiplied + __rmul__ = __mul__ def __truediv__(self, other): + divided = deepcopy(self) if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): divided.on_tth[1] = other / self.on_tth[1] @@ -294,17 +259,16 @@ def __truediv__(self, other): divided.on_q[1] = self.on_q[1] / other.on_q[1] return divided - def __rtruediv__(self, other): - divided = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): - 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(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] - return divided + __rmul__ = __mul__ + + 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 self_yarray.shape != other_yarray.shape: + raise ValueError(y_grid_length_mismatch_emsg) @property def all_arrays(self): diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index ccab71fe..979a7efe 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -713,59 +713,93 @@ def test_copy_object(do_minimal): @pytest.mark.parametrize( - "starting_all_arrays, scalar_to_add, expected_all_arrays", + "operation, starting_all_arrays, scalar_value, expected_all_arrays", [ - # 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 + # C1: Test scalar addition to yarray values (intensity), expect no change to xarrays (q, tth, d) + ( # 1. Add integer 5 + "add", np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), 5, 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 yarray to be added by 5.1 + ( # 2. Add float 5.1 + "add", np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), 5.1, np.array([[6.1, 0.51763809, 30.0, 12.13818192], [7.1, 1.0, 60.0, 6.28318531]]), ), + # C2. Test scalar subtraction to yarray values (intensity), expect no change to xarrays (q, tth, d) + ( # 1. Subtract integer 1 + "sub", + np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), + 1, + np.array([[0.0, 0.51763809, 30.0, 12.13818192], [1.0, 1.0, 60.0, 6.28318531]]), + ), + ( # 2. Subtract float 0.5 + "sub", + np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), + 0.5, + np.array([[0.5, 0.51763809, 30.0, 12.13818192], [1.5, 1.0, 60.0, 6.28318531]]), + ), ], ) -def test_addition_operator_by_scalar(starting_all_arrays, scalar_to_add, expected_all_arrays, do_minimal_tth): +def test_scalar_operations(operation, 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_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) + + if operation == "add": + result_right = do + scalar_value + result_left = scalar_value + do + elif operation == "sub": + result_right = do - scalar_value + result_left = scalar_value - do + + assert np.allclose(result_right.all_arrays, expected_all_arrays) + assert np.allclose(result_left.all_arrays, expected_all_arrays) @pytest.mark.parametrize( - "do_1_all_arrays, " - "do_2_all_arrays, " - "expected_do_1_all_arrays_with_y_summed, " - "expected_do_2_all_arrays_with_y_summed", + "operation, " "expected_do_1_all_arrays_with_y_modified, " "expected_do_2_all_arrays_with_y_modified", [ - # 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]]),), - (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]]),), + # Test addition of two DO objects, expect combined yarray values + ( + "add", + 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]]), + ), + # Test subtraction of two DO objects, expect differences in yarray values + ( + "sub", + np.array([[0.0, 0.51763809, 30.0, 12.13818192], [0.0, 1.0, 60.0, 6.28318531]]), + np.array([[0.0, 6.28318531, 100.70777771, 1], [0.0, 3.14159265, 45.28748053, 2.0]]), ), ], ) -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, +def test_binary_operator_on_do( + operation, + expected_do_1_all_arrays_with_y_modified, + expected_do_2_all_arrays_with_y_modified, 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) + assert np.allclose( + do_1.all_arrays, np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]) + ) + assert np.allclose( + do_2.all_arrays, np.array([[1.0, 6.28318531, 100.70777771, 1], [2.0, 3.14159265, 45.28748053, 2.0]]) + ) + + if operation == "add": + do_1_y_modified = do_1 + do_2 + do_2_y_modified = do_2 + do_1 + elif operation == "sub": + do_1_y_modified = do_1 - do_2 + do_2_y_modified = do_2 - do_1 + + assert np.allclose(do_1_y_modified.all_arrays, expected_do_1_all_arrays_with_y_modified) + assert np.allclose(do_2_y_modified.all_arrays, expected_do_2_all_arrays_with_y_modified) def test_addition_operator_invalid_type(do_minimal_tth, invalid_add_type_error_msg): @@ -775,6 +809,10 @@ 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 + with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): + do - "string_value" + with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): + "string_value" - do def test_addition_operator_invalid_yarray_length(do_minimal, do_minimal_tth, y_grid_size_mismatch_error_msg): @@ -785,3 +823,5 @@ def test_addition_operator_invalid_yarray_length(do_minimal, do_minimal_tth, y_g 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 + with pytest.raises(ValueError, match=re.escape(y_grid_size_mismatch_error_msg)): + do_1 - do_2 From ddeeb266a7b9e60e0e5b40e1059b43cab3d0d954 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 29 Dec 2024 18:17:43 -0500 Subject: [PATCH 2/8] feat: implement __mul__ for DiffracitonObject wih test funcs --- src/diffpy/utils/diffraction_objects.py | 19 +++++++------------ tests/test_diffraction_objects.py | 25 ++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 58393ef4..1a88a147 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -229,18 +229,13 @@ def __sub__(self, other): __rsub__ = __sub__ def __mul__(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 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(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] - return multiplied + self._check_operation_compatibility(other) + multiplied_do = deepcopy(self) + if isinstance(other, (int, float)): + multiplied_do._all_arrays[:, 0] *= other + if isinstance(other, DiffractionObject): + multiplied_do._all_arrays[:, 0] *= other.all_arrays[:, 0] + return multiplied_do __rmul__ = __mul__ diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 979a7efe..a21ed57d 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -741,18 +741,33 @@ def test_copy_object(do_minimal): 0.5, np.array([[0.5, 0.51763809, 30.0, 12.13818192], [1.5, 1.0, 60.0, 6.28318531]]), ), + # C2. Test scalar multiplication to yarray values (intensity), expect no change to xarrays (q, tth, d) + ( # 1. Multipliy by integer 2 + "mul", + np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), + 2, + np.array([[2.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]), + ), + ( # 2. Multipliy by float 0.5 + "mul", + np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), + 2.5, + np.array([[2.5, 0.51763809, 30.0, 12.13818192], [5.0, 1.0, 60.0, 6.28318531]]), + ), ], ) def test_scalar_operations(operation, starting_all_arrays, scalar_value, expected_all_arrays, do_minimal_tth): do = do_minimal_tth assert np.allclose(do.all_arrays, starting_all_arrays) - if operation == "add": result_right = do + scalar_value result_left = scalar_value + do elif operation == "sub": result_right = do - scalar_value result_left = scalar_value - do + elif operation == "mul": + result_right = do * scalar_value + result_left = scalar_value * do assert np.allclose(result_right.all_arrays, expected_all_arrays) assert np.allclose(result_left.all_arrays, expected_all_arrays) @@ -773,6 +788,11 @@ def test_scalar_operations(operation, starting_all_arrays, scalar_value, expecte np.array([[0.0, 0.51763809, 30.0, 12.13818192], [0.0, 1.0, 60.0, 6.28318531]]), np.array([[0.0, 6.28318531, 100.70777771, 1], [0.0, 3.14159265, 45.28748053, 2.0]]), ), + ( + "mul", + np.array([[1.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]), + np.array([[1.0, 6.28318531, 100.70777771, 1], [4.0, 3.14159265, 45.28748053, 2.0]]), + ), ], ) def test_binary_operator_on_do( @@ -797,6 +817,9 @@ def test_binary_operator_on_do( elif operation == "sub": do_1_y_modified = do_1 - do_2 do_2_y_modified = do_2 - do_1 + elif operation == "mul": + do_1_y_modified = do_1 * do_2 + do_2_y_modified = do_2 * do_1 assert np.allclose(do_1_y_modified.all_arrays, expected_do_1_all_arrays_with_y_modified) assert np.allclose(do_2_y_modified.all_arrays, expected_do_2_all_arrays_with_y_modified) From eb2941b4e0613aabbb7d182d80b47602796ab952 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 29 Dec 2024 18:42:53 -0500 Subject: [PATCH 3/8] feat: add __div__ function to DiffractionObject --- src/diffpy/utils/diffraction_objects.py | 22 ++-- tests/test_diffraction_objects.py | 139 +++++++++++++++--------- 2 files changed, 97 insertions(+), 64 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 1a88a147..477b1f2c 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -240,21 +240,15 @@ def __mul__(self, other): __rmul__ = __mul__ def __truediv__(self, other): + self._check_operation_compatibility(other) + divided_do = deepcopy(self) + if isinstance(other, (int, float)): + divided_do._all_arrays[:, 0] /= other + if isinstance(other, DiffractionObject): + divided_do._all_arrays[:, 0] /= other.all_arrays[:, 0] + return divided_do - divided = deepcopy(self) - if isinstance(other, int) or isinstance(other, float) or isinstance(other, np.ndarray): - divided.on_tth[1] = other / self.on_tth[1] - divided.on_q[1] = other / self.on_q[1] - 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(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] - return divided - - __rmul__ = __mul__ + __rtruediv__ = __truediv__ def _check_operation_compatibility(self, other): if not isinstance(other, (DiffractionObject, int, float)): diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index a21ed57d..6773aced 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -713,64 +713,84 @@ def test_copy_object(do_minimal): @pytest.mark.parametrize( - "operation, starting_all_arrays, scalar_value, expected_all_arrays", + "operation, starting_yarray, scalar_value, expected_yarray", [ - # C1: Test scalar addition to yarray values (intensity), expect no change to xarrays (q, tth, d) - ( # 1. Add integer 5 + # C1: Test scalar addition to y-values (intensity), expect no change to x-values (q, tth, d) + ( # 1. Add 5 "add", - np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), + np.array([1.0, 2.0]), 5, - np.array([[6.0, 0.51763809, 30.0, 12.13818192], [7.0, 1.0, 60.0, 6.28318531]]), + np.array([6.0, 7.0]), ), - ( # 2. Add float 5.1 + ( # 2. Add 5.1 "add", - np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), + np.array([1.0, 2.0]), 5.1, - np.array([[6.1, 0.51763809, 30.0, 12.13818192], [7.1, 1.0, 60.0, 6.28318531]]), + np.array([6.1, 7.1]), ), - # C2. Test scalar subtraction to yarray values (intensity), expect no change to xarrays (q, tth, d) - ( # 1. Subtract integer 1 + # C2: Test scalar subtraction to y-values (intensity), expect no change to x-values (q, tth, d) + ( # 1. Subtract 1 "sub", - np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), + np.array([1.0, 2.0]), 1, - np.array([[0.0, 0.51763809, 30.0, 12.13818192], [1.0, 1.0, 60.0, 6.28318531]]), + np.array([0.0, 1.0]), ), - ( # 2. Subtract float 0.5 + ( # 2. Subtract 0.5 "sub", - np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), + np.array([1.0, 2.0]), 0.5, - np.array([[0.5, 0.51763809, 30.0, 12.13818192], [1.5, 1.0, 60.0, 6.28318531]]), + np.array([0.5, 1.5]), ), - # C2. Test scalar multiplication to yarray values (intensity), expect no change to xarrays (q, tth, d) - ( # 1. Multipliy by integer 2 + # C3: Test scalar multiplication to y-values (intensity), expect no change to x-values (q, tth, d) + ( # 1. Multiply by 2 "mul", - np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), + np.array([1.0, 2.0]), 2, - np.array([[2.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]), + np.array([2.0, 4.0]), ), - ( # 2. Multipliy by float 0.5 + ( # 2. Multiply by 2.5 "mul", - np.array([[1.0, 0.51763809, 30.0, 12.13818192], [2.0, 1.0, 60.0, 6.28318531]]), + np.array([1.0, 2.0]), 2.5, - np.array([[2.5, 0.51763809, 30.0, 12.13818192], [5.0, 1.0, 60.0, 6.28318531]]), + np.array([2.5, 5.0]), + ), + # C4: Test scalar division to y-values (intensity), expect no change to x-values (q, tth, d) + ( # 1. Divide by 2 + "div", + np.array([1.0, 2.0]), + 2, + np.array([0.5, 1.0]), + ), + ( # 2. Divide by 2.5 + "div", + np.array([1.0, 2.0]), + 2.5, + np.array([0.4, 0.8]), ), ], ) -def test_scalar_operations(operation, starting_all_arrays, scalar_value, expected_all_arrays, do_minimal_tth): +def test_scalar_operations(operation, starting_yarray, scalar_value, expected_yarray, do_minimal_tth): do = do_minimal_tth - assert np.allclose(do.all_arrays, starting_all_arrays) + expected_xarray_constant = np.array([[0.51763809, 30.0, 12.13818192], [1.0, 60.0, 6.28318531]]) + assert np.allclose(do.all_arrays[:, [1, 2, 3]], expected_xarray_constant) + assert np.allclose(do.all_arrays[:, 0], starting_yarray) if operation == "add": - result_right = do + scalar_value - result_left = scalar_value + do + do_right_op = do + scalar_value + do_left_op = scalar_value + do elif operation == "sub": - result_right = do - scalar_value - result_left = scalar_value - do + do_right_op = do - scalar_value + do_left_op = scalar_value - do elif operation == "mul": - result_right = do * scalar_value - result_left = scalar_value * do - - assert np.allclose(result_right.all_arrays, expected_all_arrays) - assert np.allclose(result_left.all_arrays, expected_all_arrays) + do_right_op = do * scalar_value + do_left_op = scalar_value * do + elif operation == "div": + do_right_op = do / scalar_value + do_left_op = scalar_value / do + assert np.allclose(do_right_op.all_arrays[:, 0], expected_yarray) + assert np.allclose(do_left_op.all_arrays[:, 0], expected_yarray) + # Ensure x-values are unchanged + assert np.allclose(do_right_op.all_arrays[:, [1, 2, 3]], expected_xarray_constant) + assert np.allclose(do_left_op.all_arrays[:, [1, 2, 3]], expected_xarray_constant) @pytest.mark.parametrize( @@ -793,6 +813,11 @@ def test_scalar_operations(operation, starting_all_arrays, scalar_value, expecte np.array([[1.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]), np.array([[1.0, 6.28318531, 100.70777771, 1], [4.0, 3.14159265, 45.28748053, 2.0]]), ), + ( + "div", + np.array([[1.0, 0.51763809, 30.0, 12.13818192], [1.0, 1.0, 60.0, 6.28318531]]), + np.array([[1.0, 6.28318531, 100.70777771, 1], [1.0, 3.14159265, 45.28748053, 2.0]]), + ), ], ) def test_binary_operator_on_do( @@ -820,31 +845,45 @@ def test_binary_operator_on_do( elif operation == "mul": do_1_y_modified = do_1 * do_2 do_2_y_modified = do_2 * do_1 + elif operation == "div": + do_1_y_modified = do_1 / do_2 + do_2_y_modified = do_2 / do_1 assert np.allclose(do_1_y_modified.all_arrays, expected_do_1_all_arrays_with_y_modified) assert np.allclose(do_2_y_modified.all_arrays, expected_do_2_all_arrays_with_y_modified) -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 +def test_operator_invalid_type(do_minimal_tth, invalid_add_type_error_msg): do = do_minimal_tth - with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): - do + "string_value" - with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): - "string_value" + do - with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): - do - "string_value" - with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): - "string_value" - do - - -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 + invalid_value = "string_value" + + operations = [ + (lambda x, y: x + y), # Test addition + (lambda x, y: x - y), # Test subtraction + (lambda x, y: x * y), # Test multiplication + (lambda x, y: x / y), # Test division + ] + + # Test each operation with both orderings of operands + for operation in operations: + with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): + operation(do, invalid_value) + with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): + operation(invalid_value, do) + + +@pytest.mark.parametrize("operation", ["add", "sub", "mul", "div"]) +def test_operator_invalid_yarray_length(operation, do_minimal, do_minimal_tth, y_grid_size_mismatch_error_msg): 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 - with pytest.raises(ValueError, match=re.escape(y_grid_size_mismatch_error_msg)): - do_1 - do_2 + if operation == "add": + do_1 + do_2 + elif operation == "sub": + do_1 - do_2 + elif operation == "mul": + do_1 * do_2 + elif operation == "div": + do_1 / do_2 From 48e4c5883a4d50782c1b0bdb6f22c6eb67ae04ee Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 29 Dec 2024 18:45:20 -0500 Subject: [PATCH 4/8] chore: add news --- news/op-mul-sub-div.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/op-mul-sub-div.rst diff --git a/news/op-mul-sub-div.rst b/news/op-mul-sub-div.rst new file mode 100644 index 00000000..9fe75ccc --- /dev/null +++ b/news/op-mul-sub-div.rst @@ -0,0 +1,23 @@ +**Added:** + +* addition, multiplication, subtraction, and division operators between two DiffractionObject instances or a scalar value with another DiffractionObject for modifying yarray (intensity) values. + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From e8653efc5613e319e30478aa7acd0c13b7de9cec Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 29 Dec 2024 18:49:40 -0500 Subject: [PATCH 5/8] test: improve comments for tests --- tests/test_diffraction_objects.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 6773aced..6c390e16 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -796,24 +796,23 @@ def test_scalar_operations(operation, starting_yarray, scalar_value, expected_ya @pytest.mark.parametrize( "operation, " "expected_do_1_all_arrays_with_y_modified, " "expected_do_2_all_arrays_with_y_modified", [ - # Test addition of two DO objects, expect combined yarray values - ( + ( # Test addition of two DO objects, expect combined yarray values "add", 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]]), ), - # Test subtraction of two DO objects, expect differences in yarray values - ( + ( # Test subtraction of two DO objects, expect differences in yarray values "sub", np.array([[0.0, 0.51763809, 30.0, 12.13818192], [0.0, 1.0, 60.0, 6.28318531]]), np.array([[0.0, 6.28318531, 100.70777771, 1], [0.0, 3.14159265, 45.28748053, 2.0]]), ), - ( + ( # Test multiplication of two DO objects, expect multiplication in yarray values + "mul", np.array([[1.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]), np.array([[1.0, 6.28318531, 100.70777771, 1], [4.0, 3.14159265, 45.28748053, 2.0]]), ), - ( + ( # Test division of two DO objects, expect division in yarray values "div", np.array([[1.0, 0.51763809, 30.0, 12.13818192], [1.0, 1.0, 60.0, 6.28318531]]), np.array([[1.0, 6.28318531, 100.70777771, 1], [1.0, 3.14159265, 45.28748053, 2.0]]), @@ -856,15 +855,13 @@ def test_binary_operator_on_do( def test_operator_invalid_type(do_minimal_tth, invalid_add_type_error_msg): do = do_minimal_tth invalid_value = "string_value" - operations = [ (lambda x, y: x + y), # Test addition (lambda x, y: x - y), # Test subtraction (lambda x, y: x * y), # Test multiplication (lambda x, y: x / y), # Test division ] - - # Test each operation with both orderings of operands + # Add a string to a DiffractionObject, expect TypeError for operation in operations: with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): operation(do, invalid_value) @@ -878,6 +875,7 @@ def test_operator_invalid_yarray_length(operation, do_minimal, do_minimal_tth, y do_2 = do_minimal_tth assert len(do_1.all_arrays[:, 0]) == 0 assert len(do_2.all_arrays[:, 0]) == 2 + # Add two DO objets with different yarray lengths, expect ValueError with pytest.raises(ValueError, match=re.escape(y_grid_size_mismatch_error_msg)): if operation == "add": do_1 + do_2 From 116280c1a8dfef0197b90f06cf3a6f3ce9719f52 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 29 Dec 2024 23:50:46 +0000 Subject: [PATCH 6/8] [pre-commit.ci] auto fixes from pre-commit hooks --- tests/test_diffraction_objects.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 6c390e16..067fce5d 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -807,7 +807,6 @@ def test_scalar_operations(operation, starting_yarray, scalar_value, expected_ya np.array([[0.0, 6.28318531, 100.70777771, 1], [0.0, 3.14159265, 45.28748053, 2.0]]), ), ( # Test multiplication of two DO objects, expect multiplication in yarray values - "mul", np.array([[1.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]), np.array([[1.0, 6.28318531, 100.70777771, 1], [4.0, 3.14159265, 45.28748053, 2.0]]), From 6656dabc93ffd54ebcc770fab17e8124da9bc5c7 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 29 Dec 2024 18:53:22 -0500 Subject: [PATCH 7/8] test: add higher-level test comments --- tests/test_diffraction_objects.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 067fce5d..17f7ca19 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -715,6 +715,7 @@ def test_copy_object(do_minimal): @pytest.mark.parametrize( "operation, starting_yarray, scalar_value, expected_yarray", [ + # Test scalar addition, subtraction, multiplication, and division to y-values by adding a scalar value # C1: Test scalar addition to y-values (intensity), expect no change to x-values (q, tth, d) ( # 1. Add 5 "add", @@ -796,6 +797,7 @@ def test_scalar_operations(operation, starting_yarray, scalar_value, expected_ya @pytest.mark.parametrize( "operation, " "expected_do_1_all_arrays_with_y_modified, " "expected_do_2_all_arrays_with_y_modified", [ + # Test addition, subtraction, multiplication, and division of two DO objects ( # Test addition of two DO objects, expect combined yarray values "add", np.array([[2.0, 0.51763809, 30.0, 12.13818192], [4.0, 1.0, 60.0, 6.28318531]]), @@ -852,6 +854,7 @@ def test_binary_operator_on_do( def test_operator_invalid_type(do_minimal_tth, invalid_add_type_error_msg): + # Add a string to a DiffractionObject, expect TypeError do = do_minimal_tth invalid_value = "string_value" operations = [ @@ -860,7 +863,6 @@ def test_operator_invalid_type(do_minimal_tth, invalid_add_type_error_msg): (lambda x, y: x * y), # Test multiplication (lambda x, y: x / y), # Test division ] - # Add a string to a DiffractionObject, expect TypeError for operation in operations: with pytest.raises(TypeError, match=re.escape(invalid_add_type_error_msg)): operation(do, invalid_value) @@ -870,11 +872,11 @@ def test_operator_invalid_type(do_minimal_tth, invalid_add_type_error_msg): @pytest.mark.parametrize("operation", ["add", "sub", "mul", "div"]) def test_operator_invalid_yarray_length(operation, do_minimal, do_minimal_tth, y_grid_size_mismatch_error_msg): + # Add two DO objects with different yarray lengths, expect ValueError 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 - # Add two DO objets with different yarray lengths, expect ValueError with pytest.raises(ValueError, match=re.escape(y_grid_size_mismatch_error_msg)): if operation == "add": do_1 + do_2 From 21711fb8a3d15030e5b74d63ccbfe09830395acf Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sun, 29 Dec 2024 21:16:25 -0500 Subject: [PATCH 8/8] fix: shorten all_arrays shape check --- src/diffpy/utils/diffraction_objects.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/diffpy/utils/diffraction_objects.py b/src/diffpy/utils/diffraction_objects.py index 477b1f2c..bf53a5ac 100644 --- a/src/diffpy/utils/diffraction_objects.py +++ b/src/diffpy/utils/diffraction_objects.py @@ -254,9 +254,7 @@ 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 self_yarray.shape != other_yarray.shape: + if self.all_arrays.shape != other.all_arrays.shape: raise ValueError(y_grid_length_mismatch_emsg) @property