Skip to content

Commit

Permalink
Merge pull request #47 from mplough-kobold/matt-refactor-tests-as-pytest
Browse files Browse the repository at this point in the history
Remove nosetest and convert tests to pytest
  • Loading branch information
jcapriot authored Sep 26, 2024
2 parents 6b892b7 + 089e975 commit cf3f2e0
Show file tree
Hide file tree
Showing 13 changed files with 293 additions and 463 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/python-package-conda.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ jobs:
- name: Run Tests
run: |
pytest --cov-config=.coveragerc --cov-report=xml --cov=pymatsolver -s -v
make coverage
- name: Test Documentation
if: ${{ (matrix.os == 'ubuntu-latest') && (matrix.python-version == '3.11') }}
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.coverage
coverage.xml
*.pyc
*.so
build/
Expand Down
5 changes: 2 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ build:
python setup.py build_ext --inplace

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

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

tests:
nosetests --logging-level=INFO
pytest

docs:
cd docs;make html
Expand Down
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ From a clean install on Ubuntu:
./miniconda.sh -b
export PATH=/root/anaconda/bin:/root/miniconda/bin:$PATH
conda update --yes conda
conda install --yes numpy scipy matplotlib cython ipython nose
conda install --yes numpy scipy matplotlib cython ipython pytest coverage
git clone https://github.com/rowanc1/pymatsolver.git
cd pymatsolver
Expand Down
2 changes: 2 additions & 0 deletions pymatsolver/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@
from .direct import Solver
from .direct import SolverLU

from .solvers import PymatsolverAccuracyError

BicgJacobi = BiCGJacobi # backwards compatibility

try:
Expand Down
1 change: 1 addition & 0 deletions pymatsolver/direct/pardiso.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ def factor(self, A=None):
if not self._factored:
self.solver.refactor(self.A)
self._factored = True

def _solveM(self, rhs):
self.factor()
sol = self.solver.solve(rhs)
Expand Down
8 changes: 6 additions & 2 deletions pymatsolver/solvers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
import warnings


class PymatsolverAccuracyError(Exception):
pass


