Skip to content

Commit 88d8d73

Browse files
authored
Merge pull request #289 from yucongalicechen/scaleto2
add new features in `scale_to()` so user can run without an x-value
2 parents 3f10ca9 + 0691e54 commit 88d8d73

File tree

4 files changed

+105
-77
lines changed

4 files changed

+105
-77
lines changed

Diff for: doc/source/examples/diffraction_objects_example.rst

+10-1
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,16 @@ we would replace the code above with
104104
plt.show()
105105
106106
The ``scale_to()`` method returns a new ``DiffractionObject`` which we can assign to a new
107-
variable and make use of,
107+
variable and make use of.
108+
109+
The default behavior is to align the objects based on the maximal value of each diffraction object.
110+
111+
.. code-block:: python
112+
113+
scaled_measured = measured.scale_to(calculated)
114+
115+
If this doesn't give the desirable results, you can specify an ``xtype=value`` to scale
116+
based on the closest x-value in both objects. For example:
108117

109118
.. code-block:: python
110119

Diff for: news/scaleto-max.rst

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
**Added:**
2+
3+
* new feature in `scale_to()`: default scaling is based on the max q-value in each diffraction object.
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>

Diff for: src/diffpy/utils/diffraction_objects.py

+21-10
Original file line numberDiff line numberDiff line change
@@ -377,37 +377,48 @@ def on_tth(self):
377377
def on_d(self):
378378
return [self.all_arrays[:, 3], self.all_arrays[:, 0]]
379379

380-
def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=0):
380+
def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=None):
381381
"""Returns a new diffraction object which is the current object but
382382
rescaled in y to the target.
383383
384-
The y-value in the target at the closest specified x-value will be used as the factor to scale to.
384+
By default, if `q`, `tth`, or `d` are not provided, scaling is based on the max intensity from each object.
385+
Otherwise, y-value in the target at the closest specified x-value will be used as the factor to scale to.
385386
The entire array is scaled by this factor so that one object places on top of the other at that point.
386-
If multiple values of `q`, `tth`, or `d` are provided, or none are provided, an error will be raised.
387+
If multiple values of `q`, `tth`, or `d` are provided, an error will be raised.
387388
388389
Parameters
389390
----------
390391
target_diff_object: DiffractionObject
391-
the diffraction object you want to scale the current one onto
392+
The diffraction object you want to scale the current one onto.
392393
393-
q, tth, d : float, optional, must specify exactly one of them
394+
q, tth, d : float, optional, default is None
394395
The value of the x-array where you want the curves to line up vertically.
395396
Specify a value on one of the allowed grids, q, tth, or d), e.g., q=10.
396397
397-
offset : float, optional, default is 0
398-
an offset to add to the scaled y-values
398+
offset : float, optional, default is None
399+
The offset to add to the scaled y-values.
399400
400401
Returns
401402
-------
402-
the rescaled DiffractionObject as a new object
403+
scaled : DiffractionObject
404+
The rescaled DiffractionObject as a new object.
403405
"""
406+
if offset is None:
407+
offset = 0
404408
scaled = self.copy()
405409
count = sum([q is not None, tth is not None, d is not None])
406-
if count != 1:
410+
if count > 1:
407411
raise ValueError(
408-
"You must specify exactly one of 'q', 'tth', or 'd'. Please rerun specifying only one."
412+
"You must specify none or exactly one of 'q', 'tth', or 'd'. "
413+
"Please provide either none or one value."
409414
)
410415

416+
if count == 0:
417+
q_target_max = max(target_diff_object.on_q()[1])
418+
q_self_max = max(self.on_q()[1])
419+
scaled._all_arrays[:, 0] = scaled._all_arrays[:, 0] * q_target_max / q_self_max + offset
420+
return scaled
421+
411422
xtype = "q" if q is not None else "tth" if tth is not None else "d"
412423
data = self.on_xtype(xtype)
413424
target = target_diff_object.on_xtype(xtype)

Diff for: tests/test_diffraction_objects.py

