Skip to content

Commit 08de7c9

Browse files
authored
Merge pull request diffpy#199 from yucongalicechen/d-tth
add d_to_tth, tth_to_d
2 parents 443fe36 + 73d801f commit 08de7c9

File tree

3 files changed

+186
-17
lines changed

3 files changed

+186
-17
lines changed

news/d-tth.rst

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
**Added:**
2+
3+
* functions to convert between d and tth
4+
5+
**Changed:**
6+
7+
* <news item>
8+
9+
**Deprecated:**
10+
11+
* <news item>
12+
13+
**Removed:**
14+
15+
* <news item>
16+
17+
**Fixed:**
18+
19+
* <news item>
20+
21+
**Security:**
22+
23+
* <news item>

src/diffpy/utils/transforms.py

Lines changed: 60 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@
1212
"and number is the wavelength in angstroms."
1313
)
1414
invalid_tth_emsg = "Two theta exceeds 180 degrees. Please check the input values for errors."
15-
invalid_q_or_wavelength_emsg = (
16-
"The supplied q-array and wavelength will result in an impossible two-theta. "
15+
invalid_q_or_d_or_wavelength_emsg = (
16+
"The supplied input array and wavelength will result in an impossible two-theta. "
1717
"Please check these values and re-instantiate the DiffractionObject with correct values."
1818
)
1919
inf_output_wmsg = (
20-
"INFO: The largest d-value in the array is infinite. This is allowed, but it will not be plotted."
20+
"INFO: The largest output value in the array is infinite. This is allowed, but it will not be plotted."
2121
)
2222

2323

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

3232

3333
def q_to_tth(q, wavelength):
@@ -141,9 +141,38 @@ def q_to_d(q):
141141
return 2.0 * np.pi / copy(q)
142142

143143

144-
def tth_to_d(ttharray, wavelength):
145-
qarray = tth_to_q(ttharray, wavelength)
146-
return 2.0 * np.pi / copy(qarray)
144+
def tth_to_d(tth, wavelength):
145+
r"""
146+
Helper function to convert two-theta to d on independent variable axis.
147+
148+
The formula is .. math:: d = \frac{\lambda}{2 \sin\left(\frac{2\theta}{2}\right)}.
149+
150+
Here we convert tth to q first, then to d.
151+
152+
Parameters
153+
----------
154+
tth : 1D array
155+
The array of :math:`2\theta` values np.array([tths]).
156+
The units of tth are expected in degrees.
157+
158+
wavelength : float
159+
Wavelength of the incoming x-rays/neutrons/electrons
160+
161+
Returns
162+
-------
163+
d : 1D array
164+
The array of :math:`d` values np.array([ds]).
165+
"""
166+
q = tth_to_q(tth, wavelength)
167+
d = copy(tth)
168+
if wavelength is None:
169+
warnings.warn(wavelength_warning_emsg, UserWarning)
170+
for i, _ in enumerate(tth):
171+
d[i] = i
172+
return d
173+
if 0 in q:
174+
warnings.warn(inf_output_wmsg)
175+
return 2.0 * np.pi / copy(q)
147176

148177

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

168197

169-
def d_to_tth(darray, wavelength):
170-
qarray = d_to_q(darray)
171-
return q_to_tth(qarray, wavelength)
198+
def d_to_tth(d, wavelength):
199+
r"""
200+
Helper function to convert d to two-theta on independent variable axis.
201+
202+
The formula is .. math:: 2\theta = 2 \arcsin\left(\frac{\lambda}{2d}\right).
203+
204+
Here we convert d to q first, then to tth.
205+
206+
Parameters
207+
----------
208+
d : 1D array
209+
The array of :math:`d` values np.array([ds]).
210+
211+
wavelength : float
212+
Wavelength of the incoming x-rays/neutrons/electrons
213+
214+
Returns
215+
-------
216+
tth : 1D array
217+
The array of :math:`2\theta` values np.array([tths]).
218+
The units of tth are expected in degrees.
219+
"""
220+
q = d_to_q(d)
221+
return q_to_tth(q, wavelength)

tests/test_transforms.py

Lines changed: 103 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import numpy as np
22
import pytest
33

4-
from diffpy.utils.transforms import d_to_q, q_to_d, q_to_tth, tth_to_q
4+
from diffpy.utils.transforms import d_to_q, d_to_tth, q_to_d, q_to_tth, tth_to_d, tth_to_q
55

66
params_q_to_tth = [
77
# UC1: Empty q values, no wavelength, return empty arrays
@@ -31,7 +31,7 @@ def test_q_to_tth(inputs, expected):
3131
[4 * np.pi, np.array([0.2, 0.4, 0.6, 0.8, 1, 1.2])],
3232
[
3333
ValueError,
34-
"The supplied q-array and wavelength will result in an impossible two-theta. "
34+
"The supplied input array and wavelength will result in an impossible two-theta. "
3535
"Please check these values and re-instantiate the DiffractionObject with correct values.",
3636
],
3737
),
@@ -40,7 +40,7 @@ def test_q_to_tth(inputs, expected):
4040
[100, np.array([0, 0.2, 0.4, 0.6, 0.8, 1])],
4141
[
4242
ValueError,
43-
"The supplied q-array and wavelength will result in an impossible two-theta. "
43+
"The supplied input array and wavelength will result in an impossible two-theta. "
4444
"Please check these values and re-instantiate the DiffractionObject with correct values.",
4545
],
4646
),
@@ -103,8 +103,8 @@ def test_tth_to_q_bad(inputs, expected):
103103
([np.array([])], np.array([])),
104104
# UC2: User specified valid q values
105105
(
106-
[np.array([5 * np.pi, 4 * np.pi, 3 * np.pi, 2 * np.pi, np.pi, 0])],
107-
np.array([0.4, 0.5, 0.66667, 1, 2, np.inf]),
106+
[np.array([0, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi])],
107+
np.array([np.inf, 2, 1, 0.66667, 0.5, 0.4]),
108108
),
109109
]
110110