class Base():
_accuracy_tol = 1e-6
_check_accuracy = False
Expand Down Expand Up @@ -57,7 +61,7 @@ def _transposeClass(self):
def T(self):
"The transpose operator for this class"
if self._transposeClass is None:
raise Exception(
raise NotImplementedError(
'The transpose for the {} class is not possible.'.format(
self.__name__
)
Expand All @@ -74,7 +78,7 @@ def _compute_accuracy(self, rhs, x):
msg = 'Accuracy on solve is above tolerance: {0:e} > {1:e}'.format(
nrm, self.accuracy_tol
)
raise Exception(msg)
raise PymatsolverAccuracyError(msg)

def _solve(self, rhs):

Expand Down
33 changes: 14 additions & 19 deletions tests/test_Basic.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,25 @@
import unittest
import numpy as np
import numpy.testing as npt
import pytest
import scipy.sparse as sp
from pymatsolver import Diagonal

TOL = 1e-12


class TestBasic(unittest.TestCase):
def test_DiagonalSolver():

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

A = sp.identity(5)*2.0
rhs = np.c_[np.arange(1, 6), np.arange(2, 11, 2)]
X = Diagonal(A) * rhs
x = Diagonal(A) * rhs[:, 0]
sol = rhs/2.0

sol = rhs/2.0
with pytest.raises(TypeError):
Diagonal(A, check_accuracy=np.array([1, 2, 3]))
with pytest.raises(TypeError):
Diagonal(A, accuracy_tol=0)

with self.assertRaises(TypeError):
Diagonal(A, check_accuracy=np.array([1, 2, 3]))
with self.assertRaises(TypeError):
Diagonal(A, accuracy_tol=0)

self.assertLess(np.linalg.norm(sol-X, np.inf), TOL)
self.assertLess(np.linalg.norm(sol[:, 0]-x, np.inf), TOL)


if __name__ == '__main__':
unittest.main()
npt.assert_allclose(sol, X, atol=TOL)
npt.assert_allclose(sol[:, 0], x, atol=TOL)
120 changes: 33 additions & 87 deletions tests/test_BicgJacobi.py
Original file line number Diff line number Diff line change
@@ -1,90 +1,36 @@
import unittest
from pymatsolver import BicgJacobi
import numpy as np
import numpy.testing as npt
import scipy.sparse as sp

TOL = 1e-6


class TestBicgJacobi(unittest.TestCase):

def setUp(self):

nSize = 100
A = sp.rand(nSize, nSize, 0.05, format='csr', random_state=100)
A = A + sp.spdiags(np.ones(nSize), 0, nSize, nSize)
A = A.T*A
A = A.tocsr()
np.random.seed(1)
sol = np.random.rand(nSize, 4)
rhs = A.dot(sol)

self.A = A
self.rhs = rhs
self.sol = sol

def test(self):
rhs = self.rhs
Ainv = BicgJacobi(self.A, symmetric=True)
solb = Ainv*rhs
for i in range(3):
err = np.linalg.norm(
self.A*solb[:, i] - rhs[:, i]) / np.linalg.norm(rhs[:, i])
self.assertLess(err, TOL)
Ainv.clean()

def test_T(self):
rhs = self.rhs
Ainv = BicgJacobi(self.A, symmetric=True)
Ainv.maxIter = 2000
AinvT = Ainv.T
solb = AinvT*rhs
for i in range(3):
err = np.linalg.norm(
self.A.T*solb[:, i] - rhs[:, i]) / np.linalg.norm(rhs[:, i])
self.assertLess(err, TOL)
Ainv.clean()


class TestBicgJacobiComplex(unittest.TestCase):

def setUp(self):
nSize = 100
A = sp.rand(nSize, nSize, 0.05, format='csr', random_state=100)
A.data = A.data + 1j*np.random.rand(A.nnz)
A = A.T.dot(A) + sp.spdiags(np.ones(nSize), 0, nSize, nSize)
A = A.tocsr()

np.random.seed(1)
sol = np.random.rand(nSize, 5) + 1j*np.random.rand(nSize, 5)
rhs = A.dot(sol)

self.A = A
self.rhs = rhs
self.sol = sol

def test(self):
rhs = self.rhs
Ainv = BicgJacobi(self.A, symmetric=True)
solb = Ainv*rhs
for i in range(3):
err = np.linalg.norm(
self.A*solb[:, i] - rhs[:, i]) / np.linalg.norm(rhs[:, i])
self.assertLess(err, TOL)
Ainv.clean()

def test_T(self):
rhs = self.rhs
Ainv = BicgJacobi(self.A, symmetric=True)
Ainv.maxIter = 2000
AinvT = Ainv.T
solb = AinvT*rhs
for i in range(3):
err = np.linalg.norm(
self.A.T*solb[:, i] - rhs[:, i]) / np.linalg.norm(rhs[:, i])
self.assertLess(err, TOL)
Ainv.clean()


if __name__ == '__main__':
unittest.main()
import pytest

TOL = 1e-5

@pytest.fixture()
def test_mat_data():
nSize = 100
A = sp.rand(nSize, nSize, 0.05, format='csr', random_state=100)
A = A + sp.spdiags(np.ones(nSize), 0, nSize, nSize)
A = A.T*A
A = A.tocsr()
np.random.seed(1)
sol = np.random.rand(nSize, 4)
rhs = A.dot(sol)
return A, sol, rhs


@pytest.mark.parametrize('transpose', [True, False])
@pytest.mark.parametrize('dtype', [np.float64, np.complex128])
def test_solve(test_mat_data, dtype, transpose):
A, rhs, sol = test_mat_data
A = A.astype(dtype)
rhs = rhs.astype(dtype)
sol = sol.astype(dtype)
if transpose:
A = A.T
Ainv = BicgJacobi(A, symmetric=True).T
else:
Ainv = BicgJacobi(A, symmetric=True)
Ainv.maxiter = 2000
solb = Ainv * rhs
npt.assert_allclose(rhs, A @ solb, atol=TOL)
132 changes: 55 additions & 77 deletions tests/test_Mumps.py
Original file line number Diff line number Diff line change
@@ -1,83 +1,61 @@
import unittest
import numpy as np
import scipy.sparse as sp
import pymatsolver
import pytest
import numpy.testing as npt

TOL = 1e-11

if pymatsolver.AvailableSolvers['Mumps']:
class TestMumps(unittest.TestCase):

def setUp(self):
n = 5
irn = np.r_[0, 1, 3, 4, 1, 0, 4, 2, 1, 2, 0, 2]
jcn = np.r_[1, 2, 2, 4, 0, 0, 1, 3, 4, 1, 2, 2]
a = np.r_[
3.0, -3.0, 2.0, 1.0, 3.0, 2.0,
4.0, 2.0, 6.0, -1.0, 4.0, 1.0
]
rhs = np.r_[20.0, 24.0, 9.0, 6.0, 13.0]
rhs = np.c_[rhs, 10*rhs, 100*rhs]
sol = np.r_[1., 2., 3., 4., 5.]
sol = np.c_[sol, 10*sol, 100*sol]
A = sp.coo_matrix((a, (irn, jcn)), shape=(n, n))
self.A = A
self.rhs = rhs
self.sol = sol

def test_1to5(self):
rhs = self.rhs
sol = self.sol
Ainv = pymatsolver.Mumps(self.A)
for i in range(3):
self.assertLess(
np.linalg.norm(Ainv * rhs[:, i] - sol[:, i]), TOL
)
self.assertLess(np.linalg.norm(Ainv * rhs - sol, np.inf), TOL)

def test_1to5_cmplx(self):
rhs = self.rhs.astype(complex)
sol = self.sol.astype(complex)
self.A = self.A.astype(complex)
Ainv = pymatsolver.Mumps(self.A)
for i in range(3):
self.assertLess(
np.linalg.norm(Ainv * rhs[:, i] - sol[:, i]), TOL
)
self.assertLess(np.linalg.norm(Ainv * rhs - sol, np.inf), TOL)
if not pymatsolver.AvailableSolvers['Mumps']:
pytest.skip(reason="MUMPS solver is not installed", allow_module_level=True)

def test_1to5_T(self):
rhs = self.rhs
sol = self.sol
Ainv = pymatsolver.Mumps(self.A)
AinvT = Ainv.T
for i in range(3):
self.assertLess(
np.linalg.norm(AinvT.T * rhs[:, i] - sol[:, i]), TOL
)
self.assertLess(np.linalg.norm(AinvT.T * rhs - sol, np.inf), TOL)

# def test_singular(self):
# A = sp.identity(5).tocsr()
# A[-1, -1] = 0
# self.assertRaises(Exception, pymatsolver.Mumps, A)

def test_multiFactorsInMem(self):
n = 100
A = sp.rand(n, n, 0.7)+sp.identity(n)
x = np.ones((n, 10))
rhs = A * x
solvers = [pymatsolver.Mumps(A) for _ in range(20)]

for Ainv in solvers:
self.assertLess(
np.linalg.norm(Ainv * rhs - x)/np.linalg.norm(rhs), TOL)
Ainv.clean()

for Ainv in solvers:
self.assertLess(
np.linalg.norm(Ainv * rhs - x)/np.linalg.norm(rhs), TOL
)
TOL = 1e-11

if __name__ == '__main__':
unittest.main()
@pytest.fixture()
def test_mat_data():
n = 5
irn = np.r_[0, 1, 3, 4, 1, 0, 4, 2, 1, 2, 0, 2]
jcn = np.r_[1, 2, 2, 4, 0, 0, 1, 3, 4, 1, 2, 2]
a = np.r_[
3.0, -3.0, 2.0, 1.0, 3.0, 2.0,
4.0, 2.0, 6.0, -1.0, 4.0, 1.0
]
rhs = np.r_[20.0, 24.0, 9.0, 6.0, 13.0]
rhs = np.c_[rhs, 10 * rhs, 100 * rhs]
sol = np.r_[1., 2., 3., 4., 5.]
sol = np.c_[sol, 10 * sol, 100 * sol]
A = sp.coo_matrix((a, (irn, jcn)), shape=(n, n))
return A, rhs, sol

@pytest.mark.parametrize('transpose', [True, False])
@pytest.mark.parametrize('dtype', [np.float64, np.complex128])
def test_solve(test_mat_data, dtype, transpose):
A, rhs, sol = test_mat_data
sol = sol.astype(dtype)
rhs = rhs.astype(dtype)
A = A.astype(dtype)
if transpose:
Ainv = pymatsolver.Mumps(A.T).T
else:
Ainv = pymatsolver.Mumps(A)
for i in range(3):
npt.assert_allclose(Ainv * rhs[:, i], sol[:, i], atol=TOL)
npt.assert_allclose(Ainv * rhs, sol, atol=TOL)


# def test_singular(self):
# A = sp.identity(5).tocsr()
# A[-1, -1] = 0
# self.assertRaises(Exception, pymatsolver.Mumps, A)

def test_multiFactorsInMem():
n = 100
A = sp.rand(n, n, 0.7)+sp.identity(n)
x = np.ones((n, 10))
rhs = A * x
solvers = [pymatsolver.Mumps(A) for _ in range(20)]

for Ainv in solvers:
npt.assert_allclose(Ainv * rhs, x, rtol=TOL)
Ainv.clean()

for Ainv in solvers:
npt.assert_allclose(Ainv * rhs, x, rtol=TOL)
Loading

0 comments on commit cf3f2e0

Please sign in to comment.