Skip to content

Commit cf3f2e0

Browse files
authored
Merge pull request #47 from mplough-kobold/matt-refactor-tests-as-pytest
Remove nosetest and convert tests to pytest
2 parents 6b892b7 + 089e975 commit cf3f2e0

File tree

13 files changed

+293
-463
lines changed

13 files changed

+293
-463
lines changed

.github/workflows/python-package-conda.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ jobs:
6060
6161
- name: Run Tests
6262
run: |
63-
pytest --cov-config=.coveragerc --cov-report=xml --cov=pymatsolver -s -v
63+
make coverage
6464
6565
- name: Test Documentation
6666
if: ${{ (matrix.os == 'ubuntu-latest') && (matrix.python-version == '3.11') }}

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
.coverage
2+
coverage.xml
13
*.pyc
24
*.so
35
build/

Makefile

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ build:
44
python setup.py build_ext --inplace
55

66
coverage:
7-
nosetests --logging-level=INFO --with-coverage --cover-package=pymatsolver --cover-html
8-
open cover/index.html
7+
pytest --cov-config=.coveragerc --cov-report=xml --cov=pymatsolver -s -v
98

109
lint:
1110
pylint --output-format=html pymatsolver > pylint.html
@@ -14,7 +13,7 @@ graphs:
1413
pyreverse -my -A -o pdf -p pymatsolver pymatsolver/**.py pymatsolver/**/**.py
1514

1615
tests:
17-
nosetests --logging-level=INFO
16+
pytest
1817

1918
docs:
2019
cd docs;make html

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ From a clean install on Ubuntu:
6060
./miniconda.sh -b
6161
export PATH=/root/anaconda/bin:/root/miniconda/bin:$PATH
6262
conda update --yes conda
63-
conda install --yes numpy scipy matplotlib cython ipython nose
63+
conda install --yes numpy scipy matplotlib cython ipython pytest coverage
6464
6565
git clone https://github.com/rowanc1/pymatsolver.git
6666
cd pymatsolver

pymatsolver/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@
7373
from .direct import Solver
7474
from .direct import SolverLU
7575

76+
from .solvers import PymatsolverAccuracyError
77+
7678
BicgJacobi = BiCGJacobi # backwards compatibility
7779

7880
try:

pymatsolver/direct/pardiso.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ def factor(self, A=None):
7171
if not self._factored:
7272
self.solver.refactor(self.A)
7373
self._factored = True
74+
7475
def _solveM(self, rhs):
7576
self.factor()
7677
sol = self.solver.solve(rhs)

pymatsolver/solvers.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
import warnings
33

44

