Skip to content

Commit 5d07b9a

Browse files
add news and more tests
1 parent d65dce1 commit 5d07b9a

File tree

3 files changed

+122
-67
lines changed

3 files changed

+122
-67
lines changed

news/scaleto.rst

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
**Added:**
2+
3+
* functionality to rescale diffraction objects, placing one on top of another at a specified point
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 & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -364,41 +364,43 @@ def on_tth(self):
364364
def on_d(self):
365365
return [self.all_arrays[:, 3], self.all_arrays[:, 0]]
366366

367-
def scale_to(self, target_diff_object, xtype=None, xvalue=None):
368-
f"""
367+
def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=0):
368+
"""
369369
returns a new diffraction object which is the current object but rescaled in y to the target
370370
371+
The y-value in the target at the closest specified x-value will be used as the factor to scale to.
372+
The entire array is scaled by this factor so that one object places on top of the other at that point.
373+
If multiple values of `q`, `tth`, or `d` are provided, the priority is `q` > `tth` > `d`.
374+
If none are provided, the midpoint of the current object's `q`-array will be used.
375+
371376
Parameters
372377
----------
373378
target_diff_object: DiffractionObject
374-
the diffraction object you want to scale the current one on to
375-
xtype: string, optional. Default is Q
376-
the xtype, from {XQUANTITIES}, that you will specify a point from to scale to
377-
xvalue: float. Default is the midpoint of the array
378-
the y-value in the target at this x-value will be used as the factor to scale to.
379-
The entire array is scaled be the factor that places on on top of the other at that point.
380-
xvalue does not have to be in the x-array, the point closest to this point will be used for the scaling.
379+
the diffraction object you want to scale the current one onto
380+
381+
q, tth, d : float, optional, default is the midpoint of the current object's `q`-array
382+
the xvalue (in `q`, `tth`, or `d` space) to align the current and target objects
383+
384+
offset : float, optional, default is 0
385+
an offset to add to the scaled y-values
381386
382387
Returns
383388
-------
384389
the rescaled DiffractionObject as a new object
385-
386390
"""
387391
scaled = deepcopy(self)
388-
if xtype is None:
389-
xtype = "q"
392+
xtype = "q" if q is not None else "tth" if tth is not None else "d" if d is not None else "q"
393+
data, target = self.on_xtype(xtype), target_diff_object.on_xtype(xtype)
394+
if len(data[0]) == 0 or len(target[0]) == 0:
395+
raise ValueError("I cannot scale diffraction objects with empty arrays.")
390396

391-
data = self.on_xtype(xtype)
392-
target = target_diff_object.on_xtype(xtype)
393-
if len(data[0]) == 0 or len(target[0]) == 0 or len(data[0]) != len(target[0]):
394-
raise ValueError("I cannot scale two diffraction objects with empty or different lengths.")
397+
xvalue = q if xtype == "q" else tth if xtype == "tth" else d
395398
if xvalue is None:
396-
xvalue = data[0][0] + (data[0][-1] - data[0][0]) / 2.0
399+
xvalue = (data[0][0] + data[0][-1]) / 2.0
397400

398-
xindex = (np.abs(data[0] - xvalue)).argmin()
399-
ytarget = target[1][xindex]
400-
yself = data[1][xindex]
401-
scaled._all_arrays[:, 0] = data[1] * ytarget / yself
401+
x_data, x_target = (np.abs(data[0] - xvalue)).argmin(), (np.abs(target[0] - xvalue)).argmin()
402+
y_data, y_target = data[1][x_data], target[1][x_target]
403+
scaled._all_arrays[:, 0] = data[1] * y_target / y_data + offset
402404
return scaled
403405

404406
def on_xtype(self, xtype):

tests/test_diffraction_objects.py

Lines changed: 76 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -224,96 +224,126 @@ def test_on_xtype_bad():
224224

225225

226226
params_scale_to = [
227-
# UC1: xvalue exact match
227+
# UC1: same x-array and y-array, check offset
228228
(
229229
[
230230
np.array([10, 15, 25, 30, 60, 140]),
231-
np.array([10, 20, 25, 30, 60, 100]),
231+
np.array([2, 3, 4, 5, 6, 7]),
232232
"tth",
233233
2 * np.pi,
234234
np.array([10, 15, 25, 30, 60, 140]),
235235
np.array([2, 3, 4, 5, 6, 7]),
236236
"tth",
237237
2 * np.pi,
238+
None,
239+
60,
240+
None,
241+
2.1,
242+
],
243+
["tth", np.array([4.1, 5.1, 6.1, 7.1, 8.1, 9.1])],
244+
),
245+
# UC2: same length x-arrays with exact x-value match
246+
(
247+
[
248+
np.array([10, 15, 25, 30, 60, 140]),
249+
np.array([10, 20, 25, 30, 60, 100]),
250+
"tth",
251+
2 * np.pi,
252+
np.array([10, 20, 25, 30, 60, 140]),
253+
np.array([2, 3, 4, 5, 6, 7]),
238254
"tth",
255+
2 * np.pi,
256+
None,
239257
60,
258+
None,
259+
0,
240260
],
241-
[np.array([1, 2, 2.5, 3, 6, 10])],
261+
["tth", np.array([1, 2, 2.5, 3, 6, 10])],
242262
),
243-
# UC2: xvalue approximate match
263+
# UC3: same length x-arrays with approximate x-value match
244264
(
245265
[
246-
np.array([0.11, 0.24, 0.31, 0.4]),
266+
np.array([0.12, 0.24, 0.31, 0.4]),
247267
np.array([10, 20, 40, 60]),
248268
"q",
249269
2 * np.pi,
250-
np.array([0.11, 0.24, 0.31, 0.4]),
270+
np.array([0.14, 0.24, 0.31, 0.4]),
251271
np.array([1, 3, 4, 5]),
252272
"q",
253273
2 * np.pi,
254-
"q",
255274
0.1,
275+
None,
276+
None,
277+
0,
256278
],
257-
[np.array([1, 2, 4, 6])],
279+
["q", np.array([1, 2, 4, 6])],
258280
),
259-
]
260-
261-
262-
@pytest.mark.parametrize("inputs, expected", params_scale_to)
263-
def test_scale_to(inputs, expected):
264-
orig_diff_object = DiffractionObject(xarray=inputs[0], yarray=inputs[1], xtype=inputs[2], wavelength=inputs[3])
265-
target_diff_object = DiffractionObject(
266-
xarray=inputs[4], yarray=inputs[5], xtype=inputs[6], wavelength=inputs[7]
267-
)
268-
scaled_diff_object = orig_diff_object.scale_to(target_diff_object, xtype=inputs[8], xvalue=inputs[9])
269-
# Check the intensity data is same as expected
270-
assert np.allclose(scaled_diff_object.on_xtype(inputs[8])[1], expected[0])
271-
272-
273-
params_scale_to_bad = [
274-
# UC1: at least one of the y-arrays is empty
281+
# UC4: different x-array lengths with approximate x-value match
275282
(
276283
[
277-
np.array([]),
278-
np.array([]),
284+
np.array([10, 25, 30.1, 40.2, 61, 120, 140]),
285+
np.array([10, 20, 30, 40, 50, 60, 100]),
279286
"tth",
280287
2 * np.pi,
281-
np.array([11, 14, 16, 20, 25, 30]),
282-
np.array([2, 3, 4, 5, 6, 7]),
288+
np.array([20, 25.5, 32, 45, 50, 62, 100, 125, 140]),
289+
np.array([1.1, 2, 3, 3.5, 4, 5, 10, 12, 13]),
283290
"tth",
284291
2 * np.pi,
285-
"tth",
292+
None,
286293
60,
287-
]
294+
None,
295+
0,
296+
],
297+
# scaling factor is calculated at index = 5 for self and index = 6 for target
298+
["tth", np.array([1, 2, 3, 4, 5, 6, 10])],
288299
),
289-
# UC2: diffraction objects with different array lengths
300+
# UC5: user specified multiple x-values, prioritize q > tth > d
290301
(
291302
[
292-
np.array([0.11, 0.24, 0.31, 0.4, 0.5]),
293-
np.array([10, 20, 40, 50, 60]),
294-
"q",
303+
np.array([10, 25, 30.1, 40.2, 61, 120, 140]),
304+
np.array([10, 20, 30, 40, 50, 60, 100]),
305+
"tth",
295306
2 * np.pi,
296-
np.array([0.1, 0.15, 0.3, 0.4]),
297-
np.array([1, 3, 4, 5]),
298-
"q",
307+
np.array([20, 25.5, 32, 45, 50, 62, 100, 125, 140]),
308+
np.array([1.1, 2, 3, 3.5, 4, 5, 10, 12, 13]),
309+
"tth",
299310
2 * np.pi,
300-
"q",
301-
0.1,
302-
]
311+
None,
312+
60,
313+
10,
314+
0,
315+
],
316+
["tth", np.array([1, 2, 3, 4, 5, 6, 10])],
303317
),
304318
]
305319

306320

307-
@pytest.mark.parametrize("inputs", params_scale_to_bad)
308-
def test_scale_to_bad(inputs):
321+
@pytest.mark.parametrize("inputs, expected", params_scale_to)
322+
def test_scale_to(inputs, expected):
309323
orig_diff_object = DiffractionObject(xarray=inputs[0], yarray=inputs[1], xtype=inputs[2], wavelength=inputs[3])
310324
target_diff_object = DiffractionObject(
311325
xarray=inputs[4], yarray=inputs[5], xtype=inputs[6], wavelength=inputs[7]
312326
)
313-
with pytest.raises(
314-
ValueError, match="I cannot scale two diffraction objects with empty or different lengths."
315-
):
316-
orig_diff_object.scale_to(target_diff_object, xtype=inputs[8], xvalue=inputs[9])
327+
scaled_diff_object = orig_diff_object.scale_to(
328+
target_diff_object, q=inputs[8], tth=inputs[9], d=inputs[10], offset=inputs[11]
329+
)
330+
# Check the intensity data is same as expected
331+
assert np.allclose(scaled_diff_object.on_xtype(expected[0])[1], expected[1])
332+
333+
334+
def test_scale_to_bad():
335+
# UC1: at least one of the y-arrays is empty
336+
orig_diff_object = DiffractionObject(
337+
xarray=np.array([]), yarray=np.array([]), xtype="tth", wavelength=2 * np.pi
338+
)
339+
target_diff_object = DiffractionObject(
340+
xarray=np.array([11, 14, 16, 20, 25, 30]),
341+
yarray=np.array([2, 3, 4, 5, 6, 7]),
342+
xtype="tth",
343+
wavelength=2 * np.pi,
344+
)
345+
with pytest.raises(ValueError, match="I cannot scale diffraction objects with empty arrays."):
346+
orig_diff_object.scale_to(target_diff_object, tth=20)
317347

318348

319349
params_index = [

0 commit comments

Comments
 (0)