+51-66
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,23 @@ def test_init_invalid_xtype():
191191
"org_do_args, target_do_args, scale_inputs, expected",
192192
[
193193
# Test whether the original y-array is scaled as expected
194-
( # C1: Same x-arrays
194+
( # C1: none of q, tth, d, provided, expect to scale on the maximal intensity from each object
195+
{
196+
"xarray": np.array([0.1, 0.2, 0.3]),
197+
"yarray": np.array([1, 2, 3]),
198+
"xtype": "q",
199+
"wavelength": 2 * np.pi,
200+
},
201+
{
202+
"xarray": np.array([0.05, 0.1, 0.2, 0.3]),
203+
"yarray": np.array([5, 10, 20, 30]),
204+
"xtype": "q",
205+
"wavelength": 2 * np.pi,
206+
},
207+
{},
208+
{"xtype": "q", "yarray": np.array([10, 20, 30])},
209+
),
210+
( # C2: Same x-arrays
195211
# x-value has exact matches at tth=60 (y=60) and tth=60 (y=6),
196212
# for original and target diffraction objects,
197213
# expect original y-array to multiply by 6/60=1/10
@@ -207,15 +223,10 @@ def test_init_invalid_xtype():
207223
"xtype": "tth",
208224
"wavelength": 2 * np.pi,
209225
},
210-
{
211-
"q": None,
212-
"tth": 60,
213-
"d": None,
214-
"offset": 0,
215-
},
226+
{"tth": 60},
216227
{"xtype": "tth", "yarray": np.array([1, 2, 2.5, 3, 6, 10])},
217228
),
218-
( # C2: Different x-arrays with same length,
229+
( # C3: Different x-arrays with same length,
219230
# x-value has closest match at q=0.12 (y=10) and q=0.14 (y=1)
220231
# for original and target diffraction objects,
221232
# expect original y-array to multiply by 1/10
@@ -231,15 +242,10 @@ def test_init_invalid_xtype():
231242
"xtype": "q",
232243
"wavelength": 2 * np.pi,
233244
},
234-
{
235-
"q": 0.1,
236-
"tth": None,
237-
"d": None,
238-
"offset": 0,
239-
},
245+
{"q": 0.1},
240246
{"xtype": "q", "yarray": np.array([1, 2, 4, 6])},
241247
),
242-
( # C3: Different x-array lengths
248+
( # C4: Different x-array lengths
243249
# x-value has closest matches at tth=61 (y=50) and tth=62 (y=5),
244250
# for original and target diffraction objects,
245251
# expect original y-array to multiply by 5/50=1/10
@@ -255,43 +261,48 @@ def test_init_invalid_xtype():
255261
"xtype": "tth",
256262
"wavelength": 2 * np.pi,
257263
},
258-
{
259-
"q": None,
260-
"tth": 60,
261-
"d": None,
262-
"offset": 0,
263-
},
264+
{"tth": 60},
264265
{"xtype": "tth", "yarray": np.array([1, 2, 3, 4, 5, 6, 10])},
265266
),
266-
( # C4: Same x-array and y-array with 2.1 offset, expect y-array to shift up by 2.1
267+
( # C5.1: Reuse test case from C1, none of q, tth, d, provided, but include an offset,
268+
# expect scaled y-array in C1 to shift up by 2
267269
{
268-
"xarray": np.array([10, 15, 25, 30, 60, 140]),
269-
"yarray": np.array([2, 3, 4, 5, 6, 7]),
270-
"xtype": "tth",
270+
"xarray": np.array([0.1, 0.2, 0.3]),
271+
"yarray": np.array([1, 2, 3]),
272+
"xtype": "q",
271273
"wavelength": 2 * np.pi,
272274
},
273275
{
274-
"xarray": np.array([10, 15, 25, 30, 60, 140]),
275-
"yarray": np.array([2, 3, 4, 5, 6, 7]),
276+
"xarray": np.array([0.05, 0.1, 0.2, 0.3]),
277+
"yarray": np.array([5, 10, 20, 30]),
278+
"xtype": "q",
279+
"wavelength": 2 * np.pi,
280+
},
281+
{"offset": 2},
282+
{"xtype": "q", "yarray": np.array([12, 22, 32])},
283+
),
284+
( # C5.2: Reuse test case from C4, but include an offset, expect scaled y-array in C4 to shift up by 2
285+
{
286+
"xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]),
287+
"yarray": np.array([10, 20, 30, 40, 50, 60, 100]),
276288
"xtype": "tth",
277289
"wavelength": 2 * np.pi,
278290
},
279291
{
280-
"q": None,
281-
"tth": 60,
282-
"d": None,
283-
"offset": 2.1,
292+
"xarray": np.array([20, 25.5, 32, 45, 50, 62, 100, 125, 140]),
293+
"yarray": np.array([1.1, 2, 3, 3.5, 4, 5, 10, 12, 13]),
294+
"xtype": "tth",
295+
"wavelength": 2 * np.pi,
284296
},
285-
{"xtype": "tth", "yarray": np.array([4.1, 5.1, 6.1, 7.1, 8.1, 9.1])},
297+
{"tth": 60, "offset": 2},
298+
{"xtype": "tth", "yarray": np.array([3, 4, 5, 6, 7, 8, 12])},
286299
),
287300
],
288301
)
289302
def test_scale_to(org_do_args, target_do_args, scale_inputs, expected):
290303
original_do = DiffractionObject(**org_do_args)
291304
target_do = DiffractionObject(**target_do_args)
292-
scaled_do = original_do.scale_to(
293-
target_do, q=scale_inputs["q"], tth=scale_inputs["tth"], d=scale_inputs["d"], offset=scale_inputs["offset"]
294-
)
305+
scaled_do = original_do.scale_to(target_do, **scale_inputs)
295306
# Check the intensity data is the same as expected
296307
assert np.allclose(scaled_do.on_xtype(expected["xtype"])[1], expected["yarray"])
297308

