Skip to content

Commit 73d801f

Browse files
merge from main
2 parents 25db4e7 + 3aef608 commit 73d801f

File tree

6 files changed

+175
-24
lines changed

6 files changed

+175
-24
lines changed

news/array_index.rst

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
**Added:**
2+
3+
* function to return the index of the closest value to the specified value in an array.
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>

news/d-q.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 q
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/diffraction_objects.py

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -259,15 +259,29 @@ def _set_array_from_range(self, begin, end, step_size=None, n_steps=None):
259259
array = np.linspace(begin, end, n_steps)
260260
return array
261261

262-
def get_angle_index(self, angle):
263-
count = 0
264-
for i, target in enumerate(self.angles):
265-
if angle == target:
266-
return i
267-
else:
268-
count += 1
269-
if count >= len(self.angles):
270-
raise IndexError(f"WARNING: no angle {angle} found in angles list")
262+
def get_array_index(self, value, xtype=None):
263+
"""
264+
returns the index of the closest value in the array associated with the specified xtype
265+
266+
Parameters
267+
----------
268+
xtype str
269+
the xtype used to access the array
270+
value float
271+
the target value to search for
272+
273+
Returns
274+
-------
275+
the index of the value in the array
276+
"""
277+
278+
if xtype is None:
279+
xtype = self.input_xtype
280+
array = self.on_xtype(xtype)[0]
281+
if len(array) == 0:
282+
raise ValueError(f"The '{xtype}' array is empty. Please ensure it is initialized.")
283+
i = (np.abs(array - value)).argmin()
284+
return i
271285

272286
def _set_xarrays(self, xarray, xtype):
273287
self._all_arrays = np.empty(shape=(len(xarray), 4))

src/diffpy/utils/transforms.py

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
"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
)
19-
inf_output_wmsg = "WARNING: The largest output is infinite and cannot be plotted."
19+
inf_output_wmsg = (
20+
"INFO: The largest output value in the array is infinite. This is allowed, but it will not be plotted."
21+
)
2022

2123

2224
def _validate_inputs(q, wavelength):
@@ -59,7 +61,6 @@ def q_to_tth(q, wavelength):
5961
-------
6062
tth : 1D array
6163
The array of :math:`2\theta` values in degrees numpy.array([tths]).
62-
This is the correct format for loading into diffpy.utils.DiffractionOject.on_tth
6364
"""
6465
_validate_inputs(q, wavelength)
6566
q.astype(float)
@@ -93,21 +94,18 @@ def tth_to_q(tth, wavelength):
9394
9495
Parameters
9596
----------
96-
tth : 2D array
97-
The array of :math:`2\theta` values and :math: 'i' intensity values, np.array([[tths], [is]]).
98-
This is the same format as, and so can accept, diffpy.utils.DiffractionOject.on_tth
97+
tth : 1D array
98+
The array of :math:`2\theta` values np.array([tths]).
9999
The units of tth are expected in degrees.
100100
101101
wavelength : float
102102
Wavelength of the incoming x-rays/neutrons/electrons
103103
104104
Returns
105105
-------
106-
on_q : 2D array
107-
The array of :math:`q` values and :math: 'i' intensity values unchanged,
108-
np.array([[qs], [is]]).
106+
q : 1D array
107+
The array of :math:`q` values np.array([qs]).
109108
The units for the q-values are the inverse of the units of the provided wavelength.
110-
This is the correct format for loading into diffpy.utils.DiffractionOject.on_q
111109
"""
112110
tth.astype(float)
113111
if np.any(np.deg2rad(tth) > np.pi):
@@ -117,13 +115,30 @@ def tth_to_q(tth, wavelength):
117115
pre_factor = (4.0 * np.pi) / wavelength
118116
q = pre_factor * np.sin(np.deg2rad(tth / 2))
119117
else: # return intensities vs. an x-array that is just the index
118+
warnings.warn(wavelength_warning_emsg, UserWarning)
120119
for i, _ in enumerate(q):
121120
q[i] = i
122121
return q
123122

124123

125-
def q_to_d(qarray):
126-
return 2.0 * np.pi / copy(qarray)
124+
def q_to_d(q):
125+
r"""
126+
Helper function to convert q to d on independent variable axis, using :math:`d = \frac{2 \pi}{q}`.
127+
128+
Parameters
129+
----------
130+
q : 1D array
131+
The array of :math:`q` values np.array([qs]).
132+
The units of q must be reciprocal of the units of wavelength.
133+
134+
Returns
135+
-------
136+
d : 1D array
137+
The array of :math:`d` values np.array([ds]).
138+
"""
139+
if 0 in q:
140+
print(inf_output_wmsg)
141+
return 2.0 * np.pi / copy(q)
127142

