import re
import uuid
from pathlib import Path
from uuid import UUID
import numpy as np
import pytest
from deepdiff import DeepDiff
from freezegun import freeze_time
from diffpy.utils.diffraction_objects import XQUANTITIES, DiffractionObject
@pytest.mark.parametrize(
"do_args_1, do_args_2, expected_equality, wavelength_warning_expected",
[
# Test when __eq__ returns True and False
( # C1: Identical args, expect equality
{
"name": "same",
"scat_quantity": "x-ray",
"wavelength": 0.71,
"xtype": "q",
"xarray": np.array([1.0, 2.0]),
"yarray": np.array([100.0, 200.0]),
"metadata": {"thing1": 1},
},
{
"name": "same",
"scat_quantity": "x-ray",
"wavelength": 0.71,
"xtype": "q",
"xarray": np.array([1.0, 2.0]),
"yarray": np.array([100.0, 200.0]),
"metadata": {"thing1": 1},
},
True,
False,
),
( # C2: Different names, expect inequality
{
"name": "something",
"xtype": "tth",
"xarray": np.empty(0),
"yarray": np.empty(0),
"metadata": {"thing1": 1, "thing2": "thing2"},
},
{
"name": "something else",
"xtype": "tth",
"xarray": np.empty(0),
"yarray": np.empty(0),
"metadata": {"thing1": 1, "thing2": "thing2"},
},
False,
True,
),
( # C3: One without wavelength, expect inequality
{
"wavelength": 0.71,
"xtype": "tth",
"xarray": np.empty(0),
"yarray": np.empty(0),
"metadata": {"thing1": 1, "thing2": "thing2"},
},
{
"xtype": "tth",
"xarray": np.empty(0),
"yarray": np.empty(0),
"metadata": {"thing1": 1, "thing2": "thing2"},
},
False,
True,
),
( # C4: Different wavelength values, expect inequality
{
"wavelength": 0.71,
"xtype": "tth",
"xarray": np.empty(0),
"yarray": np.empty(0),
"metadata": {"thing1": 1, "thing2": "thing2"},
},
{
"wavelength": 0.711,
"xtype": "tth",
"xarray": np.empty(0),
"yarray": np.empty(0),
"metadata": {"thing1": 1, "thing2": "thing2"},
},
False,
False,
),
( # C5: Different scat_quantity, expect inequality
{
"scat_quantity": "x-ray",
"xtype": "tth",
"xarray": np.empty(0),
"yarray": np.empty(0),
"metadata": {"thing1": 1, "thing2": "thing2"},
},
{
"scat_quantity": "neutron",
"xtype": "tth",
"xarray": np.empty(0),
"yarray": np.empty(0),
"metadata": {"thing1": 1, "thing2": "thing2"},
},
False,
True,
),
( # C6: Different q xarray values, expect inequality
{
"xtype": "q",
"xarray": np.array([1.0, 2.0]),
"yarray": np.array([100.0, 200.0]),
},
{
"xtype": "q",
"xarray": np.array([3.0, 4.0]),
"yarray": np.array([100.0, 200.0]),
"metadata": {"thing1": 1, "thing2": "thing2"},
},
False,
True,
),
( # C7: Different metadata, expect inequality
{
"xtype": "q",
"xarray": np.empty(0),
"yarray": np.empty(0),
"metadata": {"thing1": 0, "thing2": "thing2"},
},
{
"xtype": "q",
"xarray": np.empty(0),
"yarray": np.empty(0),
"metadata": {"thing1": 1, "thing2": "thing2"},
},
False,
True,
),
],
)
def test_diffraction_objects_equality(
do_args_1,
do_args_2,
expected_equality,
wavelength_warning_expected,
wavelength_warning_msg,
):
if wavelength_warning_expected:
with pytest.warns(
UserWarning, match=re.escape(wavelength_warning_msg)
):
do_1 = DiffractionObject(**do_args_1)
do_2 = DiffractionObject(**do_args_2)
else:
do_1 = DiffractionObject(**do_args_1)
do_2 = DiffractionObject(**do_args_2)
assert (do_1 == do_2) == expected_equality
@pytest.mark.parametrize(
"xtype, expected_xarray",
[
# Test whether on_xtype returns the correct xarray values.
# C1: tth to tth, expect no change in xarray value
# 1. "tth" provided, expect tth
("tth", np.array([30, 60])),
# 2. "2theta" provided, expect tth
("2theta", np.array([30, 60])),
# C2: "q" provided, expect q converted from tth
("q", np.array([0.51764, 1])),
# C3: "d" provided, expect d converted from tth
("d", np.array([12.13818, 6.28319])),
],
)
def test_on_xtype(xtype, expected_xarray, do_minimal_tth):
do = do_minimal_tth
actual_xrray, actual_yarray = do.on_xtype(xtype)
assert np.allclose(actual_xrray, expected_xarray)
assert np.allclose(actual_yarray, np.array([1, 2]))
def test_init_invalid_xtype():
with pytest.raises(
ValueError,
match=re.escape(
f"I don't know how to handle the xtype, 'invalid_type'. "
f"Please rerun specifying an xtype from {*XQUANTITIES, }"
),
):
return DiffractionObject(
xarray=np.empty(0),
yarray=np.empty(0),
xtype="invalid_type",
wavelength=1.54,
)
@pytest.mark.parametrize(
"org_do_args, target_do_args, scale_inputs, expected",
[
# Test whether the original y-array is scaled as expected
( # C1: none of q, tth, d, provided, expect to scale on the maximal
# intensity from each object
{
"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
{
"xarray": np.array([10, 15, 25, 30, 60, 140]),
"yarray": np.array([10, 20, 25, 30, 60, 100]),
"xtype": "tth",
"wavelength": 2 * np.pi,
},
{
"xarray": np.array([10, 20, 25, 30, 60, 140]),
"yarray": np.array([2, 3, 4, 5, 6, 7]),
"xtype": "tth",
"wavelength": 2 * np.pi,
},
{"tth": 60},
{"xtype": "tth", "yarray": np.array([1, 2, 2.5, 3, 6, 10])},
),
( # 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
{
"xarray": np.array([0.12, 0.24, 0.31, 0.4]),
"yarray": np.array([10, 20, 40, 60]),
"xtype": "q",
"wavelength": 2 * np.pi,
},
{
"xarray": np.array([0.14, 0.24, 0.31, 0.4]),
"yarray": np.array([1, 3, 4, 5]),
"xtype": "q",
"wavelength": 2 * np.pi,
},
{"q": 0.1},
{"xtype": "q", "yarray": np.array([1, 2, 4, 6])},
),
( # 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
{
"xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]),
"yarray": np.array([10, 20, 30, 40, 50, 60, 100]),
"xtype": "tth",
"wavelength": 2 * np.pi,
},
{
"xarray": np.array([20, 25.5, 32, 45, 50, 62, 100, 125, 140]),
"yarray": np.array([1.1, 2, 3, 3.5, 4, 5, 10, 12, 13]),
"xtype": "tth",
"wavelength": 2 * np.pi,
},
{"tth": 60},
{"xtype": "tth", "yarray": np.array([1, 2, 3, 4, 5, 6, 10])},
),
( # C5.1: Reuse test case from C1, none of q, tth, d, provided, but
# include an offset, expect scaled y-array in C1 to shift up by 2
{
"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,
},
{"offset": 2},
{"xtype": "q", "yarray": np.array([12, 22, 32])},
),
( # C5.2: Reuse test case from C4, but include an offset, expect
# scaled y-array in C4 to shift up by 2
{
"xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]),
"yarray": np.array([10, 20, 30, 40, 50, 60, 100]),
"xtype": "tth",
"wavelength": 2 * np.pi,
},
{
"xarray": np.array([20, 25.5, 32, 45, 50, 62, 100, 125, 140]),
"yarray": np.array([1.1, 2, 3, 3.5, 4, 5, 10, 12, 13]),
"xtype": "tth",
"wavelength": 2 * np.pi,
},
{"tth": 60, "offset": 2},
{"xtype": "tth", "yarray": np.array([3, 4, 5, 6, 7, 8, 12])},
),
],
)
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, **scale_inputs)
# Check the intensity data is the same as expected
assert np.allclose(
scaled_do.on_xtype(expected["xtype"])[1], expected["yarray"]
)
@pytest.mark.parametrize(
"org_do_args, target_do_args, scale_inputs",
[
# Test expected errors produced from scale_to() with invalid inputs
( # 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]),
"xtype": "tth",
"wavelength": 2 * np.pi,
},
{
"xarray": np.array([20, 25.5, 32, 45, 50, 62, 100, 125, 140]),
"yarray": np.array([1.1, 2, 3, 3.5, 4, 5, 10, 12, 13]),
"xtype": "tth",
"wavelength": 2 * np.pi,
},
{
"tth": 60,
"d": 10,
},
),
],
)
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 none or exactly one of 'q', 'tth', or 'd'. "
"Please provide either none or one value.",
):
original_do.scale_to(target_do, **scale_inputs)
@pytest.mark.parametrize(
"do_args, get_array_index_inputs, expected_index",
[
# Test get_array_index() returns the expected index given xtype and
# value
( # C1: Target value is in the xarray and xtype is identical, expect
# exact index match
{
"wavelength": 4 * np.pi,
"xarray": np.array([30.005, 60]),
"yarray": np.array([1, 2]),
"xtype": "tth",
},
{
"xtype": "tth",
"value": 30.005,
},
0,
),
# C2: Target value lies in the array, expect the closest index
( # 1. xtype(tth) is equal to self._input_xtype(tth)
{
"wavelength": 4 * np.pi,
"xarray": np.array([30, 60]),
"yarray": np.array([1, 2]),
"xtype": "tth",
},
{
"xtype": "tth",
"value": 45,
},
0,
),
( # 2. use default xtype(equal to self._input_xtype)
{
"wavelength": 4 * np.pi,
"xarray": np.array([30, 60]),
"yarray": np.array([1, 2]),
"xtype": "tth",
},
{
"xtype": None,
"value": 45,
},
0,
),
( # 3. xtype(q) is different from self._input_xtype(tth)
{
"wavelength": 4 * np.pi,
"xarray": np.array([30, 60]),
"yarray": np.array([1, 2]),
"xtype": "tth",
},
{
"xtype": "q",
"value": 0.5,
},
1,
),
# C3: Target value out of the range, expect the closest index
( # 1. Test with xtype of "q"
{
"wavelength": 4 * np.pi,
"xarray": np.array([0.25, 0.5, 0.71]),
"yarray": np.array([1, 2, 3]),
"xtype": "q",
},
{
"xtype": "q",
"value": 0.1,
},
0,
),
( # 2. Test with xtype of "tth"
{
"wavelength": 4 * np.pi,
"xarray": np.array([30, 60]),
"yarray": np.array([1, 2]),
"xtype": "tth",
},
{
"xtype": "tth",
"value": 63,
},
1,
),
],
)
def test_get_array_index(do_args, get_array_index_inputs, expected_index):
do = DiffractionObject(**do_args)
actual_index = do.get_array_index(
get_array_index_inputs["value"], get_array_index_inputs["xtype"]
)
assert actual_index == expected_index
def test_get_array_index_bad():
# empty array in DiffractionObject
do = DiffractionObject(
wavelength=2 * np.pi,
xarray=np.array([]),
yarray=np.array([]),
xtype="tth",
)
with pytest.raises(
ValueError,
match=re.escape(
"The 'tth' array is empty. Please ensure it is initialized."
),
):
do.get_array_index(xtype="tth", xvalue=30)
# non-existing xtype
do = DiffractionObject(
wavelength=4 * np.pi,
xarray=np.array([30, 60]),
yarray=np.array([1, 2]),
xtype="tth",
)
non_existing_xtype = "non_existing_xtype"
with pytest.raises(
ValueError,
match=re.escape(
f"I don't know how to handle the xtype, '{non_existing_xtype}'. "
f"Please rerun specifying an xtype from {*XQUANTITIES, }"
),
):
do.get_array_index(xtype=non_existing_xtype, xvalue=30)
def test_dump(tmp_path, mocker):
x, y = np.linspace(0, 5, 6), np.linspace(0, 5, 6)
directory = Path(tmp_path)
file = directory / "testfile"
with pytest.warns(
RuntimeWarning, match="divide by zero encountered in divide"
):
do = DiffractionObject(
wavelength=1.54,
name="test",
scat_quantity="x-ray",
xarray=np.array(x),
yarray=np.array(y),
xtype="q",
metadata={
"thing1": 1,
"thing2": "thing2",
"package_info": {"package2": "3.4.5"},
},
)
mocker.patch("importlib.metadata.version", return_value="3.3.0")
with freeze_time("2012-01-14"):
do.dump(file, "q")
with open(file, "r") as f:
actual = f.read()
expected = (
"[DiffractionObject]\n"
"name = test\n"
"wavelength = 1.54\n"
"scat_quantity = x-ray\n"
"thing1 = 1\n"
"thing2 = thing2\n"
"package_info = {'package2': '3.4.5', 'diffpy.utils': '3.3.0'}\n"
"creation_time = 2012-01-14 00:00:00\n\n"
"#### start data\n0.000000000000000000e+00 0.000000000000000000e+00\n"
"1.000000000000000000e+00 1.000000000000000000e+00\n"
"2.000000000000000000e+00 2.000000000000000000e+00\n"
"3.000000000000000000e+00 3.000000000000000000e+00\n"
"4.000000000000000000e+00 4.000000000000000000e+00\n"
"5.000000000000000000e+00 5.000000000000000000e+00\n"
)
assert actual == expected
@pytest.mark.parametrize(
(
"do_init_args, expected_do_dict, "
"divide_by_zero_warning_expected, wavelength_warning_expected"
),
[
# Test __dict__ of DiffractionObject instance initialized with valid
# arguments
( # C1: Instantiate DO with empty arrays, expect it to be a valid DO,
# but with everything empty
{
"xarray": np.empty(0),
"yarray": np.empty(0),
"xtype": "tth",
},
{
"_all_arrays": np.array([]),
"_input_xtype": "tth",
"metadata": {},
"name": "",
"scat_quantity": "",
"qmin": np.float64(np.inf),
"qmax": np.float64(0.0),
"tthmin": np.float64(np.inf),
"tthmax": np.float64(0.0),
"dmin": np.float64(np.inf),
"dmax": np.float64(0.0),
"wavelength": None,
},
False,
True,
),
( # C2: Instantiate just DO with empty array like in C1 but with
# wavelength, xtype, name, and metadata expect a valid DO with
# empty arrays, but with some non-array attributes
{
"xarray": np.empty(0),
"yarray": np.empty(0),
"xtype": "tth",
"name": "test_name",
"wavelength": 1.54,
"metadata": {"item_1": "1", "item_2": "2"},
},
{
"_all_arrays": np.array([]),
"_input_xtype": "tth",
"metadata": {"item_1": "1", "item_2": "2"},
"name": "test_name",
"scat_quantity": "",
"qmin": np.float64(np.inf),
"qmax": np.float64(0.0),
"tthmin": np.float64(np.inf),
"tthmax": np.float64(0.0),
"dmin": np.float64(np.inf),
"dmax": np.float64(0.0),
"wavelength": 1.54,
},
False,
False,
),
( # C3: Minimum arguments provided for init with non-empty values
# for xarray and yarray and wavelength expect all attributes set
# without None
{
"xarray": np.array([0.0, 90.0, 180.0]),
"yarray": np.array([1.0, 2.0, 3.0]),
"xtype": "tth",
"wavelength": 4.0 * np.pi,
},
{
"_all_arrays": np.array(
[
[1.0, 0.0, 0.0, np.float64(np.inf)],
[2.0, 1.0 / np.sqrt(2), 90.0, np.sqrt(2) * 2 * np.pi],
[3.0, 1.0, 180.0, 1.0 * 2 * np.pi],
]
),
"metadata": {},
"_input_xtype": "tth",
"name": "",
"scat_quantity": "",
"qmin": np.float64(0.0),
"qmax": np.float64(1.0),
"tthmin": np.float64(0.0),
"tthmax": np.float64(180.0),
"dmin": np.float64(2 * np.pi),
"dmax": np.float64(np.inf),
"wavelength": 4.0 * np.pi,
},
True,
False,
),
( # C4: Same as C3, but with an optional scat_quantity argument,
# expect non-empty string for scat_quantity
{
"xarray": np.array(
[np.inf, 2 * np.sqrt(2) * np.pi, 2 * np.pi]
),
"yarray": np.array([1.0, 2.0, 3.0]),
"xtype": "d",
"wavelength": 4.0 * np.pi,
"scat_quantity": "x-ray",
},
{
"_all_arrays": np.array(
[
[1.0, 0.0, 0.0, np.float64(np.inf)],
[2.0, 1.0 / np.sqrt(2), 90.0, np.sqrt(2) * 2 * np.pi],
[3.0, 1.0, 180.0, 1.0 * 2 * np.pi],
]
),
"metadata": {},
"_input_xtype": "d",
"name": "",
"scat_quantity": "x-ray",
"qmin": np.float64(0.0),
"qmax": np.float64(1.0),
"tthmin": np.float64(0.0),
"tthmax": np.float64(180.0),
"dmin": np.float64(2 * np.pi),
"dmax": np.float64(np.inf),
"wavelength": 4.0 * np.pi,
},
False,
False,
),
],
)
def test_init_valid(
do_init_args,
expected_do_dict,
divide_by_zero_warning_expected,
wavelength_warning_expected,
wavelength_warning_msg,
):
if divide_by_zero_warning_expected:
with pytest.warns(
RuntimeWarning, match="divide by zero encountered in divide"
):
actual_do_dict = DiffractionObject(**do_init_args).__dict__
elif wavelength_warning_expected:
with pytest.warns(
UserWarning, match=re.escape(wavelength_warning_msg)
):
actual_do_dict = DiffractionObject(**do_init_args).__dict__
else:
actual_do_dict = DiffractionObject(**do_init_args).__dict__
diff = DeepDiff(
actual_do_dict,
expected_do_dict,
ignore_order=True,
significant_digits=13,
exclude_paths="root['_uuid']",
)
assert diff == {}
@pytest.mark.parametrize(
"do_init_args, expected_error_msg",
[
# Test expected error messages when 3 required arguments not provided
# in DiffractionObject init
( # C1: No arguments provided, expect 3 required positional
# arguments error
{},
(
"missing 3 required positional arguments: "
"'xarray', 'yarray', and 'xtype'"
),
),
( # C2: Only xarray and yarray provided, expect 1 required
# positional argument error
{"xarray": np.array([0.0, 90.0]), "yarray": np.array([0.0, 90.0])},
"missing 1 required positional argument: 'xtype'",
),
],
)
def test_init_invalid_args(
do_init_args,
expected_error_msg,
):
with pytest.raises(TypeError, match=expected_error_msg):
DiffractionObject(**do_init_args)
def test_all_array_getter(do_minimal_tth):
actual_do = do_minimal_tth
print(actual_do.all_arrays)
expected_all_arrays = [
[1, 0.51763809, 30, 12.13818192],
[2, 1, 60, 6.28318531],
]
assert np.allclose(actual_do.all_arrays, expected_all_arrays)
def test_all_array_setter(do_minimal):
do = do_minimal
with pytest.raises(
AttributeError,
match="Direct modification of attribute 'all_arrays' is not allowed. "
"Please use 'input_data' to modify 'all_arrays'.",
):
do.all_arrays = np.empty((4, 4))
def test_uuid_getter(do_minimal):
do = do_minimal
assert hasattr(do, "uuid")
assert isinstance(do.uuid, UUID)
assert len(str(do.uuid)) == 36
def test_uuid_getter_with_mock(mocker, do_minimal):
mocker.patch.object(
DiffractionObject,
"uuid",
new_callable=lambda: UUID("d67b19c6-3016-439f-81f7-cf20a04bee87"),
)
do = do_minimal
assert do.uuid == UUID("d67b19c6-3016-439f-81f7-cf20a04bee87")
def test_uuid_setter_error(do_minimal):
do = do_minimal
with pytest.raises(
AttributeError,
match=(
"Direct modification of attribute 'uuid' is not allowed. "
"Please use 'input_data' to modify 'uuid'."
),
):
do.uuid = uuid.uuid4()
def test_xarray_yarray_length_mismatch():
with pytest.raises(
ValueError,
match="'xarray' and 'yarray' are different lengths. "
"They must correspond to each other and have the same length. Please "
"re-initialize 'DiffractionObject'with valid 'xarray' and 'yarray's",
):
DiffractionObject(
xarray=np.array([1.0, 2.0]),
yarray=np.array([0.0, 0.0, 0.0]),
xtype="tth",
wavelength=1.54,
)
def test_input_xtype_getter(do_minimal):
do = do_minimal
assert do.input_xtype == "tth"
def test_input_xtype_setter_error(do_minimal):
do = do_minimal
with pytest.raises(
AttributeError,
match="Direct modification of attribute 'input_xtype' is not allowed. "
"Please use 'input_data' to modify 'input_xtype'.",
):
do.input_xtype = "q"
def test_copy_object(do_minimal):
do = do_minimal
do_copy = do.copy()
assert do == do_copy
assert id(do) != id(do_copy)
@pytest.mark.parametrize(
"operation, starting_yarray, scalar_value, expected_yarray",
[
# Test scalar addition, subtraction, multiplication, and division to
# y-values by adding a scalar value
# C1: Test scalar addition to y-values (intensity), expect no change
# to x-values (q, tth, d)
( # 1. Add 5
"add",
np.array([1.0, 2.0]),
5,
np.array([6.0, 7.0]),
),
( # 2. Add 5.1
"add",
np.array([1.0, 2.0]),
5.1,
np.array([6.1, 7.1]),
),
# C2: Test scalar subtraction to y-values (intensity), expect no
# change to x-values (q, tth, d)
( # 1. Subtract 1
"sub",
np.array([1.0, 2.0]),
1,
np.array([0.0, 1.0]),
),
( # 2. Subtract 0.5
"sub",
np.array([1.0, 2.0]),
0.5,
np.array([0.5, 1.5]),
),
# C3: Test scalar multiplication to y-values (intensity), expect no
# change to x-values (q, tth, d)
( # 1. Multiply by 2
"mul",
np.array([1.0, 2.0]),
2,
np.array([2.0, 4.0]),
),
( # 2. Multiply by 2.5
"mul",
np.array([1.0, 2.0]),
2.5,
np.array([2.5, 5.0]),
),
# C4: Test scalar division to y-values (intensity), expect no change
# to x-values (q, tth, d)
( # 1. Divide by 2
"div",
np.array([1.0, 2.0]),
2,
np.array([0.5, 1.0]),
),
( # 2. Divide by 2.5
"div",
np.array([1.0, 2.0]),
2.5,
np.array([0.4, 0.8]),
),
],
)
def test_scalar_operations(
operation, starting_yarray, scalar_value, expected_yarray, do_minimal_tth
):
do = do_minimal_tth
expected_xarray_constant = np.array(
[[0.51763809, 30.0, 12.13818192], [1.0, 60.0, 6.28318531]]
)
assert np.allclose(do.all_arrays[:, [1, 2, 3]], expected_xarray_constant)
assert np.allclose(do.all_arrays[:, 0], starting_yarray)
if operation == "add":
do_right_op = do + scalar_value
do_left_op = scalar_value + do
elif operation == "sub":
do_right_op = do - scalar_value
do_left_op = scalar_value - do
elif operation == "mul":
do_right_op = do * scalar_value
do_left_op = scalar_value * do
elif operation == "div":
do_right_op = do / scalar_value
do_left_op = scalar_value / do
assert np.allclose(do_right_op.all_arrays[:, 0], expected_yarray)
assert np.allclose(do_left_op.all_arrays[:, 0], expected_yarray)
# Ensure x-values are unchanged
assert np.allclose(
do_right_op.all_arrays[:, [1, 2, 3]], expected_xarray_constant
)
assert np.allclose(
do_left_op.all_arrays[:, [1, 2, 3]], expected_xarray_constant
)
@pytest.mark.parametrize(
(
"operation, expected_do_1_all_arrays_with_y_modified, "
"expected_do_2_all_arrays_with_y_modified"
),
[
# Test addition, subtraction, multiplication, and division of two DO
# objects
( # Test addition of two DO objects, expect combined yarray values
"add",
np.array(
[
[2.0, 0.51763809, 30.0, 12.13818192],
[4.0, 1.0, 60.0, 6.28318531],
]
),
np.array(
[
[2.0, 0.51763809, 30.0, 12.13818192],
[4.0, 1.0, 60.0, 6.28318531],
]
),
),
( # Test subtraction of two DO objects, expect differences in yarray
# values
"sub",
np.array(
[
[0.0, 0.51763809, 30.0, 12.13818192],
[0.0, 1.0, 60.0, 6.28318531],
]
),
np.array(
[
[0.0, 0.51763809, 30.0, 12.13818192],
[0.0, 1.0, 60.0, 6.28318531],
]
),
),
( # Test multiplication of two DO objects, expect multiplication in
# yarray values
"mul",
np.array(
[
[1.0, 0.51763809, 30.0, 12.13818192],
[4.0, 1.0, 60.0, 6.28318531],
]
),
np.array(
[
[1.0, 0.51763809, 30.0, 12.13818192],
[4.0, 1.0, 60.0, 6.28318531],
]
),
),
( # Test division of two DO objects, expect division in yarray values
"div",
np.array(
[
[1.0, 0.51763809, 30.0, 12.13818192],
[1.0, 1.0, 60.0, 6.28318531],
]
),
np.array(
[
[1.0, 0.51763809, 30.0, 12.13818192],
[1.0, 1.0, 60.0, 6.28318531],
]
),
),
],
)
def test_binary_operator_on_do(
operation,
expected_do_1_all_arrays_with_y_modified,
expected_do_2_all_arrays_with_y_modified,
do_minimal_tth,
):
do_1 = do_minimal_tth
do_2 = do_minimal_tth
assert np.allclose(
do_1.all_arrays,
np.array(
[
[1.0, 0.51763809, 30.0, 12.13818192],
[2.0, 1.0, 60.0, 6.28318531],
]
),
)
assert np.allclose(
do_2.all_arrays,
np.array(
[
[1.0, 0.51763809, 30.0, 12.13818192],
[2.0, 1.0, 60.0, 6.28318531],
]
),
)
if operation == "add":
do_1_y_modified = do_1 + do_2
do_2_y_modified = do_2 + do_1
elif operation == "sub":
do_1_y_modified = do_1 - do_2
do_2_y_modified = do_2 - do_1
elif operation == "mul":
do_1_y_modified = do_1 * do_2
do_2_y_modified = do_2 * do_1
elif operation == "div":
do_1_y_modified = do_1 / do_2
do_2_y_modified = do_2 / do_1
assert np.allclose(
do_1_y_modified.all_arrays, expected_do_1_all_arrays_with_y_modified
)
assert np.allclose(
do_2_y_modified.all_arrays, expected_do_2_all_arrays_with_y_modified
)
def test_operator_invalid_type(do_minimal_tth, invalid_add_type_error_msg):
# Add a string to a DiffractionObject, expect TypeError
do = do_minimal_tth
invalid_value = "string_value"
operations = [
(lambda x, y: x + y), # Test addition
(lambda x, y: x - y), # Test subtraction
(lambda x, y: x * y), # Test multiplication
(lambda x, y: x / y), # Test division
]
for operation in operations:
with pytest.raises(
TypeError, match=re.escape(invalid_add_type_error_msg)
):
operation(do, invalid_value)
with pytest.raises(
TypeError, match=re.escape(invalid_add_type_error_msg)
):
operation(invalid_value, do)
@pytest.mark.parametrize("operation", ["add", "sub", "mul", "div"])
def test_operator_invalid_xarray_values_not_equal(
operation, do_minimal_tth, do_minimal_d, x_values_not_equal_error_msg
):
# Add two DO objects with different xarray values but equal in shape,
# expect ValueError
do_1 = do_minimal_tth
do_2 = do_minimal_d
with pytest.raises(
ValueError, match=re.escape(x_values_not_equal_error_msg)
):
if operation == "add":
do_1 + do_2
elif operation == "sub":
do_1 - do_2
elif operation == "mul":
do_1 * do_2
elif operation == "div":
do_1 / do_2
@pytest.mark.parametrize("operation", ["add", "sub", "mul", "div"])
def test_operator_invalid_xarray_shape_not_equal(
operation, do_minimal, do_minimal_tth, x_values_not_equal_error_msg
):
# Add two DO objects with different xarrays shape, expect ValueError
do_1 = do_minimal
do_2 = do_minimal_tth
with pytest.raises(
ValueError, match=re.escape(x_values_not_equal_error_msg)
):
if operation == "add":
do_1 + do_2
elif operation == "sub":
do_1 - do_2
elif operation == "mul":
do_1 * do_2
elif operation == "div":
do_1 / do_2