Skip to content

Commit 4f54439

Browse files
authored
Merge branch 'main' into no-empty-object
2 parents e285214 + 7b6ee2e commit 4f54439

File tree

9 files changed

+168
-76
lines changed

9 files changed

+168
-76
lines changed

doc/source/examples/resampleexample.rst

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ given enough datapoints.
5858
nickel_resample = wsinterp(grid, nickel_grid, nickel_func)
5959
target_resample = wsinterp(grid, target_grid, target_func)
6060

61-
We can now plot the difference to see that these two functions are quite similar.:
61+
We can now plot the difference to see that these two functions are quite similar.::
6262

6363
plt.plot(grid, target_resample)
6464
plt.plot(grid, nickel_resample)
@@ -78,3 +78,10 @@ given enough datapoints.
7878
In the case of our dataset, our band-limit is ``qmax=25.0`` and our function spans :math:`r \in (0.0, 60.0)`.
7979
Thus, our original grid requires :math:`25.0 * 60.0 / \pi < 478`. Since our grid has :math:`601` datapoints, our
8080
reconstruction was perfect as shown from the comparison between ``Nickel.gr`` and ``NiTarget.gr``.
81+
82+
This computation is implemented in the function ``nsinterp``.::
83+
84+
from diffpy.utils.resampler import nsinterp
85+
qmin = 0
86+
qmax = 25
87+
nickel_resample = (nickel_grid, nickel_func, qmin, qmax)

news/nsinterp.rst

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
**Added:**
2+
3+
* Function nsinterp for automatic interpolation onto the Nyquist-Shannon grid.
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/resample-dep.rst

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
**Added:**
2+
3+
* <news item>
4+
5+
**Changed:**
6+
7+
* <news item>
8+
9+
**Deprecated:**
10+
11+
* `resample` function in resampler. Replaced with `wsinterp` with better functionality.
12+
13+
**Removed:**
14+
15+
* <news item>
16+
17+
**Fixed:**
18+
19+
* <news item>
20+
21+
**Security:**
22+
23+
* <news item>

news/rm-range-methods.rst

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
**Added:**
2+
3+
* <news item>
4+
5+
**Changed:**
6+
7+
* <news item>
8+
9+
**Deprecated:**
10+
11+
* <news item>
12+
13+
**Removed:**
14+
15+
* `set_angles_from_list`, `set_angles_from`, `set_qs_from_range` methods in `DiffractionObject`
16+
17+
**Fixed:**
18+
19+
* <news item>
20+
21+
**Security:**
22+
23+
* <news item>

src/diffpy/utils/diffraction_objects.py

Lines changed: 0 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -270,73 +270,6 @@ def id(self):
270270
def id(self, _):
271271
raise AttributeError(_setter_wmsg("id"))
272272

