Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions news/d-tth.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
**Added:**

* functions to convert between d and tth

**Changed:**

* <news item>

**Deprecated:**

* <news item>

**Removed:**

* <news item>

**Fixed:**

* <news item>

**Security:**

* <news item>
70 changes: 60 additions & 10 deletions src/diffpy/utils/transforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@
"and number is the wavelength in angstroms."
)
invalid_tth_emsg = "Two theta exceeds 180 degrees. Please check the input values for errors."
invalid_q_or_wavelength_emsg = (
"The supplied q-array and wavelength will result in an impossible two-theta. "
invalid_q_or_d_or_wavelength_emsg = (
"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."
)
inf_output_wmsg = (
"INFO: The largest d-value in the array is infinite. This is allowed, but it will not be plotted."
"INFO: The largest output value in the array is infinite. This is allowed, but it will not be plotted."
)


Expand All @@ -27,7 +27,7 @@ def _validate_inputs(q, wavelength):
return np.empty(0)
pre_factor = wavelength / (4 * np.pi)
if np.any(np.abs(q * pre_factor) > 1.0):
raise ValueError(invalid_q_or_wavelength_emsg)
raise ValueError(invalid_q_or_d_or_wavelength_emsg)


def q_to_tth(q, wavelength):
Expand Down Expand Up @@ -141,9 +141,38 @@ def q_to_d(q):
return 2.0 * np.pi / copy(q)


def tth_to_d(ttharray, wavelength):
qarray = tth_to_q(ttharray, wavelength)
return 2.0 * np.pi / copy(qarray)
def tth_to_d(tth, wavelength):
r"""
Helper function to convert two-theta to d on independent variable axis.

The formula is .. math:: d = \frac{\lambda}{2 \sin\left(\frac{2\theta}{2}\right)}.

Here we convert tth to q first, then to d.

Parameters
----------
tth : 1D array
The array of :math:`2\theta` values np.array([tths]).
The units of tth are expected in degrees.

wavelength : float
Wavelength of the incoming x-rays/neutrons/electrons

Returns
-------
d : 1D array
The array of :math:`d` values np.array([ds]).
"""
q = tth_to_q(tth, wavelength)
d = copy(tth)
if wavelength is None:
warnings.warn(wavelength_warning_emsg, UserWarning)
for i, _ in enumerate(tth):
d[i] = i
return d
if 0 in q:
warnings.warn(inf_output_wmsg)
return 2.0 * np.pi / copy(q)


def d_to_q(d):
Expand All @@ -166,6 +195,27 @@ def d_to_q(d):
return 2.0 * np.pi / copy(d)


def d_to_tth(darray, wavelength):
qarray = d_to_q(darray)
return q_to_tth(qarray, wavelength)
def d_to_tth(d, wavelength):
r"""
Helper function to convert d to two-theta on independent variable axis.

The formula is .. math:: 2\theta = 2 \arcsin\left(\frac{\lambda}{2d}\right).

Here we convert d to q first, then to tth.

Parameters
----------
d : 1D array
The array of :math:`d` values np.array([ds]).

wavelength : float
Wavelength of the incoming x-rays/neutrons/electrons

Returns
-------
tth : 1D array
The array of :math:`2\theta` values np.array([tths]).
The units of tth are expected in degrees.
"""
q = d_to_q(d)
return q_to_tth(q, wavelength)
110 changes: 103 additions & 7 deletions tests/test_transforms.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import numpy as np
import pytest

from diffpy.utils.transforms import d_to_q, q_to_d, q_to_tth, tth_to_q
from diffpy.utils.transforms import d_to_q, d_to_tth, q_to_d, q_to_tth, tth_to_d, tth_to_q

params_q_to_tth = [
# UC1: Empty q values, no wavelength, return empty arrays
Expand Down Expand Up @@ -31,7 +31,7 @@ def test_q_to_tth(inputs, expected):
[4 * np.pi, np.array([0.2, 0.4, 0.6, 0.8, 1, 1.2])],
[
ValueError,
"The supplied q-array and wavelength will result in an impossible two-theta. "
"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.",
],
),
Expand All @@ -40,7 +40,7 @@ def test_q_to_tth(inputs, expected):
[100, np.array([0, 0.2, 0.4, 0.6, 0.8, 1])],
[
ValueError,
"The supplied q-array and wavelength will result in an impossible two-theta. "
"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.",
],
),
Expand Down Expand Up @@ -103,8 +103,8 @@ def test_tth_to_q_bad(inputs, expected):
([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]),
[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]),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