128143

129144
def tth_to_d(tth, wavelength):
@@ -160,8 +175,24 @@ def tth_to_d(tth, wavelength):
160175
return 2.0 * np.pi / copy(q)
161176

162177

163-
def d_to_q(darray):
164-
return 2.0 * np.pi / copy(darray)
178+
def d_to_q(d):
179+
r"""
180+
Helper function to convert q to d using :math:`d = \frac{2 \pi}{q}`.
181+
182+
Parameters
183+
----------
184+
d : 1D array
185+
The array of :math:`d` values np.array([ds]).
186+
187+
Returns
188+
-------
189+
q : 1D array
190+
The array of :math:`q` values np.array([qs]).
191+
The units of q must be reciprocal of the units of wavelength.
192+
"""
193+
if 0 in d:
194+
warnings.warn(inf_output_wmsg)
195+
return 2.0 * np.pi / copy(d)
165196

166197

167198
def d_to_tth(d, wavelength):

tests/test_diffraction_objects.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import re
12
from pathlib import Path
23

34
import numpy as np
@@ -212,6 +213,31 @@ def _test_valid_diffraction_objects(actual_diffraction_object, function, expecte
212213
return np.allclose(actual_array, expected_array)
213214

214215

216+
params_index = [
217+
# UC1: exact match
218+
([4 * np.pi, np.array([30.005, 60]), np.array([1, 2]), "tth", "tth", 30.005], [0]),
219+
# UC2: target value lies in the array, returns the (first) closest index
220+
([4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 45], [0]),
221+
([4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "q", 0.25], [0]),
222+
# UC3: target value out of the range, returns the closest index
223+
([4 * np.pi, np.array([0.25, 0.5, 0.71]), np.array([1, 2, 3]), "q", "q", 0.1], [0]),
224+
([4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 63], [1]),
225+
]
226+
227+
228+
@pytest.mark.parametrize("inputs, expected", params_index)
229+
def test_get_array_index(inputs, expected):
230+
test = DiffractionObject(wavelength=inputs[0], xarray=inputs[1], yarray=inputs[2], xtype=inputs[3])
231+
actual = test.get_array_index(value=inputs[5], xtype=inputs[4])
232+
assert actual == expected[0]
233+
234+
235+
def test_get_array_index_bad():
236+
test = DiffractionObject(wavelength=2 * np.pi, xarray=np.array([]), yarray=np.array([]), xtype="tth")
237+
with pytest.raises(ValueError, match=re.escape("The 'tth' array is empty. Please ensure it is initialized.")):
238+
test.get_array_index(value=30)
239+
240+
215241
def test_dump(tmp_path, mocker):
216242
x, y = np.linspace(0, 5, 6), np.linspace(0, 5, 6)
217243
directory = Path(tmp_path)

tests/test_transforms.py

Lines changed: 36 additions & 2 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_tth, q_to_tth, tth_to_d, 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
@@ -64,7 +64,7 @@ def test_q_to_tth_bad(inputs, expected):
6464
np.array([0, 1, 2, 3, 4, 5]),
6565
),
6666
# UC3: User specified valid tth values between 0-180 degrees (with wavelength)
67-
# expected q vales are sin15, sin30, sin45, sin60, sin90
67+
# expected q values are sin15, sin30, sin45, sin60, sin90
6868
(
6969
[4 * np.pi, np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0])],
7070
np.array([0, 0.258819, 0.5, 0.707107, 0.866025, 1]),
@@ -98,6 +98,40 @@ def test_tth_to_q_bad(inputs, expected):
9898
tth_to_q(inputs[1], inputs[0])
9999

100100

101+
params_q_to_d = [
102+
# UC1: User specified empty q values
103+
([np.array([])], np.array([])),
104+
# UC2: User specified valid q values
105+
(
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]),
108+
),
109+
]
110+
111+
112+
@pytest.mark.parametrize("inputs, expected", params_q_to_d)
113+
def test_q_to_d(inputs, expected):
114+
actual = q_to_d(inputs[0])
115+
assert np.allclose(actual, expected)
116+
117+
118+
params_d_to_q = [
119+
# UC1: User specified empty d values
120+
([np.array([])], np.array([])),
121+
# UC2: User specified valid d values
122+
(
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]),
125+
),
126+
]
127+
128+
129+
@pytest.mark.parametrize("inputs, expected", params_d_to_q)
130+
def test_d_to_q(inputs, expected):
131+
actual = d_to_q(inputs[0])
132+
assert np.allclose(actual, expected)
133+
134+
101135
params_tth_to_d = [
102136
# UC0: User specified empty tth values (without wavelength)
103137
([None, np.array([])], np.array([])),

0 commit comments

Comments
 (0)