@@ -120,8 +120,8 @@ def test_q_to_d(inputs, expected):
120120
([np.array([])], np.array([])),
121121
# UC2: User specified valid d values
122122
(
123-
[np.array([0, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi])],
124-
np.array([np.inf, 2, 1, 0.66667, 0.5, 0.4]),
123+
[np.array([5 * np.pi, 4 * np.pi, 3 * np.pi, 2 * np.pi, np.pi, 0])],
124+
np.array([0.4, 0.5, 0.66667, 1, 2, np.inf]),
125125
),
126126
]
127127

@@ -130,3 +130,99 @@ def test_q_to_d(inputs, expected):
130130
def test_d_to_q(inputs, expected):
131131
actual = d_to_q(inputs[0])
132132
assert np.allclose(actual, expected)
133+
134+
135+
params_tth_to_d = [
136+
# UC0: User specified empty tth values (without wavelength)
137+
([None, np.array([])], np.array([])),
138+
# UC1: User specified empty tth values (with wavelength)
139+
([4 * np.pi, np.array([])], np.array([])),
140+
# UC2: User specified valid tth values between 0-180 degrees (without wavelength)
141+
(
142+
[None, np.array([0, 30, 60, 90, 120, 180])],
143+
np.array([0, 1, 2, 3, 4, 5]),
144+
),
145+
# UC3: User specified valid tth values between 0-180 degrees (with wavelength)
146+
(
147+
[4 * np.pi, np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0])],
148+
np.array([np.inf, 24.27636, 12.56637, 8.88577, 7.25520, 6.28319]),
149+
),
150+
]
151+
152+
153+
@pytest.mark.parametrize("inputs, expected", params_tth_to_d)
154+
def test_tth_to_d(inputs, expected):
155+
actual = tth_to_d(inputs[1], inputs[0])
156+
assert np.allclose(actual, expected)
157+
158+
159+
params_tth_to_d_bad = [
160+
# UC1: user specified an invalid tth value of > 180 degrees (without wavelength)
161+
(
162+
[None, np.array([0, 30, 60, 90, 120, 181])],
163+
[ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors."],
164+
),
165+
# UC2: user specified an invalid tth value of > 180 degrees (with wavelength)
166+
(
167+
[4 * np.pi, np.array([0, 30, 60, 90, 120, 181])],
168+
[ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors."],
169+
),
170+
]
171+
172+
173+
@pytest.mark.parametrize("inputs, expected", params_tth_to_d_bad)
174+
def test_tth_to_d_bad(inputs, expected):
175+
with pytest.raises(expected[0], match=expected[1]):
176+
tth_to_d(inputs[1], inputs[0])
177+
178+
179+
params_d_to_tth = [
180+
# UC1: Empty d values, no wavelength, return empty arrays
181+
([None, np.empty((0))], np.empty((0))),
182+
# UC2: Empty d values, wavelength specified, return empty arrays
183+
([4 * np.pi, np.empty((0))], np.empty(0)),
184+
# UC3: User specified valid d values, no wavelength, return empty arrays
185+
(
186+
[None, np.array([1, 0.8, 0.6, 0.4, 0.2, 0])],
187+
np.array([0, 1, 2, 3, 4, 5]),
188+
),
189+
# UC4: User specified valid d values (with wavelength)
190+
(
191+
[4 * np.pi, np.array([4 * np.pi, 4 / np.sqrt(2) * np.pi, 4 / np.sqrt(3) * np.pi])],
192+
np.array([60.0, 90.0, 120.0]),
193+
),
194+
]
195+
196+
197+
@pytest.mark.parametrize("inputs, expected", params_d_to_tth)
198+
def test_d_to_tth(inputs, expected):
199+
actual = d_to_tth(inputs[1], inputs[0])
200+
assert np.allclose(expected, actual)
201+
202+
203+
params_d_to_tth_bad = [
204+
# UC1: user specified invalid d values that result in tth > 180 degrees
205+
(
206+
[4 * np.pi, np.array([1.2, 1, 0.8, 0.6, 0.4, 0.2])],
207+
[
208+
ValueError,
209+
"The supplied input array and wavelength will result in an impossible two-theta. "
210+
"Please check these values and re-instantiate the DiffractionObject with correct values.",
211+
],
212+
),
213+
# UC2: user specified a wrong wavelength that result in tth > 180 degrees
214+
(
215+
[100, np.array([1, 0.8, 0.6, 0.4, 0.2, 0])],
216+
[
217+
ValueError,
218+
"The supplied input array and wavelength will result in an impossible two-theta. "
219+
"Please check these values and re-instantiate the DiffractionObject with correct values.",
220+
],
221+
),
222+
]
223+
224+
225+
@pytest.mark.parametrize("inputs, expected", params_d_to_tth_bad)
226+
def test_d_to_tth_bad(inputs, expected):
227+
with pytest.raises(expected[0], match=expected[1]):
228+
d_to_tth(inputs[1], inputs[0])

0 commit comments

Comments
 (0)