small edit so that we have an increasing qarray

),
]

Expand All @@ -120,8 +120,8 @@ def test_q_to_d(inputs, expected):
([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]),
[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]),
),
]

Expand All @@ -130,3 +130,99 @@ def test_q_to_d(inputs, expected):
def test_d_to_q(inputs, expected):
actual = d_to_q(inputs[0])
assert np.allclose(actual, expected)


params_tth_to_d = [
# UC0: User specified empty tth values (without wavelength)
([None, np.array([])], np.array([])),
# UC1: User specified empty tth values (with wavelength)
([4 * np.pi, np.array([])], np.array([])),
# UC2: User specified valid tth values between 0-180 degrees (without wavelength)
(
[None, np.array([0, 30, 60, 90, 120, 180])],
np.array([0, 1, 2, 3, 4, 5]),
),
# UC3: User specified valid tth values between 0-180 degrees (with wavelength)
(
[4 * np.pi, np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0])],
np.array([np.inf, 24.27636, 12.56637, 8.88577, 7.25520, 6.28319]),
),
]


@pytest.mark.parametrize("inputs, expected", params_tth_to_d)
def test_tth_to_d(inputs, expected):
actual = tth_to_d(inputs[1], inputs[0])
assert np.allclose(actual, expected)


params_tth_to_d_bad = [
# UC1: user specified an invalid tth value of > 180 degrees (without wavelength)
(
[None, np.array([0, 30, 60, 90, 120, 181])],
[ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors."],
),
# UC2: user specified an invalid tth value of > 180 degrees (with wavelength)
(
[4 * np.pi, np.array([0, 30, 60, 90, 120, 181])],
[ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors."],
),
]


@pytest.mark.parametrize("inputs, expected", params_tth_to_d_bad)
def test_tth_to_d_bad(inputs, expected):
with pytest.raises(expected[0], match=expected[1]):
tth_to_d(inputs[1], inputs[0])


params_d_to_tth = [
# UC1: Empty d values, no wavelength, return empty arrays
([None, np.empty((0))], np.empty((0))),
# UC2: Empty d values, wavelength specified, return empty arrays
([4 * np.pi, np.empty((0))], np.empty(0)),
# UC3: User specified valid d values, no wavelength, return empty arrays
(
[None, np.array([1, 0.8, 0.6, 0.4, 0.2, 0])],
np.array([0, 1, 2, 3, 4, 5]),
),
# UC4: User specified valid d values (with wavelength)
(
[4 * np.pi, np.array([4 * np.pi, 4 / np.sqrt(2) * np.pi, 4 / np.sqrt(3) * np.pi])],
np.array([60.0, 90.0, 120.0]),
),
]


@pytest.mark.parametrize("inputs, expected", params_d_to_tth)
def test_d_to_tth(inputs, expected):
actual = d_to_tth(inputs[1], inputs[0])
assert np.allclose(expected, actual)


params_d_to_tth_bad = [
# UC1: user specified invalid d values that result in tth > 180 degrees
(
[4 * np.pi, np.array([1.2, 1, 0.8, 0.6, 0.4, 0.2])],
[
ValueError,
"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.",
],
),
# UC2: user specified a wrong wavelength that result in tth > 180 degrees
(
[100, np.array([1, 0.8, 0.6, 0.4, 0.2, 0])],
[
ValueError,
"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.mark.parametrize("inputs, expected", params_d_to_tth_bad)
def test_d_to_tth_bad(inputs, expected):
with pytest.raises(expected[0], match=expected[1]):
d_to_tth(inputs[1], inputs[0])
Loading