From 9ae8d9ec6c92ff7aa3cd4b4faa61a5e38ccf95cf Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sun, 3 Nov 2024 14:40:10 -0500 Subject: [PATCH 01/13] add converter functions --- .../scattering_objects/diffraction_objects.py | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index bd5b16db..398a9664 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -362,6 +362,102 @@ def tth_to_q(self): pre_factor = (4 * np.pi) / wavelength return pre_factor * np.sin(two_theta / 2) + def q_to_d(self): + r""" + Helper function to convert q to d using :math:`d = \frac{2 \pi}{q}` + + adds a small value (epsilon = 1e-10) to `q` where `q` is close to zero to avoid division by zero + + Parameters + ---------- + q : array + An array of :math:`q` values + + Returns + ------- + d : array + An array of :math:`d` values in the inverse of the units of ``wavelength`` + """ + epsilon = 1e-10 + q = np.asarray(self.on_q[0]) + q = np.where(np.abs(q) < epsilon, q + epsilon, q) + return (2 * np.pi) / q + + def d_to_q(self): + r""" + Helper function to convert d to q using :math:`q = \frac{2 \pi}{d}` + + adds a small value (epsilon = 1e-10) to `d` where `d` is close to zero to avoid division by zero + + Parameters + ---------- + d : array + An array of :math:`d` values + + Returns + ------- + q : array + An array of :math:`q` values in the inverse of the units of ``wavelength`` + """ + epsilon = 1e-10 + d = np.asarray(self.on_d[0]) + d = np.where(np.abs(d) < epsilon, d + epsilon, d) + return (2 * np.pi) / d + + def tth_to_d(self): + r""" + Helper function to convert two-theta to d + + uses the formula .. math:: d = \frac{\lambda}{2 \sin\left(\frac{2\theta}{2}\right)}, + and adds a small value (epsilon = 1e-10) to sin where sin is close to zero to avoid division by zero + + Parameters + ---------- + two_theta : array + An array of :math:`2\theta` values in units of degrees + + wavelength : float + Wavelength of the incoming x-rays + + Returns + ------- + d : array + An array of :math:`d` values in the inverse of the units + of ``wavelength`` + """ + epsilon = 1e-10 + two_theta = np.asarray(np.deg2rad(self.on_tth[0])) + wavelength = float(self.wavelength) + sin_two_theta = np.sin(two_theta / 2) + sin_two_theta = np.where(np.abs(sin_two_theta) < epsilon, sin_two_theta + epsilon, sin_two_theta) + return wavelength / (2 * sin_two_theta) + + def d_to_tth(self): + r""" + Helper function to convert d to two-theta + + uses the formula .. math:: 2\theta = 2 \arcsin\left(\frac{\lambda}{2d}\right), + and adds a small value (epsilon = 1e-10) to `d` where `d` is close to zero to avoid division by zero + + Parameters + ---------- + d : array + An array of :math:`d` values + + wavelength : float + Wavelength of the incoming x-rays + + Returns + ------- + two_theta : array + An array of :math:`2\theta` values in radians + """ + epsilon = 1e-10 + d = np.asarray(self.on_d[0]) + d = np.where(np.abs(d) < epsilon, d + epsilon, d) + wavelength = float(self.wavelength) + return np.rad2deg(2.0 * np.arcsin(wavelength / (2 * d))) + def set_all_arrays(self): master_array, xtype = self._get_original_array() if xtype == "q": From 4afc6a673e5c659aadf9272af9ab187ed8e20271 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sun, 3 Nov 2024 15:30:01 -0500 Subject: [PATCH 02/13] automatically set xtype=d when creating diffraction objects --- .../utils/scattering_objects/diffraction_objects.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index 398a9664..9350bd92 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -463,13 +463,24 @@ def set_all_arrays(self): if xtype == "q": self.on_tth[0] = self.q_to_tth() self.on_tth[1] = master_array[1] + self.on_d[0] = self.q_to_d() + self.on_d[1] = master_array[1] if xtype == "tth": self.on_q[0] = self.tth_to_q() self.on_q[1] = master_array[1] + self.on_d[0] = self.tth_to_d() + self.on_d[1] = master_array[1] + if xtype == "d": + self.on_tth[0] = self.d_to_tth() + self.on_tth[1] = master_array[1] + self.on_q[0] = self.d_to_q() + self.on_q[1] = master_array[1] self.tthmin = self.on_tth[0][0] self.tthmax = self.on_tth[0][-1] self.qmin = self.on_q[0][0] self.qmax = self.on_q[0][-1] + self.dmin = self.on_d[0][0] + self.dmax = self.on_d[0][-1] def _get_original_array(self): if self.input_xtype in QQUANTITIES: From 8667f3b163fd8cf6d8ca685999ef9b60c2167807 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Mon, 4 Nov 2024 16:34:46 -0500 Subject: [PATCH 03/13] fix error in creating DO on xtype=d --- src/diffpy/utils/scattering_objects/diffraction_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index 9350bd92..a96a2f80 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -283,7 +283,7 @@ def insert_scattering_quantity( elif xtype.lower() in ANGLEQUANTITIES: self.on_tth = [np.array(xarray), np.array(yarray)] elif xtype.lower() in DQUANTITIES: - self.on_tth = [np.array(xarray), np.array(yarray)] + self.on_d = [np.array(xarray), np.array(yarray)] self.set_all_arrays() def q_to_tth(self): From 2f116344da8e740809ce7952011d58da44ed408d Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Mon, 4 Nov 2024 16:48:33 -0500 Subject: [PATCH 04/13] add tests for conversions between tth, q, and d --- tests/test_diffraction_objects.py | 50 +++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index a155b95e..0f9708c9 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -231,6 +231,56 @@ def test_diffraction_objects_equality(inputs1, inputs2, expected): assert (diffraction_object1 == diffraction_object2) == expected +def test_q_to_tth(): + actual = Diffraction_object(wavelength=0.71) + setattr(actual, "on_q", [[0, 4.58087], [1, 1]]) + actual_tth = actual.q_to_tth() + expected_tth = [0, 30] + assert np.allclose(actual_tth, expected_tth) + + +def test_tth_to_q(): + actual = Diffraction_object(wavelength=0.71) + setattr(actual, "on_tth", [[0, 30], [1, 1]]) + actual_q = actual.tth_to_q() + expected_q = [0, 4.58087] + assert np.allclose(actual_q, expected_q) + + +def test_q_to_d(): + actual = Diffraction_object(wavelength=0.71) + setattr(actual, "on_q", [[0, 4.58087], [1, 1]]) + actual_d = actual.q_to_d() + expected_d = [62831853071.8, 1.37161] + assert np.allclose(actual_d, expected_d) + + +def test_d_to_q(): + actual = Diffraction_object(wavelength=0.71) + setattr(actual, "on_d", [[1e10, 1.37161], [1, 1]]) + actual_q = actual.d_to_q() + expected_q = [0, 4.58087] + assert np.allclose(actual_q, expected_q) + + +def test_tth_to_d(): + actual = Diffraction_object(wavelength=0.71) + setattr(actual, "on_tth", [[0, 30], [1, 1]]) + actual_d = actual.tth_to_d() + expected_d = [3550000000, 1.37161] + assert np.allclose(actual_d, expected_d) + + +def test_d_to_tth(): + actual = Diffraction_object(wavelength=0.71) + setattr(actual, "on_d", [[1e10, 1.37161], [1, 1]]) + actual_tth = actual.d_to_tth() + expected_tth = [0, 30] + assert np.allclose(actual_tth, expected_tth) + + +#def test_set_all_arrays(): + def test_dump(tmp_path, mocker): x, y = np.linspace(0, 5, 6), np.linspace(0, 5, 6) directory = Path(tmp_path) From 5423c9c6f3717a16bbf0044721f7cc761aa0e1d0 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Mon, 4 Nov 2024 17:20:21 -0500 Subject: [PATCH 05/13] add test for set_all_arrays --- tests/test_diffraction_objects.py | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 0f9708c9..6cbcea57 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -279,7 +279,36 @@ def test_d_to_tth(): assert np.allclose(actual_tth, expected_tth) -#def test_set_all_arrays(): +params_array = [ + (["q", "on_q", [4.58087, 8.84956], [1, 2]]), + (["tth", "on_tth", [30, 60], [1, 2]]), + (["d", "on_d", [1.37161, 0.71], [1, 2]]), +] + + +@pytest.mark.parametrize("inputs", params_array) +def test_set_all_arrays(inputs): + input_xtype, on_xtype, xarray, yarray = inputs + expected_values = { + "on_tth": [np.array([30, 60]), np.array([1, 2])], + "on_q": [np.array([4.58087, 8.84956]), np.array([1, 2])], + "on_d": [np.array([1.37161, 0.71]), np.array([1, 2])], + "tthmin": 30, + "tthmax": 60, + "qmin": 4.58087, + "qmax": 8.84956, + "dmin": 1.37161, + "dmax": 0.71, + } + + actual = Diffraction_object(wavelength=0.71) + setattr(actual, "input_xtype", input_xtype) + setattr(actual, on_xtype, [xarray, yarray]) + actual.set_all_arrays() + for attr, expected in expected_values.items(): + actual_value = getattr(actual, attr) + assert np.allclose(actual_value, expected) + def test_dump(tmp_path, mocker): x, y = np.linspace(0, 5, 6), np.linspace(0, 5, 6) From 5c46816517e33f2ca05406acb531e7f85ae29a2f Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Wed, 6 Nov 2024 10:47:15 -0500 Subject: [PATCH 06/13] add news --- news/xtype.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/xtype.rst diff --git a/news/xtype.rst b/news/xtype.rst new file mode 100644 index 00000000..dbe976ec --- /dev/null +++ b/news/xtype.rst @@ -0,0 +1,23 @@ +**Added:** + +* functions to allow conversions between d and tth, and d and q. + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 07274d4da9527996d241db89b1311b3b3f5fefdd Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Tue, 12 Nov 2024 15:33:14 -0500 Subject: [PATCH 07/13] change an array to the array --- .../scattering_objects/diffraction_objects.py | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index a96a2f80..5df3628a 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -305,7 +305,7 @@ def q_to_tth(self): Parameters ---------- q : array - An array of :math:`q` values + The array of :math:`q` values wavelength : float Wavelength of the incoming x-rays @@ -315,7 +315,7 @@ def q_to_tth(self): Returns ------- two_theta : array - An array of :math:`2\theta` values in radians + The array of :math:`2\theta` values in radians """ q = self.on_q[0] q = np.asarray(q) @@ -344,7 +344,7 @@ def tth_to_q(self): Parameters ---------- two_theta : array - An array of :math:`2\theta` values in units of degrees + The array of :math:`2\theta` values in units of degrees wavelength : float Wavelength of the incoming x-rays @@ -354,7 +354,7 @@ def tth_to_q(self): Returns ------- q : array - An array of :math:`q` values in the inverse of the units + The array of :math:`q` values in the inverse of the units of ``wavelength`` """ two_theta = np.asarray(np.deg2rad(self.on_tth[0])) @@ -371,12 +371,12 @@ def q_to_d(self): Parameters ---------- q : array - An array of :math:`q` values + The array of :math:`q` values Returns ------- d : array - An array of :math:`d` values in the inverse of the units of ``wavelength`` + The array of :math:`d` values in the inverse of the units of ``wavelength`` """ epsilon = 1e-10 q = np.asarray(self.on_q[0]) @@ -392,12 +392,12 @@ def d_to_q(self): Parameters ---------- d : array - An array of :math:`d` values + The array of :math:`d` values Returns ------- q : array - An array of :math:`q` values in the inverse of the units of ``wavelength`` + The array of :math:`q` values in the inverse of the units of ``wavelength`` """ epsilon = 1e-10 d = np.asarray(self.on_d[0]) @@ -414,7 +414,7 @@ def tth_to_d(self): Parameters ---------- two_theta : array - An array of :math:`2\theta` values in units of degrees + The array of :math:`2\theta` values in units of degrees wavelength : float Wavelength of the incoming x-rays @@ -422,7 +422,7 @@ def tth_to_d(self): Returns ------- d : array - An array of :math:`d` values in the inverse of the units + The array of :math:`d` values in the inverse of the units of ``wavelength`` """ epsilon = 1e-10 @@ -442,7 +442,7 @@ def d_to_tth(self): Parameters ---------- d : array - An array of :math:`d` values + The array of :math:`d` values wavelength : float Wavelength of the incoming x-rays @@ -450,7 +450,7 @@ def d_to_tth(self): Returns ------- two_theta : array - An array of :math:`2\theta` values in radians + The array of :math:`2\theta` values in radians """ epsilon = 1e-10 d = np.asarray(self.on_d[0]) From b733b94bb955bf2c5b3143013340a890b7e7a9db Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Tue, 12 Nov 2024 16:23:02 -0500 Subject: [PATCH 08/13] fixed functions and tests for q_to_tth, tth_to_q, q_to_d, and d_to_q --- .../scattering_objects/diffraction_objects.py | 14 +++++----- tests/test_diffraction_objects.py | 26 +++++++++++-------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index 5df3628a..d12ff029 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -10,6 +10,8 @@ DQUANTITIES = ["d", "dspace"] XQUANTITIES = ANGLEQUANTITIES + DQUANTITIES + QQUANTITIES XUNITS = ["degrees", "radians", "rad", "deg", "inv_angs", "inv_nm", "nm-1", "A-1"] +QMAX = 40 +DMAX = 100 x_grid_emsg = ( "objects are not on the same x-grid. You may add them using the self.add method " @@ -378,10 +380,10 @@ def q_to_d(self): d : array The array of :math:`d` values in the inverse of the units of ``wavelength`` """ - epsilon = 1e-10 q = np.asarray(self.on_q[0]) - q = np.where(np.abs(q) < epsilon, q + epsilon, q) - return (2 * np.pi) / q + d = np.where(q != 0, (2 * np.pi) / q, np.inf) + d = np.minimum(d, DMAX) + return d[::-1] def d_to_q(self): r""" @@ -399,10 +401,10 @@ def d_to_q(self): q : array The array of :math:`q` values in the inverse of the units of ``wavelength`` """ - epsilon = 1e-10 d = np.asarray(self.on_d[0]) - d = np.where(np.abs(d) < epsilon, d + epsilon, d) - return (2 * np.pi) / d + q = np.where(d != 0, (2 * np.pi) / d, np.inf) + q = np.minimum(q, QMAX) + return q[::-1] def tth_to_d(self): r""" diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 6cbcea57..a6ee39f0 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -232,34 +232,38 @@ def test_diffraction_objects_equality(inputs1, inputs2, expected): def test_q_to_tth(): - actual = Diffraction_object(wavelength=0.71) - setattr(actual, "on_q", [[0, 4.58087], [1, 1]]) + actual = Diffraction_object(wavelength=4 * np.pi) + setattr(actual, "on_q", [[0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]]) actual_tth = actual.q_to_tth() - expected_tth = [0, 30] + # expected tth values are 2 * arcsin(q) + expected_tth = [0, 23.07392, 47.15636, 73.73980, 106.26020, 180] assert np.allclose(actual_tth, expected_tth) def test_tth_to_q(): - actual = Diffraction_object(wavelength=0.71) - setattr(actual, "on_tth", [[0, 30], [1, 1]]) + actual = Diffraction_object(wavelength=4 * np.pi) + setattr(actual, "on_tth", [[0, 30, 60, 90, 120, 180], [1, 2, 3, 4, 5, 6]]) actual_q = actual.tth_to_q() - expected_q = [0, 4.58087] + # expected q vales are sin15, sin30, sin45, sin60, sin90 + expected_q = [0, 0.258819, 0.5, 0.707107, 0.866025, 1] assert np.allclose(actual_q, expected_q) def test_q_to_d(): actual = Diffraction_object(wavelength=0.71) - setattr(actual, "on_q", [[0, 4.58087], [1, 1]]) + setattr(actual, "on_q", [[0, np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi], [1, 2, 3, 4, 5, 6]]) actual_d = actual.q_to_d() - expected_d = [62831853071.8, 1.37161] + # expected d values are DMAX=100, 2/1, 2/2, 2/3, 2/4, 2/5, and in reverse order + expected_d = [0.4, 0.5, 0.66667, 1, 2, 100] assert np.allclose(actual_d, expected_d) def test_d_to_q(): - actual = Diffraction_object(wavelength=0.71) - setattr(actual, "on_d", [[1e10, 1.37161], [1, 1]]) + actual = Diffraction_object(wavelength=1) + setattr(actual, "on_d", [[0, np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi], [1, 2, 3, 4, 5, 6]]) actual_q = actual.d_to_q() - expected_q = [0, 4.58087] + # expected q values are QMAX=40, 2/1, 2/2, 2/3, 2/4, 2/5, and in reverse order + expected_q = [0.4, 0.5, 0.66667, 1, 2, 40] assert np.allclose(actual_q, expected_q) From 0534fa8766e4d9aa9e708177612384c32d724c8e Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Tue, 12 Nov 2024 17:22:20 -0500 Subject: [PATCH 09/13] fix tth_to_d and d_to_tth --- .../scattering_objects/diffraction_objects.py | 11 +++++------ tests/test_diffraction_objects.py | 15 +++++++++------ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index d12ff029..2c628c54 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -427,12 +427,12 @@ def tth_to_d(self): The array of :math:`d` values in the inverse of the units of ``wavelength`` """ - epsilon = 1e-10 two_theta = np.asarray(np.deg2rad(self.on_tth[0])) wavelength = float(self.wavelength) sin_two_theta = np.sin(two_theta / 2) - sin_two_theta = np.where(np.abs(sin_two_theta) < epsilon, sin_two_theta + epsilon, sin_two_theta) - return wavelength / (2 * sin_two_theta) + d = np.where(sin_two_theta != 0, wavelength / (2 * sin_two_theta), np.inf) + d = np.minimum(d, DMAX) + return d[::-1] def d_to_tth(self): r""" @@ -454,11 +454,10 @@ def d_to_tth(self): two_theta : array The array of :math:`2\theta` values in radians """ - epsilon = 1e-10 d = np.asarray(self.on_d[0]) - d = np.where(np.abs(d) < epsilon, d + epsilon, d) wavelength = float(self.wavelength) - return np.rad2deg(2.0 * np.arcsin(wavelength / (2 * d))) + tth = np.where(d != 0, np.rad2deg(2.0 * np.arcsin(wavelength / (2 * d))), 180) + return tth[::-1] def set_all_arrays(self): master_array, xtype = self._get_original_array() diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index a6ee39f0..3daa2d87 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -268,18 +268,21 @@ def test_d_to_q(): def test_tth_to_d(): - actual = Diffraction_object(wavelength=0.71) - setattr(actual, "on_tth", [[0, 30], [1, 1]]) + actual = Diffraction_object(wavelength=2) + setattr(actual, "on_tth", [[0, 30, 60, 90, 120, 180], [1, 2, 3, 4, 5, 6]]) actual_d = actual.tth_to_d() - expected_d = [3550000000, 1.37161] + # expected d values are DMAX=100, 1/sin15, 1/sin30, 1/sin45, 1/sin60, 1/sin90, in reverse order + expected_d = [1, 1.1547, 1.41421, 2, 3.8637, 100] assert np.allclose(actual_d, expected_d) def test_d_to_tth(): - actual = Diffraction_object(wavelength=0.71) - setattr(actual, "on_d", [[1e10, 1.37161], [1, 1]]) + actual = Diffraction_object(wavelength=2) + setattr(actual, "on_d", [[0, 2, 4, 6, 8, 100], [1, 2, 3, 4, 5, 6]]) actual_tth = actual.d_to_tth() - expected_tth = [0, 30] + # expected tth values are 2*arcsin(1/d), in reverse order + # when d is really small we have tth to be 180 + expected_tth = [1.14593, 14.36151, 19.18814, 28.95502, 60, 180] assert np.allclose(actual_tth, expected_tth) From 01470c1a610ab053b2c06d40fdb3a9a771df1edb Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Tue, 12 Nov 2024 18:50:42 -0500 Subject: [PATCH 10/13] clean up docstring and test descriptions --- .../scattering_objects/diffraction_objects.py | 22 +++++++------------ tests/test_diffraction_objects.py | 10 ++++----- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index 2c628c54..5e908d95 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -366,9 +366,7 @@ def tth_to_q(self): def q_to_d(self): r""" - Helper function to convert q to d using :math:`d = \frac{2 \pi}{q}` - - adds a small value (epsilon = 1e-10) to `q` where `q` is close to zero to avoid division by zero + Helper function to convert q to d using :math:`d = \frac{2 \pi}{q}`, set dmax to DMAX=100 Parameters ---------- @@ -387,9 +385,7 @@ def q_to_d(self): def d_to_q(self): r""" - Helper function to convert d to q using :math:`q = \frac{2 \pi}{d}` - - adds a small value (epsilon = 1e-10) to `d` where `d` is close to zero to avoid division by zero + Helper function to convert d to q using :math:`q = \frac{2 \pi}{d}`, set qmax to QMAX=40 Parameters ---------- @@ -410,8 +406,7 @@ def tth_to_d(self): r""" Helper function to convert two-theta to d - uses the formula .. math:: d = \frac{\lambda}{2 \sin\left(\frac{2\theta}{2}\right)}, - and adds a small value (epsilon = 1e-10) to sin where sin is close to zero to avoid division by zero + uses the formula .. math:: d = \frac{\lambda}{2 \sin\left(\frac{2\theta}{2}\right)}, set dmax to DMAX=100 Parameters ---------- @@ -438,8 +433,7 @@ def d_to_tth(self): r""" Helper function to convert d to two-theta - uses the formula .. math:: 2\theta = 2 \arcsin\left(\frac{\lambda}{2d}\right), - and adds a small value (epsilon = 1e-10) to `d` where `d` is close to zero to avoid division by zero + uses the formula .. math:: 2\theta = 2 \arcsin\left(\frac{\lambda}{2d}\right), set tth to 180 when d=0 Parameters ---------- @@ -465,17 +459,17 @@ def set_all_arrays(self): self.on_tth[0] = self.q_to_tth() self.on_tth[1] = master_array[1] self.on_d[0] = self.q_to_d() - self.on_d[1] = master_array[1] + self.on_d[1] = master_array[1][::-1] if xtype == "tth": self.on_q[0] = self.tth_to_q() self.on_q[1] = master_array[1] self.on_d[0] = self.tth_to_d() - self.on_d[1] = master_array[1] + self.on_d[1] = master_array[1][::-1] if xtype == "d": self.on_tth[0] = self.d_to_tth() - self.on_tth[1] = master_array[1] + self.on_tth[1] = master_array[1][::-1] self.on_q[0] = self.d_to_q() - self.on_q[1] = master_array[1] + self.on_q[1] = master_array[1][::-1] self.tthmin = self.on_tth[0][0] self.tthmax = self.on_tth[0][-1] self.qmin = self.on_q[0][0] diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 3daa2d87..63fb19e8 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -250,19 +250,19 @@ def test_tth_to_q(): def test_q_to_d(): - actual = Diffraction_object(wavelength=0.71) + actual = Diffraction_object() setattr(actual, "on_q", [[0, np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi], [1, 2, 3, 4, 5, 6]]) actual_d = actual.q_to_d() - # expected d values are DMAX=100, 2/1, 2/2, 2/3, 2/4, 2/5, and in reverse order + # expected d values are DMAX=100, 2/1, 2/2, 2/3, 2/4, 2/5, in reverse order expected_d = [0.4, 0.5, 0.66667, 1, 2, 100] assert np.allclose(actual_d, expected_d) def test_d_to_q(): - actual = Diffraction_object(wavelength=1) + actual = Diffraction_object() setattr(actual, "on_d", [[0, np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi], [1, 2, 3, 4, 5, 6]]) actual_q = actual.d_to_q() - # expected q values are QMAX=40, 2/1, 2/2, 2/3, 2/4, 2/5, and in reverse order + # expected q values are QMAX=40, 2/1, 2/2, 2/3, 2/4, 2/5, in reverse order expected_q = [0.4, 0.5, 0.66667, 1, 2, 40] assert np.allclose(actual_q, expected_q) @@ -281,7 +281,7 @@ def test_d_to_tth(): setattr(actual, "on_d", [[0, 2, 4, 6, 8, 100], [1, 2, 3, 4, 5, 6]]) actual_tth = actual.d_to_tth() # expected tth values are 2*arcsin(1/d), in reverse order - # when d is really small we have tth to be 180 + # when d is 0, we set tth to 180 expected_tth = [1.14593, 14.36151, 19.18814, 28.95502, 60, 180] assert np.allclose(actual_tth, expected_tth) From 2de70ed70e7e95a41562bdfca793329c165e4284 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Wed, 13 Nov 2024 21:09:10 -0500 Subject: [PATCH 11/13] update q_to_tth, d_to_tth --- .../scattering_objects/diffraction_objects.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index 5e908d95..35041b92 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -323,7 +323,9 @@ def q_to_tth(self): q = np.asarray(q) wavelength = float(self.wavelength) pre_factor = wavelength / (4 * np.pi) - return np.rad2deg(2.0 * np.arcsin(q * pre_factor)) + # limit q * pre_factor to 1 to avoid invalid input to arcsin + arcsin_value = np.where(q * pre_factor <= 1, q * pre_factor, 1) + return np.rad2deg(2.0 * np.arcsin(arcsin_value)) def tth_to_q(self): r""" @@ -450,8 +452,8 @@ def d_to_tth(self): """ d = np.asarray(self.on_d[0]) wavelength = float(self.wavelength) - tth = np.where(d != 0, np.rad2deg(2.0 * np.arcsin(wavelength / (2 * d))), 180) - return tth[::-1] + pre_factor = np.where(d != 0, wavelength / (2 * d), 1) + return np.rad2deg(2.0 * np.arcsin(pre_factor))[::-1] def set_all_arrays(self): master_array, xtype = self._get_original_array() @@ -459,17 +461,17 @@ def set_all_arrays(self): self.on_tth[0] = self.q_to_tth() self.on_tth[1] = master_array[1] self.on_d[0] = self.q_to_d() - self.on_d[1] = master_array[1][::-1] + self.on_d[1] = master_array[1] if xtype == "tth": self.on_q[0] = self.tth_to_q() self.on_q[1] = master_array[1] self.on_d[0] = self.tth_to_d() - self.on_d[1] = master_array[1][::-1] + self.on_d[1] = master_array[1] if xtype == "d": self.on_tth[0] = self.d_to_tth() - self.on_tth[1] = master_array[1][::-1] + self.on_tth[1] = master_array[1] self.on_q[0] = self.d_to_q() - self.on_q[1] = master_array[1][::-1] + self.on_q[1] = master_array[1] self.tthmin = self.on_tth[0][0] self.tthmax = self.on_tth[0][-1] self.qmin = self.on_q[0][0] From 638b2164de7efaa514c59e50a035055d1ce0c6e1 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Wed, 13 Nov 2024 21:59:05 -0500 Subject: [PATCH 12/13] update tests for set_all_arrays and edge cases --- tests/test_diffraction_objects.py | 35 +++++++++++++++++-------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 63fb19e8..9a440869 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -233,10 +233,12 @@ def test_diffraction_objects_equality(inputs1, inputs2, expected): def test_q_to_tth(): actual = Diffraction_object(wavelength=4 * np.pi) - setattr(actual, "on_q", [[0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]]) + setattr(actual, "on_q", [[0, 0.2, 0.4, 0.6, 0.8, 1, 40, 60], [1, 2, 3, 4, 5, 6, 7, 8]]) actual_tth = actual.q_to_tth() # expected tth values are 2 * arcsin(q) - expected_tth = [0, 23.07392, 47.15636, 73.73980, 106.26020, 180] + # when q gets large, we set tth = 180 + # we allow q to exceed QMAX for user inputs + expected_tth = [0, 23.07392, 47.15636, 73.73980, 106.26020, 180, 180, 180] assert np.allclose(actual_tth, expected_tth) @@ -278,18 +280,19 @@ def test_tth_to_d(): def test_d_to_tth(): actual = Diffraction_object(wavelength=2) - setattr(actual, "on_d", [[0, 2, 4, 6, 8, 100], [1, 2, 3, 4, 5, 6]]) + setattr(actual, "on_d", [[0, 2, 4, 6, 8, 100, 200], [1, 2, 3, 4, 5, 6, 7]]) actual_tth = actual.d_to_tth() # expected tth values are 2*arcsin(1/d), in reverse order # when d is 0, we set tth to 180 - expected_tth = [1.14593, 14.36151, 19.18814, 28.95502, 60, 180] + # we allow d to exceed DMAX=100 for user inputs + expected_tth = [0.57296, 1.14593, 14.36151, 19.18814, 28.95502, 60, 180] assert np.allclose(actual_tth, expected_tth) params_array = [ - (["q", "on_q", [4.58087, 8.84956], [1, 2]]), - (["tth", "on_tth", [30, 60], [1, 2]]), - (["d", "on_d", [1.37161, 0.71], [1, 2]]), + (["tth", "on_tth", [30, 60, 90, 120, 150], [1, 2, 3, 4, 5]]), + (["q", "on_q", [1.626208, 3.141593, 4.442883, 5.441398, 6.069091], [1, 2, 3, 4, 5]]), + (["d", "on_d", [1.035276, 1.154701, 1.414214, 2, 3.863703], [1, 2, 3, 4, 5]]), ] @@ -297,18 +300,18 @@ def test_d_to_tth(): def test_set_all_arrays(inputs): input_xtype, on_xtype, xarray, yarray = inputs expected_values = { - "on_tth": [np.array([30, 60]), np.array([1, 2])], - "on_q": [np.array([4.58087, 8.84956]), np.array([1, 2])], - "on_d": [np.array([1.37161, 0.71]), np.array([1, 2])], + "on_tth": [np.array([30, 60, 90, 120, 150]), np.array([1, 2, 3, 4, 5])], + "on_q": [np.array([1.626208, 3.141593, 4.442883, 5.441398, 6.069091]), np.array([1, 2, 3, 4, 5])], + "on_d": [np.array([1.035276, 1.154701, 1.414214, 2, 3.863703]), np.array([1, 2, 3, 4, 5])], "tthmin": 30, - "tthmax": 60, - "qmin": 4.58087, - "qmax": 8.84956, - "dmin": 1.37161, - "dmax": 0.71, + "tthmax": 150, + "qmin": 1.626208, + "qmax": 6.069091, + "dmin": 1.035276, + "dmax": 3.863703, } - actual = Diffraction_object(wavelength=0.71) + actual = Diffraction_object(wavelength=2) setattr(actual, "input_xtype", input_xtype) setattr(actual, on_xtype, [xarray, yarray]) actual.set_all_arrays() From a2deaa15366706a43109c783eedc1525700cc6db Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Thu, 14 Nov 2024 14:47:09 -0500 Subject: [PATCH 13/13] raise valueerror for tth>180, arcsin value > 1 --- .../scattering_objects/diffraction_objects.py | 18 +++++-- tests/test_diffraction_objects.py | 47 ++++++++++++++++--- 2 files changed, 54 insertions(+), 11 deletions(-) diff --git a/src/diffpy/utils/scattering_objects/diffraction_objects.py b/src/diffpy/utils/scattering_objects/diffraction_objects.py index 35041b92..6b6e490b 100644 --- a/src/diffpy/utils/scattering_objects/diffraction_objects.py +++ b/src/diffpy/utils/scattering_objects/diffraction_objects.py @@ -323,9 +323,11 @@ def q_to_tth(self): q = np.asarray(q) wavelength = float(self.wavelength) pre_factor = wavelength / (4 * np.pi) - # limit q * pre_factor to 1 to avoid invalid input to arcsin - arcsin_value = np.where(q * pre_factor <= 1, q * pre_factor, 1) - return np.rad2deg(2.0 * np.arcsin(arcsin_value)) + if np.any(np.abs(q * pre_factor) > 1): + raise ValueError( + "Invalid input for arcsin: some values exceed the range [-1, 1]. " "Check wavelength or q values." + ) + return np.rad2deg(2.0 * np.arcsin(q * pre_factor)) def tth_to_q(self): r""" @@ -362,6 +364,8 @@ def tth_to_q(self): of ``wavelength`` """ two_theta = np.asarray(np.deg2rad(self.on_tth[0])) + if np.any(two_theta > np.pi): + raise ValueError("Two theta exceeds 180 degrees.") wavelength = float(self.wavelength) pre_factor = (4 * np.pi) / wavelength return pre_factor * np.sin(two_theta / 2) @@ -425,6 +429,8 @@ def tth_to_d(self): of ``wavelength`` """ two_theta = np.asarray(np.deg2rad(self.on_tth[0])) + if np.any(two_theta > np.pi): + raise ValueError("Two theta exceeds 180 degrees.") wavelength = float(self.wavelength) sin_two_theta = np.sin(two_theta / 2) d = np.where(sin_two_theta != 0, wavelength / (2 * sin_two_theta), np.inf) @@ -452,7 +458,11 @@ def d_to_tth(self): """ d = np.asarray(self.on_d[0]) wavelength = float(self.wavelength) - pre_factor = np.where(d != 0, wavelength / (2 * d), 1) + pre_factor = wavelength / (2 * d) + if np.any(np.abs(pre_factor) > 1): + raise ValueError( + "Invalid input for arcsin: some values exceed the range [-1, 1]. " "Check wavelength or d values." + ) return np.rad2deg(2.0 * np.arcsin(pre_factor))[::-1] def set_all_arrays(self): diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 9a440869..dcaae022 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -232,25 +232,40 @@ def test_diffraction_objects_equality(inputs1, inputs2, expected): def test_q_to_tth(): + # valid q values including edge cases when q=0 or 1, expected tth values are 2 * arcsin(q) actual = Diffraction_object(wavelength=4 * np.pi) - setattr(actual, "on_q", [[0, 0.2, 0.4, 0.6, 0.8, 1, 40, 60], [1, 2, 3, 4, 5, 6, 7, 8]]) + setattr(actual, "on_q", [[0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]]) actual_tth = actual.q_to_tth() - # expected tth values are 2 * arcsin(q) - # when q gets large, we set tth = 180 - # we allow q to exceed QMAX for user inputs - expected_tth = [0, 23.07392, 47.15636, 73.73980, 106.26020, 180, 180, 180] + expected_tth = [0, 23.07392, 47.15636, 73.73980, 106.26020, 180] assert np.allclose(actual_tth, expected_tth) +def test_q_to_tth_bad(): + # invalid q values when arcsin value is not in the range of [-1, 1] + actual = Diffraction_object(wavelength=4 * np.pi) + setattr(actual, "on_q", [[0.6, 0.8, 1, 1.2], [1, 2, 3, 4]]) + with pytest.raises(ValueError): + actual.q_to_tth() + + def test_tth_to_q(): + # valid tth values including edge cases when tth=0 or 180 degree, + # expected q vales are sin15, sin30, sin45, sin60, sin90 actual = Diffraction_object(wavelength=4 * np.pi) setattr(actual, "on_tth", [[0, 30, 60, 90, 120, 180], [1, 2, 3, 4, 5, 6]]) actual_q = actual.tth_to_q() - # expected q vales are sin15, sin30, sin45, sin60, sin90 expected_q = [0, 0.258819, 0.5, 0.707107, 0.866025, 1] assert np.allclose(actual_q, expected_q) +def test_tth_to_q_bad(): + # invalid tth values > 180 degree + actual = Diffraction_object(wavelength=4 * np.pi) + setattr(actual, "on_tth", [[0, 30, 60, 90, 120, 181], [1, 2, 3, 4, 5, 6]]) + with pytest.raises(ValueError): + actual.tth_to_q() + + def test_q_to_d(): actual = Diffraction_object() setattr(actual, "on_q", [[0, np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi], [1, 2, 3, 4, 5, 6]]) @@ -270,14 +285,23 @@ def test_d_to_q(): def test_tth_to_d(): + # valid tth values including edge cases when tth=0 or 180 degree, + # expected d values are DMAX=100, 1/sin15, 1/sin30, 1/sin45, 1/sin60, 1/sin90, in reverse order actual = Diffraction_object(wavelength=2) setattr(actual, "on_tth", [[0, 30, 60, 90, 120, 180], [1, 2, 3, 4, 5, 6]]) actual_d = actual.tth_to_d() - # expected d values are DMAX=100, 1/sin15, 1/sin30, 1/sin45, 1/sin60, 1/sin90, in reverse order expected_d = [1, 1.1547, 1.41421, 2, 3.8637, 100] assert np.allclose(actual_d, expected_d) +def test_tth_to_d_bad(): + # invalid tth values > 180 degree + actual = Diffraction_object(wavelength=2) + setattr(actual, "on_tth", [[0, 30, 60, 90, 120, 181], [1, 2, 3, 4, 5, 6]]) + with pytest.raises(ValueError): + actual.tth_to_d() + + def test_d_to_tth(): actual = Diffraction_object(wavelength=2) setattr(actual, "on_d", [[0, 2, 4, 6, 8, 100, 200], [1, 2, 3, 4, 5, 6, 7]]) @@ -289,6 +313,15 @@ def test_d_to_tth(): assert np.allclose(actual_tth, expected_tth) +def test_d_to_tth_bad(): + # invalid d values when arcsin value is not in the range of [-1, 1] + actual = Diffraction_object(wavelength=2) + setattr(actual, "on_d", [[0, 10, 20, 30], [1, 2, 3, 4]]) + with pytest.raises(ValueError): + actual.d_to_tth() + + +# Inputs cover a good valid range and provide the same expected values, ensuring conversion precision. params_array = [ (["tth", "on_tth", [30, 60, 90, 120, 150], [1, 2, 3, 4, 5]]), (["q", "on_q", [1.626208, 3.141593, 4.442883, 5.441398, 6.069091], [1, 2, 3, 4, 5]]),