5+
class PymatsolverAccuracyError(Exception):
6+
pass
7+
8+
59
class Base():
610
_accuracy_tol = 1e-6
711
_check_accuracy = False
@@ -57,7 +61,7 @@ def _transposeClass(self):
5761
def T(self):
5862
"The transpose operator for this class"
5963
if self._transposeClass is None:
60-
raise Exception(
64+
raise NotImplementedError(
6165
'The transpose for the {} class is not possible.'.format(
6266
self.__name__
6367
)
@@ -74,7 +78,7 @@ def _compute_accuracy(self, rhs, x):
7478
msg = 'Accuracy on solve is above tolerance: {0:e} > {1:e}'.format(
7579
nrm, self.accuracy_tol
7680
)
77-
raise Exception(msg)
81+
raise PymatsolverAccuracyError(msg)
7882

7983
def _solve(self, rhs):
8084

tests/test_Basic.py

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,25 @@
1-
import unittest
21
import numpy as np
2+
import numpy.testing as npt
3+
import pytest
34
import scipy.sparse as sp
45
from pymatsolver import Diagonal
56

67
TOL = 1e-12
78

89

9-
class TestBasic(unittest.TestCase):
10+
def test_DiagonalSolver():
1011

11-
def test_DiagonalSolver(self):
12+
A = sp.identity(5)*2.0
13+
rhs = np.c_[np.arange(1, 6), np.arange(2, 11, 2)]
14+
X = Diagonal(A) * rhs
15+
x = Diagonal(A) * rhs[:, 0]
1216

13-
A = sp.identity(5)*2.0
14-
rhs = np.c_[np.arange(1, 6), np.arange(2, 11, 2)]
15-
X = Diagonal(A) * rhs
16-
x = Diagonal(A) * rhs[:, 0]
17+
sol = rhs/2.0
1718

18-
sol = rhs/2.0
19+
with pytest.raises(TypeError):
20+
Diagonal(A, check_accuracy=np.array([1, 2, 3]))
21+
with pytest.raises(TypeError):
22+
Diagonal(A, accuracy_tol=0)
1923

20-
with self.assertRaises(TypeError):
21-
Diagonal(A, check_accuracy=np.array([1, 2, 3]))
22-
with self.assertRaises(TypeError):
23-
Diagonal(A, accuracy_tol=0)
24-
25-
self.assertLess(np.linalg.norm(sol-X, np.inf), TOL)
26-
self.assertLess(np.linalg.norm(sol[:, 0]-x, np.inf), TOL)
27-
28-
29-
if __name__ == '__main__':
30-
unittest.main()
24+
npt.assert_allclose(sol, X, atol=TOL)
25+
npt.assert_allclose(sol[:, 0], x, atol=TOL)

tests/test_BicgJacobi.py

Lines changed: 33 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,36 @@
1-
import unittest
21
from pymatsolver import BicgJacobi
32
import numpy as np
3+
import numpy.testing as npt
44
import scipy.sparse as sp
5-
6-
TOL = 1e-6
7-
8-
9-
class TestBicgJacobi(unittest.TestCase):
10-
11-
def setUp(self):
12-
13-
nSize = 100
14-
A = sp.rand(nSize, nSize, 0.05, format='csr', random_state=100)
15-
A = A + sp.spdiags(np.ones(nSize), 0, nSize, nSize)
16-
A = A.T*A
17-
A = A.tocsr()
18-
np.random.seed(1)
19-
sol = np.random.rand(nSize, 4)
20-
rhs = A.dot(sol)
21-
22-
self.A = A
23-
self.rhs = rhs
24-
self.sol = sol
25-
26-
def test(self):
27-
rhs = self.rhs
28-
Ainv = BicgJacobi(self.A, symmetric=True)
29-
solb = Ainv*rhs
30-
for i in range(3):
31-
err = np.linalg.norm(
32-
self.A*solb[:, i] - rhs[:, i]) / np.linalg.norm(rhs[:, i])
33-
self.assertLess(err, TOL)
34-
Ainv.clean()
35-
36-
def test_T(self):
37-
rhs = self.rhs
38-
Ainv = BicgJacobi(self.A, symmetric=True)
39-
Ainv.maxIter = 2000
40-
AinvT = Ainv.T
41-
solb = AinvT*rhs
42-
for i in range(3):
43-
err = np.linalg.norm(
44-
self.A.T*solb[:, i] - rhs[:, i]) / np.linalg.norm(rhs[:, i])
45-
self.assertLess(err, TOL)
46-
Ainv.clean()
47-
48-
49-
class TestBicgJacobiComplex(unittest.TestCase):
50-
51-
def setUp(self):
52-
nSize = 100
53-
A = sp.rand(nSize, nSize, 0.05, format='csr', random_state=100)
54-
A.data = A.data + 1j*np.random.rand(A.nnz)
55-
A = A.T.dot(A) + sp.spdiags(np.ones(nSize), 0, nSize, nSize)
56-
A = A.tocsr()
57-
58-
np.random.seed(1)
59-
sol = np.random.rand(nSize, 5) + 1j*np.random.rand(nSize, 5)
60-
rhs = A.dot(sol)
61-
62-
self.A = A
63-
self.rhs = rhs
64-
self.sol = sol
65-
66-
def test(self):
67-
rhs = self.rhs
68-
Ainv = BicgJacobi(self.A, symmetric=True)
69-
solb = Ainv*rhs
70-
for i in range(3):
71-
err = np.linalg.norm(
72-
self.A*solb[:, i] - rhs[:, i]) / np.linalg.norm(rhs[:, i])
73-
self.assertLess(err, TOL)
74-
Ainv.clean()
75-
76-
def test_T(self):
77-
rhs = self.rhs
78-
Ainv = BicgJacobi(self.A, symmetric=True)
79-
Ainv.maxIter = 2000
80-
AinvT = Ainv.T
81-
solb = AinvT*rhs
82-
for i in range(3):
83-
err = np.linalg.norm(
84-
self.A.T*solb[:, i] - rhs[:, i]) / np.linalg.norm(rhs[:, i])
85-
self.assertLess(err, TOL)
86-
Ainv.clean()
87-
88-
89-
if __name__ == '__main__':
90-
unittest.main()
5+
import pytest
6+
7+
TOL = 1e-5
8+
9+
@pytest.fixture()
10+
def test_mat_data():
11+
nSize = 100
12+
A = sp.rand(nSize, nSize, 0.05, format='csr', random_state=100)
13+
A = A + sp.spdiags(np.ones(nSize), 0, nSize, nSize)
14+
A = A.T*A
15+
A = A.tocsr()
16+
np.random.seed(1)
17+
sol = np.random.rand(nSize, 4)
18+
rhs = A.dot(sol)
19+
return A, sol, rhs
20+
21+
22+
@pytest.mark.parametrize('transpose', [True, False])
23+
@pytest.mark.parametrize('dtype', [np.float64, np.complex128])
24+
def test_solve(test_mat_data, dtype, transpose):
25+
A, rhs, sol = test_mat_data
26+
A = A.astype(dtype)
27+
rhs = rhs.astype(dtype)
28+
sol = sol.astype(dtype)
29+
if transpose:
30+
A = A.T
31+
Ainv = BicgJacobi(A, symmetric=True).T
32+
else:
33+
Ainv = BicgJacobi(A, symmetric=True)
34+
Ainv.maxiter = 2000
35+
solb = Ainv * rhs
36+
npt.assert_allclose(rhs, A @ solb, atol=TOL)

tests/test_Mumps.py

Lines changed: 55 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,61 @@
1-
import unittest
21
import numpy as np
32
import scipy.sparse as sp
43
import pymatsolver
4+
import pytest
5+
import numpy.testing as npt
56

6-
TOL = 1e-11
7-
8-
if pymatsolver.AvailableSolvers['Mumps']:
9-
class TestMumps(unittest.TestCase):
10-
11-
def setUp(self):
12-
n = 5
13-
irn = np.r_[0, 1, 3, 4, 1, 0, 4, 2, 1, 2, 0, 2]
14-
jcn = np.r_[1, 2, 2, 4, 0, 0, 1, 3, 4, 1, 2, 2]
15-
a = np.r_[
16-
3.0, -3.0, 2.0, 1.0, 3.0, 2.0,
17-
4.0, 2.0, 6.0, -1.0, 4.0, 1.0
18-
]
19-
rhs = np.r_[20.0, 24.0, 9.0, 6.0, 13.0]
20-
rhs = np.c_[rhs, 10*rhs, 100*rhs]
21-
sol = np.r_[1., 2., 3., 4., 5.]
22-
sol = np.c_[sol, 10*sol, 100*sol]
23-
A = sp.coo_matrix((a, (irn, jcn)), shape=(n, n))
24-
self.A = A
25-
self.rhs = rhs
26-
self.sol = sol
27-
28-
def test_1to5(self):
29-
rhs = self.rhs
30-
sol = self.sol
31-
Ainv = pymatsolver.Mumps(self.A)
32-
for i in range(3):
33-
self.assertLess(
34-
np.linalg.norm(Ainv * rhs[:, i] - sol[:, i]), TOL
35-
)
36-
self.assertLess(np.linalg.norm(Ainv * rhs - sol, np.inf), TOL)
37-
38-
def test_1to5_cmplx(self):
39-
rhs = self.rhs.astype(complex)
40-
sol = self.sol.astype(complex)
41-
self.A = self.A.astype(complex)
42-
Ainv = pymatsolver.Mumps(self.A)
43-
for i in range(3):
44-
self.assertLess(
45-
np.linalg.norm(Ainv * rhs[:, i] - sol[:, i]), TOL
46-
)
47-
self.assertLess(np.linalg.norm(Ainv * rhs - sol, np.inf), TOL)
7+
if not pymatsolver.AvailableSolvers['Mumps']:
8+
pytest.skip(reason="MUMPS solver is not installed", allow_module_level=True)
489

49-
def test_1to5_T(self):
50-
rhs = self.rhs
51-
sol = self.sol
52-
Ainv = pymatsolver.Mumps(self.A)
53-
AinvT = Ainv.T
54-
for i in range(3):
55-
self.assertLess(
56-
np.linalg.norm(AinvT.T * rhs[:, i] - sol[:, i]), TOL
57-
)
58-
self.assertLess(np.linalg.norm(AinvT.T * rhs - sol, np.inf), TOL)
59-
60-
# def test_singular(self):
61-
# A = sp.identity(5).tocsr()
62-
# A[-1, -1] = 0
63-
# self.assertRaises(Exception, pymatsolver.Mumps, A)
64-
65-
def test_multiFactorsInMem(self):
66-
n = 100
67-
A = sp.rand(n, n, 0.7)+sp.identity(n)
68-
x = np.ones((n, 10))
69-
rhs = A * x
70-
solvers = [pymatsolver.Mumps(A) for _ in range(20)]
71-
72-
for Ainv in solvers:
73-
self.assertLess(
74-
np.linalg.norm(Ainv * rhs - x)/np.linalg.norm(rhs), TOL)
75-
Ainv.clean()
76-
77-
for Ainv in solvers:
78-
self.assertLess(
79-
np.linalg.norm(Ainv * rhs - x)/np.linalg.norm(rhs), TOL
80-
)
10+
TOL = 1e-11
8111

82-
if __name__ == '__main__':
83-
unittest.main()
12+
@pytest.fixture()
13+
def test_mat_data():
14+
n = 5
15+
irn = np.r_[0, 1, 3, 4, 1, 0, 4, 2, 1, 2, 0, 2]
16+
jcn = np.r_[1, 2, 2, 4, 0, 0, 1, 3, 4, 1, 2, 2]
17+
a = np.r_[
18+
3.0, -3.0, 2.0, 1.0, 3.0, 2.0,
19+
4.0, 2.0, 6.0, -1.0, 4.0, 1.0
20+
]
21+
rhs = np.r_[20.0, 24.0, 9.0, 6.0, 13.0]
22+
rhs = np.c_[rhs, 10 * rhs, 100 * rhs]
23+
sol = np.r_[1., 2., 3., 4., 5.]
24+
sol = np.c_[sol, 10 * sol, 100 * sol]
25+
A = sp.coo_matrix((a, (irn, jcn)), shape=(n, n))
26+
return A, rhs, sol
27+
28+
@pytest.mark.parametrize('transpose', [True, False])
29+
@pytest.mark.parametrize('dtype', [np.float64, np.complex128])
30+
def test_solve(test_mat_data, dtype, transpose):
31+
A, rhs, sol = test_mat_data
32+
sol = sol.astype(dtype)
33+
rhs = rhs.astype(dtype)
34+
A = A.astype(dtype)
35+
if transpose:
36+
Ainv = pymatsolver.Mumps(A.T).T
37+
else:
38+
Ainv = pymatsolver.Mumps(A)
39+
for i in range(3):
40+
npt.assert_allclose(Ainv * rhs[:, i], sol[:, i], atol=TOL)
41+
npt.assert_allclose(Ainv * rhs, sol, atol=TOL)
42+
43+
44+
# def test_singular(self):
45+
# A = sp.identity(5).tocsr()
46+
# A[-1, -1] = 0
47+
# self.assertRaises(Exception, pymatsolver.Mumps, A)
48+
49+
def test_multiFactorsInMem():
50+
n = 100
51+
A = sp.rand(n, n, 0.7)+sp.identity(n)
52+
x = np.ones((n, 10))
53+
rhs = A * x
54+
solvers = [pymatsolver.Mumps(A) for _ in range(20)]
55+
56+
for Ainv in solvers:
57+
npt.assert_allclose(Ainv * rhs, x, rtol=TOL)
58+
Ainv.clean()
59+
60+
for Ainv in solvers:
61+
npt.assert_allclose(Ainv * rhs, x, rtol=TOL)

0 commit comments

Comments
 (0)