Skip to content

Fix all remaining divide by zero pytest warnings #262

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

Merged
merged 6 commits into from
Dec 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions news/pytest-warning-divide-zero.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
**Added:**

* No news added: previous news created for catching divide by zero warnings in pytest

**Changed:**

* <news item>

**Deprecated:**

* <news item>

**Removed:**

* <news item>

**Fixed:**

* <news item>

**Security:**

* <news item>
210 changes: 99 additions & 111 deletions tests/test_diffraction_objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,15 +373,16 @@ 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"
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"}},
)
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")
Expand All @@ -402,114 +403,107 @@ def test_dump(tmp_path, mocker):
assert actual == expected


test_init_valid_params = [
( # instantiate just array attributes
{
"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,
},
),
( # instantiate just array attributes
{
"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,
},
),
]


@pytest.mark.parametrize(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just moving them under @pytest.mark.parametrize - no numbers have been modified.

"init_args, expected_do_dict",
test_init_valid_params,
"do_init_args, expected_do_dict, divide_by_zero_warning_expected",
[
( # instantiate just array attributes
{
"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,
),
( # instantiate just array attributes
{
"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,
),
],
)
def test_init_valid(init_args, expected_do_dict):
actual_do_dict = DiffractionObject(**init_args).__dict__
def test_init_valid(do_init_args, expected_do_dict, divide_by_zero_warning_expected):
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__
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['_id']"
)
assert diff == {}


test_init_invalid_params = [
( # UC1: no arguments provided
{},
"missing 3 required positional arguments: 'xarray', 'yarray', and 'xtype'",
),
( # UC2: only xarray and yarray provided
{"xarray": np.array([0.0, 90.0]), "yarray": np.array([0.0, 90.0])},
"missing 1 required positional argument: 'xtype'",
),
]


@pytest.mark.parametrize("init_args, expected_error_msg", test_init_invalid_params)
@pytest.mark.parametrize(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will further refine these comments with higher level commets, etc. in the following PRs (issues have been created for these)

"do_init_args, expected_error_msg",
[
( # Case 1: no arguments provided
{},
"missing 3 required positional arguments: 'xarray', 'yarray', and 'xtype'",
),
( # Case 2: only xarray and yarray provided
{"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(
init_args,
do_init_args,
expected_error_msg,
):
with pytest.raises(TypeError, match=expected_error_msg):
DiffractionObject(**init_args)
DiffractionObject(**do_init_args)


def test_all_array_getter():
actual_do = DiffractionObject(
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,
)
expected_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],
]
)
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)


Expand Down Expand Up @@ -574,14 +568,8 @@ def test_input_xtype_setter_error(do_minimal):
do.input_xtype = "q"


def test_copy_object():
do = DiffractionObject(
name="test",
wavelength=4.0 * np.pi,
xarray=np.array([0.0, 90.0, 180.0]),
yarray=np.array([1.0, 2.0, 3.0]),
xtype="tth",
)
def test_copy_object(do_minimal):
do = do_minimal
do_copy = do.copy()
assert do == do_copy
assert id(do) != id(do_copy)
64 changes: 35 additions & 29 deletions tests/test_transforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,50 +146,55 @@ def test_q_to_d(q, expected_d, warning_expected):


@pytest.mark.parametrize(
"d, expected_q",
"d, expected_q, zero_divide_error_expected",
[
# UC1: User specified empty d values
(np.array([]), np.array([])),
(np.array([]), np.array([]), False),
# UC2: User specified valid d values
(
np.array([5 * np.pi, 4 * np.pi, 3 * np.pi, 2 * np.pi, np.pi, 0]),
np.array([0.4, 0.5, 0.66667, 1, 2, np.inf]),
True,
),
],
)
def test_d_to_q(d, expected_q):
actual_q = d_to_q(d)
def test_d_to_q(d, expected_q, zero_divide_error_expected):
if zero_divide_error_expected:
with pytest.warns(RuntimeWarning, match="divide by zero encountered in divide"):
actual_q = d_to_q(d)
else:
actual_q = d_to_q(d)
assert np.allclose(actual_q, expected_q)


