Skip to content
Merged
2 changes: 1 addition & 1 deletion news/scaleto-max.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
**Added:**

* new feature in `scale_to()` to run without specifying an x-value
* new feature in `scale_to()`: default scaling is based on the max q-value in each diffraction object.

**Changed:**

Expand Down
14 changes: 8 additions & 6 deletions src/diffpy/utils/diffraction_objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -396,32 +396,34 @@ def on_tth(self):
def on_d(self):
return [self.all_arrays[:, 3], self.all_arrays[:, 0]]

def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=0):
def scale_to(self, target_diff_object, q=None, tth=None, d=None, offset=None):
"""Returns a new diffraction object which is the current object but
rescaled in y to the target.

The y-value in the target at the closest specified x-value will be used as the factor to scale to.
By default, if `q`, `tth`, or `d` are not provided, scaling is based on the max q-value from each object.
Otherwise, y-value in the target at the closest specified x-value will be used as the factor to scale to.
The entire array is scaled by this factor so that one object places on top of the other at that point.
If none of `q`, `tth`, or `d` are provided,
the scaling will be based on the maximal x-array value from both objects.
If multiple values of `q`, `tth`, or `d` are provided, an error will be raised.

Parameters
----------
target_diff_object: DiffractionObject
the diffraction object you want to scale the current one onto

q, tth, d : float, optional, default is q with the maximal x-array value of the current object
q, tth, d : float, optional, default is the max q-value from each object
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"default is None"

The value of the x-array where you want the curves to line up vertically.
Specify a value on one of the allowed grids, q, tth, or d), e.g., q=10.

offset : float, optional, default is 0
offset : float, optional, default is None
an offset to add to the scaled y-values

Returns
-------
the rescaled DiffractionObject as a new object
"""
if offset is None:
offset = 0

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove empty line

scaled = self.copy()
count = sum([q is not None, tth is not None, d is not None])
if count > 1:
Expand Down
87 changes: 26 additions & 61 deletions tests/test_diffraction_objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,23 @@ def test_init_invalid_xtype():
"org_do_args, target_do_args, scale_inputs, expected",
[
# Test whether the original y-array is scaled as expected
( # C1: Same x-arrays
( # C1: none of q, tth, d, provided, expect to scale on the maximal x-arrays
{
"xarray": np.array([0.1, 0.2, 0.3]),
"yarray": np.array([1, 2, 3]),
"xtype": "q",
"wavelength": 2 * np.pi,
},
{
"xarray": np.array([0.05, 0.1, 0.2, 0.3]),
"yarray": np.array([5, 10, 20, 30]),
"xtype": "q",
"wavelength": 2 * np.pi,
},
{},
{"xtype": "q", "yarray": np.array([10, 20, 30])},
),
( # C2: Same x-arrays
# x-value has exact matches at tth=60 (y=60) and tth=60 (y=6),
# for original and target diffraction objects,
# expect original y-array to multiply by 6/60=1/10
Expand All @@ -207,15 +223,10 @@ def test_init_invalid_xtype():
"xtype": "tth",
"wavelength": 2 * np.pi,
},
{
"q": None,
"tth": 60,
"d": None,
"offset": 0,
},
{"tth": 60},
{"xtype": "tth", "yarray": np.array([1, 2, 2.5, 3, 6, 10])},
),
( # C2: Different x-arrays with same length,
( # C3: Different x-arrays with same length,
# x-value has closest match at q=0.12 (y=10) and q=0.14 (y=1)
# for original and target diffraction objects,
# expect original y-array to multiply by 1/10
Expand All @@ -231,15 +242,10 @@ def test_init_invalid_xtype():
"xtype": "q",
"wavelength": 2 * np.pi,
},
{
"q": 0.1,
"tth": None,
"d": None,
"offset": 0,
},
{"q": 0.1},
{"xtype": "q", "yarray": np.array([1, 2, 4, 6])},
),
( # C3: Different x-array lengths
( # C4: Different x-array lengths
# x-value has closest matches at tth=61 (y=50) and tth=62 (y=5),
# for original and target diffraction objects,
# expect original y-array to multiply by 5/50=1/10
Expand All @@ -255,15 +261,10 @@ def test_init_invalid_xtype():
"xtype": "tth",
"wavelength": 2 * np.pi,
},
{
"q": None,
"tth": 60,
"d": None,
"offset": 0,
},
{"tth": 60},
{"xtype": "tth", "yarray": np.array([1, 2, 3, 4, 5, 6, 10])},
),
( # C4: Same x-array and y-array with 2.1 offset, expect y-array to shift up by 2.1
( # C5: Same x-array and y-array with 2.1 offset, expect y-array to shift up by 2.1
{
"xarray": np.array([10, 15, 25, 30, 60, 140]),
"yarray": np.array([2, 3, 4, 5, 6, 7]),
Expand All @@ -276,43 +277,15 @@ def test_init_invalid_xtype():
"xtype": "tth",
"wavelength": 2 * np.pi,
},
{
"q": None,
"tth": 60,
"d": None,
"offset": 2.1,
},
{"tth": 60, "offset": 2.1},
{"xtype": "tth", "yarray": np.array([4.1, 5.1, 6.1, 7.1, 8.1, 9.1])},
),
( # C5: none of q, tth, d, provided, expect to scale on the maximal x-arrays
{
"xarray": np.array([0.1, 0.2, 0.3]),
"yarray": np.array([1, 2, 3]),
"xtype": "q",
"wavelength": 2 * np.pi,
},
{
"xarray": np.array([0.05, 0.1, 0.2, 0.3]),
"yarray": np.array([5, 10, 20, 30]),
"xtype": "q",
"wavelength": 2 * np.pi,
},
{
"q": None,
"tth": None,
"d": None,
"offset": 0,
},
{"xtype": "q", "yarray": np.array([10, 20, 30])},
),
],
)
def test_scale_to(org_do_args, target_do_args, scale_inputs, expected):
original_do = DiffractionObject(**org_do_args)
target_do = DiffractionObject(**target_do_args)
scaled_do = original_do.scale_to(
target_do, q=scale_inputs["q"], tth=scale_inputs["tth"], d=scale_inputs["d"], offset=scale_inputs["offset"]
)
scaled_do = original_do.scale_to(target_do, **scale_inputs)
# Check the intensity data is the same as expected
assert np.allclose(scaled_do.on_xtype(expected["xtype"])[1], expected["yarray"])

Expand All @@ -335,10 +308,8 @@ def test_scale_to(org_do_args, target_do_args, scale_inputs, expected):
"wavelength": 2 * np.pi,
},
{
"q": None,
"tth": 60,
"d": 10,
"offset": 0,
},
),
],
Expand All @@ -351,13 +322,7 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs):
match="You must specify none or exactly one of 'q', 'tth', or 'd'. "
"Please provide either none or one value.",
):
original_do.scale_to(
target_do,
q=scale_inputs["q"],
tth=scale_inputs["tth"],
d=scale_inputs["d"],
offset=scale_inputs["offset"],
)
original_do.scale_to(target_do, **scale_inputs)


@pytest.mark.parametrize(
Expand Down
Loading