@@ -300,27 +311,7 @@ def test_scale_to(org_do_args, target_do_args, scale_inputs, expected):
300311
"org_do_args, target_do_args, scale_inputs",
301312
[
302313
# Test expected errors produced from scale_to() with invalid inputs
303-
( # C1: none of q, tth, d, provided, expect ValueError
304-
{
305-
"xarray": np.array([0.1, 0.2, 0.3]),
306-
"yarray": np.array([1, 2, 3]),
307-
"xtype": "q",
308-
"wavelength": 2 * np.pi,
309-
},
310-
{
311-
"xarray": np.array([0.05, 0.1, 0.2, 0.3]),
312-
"yarray": np.array([5, 10, 20, 30]),
313-
"xtype": "q",
314-
"wavelength": 2 * np.pi,
315-
},
316-
{
317-
"q": None,
318-
"tth": None,
319-
"d": None,
320-
"offset": 0,
321-
},
322-
),
323-
( # C2: tth and d both provided, expect ValueErrort
314+
( # C2: tth and d both provided, expect ValueError
324315
{
325316
"xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]),
326317
"yarray": np.array([10, 20, 30, 40, 50, 60, 100]),
@@ -334,10 +325,8 @@ def test_scale_to(org_do_args, target_do_args, scale_inputs, expected):
334325
"wavelength": 2 * np.pi,
335326
},
336327
{
337-
"q": None,
338328
"tth": 60,
339329
"d": 10,
340-
"offset": 0,
341330
},
342331
),
343332
],
@@ -346,15 +335,11 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs):
346335
original_do = DiffractionObject(**org_do_args)
347336
target_do = DiffractionObject(**target_do_args)
348337
with pytest.raises(
349-
ValueError, match="You must specify exactly one of 'q', 'tth', or 'd'. Please rerun specifying only one."
338+
ValueError,
339+
match="You must specify none or exactly one of 'q', 'tth', or 'd'. "
340+
"Please provide either none or one value.",
350341
):
351-
original_do.scale_to(
352-
target_do,
353-
q=scale_inputs["q"],
354-
tth=scale_inputs["tth"],
355-
d=scale_inputs["d"],
356-
offset=scale_inputs["offset"],
357-
)
342+
original_do.scale_to(target_do, **scale_inputs)
358343

359344

360345
@pytest.mark.parametrize(

0 commit comments

Comments
 (0)