@pytest.mark.parametrize(
"wavelength, tth, expected_d",
"wavelength, tth, expected_d, divide_by_zero_warning_expected",
[
# UC0: User specified empty tth values (without wavelength)
(None, np.array([]), np.array([])),
# UC1: User specified empty tth values (with wavelength)
(4 * np.pi, np.array([]), np.array([])),
# UC2: User specified valid tth values between 0-180 degrees (without wavelength)
(
None,
np.array([0, 30, 60, 90, 120, 180]),
np.array([0, 1, 2, 3, 4, 5]),
),
# UC3: User specified valid tth values between 0-180 degrees (with wavelength)
# Test conversion of q to d with valid values
# Case 1: empty tth values, no, expect empty d values
(None, np.array([]), np.array([]), False),
# Case 2: empty tth values, wavelength provided, expect empty d values
(4 * np.pi, np.array([]), np.array([]), False),
# Case 3: User specified valid tth values between 0-180 degrees (without wavelength)
(None, np.array([0, 30, 60, 90, 120, 180]), np.array([0, 1, 2, 3, 4, 5]), False),
# Case 4: User specified valid tth values between 0-180 degrees (with wavelength)
(
4 * np.pi,
np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0]),
np.array([np.inf, 24.27636, 12.56637, 8.88577, 7.25520, 6.28319]),
True,
),
],
)
def test_tth_to_d(wavelength, tth, expected_d, wavelength_warning_msg):
def test_tth_to_d(wavelength, tth, expected_d, divide_by_zero_warning_expected, wavelength_warning_msg):
if wavelength is None:
with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)):
actual_d = tth_to_d(tth, wavelength)
elif divide_by_zero_warning_expected:
with pytest.warns(RuntimeWarning, match="divide by zero encountered in divide"):
actual_d = tth_to_d(tth, wavelength)
else:
actual_d = tth_to_d(tth, wavelength)

assert np.allclose(actual_d, expected_d)


Expand Down Expand Up @@ -218,30 +223,31 @@ def test_tth_to_d_invalid(wavelength, tth, expected_error_type, expected_error_m


@pytest.mark.parametrize(
"wavelength, d, expected_tth",
"wavelength, d, expected_tth, divide_by_zero_warning_expected",
[
# UC1: Empty d values, no wavelength, return empty arrays
(None, np.empty((0)), np.empty((0))),
(None, np.empty((0)), np.empty((0)), False),
# UC2: Empty d values, wavelength specified, return empty arrays
(4 * np.pi, np.empty((0)), np.empty(0)),
(4 * np.pi, np.empty((0)), np.empty(0), False),
# UC3: User specified valid d values, no wavelength, return empty arrays
(
None,
np.array([1, 0.8, 0.6, 0.4, 0.2, 0]),
np.array([0, 1, 2, 3, 4, 5]),
),
(None, np.array([1, 0.8, 0.6, 0.4, 0.2, 0]), np.array([0, 1, 2, 3, 4, 5]), True),
# UC4: User specified valid d values (with wavelength)
(
4 * np.pi,
np.array([4 * np.pi, 4 / np.sqrt(2) * np.pi, 4 / np.sqrt(3) * np.pi]),
np.array([60.0, 90.0, 120.0]),
False,
),
],
)
def test_d_to_tth(wavelength, d, expected_tth, wavelength_warning_msg):
if wavelength is None:
def test_d_to_tth(wavelength, d, expected_tth, divide_by_zero_warning_expected, wavelength_warning_msg):
if wavelength is None and not divide_by_zero_warning_expected:
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am thinking we could make a reusable test util function under conftest.py - that takes in d_to_tth, tth_to_d, etc and capture these warning messages.

with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)):
actual_tth = d_to_tth(d, wavelength)
elif wavelength is None and divide_by_zero_warning_expected:
with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)):
with pytest.warns(RuntimeWarning, match="divide by zero encountered in divide"):
actual_tth = d_to_tth(d, wavelength)
else:
actual_tth = d_to_tth(d, wavelength)

Expand All @@ -254,7 +260,7 @@ def test_d_to_tth(wavelength, d, expected_tth, wavelength_warning_msg):
# UC1: user specified invalid d values that result in tth > 180 degrees
(4 * np.pi, np.array([1.2, 1, 0.8, 0.6, 0.4, 0.2]), ValueError),
# UC2: user specified a wrong wavelength that result in tth > 180 degrees
(100, np.array([1, 0.8, 0.6, 0.4, 0.2, 0]), ValueError),
(100, np.array([1.2, 1, 0.8, 0.6, 0.4, 0.2]), ValueError),
],
)
def test_d_to_tth_bad(wavelength, d, expected_error_type, invalid_q_or_d_or_wavelength_error_msg):
Expand Down
Loading