From 0ba67ca4b3c7243829bf65bc77874b572ec5f25e Mon Sep 17 00:00:00 2001 From: Saransh Chopra Date: Mon, 23 Sep 2024 21:23:04 +0200 Subject: [PATCH 1/6] gh-161: cleanup and add more tests for glass.core --- tests/core/test_algorithm.py | 15 +--------- tests/core/test_array.py | 54 ++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 14 deletions(-) diff --git a/tests/core/test_algorithm.py b/tests/core/test_algorithm.py index 03441b3..c6bf773 100644 --- a/tests/core/test_algorithm.py +++ b/tests/core/test_algorithm.py @@ -1,16 +1,3 @@ -import pytest - -# check if scipy is available for testing -try: - import scipy -except ImportError: - HAVE_SCIPY = False -else: - del scipy - HAVE_SCIPY = True - - -@pytest.mark.skipif(not HAVE_SCIPY, reason="test requires SciPy") def test_nnls(rng): import numpy as np from scipy.optimize import nnls as nnls_scipy @@ -23,4 +10,4 @@ def test_nnls(rng): x_glass = nnls_glass(a, b) x_scipy, _ = nnls_scipy(a, b) - np.testing.assert_allclose(x_glass, x_scipy) + assert np.allclose(x_glass, x_scipy) diff --git a/tests/core/test_array.py b/tests/core/test_array.py index 8268d80..0e786bb 100644 --- a/tests/core/test_array.py +++ b/tests/core/test_array.py @@ -2,6 +2,26 @@ import numpy.testing as npt +def broadcast_first(): + from glass.core.array import broadcast_first + + a = np.ones((2, 3, 4)) + b = np.ones((2, 1, 4)) + + a_b, b_b = broadcast_first(a, b) + + assert a_b.shape == (2, 3, 4) + assert b_b.shape == (2, 3, 4) + + a = np.ones((2, 2, 2)) + b = np.ones((2, 1)) + + a_b, b_b = broadcast_first(a, b) + + assert a_b.shape == (2, 2, 2) + assert b_b.shape == (2, 2, 2) + + def test_broadcast_leading_axes(): from glass.core.array import broadcast_leading_axes @@ -114,3 +134,37 @@ def test_trapz_product(): s = trapz_product((x1, f1), (x2, f2)) assert np.allclose(s, 1.0) + + +def test_cumtrapz(): + import numpy as np + + from glass.core.array import cumtrapz + + f = np.array([1, 2, 3, 4]) + x = np.array([0, 1, 2, 3]) + + result = cumtrapz(f, x) + assert np.allclose(result, np.array([0, 1, 4, 7])) + + result = cumtrapz(f, x, dtype=float) + assert np.allclose(result, np.array([0.0, 1.5, 4.0, 7.5])) + + result = cumtrapz(f, x, dtype=float, out=np.zeros((4,))) + assert np.allclose(result, np.array([0.0, 1.5, 4.0, 7.5])) + + f = np.array([[1, 4, 9, 16], [2, 3, 5, 7]]) + x = np.array([0, 1, 2.5, 4]) + + result = cumtrapz(f, x) + assert np.allclose(result, np.array([[[0, 2, 12, 31], [0, 2, 8, 17]]])) + + result = cumtrapz(f, x, dtype=float) + assert np.allclose( + result, np.array([[[0.0, 2.5, 12.25, 31.0], [0.0, 2.5, 8.5, 17.5]]]) + ) + + result = cumtrapz(f, x, dtype=float, out=np.zeros((2, 4))) + assert np.allclose( + result, np.array([[[0.0, 2.5, 12.25, 31.0], [0.0, 2.5, 8.5, 17.5]]]) + ) From ac2d00d9492a006800d5182e3811a5db2195d0f2 Mon Sep 17 00:00:00 2001 From: Saransh Chopra Date: Tue, 24 Sep 2024 12:46:40 +0200 Subject: [PATCH 2/6] bump coverage for core.algorithms --- glass/core/algorithm.py | 2 +- tests/core/test_algorithm.py | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/glass/core/algorithm.py b/glass/core/algorithm.py index 88c310a..01412be 100644 --- a/glass/core/algorithm.py +++ b/glass/core/algorithm.py @@ -7,7 +7,7 @@ import numpy as np if TYPE_CHECKING: - from numpy.typing import ArrayLike + from numpy.typing import ArrayLike # pragma: no cover def nnls( diff --git a/tests/core/test_algorithm.py b/tests/core/test_algorithm.py index c6bf773..cc6ff41 100644 --- a/tests/core/test_algorithm.py +++ b/tests/core/test_algorithm.py @@ -1,3 +1,6 @@ +import pytest + + def test_nnls(rng): import numpy as np from scipy.optimize import nnls as nnls_scipy @@ -11,3 +14,10 @@ def test_nnls(rng): x_scipy, _ = nnls_scipy(a, b) assert np.allclose(x_glass, x_scipy) + + with pytest.raises(ValueError, match="input `a` is not a matrix"): + nnls_glass(b, a) + with pytest.raises(ValueError, match="input `b` is not a vector"): + nnls_glass(a, a) + with pytest.raises(ValueError, match="the shapes of `a` and `b` do not match"): + nnls_glass(a.T, b) From d67ce7997daf280ad35f9ae26db0f9b027ba38dc Mon Sep 17 00:00:00 2001 From: Saransh Chopra Date: Wed, 25 Sep 2024 17:34:19 +0200 Subject: [PATCH 3/6] global coverage config, scipy fences back --- glass/core/algorithm.py | 2 +- tests/core/test_algorithm.py | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/glass/core/algorithm.py b/glass/core/algorithm.py index 01412be..88c310a 100644 --- a/glass/core/algorithm.py +++ b/glass/core/algorithm.py @@ -7,7 +7,7 @@ import numpy as np if TYPE_CHECKING: - from numpy.typing import ArrayLike # pragma: no cover + from numpy.typing import ArrayLike def nnls( diff --git a/tests/core/test_algorithm.py b/tests/core/test_algorithm.py index cc6ff41..124ab62 100644 --- a/tests/core/test_algorithm.py +++ b/tests/core/test_algorithm.py @@ -1,6 +1,16 @@ import pytest +# check if scipy is available for testing +try: + import scipy +except ImportError: + HAVE_SCIPY = False +else: + del scipy + HAVE_SCIPY = True + +@pytest.mark.skipif(not HAVE_SCIPY, reason="test requires SciPy") def test_nnls(rng): import numpy as np from scipy.optimize import nnls as nnls_scipy @@ -13,7 +23,7 @@ def test_nnls(rng): x_glass = nnls_glass(a, b) x_scipy, _ = nnls_scipy(a, b) - assert np.allclose(x_glass, x_scipy) + np.testing.assert_allclose(x_glass, x_scipy) with pytest.raises(ValueError, match="input `a` is not a matrix"): nnls_glass(b, a) From 4f5c627cfa1f6d48d1636d3eb272ec7fc1047725 Mon Sep 17 00:00:00 2001 From: Saransh Chopra Date: Thu, 26 Sep 2024 19:42:42 +0200 Subject: [PATCH 4/6] Use numpy.testing --- tests/core/test_array.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/tests/core/test_array.py b/tests/core/test_array.py index 0e786bb..68a30f3 100644 --- a/tests/core/test_array.py +++ b/tests/core/test_array.py @@ -145,26 +145,27 @@ def test_cumtrapz(): x = np.array([0, 1, 2, 3]) result = cumtrapz(f, x) - assert np.allclose(result, np.array([0, 1, 4, 7])) + npt.assert_allclose(result, np.array([0, 1, 4, 7])) result = cumtrapz(f, x, dtype=float) - assert np.allclose(result, np.array([0.0, 1.5, 4.0, 7.5])) + npt.assert_allclose(result, np.array([0.0, 1.5, 4.0, 7.5])) result = cumtrapz(f, x, dtype=float, out=np.zeros((4,))) - assert np.allclose(result, np.array([0.0, 1.5, 4.0, 7.5])) + npt.assert_allclose(result, np.array([0.0, 1.5, 4.0, 7.5])) f = np.array([[1, 4, 9, 16], [2, 3, 5, 7]]) x = np.array([0, 1, 2.5, 4]) result = cumtrapz(f, x) - assert np.allclose(result, np.array([[[0, 2, 12, 31], [0, 2, 8, 17]]])) + npt.assert_allclose(result, np.array([[0, 2, 12, 31], [0, 2, 8, 17]])) result = cumtrapz(f, x, dtype=float) - assert np.allclose( - result, np.array([[[0.0, 2.5, 12.25, 31.0], [0.0, 2.5, 8.5, 17.5]]]) + npt.assert_allclose( + result, + np.array([[0.0, 2.5, 12.25, 31.0], [0.0, 2.5, 8.5, 17.5]]), ) result = cumtrapz(f, x, dtype=float, out=np.zeros((2, 4))) - assert np.allclose( - result, np.array([[[0.0, 2.5, 12.25, 31.0], [0.0, 2.5, 8.5, 17.5]]]) + npt.assert_allclose( + result, np.array([[0.0, 2.5, 12.25, 31.0], [0.0, 2.5, 8.5, 17.5]]) ) From e4a67fcb9125fc8e6f8a5d43cd2844624b92c4b8 Mon Sep 17 00:00:00 2001 From: Saransh Chopra Date: Fri, 27 Sep 2024 12:43:50 +0200 Subject: [PATCH 5/6] compare results with scipy's implementation --- tests/core/test_algorithm.py | 4 ++++ tests/core/test_array.py | 42 +++++++++++++++++++++--------------- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/tests/core/test_algorithm.py b/tests/core/test_algorithm.py index 124ab62..22cc349 100644 --- a/tests/core/test_algorithm.py +++ b/tests/core/test_algorithm.py @@ -17,6 +17,8 @@ def test_nnls(rng): from glass.core.algorithm import nnls as nnls_glass + # cross-check output with scipy's nnls + a = rng.standard_normal((100, 20)) b = rng.standard_normal((100,)) @@ -25,6 +27,8 @@ def test_nnls(rng): np.testing.assert_allclose(x_glass, x_scipy) + # check matrix and vector's shape + with pytest.raises(ValueError, match="input `a` is not a matrix"): nnls_glass(b, a) with pytest.raises(ValueError, match="input `b` is not a vector"): diff --git a/tests/core/test_array.py b/tests/core/test_array.py index 68a30f3..9fafac9 100644 --- a/tests/core/test_array.py +++ b/tests/core/test_array.py @@ -1,5 +1,15 @@ import numpy as np import numpy.testing as npt +import pytest + +# check if scipy is available for testing +try: + import scipy +except ImportError: + HAVE_SCIPY = False +else: + del scipy + HAVE_SCIPY = True def broadcast_first(): @@ -136,36 +146,34 @@ def test_trapz_product(): assert np.allclose(s, 1.0) +@pytest.mark.skipif(not HAVE_SCIPY, reason="test requires SciPy") def test_cumtrapz(): - import numpy as np + from scipy.integrate import cumulative_trapezoid from glass.core.array import cumtrapz f = np.array([1, 2, 3, 4]) x = np.array([0, 1, 2, 3]) - result = cumtrapz(f, x) - npt.assert_allclose(result, np.array([0, 1, 4, 7])) + glass_ct = cumtrapz(f, x) + npt.assert_allclose(glass_ct, np.array([0, 1, 4, 7])) - result = cumtrapz(f, x, dtype=float) - npt.assert_allclose(result, np.array([0.0, 1.5, 4.0, 7.5])) + glass_ct = cumtrapz(f, x, dtype=float) + scipy_ct = cumulative_trapezoid(f, x, initial=0) + npt.assert_allclose(glass_ct, scipy_ct) result = cumtrapz(f, x, dtype=float, out=np.zeros((4,))) - npt.assert_allclose(result, np.array([0.0, 1.5, 4.0, 7.5])) + npt.assert_allclose(result, scipy_ct) f = np.array([[1, 4, 9, 16], [2, 3, 5, 7]]) x = np.array([0, 1, 2.5, 4]) - result = cumtrapz(f, x) - npt.assert_allclose(result, np.array([[0, 2, 12, 31], [0, 2, 8, 17]])) + glass_ct = cumtrapz(f, x) + npt.assert_allclose(glass_ct, np.array([[0, 2, 12, 31], [0, 2, 8, 17]])) - result = cumtrapz(f, x, dtype=float) - npt.assert_allclose( - result, - np.array([[0.0, 2.5, 12.25, 31.0], [0.0, 2.5, 8.5, 17.5]]), - ) + glass_ct = cumtrapz(f, x, dtype=float) + scipy_ct = cumulative_trapezoid(f, x, initial=0) + npt.assert_allclose(glass_ct, scipy_ct) - result = cumtrapz(f, x, dtype=float, out=np.zeros((2, 4))) - npt.assert_allclose( - result, np.array([[0.0, 2.5, 12.25, 31.0], [0.0, 2.5, 8.5, 17.5]]) - ) + glass_ct = cumtrapz(f, x, dtype=float, out=np.zeros((2, 4))) + npt.assert_allclose(glass_ct, scipy_ct) From 47b3739f8779c25978c95426723b828a199af3d6 Mon Sep 17 00:00:00 2001 From: Saransh Chopra Date: Fri, 27 Sep 2024 13:14:06 +0200 Subject: [PATCH 6/6] add comments --- tests/core/test_array.py | 47 ++++++++++++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/tests/core/test_array.py b/tests/core/test_array.py index 9fafac9..de2aee6 100644 --- a/tests/core/test_array.py +++ b/tests/core/test_array.py @@ -16,20 +16,31 @@ def broadcast_first(): from glass.core.array import broadcast_first a = np.ones((2, 3, 4)) - b = np.ones((2, 1, 4)) + b = np.ones((2, 1)) - a_b, b_b = broadcast_first(a, b) + # arrays with shape ((3, 4, 2)) and ((1, 2)) are passed + # to np.broadcast_arrays; hence it works + a_a, b_a = broadcast_first(a, b) + assert a_a.shape == (2, 3, 4) + assert b_a.shape == (2, 3, 4) - assert a_b.shape == (2, 3, 4) - assert b_b.shape == (2, 3, 4) + # plain np.broadcast_arrays will not work + with pytest.raises(ValueError, match="shape mismatch"): + np.broadcast_arrays(a, b) - a = np.ones((2, 2, 2)) - b = np.ones((2, 1)) + # arrays with shape ((5, 6, 4)) and ((6, 5)) are passed + # to np.broadcast_arrays; hence it will not work + a = np.ones((4, 5, 6)) + b = np.ones((5, 6)) - a_b, b_b = broadcast_first(a, b) + with pytest.raises(ValueError, match="shape mismatch"): + broadcast_first(a, b) - assert a_b.shape == (2, 2, 2) - assert b_b.shape == (2, 2, 2) + # plain np.broadcast_arrays will work + a_a, b_a = broadcast_first(a, b) + + assert a_a.shape == (4, 5, 6) + assert b_a.shape == (4, 5, 6) def test_broadcast_leading_axes(): @@ -152,28 +163,46 @@ def test_cumtrapz(): from glass.core.array import cumtrapz + # 1D f and x + f = np.array([1, 2, 3, 4]) x = np.array([0, 1, 2, 3]) + # default dtype (int - not supported by scipy) + glass_ct = cumtrapz(f, x) npt.assert_allclose(glass_ct, np.array([0, 1, 4, 7])) + # explicit dtype (float) + glass_ct = cumtrapz(f, x, dtype=float) scipy_ct = cumulative_trapezoid(f, x, initial=0) npt.assert_allclose(glass_ct, scipy_ct) + # explicit return array + result = cumtrapz(f, x, dtype=float, out=np.zeros((4,))) + scipy_ct = cumulative_trapezoid(f, x, initial=0) npt.assert_allclose(result, scipy_ct) + # 2D f and 1D x + f = np.array([[1, 4, 9, 16], [2, 3, 5, 7]]) x = np.array([0, 1, 2.5, 4]) + # default dtype (int - not supported by scipy) + glass_ct = cumtrapz(f, x) npt.assert_allclose(glass_ct, np.array([[0, 2, 12, 31], [0, 2, 8, 17]])) + # explicit dtype (float) + glass_ct = cumtrapz(f, x, dtype=float) scipy_ct = cumulative_trapezoid(f, x, initial=0) npt.assert_allclose(glass_ct, scipy_ct) + # explicit return array + glass_ct = cumtrapz(f, x, dtype=float, out=np.zeros((2, 4))) + scipy_ct = cumulative_trapezoid(f, x, initial=0) npt.assert_allclose(glass_ct, scipy_ct)