273-
def set_angles_from_list(self, angles_list):
274-
self.angles = angles_list
275-
self.n_steps = len(angles_list) - 1.0
276-
self.begin_angle = self.angles[0]
277-
self.end_angle = self.angles[-1]
278-
279-
def set_qs_from_range(self, begin_q, end_q, step_size=None, n_steps=None):
280-
"""
281-
create an array of linear spaced Q-values
282-
283-
Parameters
284-
----------
285-
begin_q float
286-
the beginning angle
287-
end_q float
288-
the ending angle
289-
step_size float
290-
the size of the step between points. Only specify step_size or n_steps, not both
291-
n_steps integer
292-
the number of steps. Odd numbers are preferred. Only specify step_size or n_steps, not both
293-
294-
Returns
295-
-------
296-
Sets self.qs
297-
self.qs array of floats
298-
the q values in the independent array
299-
300-
"""
301-
self.qs = self._set_array_from_range(begin_q, end_q, step_size=step_size, n_steps=n_steps)
302-
303-
def set_angles_from_range(self, begin_angle, end_angle, step_size=None, n_steps=None):
304-
"""
305-
create an array of linear spaced angle-values
306-
307-
Parameters
308-
----------
309-
begin_angle float
310-
the beginning angle
311-
end_angle float
312-
the ending angle
313-
step_size float
314-
the size of the step between points. Only specify step_size or n_steps, not both
315-
n_steps integer
316-
the number of steps. Odd numbers are preferred. Only specify step_size or n_steps, not both
317-
318-
Returns
319-
-------
320-
Sets self.angles
321-
self.angles array of floats
322-
the q values in the independent array
323-
324-
"""
325-
self.angles = self._set_array_from_range(begin_angle, end_angle, step_size=step_size, n_steps=n_steps)
326-
327-
def _set_array_from_range(self, begin, end, step_size=None, n_steps=None):
328-
if step_size is not None and n_steps is not None:
329-
print(
330-
"WARNING: both step_size and n_steps have been given. n_steps will be used and step_size will be "
331-
"reset."
332-
)
333-
array = np.linspace(begin, end, n_steps)
334-
elif step_size is not None:
335-
array = np.arange(begin, end, step_size)
336-
elif n_steps is not None:
337-
array = np.linspace(begin, end, n_steps)
338-
return array
339-
340273
def get_array_index(self, value, xtype=None):
341274
"""
342275
Return the index of the closest value in the array associated with the specified xtype.

src/diffpy/utils/resampler.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
"""Various utilities related to data parsing and manipulation."""
1717

18+
import warnings
19+
1820
import numpy as np
1921

2022

@@ -77,6 +79,46 @@ def wsinterp(x, xp, fp, left=None, right=None):
7779
return fp_at_x
7880

7981

82+
def nsinterp(xp, fp, qmin=0, qmax=25, left=None, right=None):
83+
"""One-dimensional Whittaker-Shannon interpolation onto the Nyquist-Shannon grid.
84+
85+
Takes a band-limited function fp and original grid xp and resamples fp on the NS grid.
86+
Uses the minimum number of points N required by the Nyquist sampling theorem.
87+
N = (qmax-qmin)(rmax-rmin)/pi, where rmin and rmax are the ends of the real-space ranges.
88+
fp must be finite, and the user inputs qmin and qmax of the frequency-domain.
89+
90+
Parameters
91+
----------
92+
xp: ndarray
93+
The array of known x values.
94+
fp: ndarray
95+
The array of y values associated with xp.
96+
qmin: float
97+
The lower band limit in the frequency domain.
98+
qmax: float
99+
The upper band limit in the frequency domain.
100+
101+
Returns
102+
-------
103+
x: ndarray
104+
The Nyquist-Shannon grid computed for the given qmin and qmax.
105+
fp_at_x: ndarray
106+
The interpolated values at points x. Returns a single float if x is a scalar,
107+
otherwise returns a numpy.ndarray.
108+
"""
109+
# Ensure numpy array
110+
xp = np.array(xp)
111+
rmin = np.min(xp)
112+
rmax = np.max(xp)
113+
114+
nspoints = int(np.round((qmax - qmin) * (rmax - rmin) / np.pi))
115+
116+
x = np.linspace(rmin, rmax, nspoints)
117+
fp_at_x = wsinterp(x, xp, fp)
118+
119+
return x, fp_at_x
120+
121+
80122
def resample(r, s, dr):
81123
"""Resample a PDF on a new grid.
82124
@@ -97,6 +139,13 @@ def resample(r, s, dr):
97139
Returns resampled (r, s).
98140
"""
99141

142+
warnings.warn(
143+
"The 'resample' function is deprecated and will be removed in a future release (3.8.0). \n"
144+
"'resample' has been renamed 'wsinterp' to better reflect functionality. Please use 'wsinterp' instead.",
145+
DeprecationWarning,
146+
stacklevel=2,
147+
)
148+
100149
dr0 = r[1] - r[0] # Constant timestep
101150

102151
if dr0 < dr:

tests/conftest.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,11 @@ def _load(filename):
3535

3636
@pytest.fixture
3737
def do_minimal():
38-
# Create an instance of DiffractionObject with minimal setup
38+
# Create an instance of DiffractionObject with empty xarray and yarray values, and a non-empty wavelength
3939
return DiffractionObject(xarray=np.empty(0), yarray=np.empty(0), xtype="tth", wavelength=1.54)
40+
41+
@pytest.fixture
42+
def do_minimal_tth():
43+
# Create an instance of DiffractionObject with non-empty xarray, yarray, and wavelength values
44+
return DiffractionObject(wavelength=2 * np.pi, xarray=np.array([30, 60]), yarray=np.array([1, 2]), xtype="tth")
45+

tests/test_diffraction_objects.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -137,12 +137,20 @@ def test_diffraction_objects_equality(inputs1, inputs2, expected):
137137
assert (do_1 == do_2) == expected
138138

139139

140-
def test_on_xtype():
141-
do = DiffractionObject(wavelength=2 * np.pi, xarray=np.array([30, 60]), yarray=np.array([1, 2]), xtype="tth")
142-
assert np.allclose(do.on_xtype("tth"), [np.array([30, 60]), np.array([1, 2])])
143-
assert np.allclose(do.on_xtype("2theta"), [np.array([30, 60]), np.array([1, 2])])
144-
assert np.allclose(do.on_xtype("q"), [np.array([0.51764, 1]), np.array([1, 2])])
145-
assert np.allclose(do.on_xtype("d"), [np.array([12.13818, 6.28319]), np.array([1, 2])])
140+
@pytest.mark.parametrize(
141+
"xtype, expected_xarray",
142+
[
143+
("tth", np.array([30, 60])),
144+
("2theta", np.array([30, 60])),
145+
("q", np.array([0.51764, 1])),
146+
("d", np.array([12.13818, 6.28319])),
147+
],
148+
)
149+
def test_on_xtype(xtype, expected_xarray, do_minimal_tth):
150+
do = do_minimal_tth
151+
actual_xrray, actual_yarray = do.on_xtype(xtype)
152+
assert np.allclose(actual_xrray, expected_xarray)
153+
assert np.allclose(actual_yarray, np.array([1, 2]))
146154

147155

148156
def test_init_invalid_xtype():

tests/test_resample.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import numpy as np
44
import pytest
55

6-
from diffpy.utils.resampler import wsinterp
6+
from diffpy.utils.resampler import nsinterp, wsinterp
77

88

99
def test_wsinterp():
@@ -30,3 +30,23 @@ def test_wsinterp():
3030
assert np.allclose(fp_at_x[1:-1], fp)
3131
for i in range(len(x)):
3232
assert fp_at_x[i] == pytest.approx(wsinterp(x[i], xp, fp))
33+
34+
35+
def test_nsinterp():
36+
# Create a cosine function cos(2x) for x \in [0, 3pi]
37+
xp = np.linspace(0, 3 * np.pi, 100)
38+
fp = np.cos(2 * xp)
39+
40+
# Want to resample onto the grid {0, pi, 2pi, 3pi}
41+
x = np.array([0, np.pi, 2 * np.pi, 3 * np.pi])
42+
43+
# Get wsinterp result
44+
ws_f = wsinterp(x, xp, fp)
45+
46+
# Use nsinterp with qmin-qmax=4/3
47+
qmin = np.random.uniform()
48+
qmax = qmin + 4 / 3
49+
ns_x, ns_f = nsinterp(xp, fp, qmin, qmax)
50+
51+
assert np.allclose(x, ns_x)
52+
assert np.allclose(ws_f, ns_f)

0 commit comments

Comments
 (0)