diff --git a/news/d-q.rst b/news/d-q.rst new file mode 100644 index 00000000..92105f07 --- /dev/null +++ b/news/d-q.rst @@ -0,0 +1,23 @@ +**Added:** + +* functions to convert between d and q + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/utils/transforms.py b/src/diffpy/utils/transforms.py index 71e50599..0d02fe54 100644 --- a/src/diffpy/utils/transforms.py +++ b/src/diffpy/utils/transforms.py @@ -16,6 +16,9 @@ "The supplied q-array and wavelength will result in an impossible two-theta. " "Please check these values and re-instantiate the DiffractionObject with correct values." ) +inf_output_wmsg = ( + "INFO: The largest d-value in the array is infinite. This is allowed, but it will not be plotted." +) def _validate_inputs(q, wavelength): @@ -58,7 +61,6 @@ def q_to_tth(q, wavelength): ------- tth : 1D array The array of :math:`2\theta` values in degrees numpy.array([tths]). - This is the correct format for loading into diffpy.utils.DiffractionOject.on_tth """ _validate_inputs(q, wavelength) q.astype(float) @@ -92,9 +94,8 @@ def tth_to_q(tth, wavelength): Parameters ---------- - tth : 2D array - The array of :math:`2\theta` values and :math: 'i' intensity values, np.array([[tths], [is]]). - This is the same format as, and so can accept, diffpy.utils.DiffractionOject.on_tth + tth : 1D array + The array of :math:`2\theta` values np.array([tths]). The units of tth are expected in degrees. wavelength : float @@ -102,11 +103,9 @@ def tth_to_q(tth, wavelength): Returns ------- - on_q : 2D array - The array of :math:`q` values and :math: 'i' intensity values unchanged, - np.array([[qs], [is]]). + q : 1D array + The array of :math:`q` values np.array([qs]). The units for the q-values are the inverse of the units of the provided wavelength. - This is the correct format for loading into diffpy.utils.DiffractionOject.on_q """ tth.astype(float) if np.any(np.deg2rad(tth) > np.pi): @@ -116,13 +115,30 @@ def tth_to_q(tth, wavelength): pre_factor = (4.0 * np.pi) / wavelength q = pre_factor * np.sin(np.deg2rad(tth / 2)) else: # return intensities vs. an x-array that is just the index + warnings.warn(wavelength_warning_emsg, UserWarning) for i, _ in enumerate(q): q[i] = i return q -def q_to_d(qarray): - return 2.0 * np.pi / copy(qarray) +def q_to_d(q): + r""" + Helper function to convert q to d on independent variable axis, using :math:`d = \frac{2 \pi}{q}`. + + Parameters + ---------- + q : 1D array + The array of :math:`q` values np.array([qs]). + The units of q must be reciprocal of the units of wavelength. + + Returns + ------- + d : 1D array + The array of :math:`d` values np.array([ds]). + """ + if 0 in q: + print(inf_output_wmsg) + return 2.0 * np.pi / copy(q) def tth_to_d(ttharray, wavelength): @@ -130,8 +146,24 @@ def tth_to_d(ttharray, wavelength): return 2.0 * np.pi / copy(qarray) -def d_to_q(darray): - return 2.0 * np.pi / copy(darray) +def d_to_q(d): + r""" + Helper function to convert q to d using :math:`d = \frac{2 \pi}{q}`. + + Parameters + ---------- + d : 1D array + The array of :math:`d` values np.array([ds]). + + Returns + ------- + q : 1D array + The array of :math:`q` values np.array([qs]). + The units of q must be reciprocal of the units of wavelength. + """ + if 0 in d: + warnings.warn(inf_output_wmsg) + return 2.0 * np.pi / copy(d) def d_to_tth(darray, wavelength): diff --git a/tests/test_transforms.py b/tests/test_transforms.py index e8b15492..6c2e4c6e 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -1,7 +1,7 @@ import numpy as np import pytest -from diffpy.utils.transforms import q_to_tth, tth_to_q +from diffpy.utils.transforms import d_to_q, q_to_d, q_to_tth, tth_to_q params_q_to_tth = [ # UC1: Empty q values, no wavelength, return empty arrays @@ -64,7 +64,7 @@ def test_q_to_tth_bad(inputs, expected): np.array([0, 1, 2, 3, 4, 5]), ), # UC3: User specified valid tth values between 0-180 degrees (with wavelength) - # expected q vales are sin15, sin30, sin45, sin60, sin90 + # expected q values are sin15, sin30, sin45, sin60, sin90 ( [4 * np.pi, np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0])], np.array([0, 0.258819, 0.5, 0.707107, 0.866025, 1]), @@ -96,3 +96,37 @@ def test_tth_to_q(inputs, expected): def test_tth_to_q_bad(inputs, expected): with pytest.raises(expected[0], match=expected[1]): tth_to_q(inputs[1], inputs[0]) + + +params_q_to_d = [ + # UC1: User specified empty q values + ([np.array([])], np.array([])), + # UC2: User specified valid q values + ( + [np.array([5 * np.pi, 4 * np.pi, 3 * np.pi, 2 * np.pi, np.pi, 0])], + np.array([0.4, 0.5, 0.66667, 1, 2, np.inf]), + ), +] + + +@pytest.mark.parametrize("inputs, expected", params_q_to_d) +def test_q_to_d(inputs, expected): + actual = q_to_d(inputs[0]) + assert np.allclose(actual, expected) + + +params_d_to_q = [ + # UC1: User specified empty d values + ([np.array([])], np.array([])), + # UC2: User specified valid d values + ( + [np.array([0, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi])], + np.array([np.inf, 2, 1, 0.66667, 0.5, 0.4]), + ), +] + + +@pytest.mark.parametrize("inputs, expected", params_d_to_q) +def test_d_to_q(inputs, expected): + actual = d_to_q(inputs[0]) + assert np.allclose(actual, expected)