Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add new features in scale_to() so user can run without an x-value #289

Merged
merged 11 commits into from
Dec 30, 2024
10 changes: 10 additions & 0 deletions doc/source/examples/diffraction_objects_example.rst
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,16 @@ we would replace the code above with
The ``scale_to()`` method returns a new ``DiffractionObject`` which we can assign to a new
variable and make use of,

The default behavior is to align the objects based on the maximal q-value of each diffraction object,
so they will align at the intensity at these indices.

.. code-block:: python

scaled_measured = measured.scale_to(calculated)

sbillinge marked this conversation as resolved.
Show resolved Hide resolved
If this doesn't give the desirable results, you can specify an ``xtype=value`` to scale
based on the closest x-value in both objects. For example:

.. code-block:: python

scaled_measured = measured.scale_to(calculated, q=5.5)
Expand Down
23 changes: 23 additions & 0 deletions news/scaleto-max.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
**Added:**

* new feature in `scale_to()`: default scaling is based on the max q-value in each diffraction object.
sbillinge marked this conversation as resolved.
Show resolved Hide resolved

**Changed:**

* <news item>

**Deprecated:**

* <news item>

**Removed:**

* <news item>

**Fixed:**

* <news item>

**Security:**

* <news item>
23 changes: 16 additions & 7 deletions src/diffpy/utils/diffraction_objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -396,37 +396,46 @@ 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):
sbillinge marked this conversation as resolved.
Show resolved Hide resolved
"""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.
sbillinge marked this conversation as resolved.
Show resolved Hide resolved
The entire array is scaled by this factor so that one object places on top of the other at that point.
If multiple values of `q`, `tth`, or `d` are provided, or none are provided, an error will be raised.
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, must specify exactly one of them
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
sbillinge marked this conversation as resolved.
Show resolved Hide resolved

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:
if count > 1:
raise ValueError(
"You must specify exactly one of 'q', 'tth', or 'd'. Please rerun specifying only one."
"You must specify none or exactly one of 'q', 'tth', or 'd'. "
sbillinge marked this conversation as resolved.
Show resolved Hide resolved
"Please provide either none or one value."
)

if count == 0:
scaled._all_arrays[:, 0] *= max(target_diff_object.on_q()[1]) / max(self.on_q()[1])
return scaled
sbillinge marked this conversation as resolved.
Show resolved Hide resolved

xtype = "q" if q is not None else "tth" if tth is not None else "d"
data = self.on_xtype(xtype)
target = target_diff_object.on_xtype(xtype)
Expand Down
92 changes: 30 additions & 62 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]),
sbillinge marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -276,22 +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])},
),
],
)
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)
sbillinge marked this conversation as resolved.
Show resolved Hide resolved
# Check the intensity data is the same as expected
assert np.allclose(scaled_do.on_xtype(expected["xtype"])[1], expected["yarray"])

Expand All @@ -300,27 +294,7 @@ def test_scale_to(org_do_args, target_do_args, scale_inputs, expected):
"org_do_args, target_do_args, scale_inputs",
[
# Test expected errors produced from scale_to() with invalid inputs
( # C1: none of q, tth, d, provided, expect ValueError
{
"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,
},
),
( # C2: tth and d both provided, expect ValueErrort
( # C2: tth and d both provided, expect ValueError
{
"xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]),
"yarray": np.array([10, 20, 30, 40, 50, 60, 100]),
Expand All @@ -334,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 @@ -346,15 +318,11 @@ def test_scale_to_bad(org_do_args, target_do_args, scale_inputs):
original_do = DiffractionObject(**org_do_args)
target_do = DiffractionObject(**target_do_args)
with pytest.raises(
ValueError, match="You must specify exactly one of 'q', 'tth', or 'd'. Please rerun specifying only one."
ValueError,
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