diff --git a/README.md b/README.md index da66993c..d0c158a4 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,354 @@ Please follow the instructions in [python_testing_exercise.md](https://github.co ## Test logs (for submission) ### pytest log +```bash +python -m pytest +========================================================================== test session starts ========================================================================== +platform win32 -- Python 3.13.1, pytest-8.3.4, pluggy-1.5.0 +rootdir: C:\Users\Felix\Development\testing-python-exercise-wt2425 +collected 5 items + +tests\integration\test_diffusion2d.py FF [ 40%] +tests\unit\test_diffusion2d_functions.py FFF [100%] + +=============================================================================== FAILURES ================================================================================ +__________________________________________________________________ test_initialize_physical_parameters __________________________________________________________________ + + def test_initialize_physical_parameters(): + """ + Checks function SolveDiffusion2D.initialize_physical_parameters + """ + solver = SolveDiffusion2D() + + w, h, dx, dy = 5., 6., 0.2, 0.2 + d, T_cold, T_hot = 2., 300., 700. + expected_dt = 0.0050 # (dx ** 2 * dy ** 2) / (2 * d * (dx ** 2 + dy ** 2)) + + solver.initialize_domain(w, h, dx, dy) + solver.initialize_physical_parameters(d, T_cold, T_hot) + +> assert np.close(expected_dt, solver.dt), f"Returned dt is incorrect: is {solver.dt}, should be {expected_dt}" + +tests\integration\test_diffusion2d.py:22: +_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + +attr = 'close' + + def __getattr__(attr): + # Warn for expired attributes + import warnings + + if attr == "linalg": + import numpy.linalg as linalg + return linalg + elif attr == "fft": + import numpy.fft as fft + return fft + elif attr == "dtypes": + import numpy.dtypes as dtypes + return dtypes + elif attr == "random": + import numpy.random as random + return random + elif attr == "polynomial": + import numpy.polynomial as polynomial + return polynomial + elif attr == "ma": + import numpy.ma as ma + return ma + elif attr == "ctypeslib": + import numpy.ctypeslib as ctypeslib + return ctypeslib + elif attr == "exceptions": + import numpy.exceptions as exceptions + return exceptions + elif attr == "testing": + import numpy.testing as testing + return testing + elif attr == "matlib": + import numpy.matlib as matlib + return matlib + elif attr == "f2py": + import numpy.f2py as f2py + return f2py + elif attr == "typing": + import numpy.typing as typing + return typing + elif attr == "rec": + import numpy.rec as rec + return rec + elif attr == "char": + import numpy.char as char + return char + elif attr == "array_api": + raise AttributeError("`numpy.array_api` is not available from " + "numpy 2.0 onwards", name=None) + elif attr == "core": + import numpy.core as core + return core + elif attr == "strings": + import numpy.strings as strings + return strings + elif attr == "distutils": + if 'distutils' in __numpy_submodules__: + import numpy.distutils as distutils + return distutils + else: + raise AttributeError("`numpy.distutils` is not available from " + "Python 3.12 onwards", name=None) + + if attr in __future_scalars__: + # And future warnings for those that will change, but also give + # the AttributeError + warnings.warn( + f"In the future `np.{attr}` will be defined as the " + "corresponding NumPy scalar.", FutureWarning, stacklevel=2) + + if attr in __former_attrs__: + raise AttributeError(__former_attrs__[attr], name=None) + + if attr in __expired_attributes__: + raise AttributeError( + f"`np.{attr}` was removed in the NumPy 2.0 release. " + f"{__expired_attributes__[attr]}", + name=None + ) + + if attr == "chararray": + warnings.warn( + "`np.chararray` is deprecated and will be removed from " + "the main namespace in the future. Use an array with a string " + "or bytes dtype instead.", DeprecationWarning, stacklevel=2) + import numpy.char as char + return char.chararray + +> raise AttributeError("module {!r} has no attribute " + "{!r}".format(__name__, attr)) +E AttributeError: module 'numpy' has no attribute 'close'. Did you mean: 'choose'? + +..\..\AppData\Roaming\Python\Python313\site-packages\numpy\__init__.py:427: AttributeError +------------------------------------------------------------------------- Captured stdout call -------------------------------------------------------------------------- +dt = 2.5000000000000005e-05 +______________________________________________________________________ test_set_initial_condition _______________________________________________________________________ + + def test_set_initial_condition(): + """ + Checks function SolveDiffusion2D.set_initial_condition + """ + solver = SolveDiffusion2D() + + u = np.full((5, 5), 300.) + + solver.initialize_domain(1., 1., 0.2, 0.2) + solver.initialize_physical_parameters(2., 300., 700.) +> solver_u = solver.set_initial_condition() + +tests\integration\test_diffusion2d.py:35: +_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ +diffusion2d.py:66: in set_initial_condition + u = self.T_cold * np.ones((self.nx, self.ny)) +_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + +shape = (0.6150622062000001, 0.6150622062000001), dtype = None, order = 'C' + + @finalize_array_function_like + @set_module('numpy') + def ones(shape, dtype=None, order='C', *, device=None, like=None): + """ + Return a new array of given shape and type, filled with ones. + + Parameters + ---------- + shape : int or sequence of ints + Shape of the new array, e.g., ``(2, 3)`` or ``2``. + dtype : data-type, optional + The desired data-type for the array, e.g., `numpy.int8`. Default is + `numpy.float64`. + order : {'C', 'F'}, optional, default: C + Whether to store multi-dimensional data in row-major + (C-style) or column-major (Fortran-style) order in + memory. + device : str, optional + The device on which to place the created array. Default: None. + For Array-API interoperability only, so must be ``"cpu"`` if passed. + + .. versionadded:: 2.0.0 + ${ARRAY_FUNCTION_LIKE} + + .. versionadded:: 1.20.0 + + Returns + ------- + out : ndarray + Array of ones with the given shape, dtype, and order. + + See Also + -------- + ones_like : Return an array of ones with shape and type of input. + empty : Return a new uninitialized array. + zeros : Return a new array setting values to zero. + full : Return a new array of given shape filled with value. + + Examples + -------- + >>> import numpy as np + >>> np.ones(5) + array([1., 1., 1., 1., 1.]) + + >>> np.ones((5,), dtype=int) + array([1, 1, 1, 1, 1]) + + >>> np.ones((2, 1)) + array([[1.], + [1.]]) + + >>> s = (2,2) + >>> np.ones(s) + array([[1., 1.], + [1., 1.]]) + + """ + if like is not None: + return _ones_with_like( + like, shape, dtype=dtype, order=order, device=device + ) + +> a = empty(shape, dtype, order, device=device) +E TypeError: 'float' object cannot be interpreted as an integer + +..\..\AppData\Roaming\Python\Python313\site-packages\numpy\_core\numeric.py:199: TypeError +------------------------------------------------------------------------- Captured stdout call -------------------------------------------------------------------------- +dt = 2.5000000000000005e-05 +____________________________________________________________ TestDiffusion2DFunctions.test_initialize_domain ____________________________________________________________ + +self = + + def test_initialize_domain(self): + """ + Check function SolveDiffusion2D.initialize_domain + """ + solver = SolveDiffusion2D() + + nx, ny = 25., 30. + solver.initialize_domain(5., 6., 0.2, 0.2) + +> assert solver.nx == nx, f"Returned nx is incorrect: is {solver.nx}, should be {nx}" +E AssertionError: Returned nx is incorrect: is 3.075311031, should be 25.0 +E assert 3.075311031 == 25.0 +E + where 3.075311031 = .nx + +tests\unit\test_diffusion2d_functions.py:20: AssertionError +_____________________________________________________ TestDiffusion2DFunctions.test_initialize_physical_parameters ______________________________________________________ + +self = + + def test_initialize_physical_parameters(self): + """ + Checks function SolveDiffusion2D.initialize_domain + """ + solver = SolveDiffusion2D() + + dt = 0.0019 + solver.dx, solver.dy = 0.125, 0.75 + solver.initialize_physical_parameters(4., 300., 700.) +> self.assertAlmostEqual(solver.dt, dt, places=4, + msg=f"Returned dt is incorrect: is {solver.dt}, should be {dt}") +E AssertionError: 9.501689189189189e-06 != 0.0019 within 4 places (0.001890498310810811 difference) : Returned dt is incorrect: is 9.501689189189189e-06, should be 0.0019 + +tests\unit\test_diffusion2d_functions.py:33: AssertionError +------------------------------------------------------------------------- Captured stdout call -------------------------------------------------------------------------- +dt = 9.501689189189189e-06 +__________________________________________________________ TestDiffusion2DFunctions.test_set_initial_condition __________________________________________________________ + +self = + + def test_set_initial_condition(self): + """ + Checks function SolveDiffusion2D.get_initial_function + """ + solver = SolveDiffusion2D() + + solver.dx = solver.dy = 0.01 + solver.nx = solver.ny = 75 + solver.T_cold, solver.T_hot = 300., 700. + + solver_u = solver.set_initial_condition() + + r, cx, cy = 2, 5, 5 + r2 = r ** 2 + + u = solver.T_cold * np.ones((solver.nx, solver.ny)) + + for i in range(solver.nx): + for j in range(solver.ny): + p2 = (i * solver.dx - cx) ** 2 + (j * solver.dy - cy) ** 2 + if p2 < r2: + u[i, j] = solver.T_hot + + # print(solver_u, u) + + for i in range(solver.nx): + for j in range(solver.ny): +> self.assertAlmostEqual(solver_u[i, j], u[i, j], places=4, + msg=f"Returned u is incorrect: is {solver_u[i, j]}, should be {u[i, j]}") +E AssertionError: np.float64(700.0) != np.float64(300.0) within 4 places (np.float64(400.0) difference) : Returned u is incorrect: is 700.0, should be 300.0 + +tests\unit\test_diffusion2d_functions.py:64: AssertionError +======================================================================== short test summary info ======================================================================== +FAILED tests/integration/test_diffusion2d.py::test_initialize_physical_parameters - AttributeError: module 'numpy' has no attribute 'close'. Did you mean: 'choose'? +FAILED tests/integration/test_diffusion2d.py::test_set_initial_condition - TypeError: 'float' object cannot be interpreted as an integer +FAILED tests/unit/test_diffusion2d_functions.py::TestDiffusion2DFunctions::test_initialize_domain - AssertionError: Returned nx is incorrect: is 3.075311031, should be 25.0 +FAILED tests/unit/test_diffusion2d_functions.py::TestDiffusion2DFunctions::test_initialize_physical_parameters - AssertionError: 9.501689189189189e-06 != 0.0019 within 4 places (0.001890498310810811 difference) : Returned dt is incorrect: is 9.501689189189189e-06, should be 0.0019 +FAILED tests/unit/test_diffusion2d_functions.py::TestDiffusion2DFunctions::test_set_initial_condition - AssertionError: np.float64(700.0) != np.float64(300.0) within 4 places (np.float64(400.0) difference) : Returned u is incorrect: is 700.0, should be 300.0 +=========================================================================== 5 failed in 0.41s =========================================================================== +``` ### unittest log +```bash +python -m unittest tests\unit\test_diffusion2d_functions.py +Fdt = 9.501689189189189e-06 +FF +====================================================================== +FAIL: test_initialize_domain (tests.unit.test_diffusion2d_functions.TestDiffusion2DFunctions.test_initialize_domain) +Check function SolveDiffusion2D.initialize_domain +---------------------------------------------------------------------- +Traceback (most recent call last): + File "C:\Users\Felix\Development\testing-python-exercise-wt2425\tests\unit\test_diffusion2d_functions.py", line 20, in test_initialize_domain + assert solver.nx == nx, f"Returned nx is incorrect: is {solver.nx}, should be {nx}" + ^^^^^^^^^^^^^^^ +AssertionError: Returned nx is incorrect: is 3.075311031, should be 25.0 + +====================================================================== +FAIL: test_initialize_physical_parameters (tests.unit.test_diffusion2d_functions.TestDiffusion2DFunctions.test_initialize_physical_parameters) +Checks function SolveDiffusion2D.initialize_domain +---------------------------------------------------------------------- +Traceback (most recent call last): + File "C:\Users\Felix\Development\testing-python-exercise-wt2425\tests\unit\test_diffusion2d_functions.py", line 33, in test_initialize_physical_parameters + self.assertAlmostEqual(solver.dt, dt, places=4, + ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ + msg=f"Returned dt is incorrect: is {solver.dt}, should be {dt}") + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +AssertionError: 9.501689189189189e-06 != 0.0019 within 4 places (0.001890498310810811 difference) : Returned dt is incorrect: is 9.501689189189189e-06, should be 0.0019 + +====================================================================== +FAIL: test_set_initial_condition (tests.unit.test_diffusion2d_functions.TestDiffusion2DFunctions.test_set_initial_condition) +Checks function SolveDiffusion2D.get_initial_function +---------------------------------------------------------------------- +Traceback (most recent call last): + File "C:\Users\Felix\Development\testing-python-exercise-wt2425\tests\unit\test_diffusion2d_functions.py", line 64, in test_set_initial_condition + self.assertAlmostEqual(solver_u[i, j], u[i, j], places=4, + ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + msg=f"Returned u is incorrect: is {solver_u[i, j]}, should be {u[i, j]}") + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +AssertionError: np.float64(700.0) != np.float64(300.0) within 4 places (np.float64(400.0) difference) : Returned u is incorrect: is 700.0, should be 300.0 + +---------------------------------------------------------------------- +Ran 3 tests in 0.004s + +FAILED (failures=3) +``` + ## Citing The code used in this exercise is based on [Chapter 7 of the book "Learning Scientific Programming with Python"](https://scipython.com/book/chapter-7-matplotlib/examples/the-two-dimensional-diffusion-equation/). diff --git a/coverage-report.pdf b/coverage-report.pdf new file mode 100644 index 00000000..055e4b17 Binary files /dev/null and b/coverage-report.pdf differ diff --git a/diffusion2d.py b/diffusion2d.py index 51a07f2d..606c3e5b 100644 --- a/diffusion2d.py +++ b/diffusion2d.py @@ -38,6 +38,9 @@ def __init__(self): self.dt = None def initialize_domain(self, w=10., h=10., dx=0.1, dy=0.1): + # Assert all parameters are floats + assert all(isinstance(param, float) for param in [w, h, dx, dy]), "All parameters must be floats" + self.w = w self.h = h self.dx = dx @@ -45,7 +48,10 @@ def initialize_domain(self, w=10., h=10., dx=0.1, dy=0.1): self.nx = int(w / dx) self.ny = int(h / dy) - def initialize_physical_parameters(self, d=4., T_cold=300, T_hot=700): + def initialize_physical_parameters(self, d=4., T_cold=300., T_hot=700.): + # Assert all parameters are floats + assert all(isinstance(param, float) for param in [d, T_cold, T_hot]), "All parameters must be floats" + self.D = d self.T_cold = T_cold self.T_hot = T_hot diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..8a033ca5 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +pytest +numpy +matplotlib \ No newline at end of file diff --git a/tests/integration/test_diffusion2d.py b/tests/integration/test_diffusion2d.py index fd026b40..eac5426d 100644 --- a/tests/integration/test_diffusion2d.py +++ b/tests/integration/test_diffusion2d.py @@ -2,18 +2,36 @@ Tests for functionality checks in class SolveDiffusion2D """ +import numpy as np from diffusion2d import SolveDiffusion2D def test_initialize_physical_parameters(): """ - Checks function SolveDiffusion2D.initialize_domain + Checks function SolveDiffusion2D.initialize_physical_parameters """ solver = SolveDiffusion2D() + w, h, dx, dy = 5., 6., 0.2, 0.2 + d, T_cold, T_hot = 2., 300., 700. + expected_dt = 0.0050 # (dx ** 2 * dy ** 2) / (2 * d * (dx ** 2 + dy ** 2)) + + solver.initialize_domain(w, h, dx, dy) + solver.initialize_physical_parameters(d, T_cold, T_hot) + + assert np.allclose(expected_dt, solver.dt), f"Returned dt is incorrect: is {solver.dt}, should be {expected_dt}" + def test_set_initial_condition(): """ - Checks function SolveDiffusion2D.get_initial_function + Checks function SolveDiffusion2D.set_initial_condition """ solver = SolveDiffusion2D() + + u = np.full((5, 5), 300.) + + solver.initialize_domain(1., 1., 0.2, 0.2) + solver.initialize_physical_parameters(2., 300., 700.) + solver_u = solver.set_initial_condition() + + assert np.allclose(solver_u, u), f"Returned initial condition is incorrect: is {solver_u}, should be {u}" diff --git a/tests/unit/test_diffusion2d_functions.py b/tests/unit/test_diffusion2d_functions.py index c4277ffd..77c02d35 100644 --- a/tests/unit/test_diffusion2d_functions.py +++ b/tests/unit/test_diffusion2d_functions.py @@ -2,25 +2,64 @@ Tests for functions in class SolveDiffusion2D """ +import unittest +import numpy as np from diffusion2d import SolveDiffusion2D +class TestDiffusion2DFunctions(unittest.TestCase): -def test_initialize_domain(): - """ - Check function SolveDiffusion2D.initialize_domain - """ - solver = SolveDiffusion2D() + def test_initialize_domain(self): + """ + Check function SolveDiffusion2D.initialize_domain + """ + solver = SolveDiffusion2D() + nx, ny = 25., 30. + solver.initialize_domain(5., 6., 0.2, 0.2) -def test_initialize_physical_parameters(): - """ - Checks function SolveDiffusion2D.initialize_domain - """ - solver = SolveDiffusion2D() + assert solver.nx == nx, f"Returned nx is incorrect: is {solver.nx}, should be {nx}" + assert solver.ny == ny, f"Returned ny is incorrect: is {solver.ny}, should be {ny}" -def test_set_initial_condition(): - """ - Checks function SolveDiffusion2D.get_initial_function - """ - solver = SolveDiffusion2D() + def test_initialize_physical_parameters(self): + """ + Checks function SolveDiffusion2D.initialize_domain + """ + solver = SolveDiffusion2D() + + dt = 0.0019 + solver.dx, solver.dy = 0.125, 0.75 + solver.initialize_physical_parameters(4., 300., 700.) + self.assertAlmostEqual(solver.dt, dt, places=4, + msg=f"Returned dt is incorrect: is {solver.dt}, should be {dt}") + + + def test_set_initial_condition(self): + """ + Checks function SolveDiffusion2D.get_initial_function + """ + solver = SolveDiffusion2D() + + solver.dx = solver.dy = 0.3 + solver.nx = solver.ny = 75 + solver.T_cold, solver.T_hot = 300., 700. + + solver_u = solver.set_initial_condition() + + r, cx, cy = 2, 5, 5 + r2 = r ** 2 + + u = solver.T_cold * np.ones((solver.nx, solver.ny)) + + for i in range(solver.nx): + for j in range(solver.ny): + p2 = (i * solver.dx - cx) ** 2 + (j * solver.dy - cy) ** 2 + if p2 < r2: + u[i, j] = solver.T_hot + + # print(solver_u, u) + + for i in range(solver.nx): + for j in range(solver.ny): + self.assertAlmostEqual(solver_u[i, j], u[i, j], places=4, + msg=f"Returned u is incorrect: is {solver_u[i, j]}, should be {u[i, j]}") diff --git a/tox.toml b/tox.toml new file mode 100644 index 00000000..9c2f96ee --- /dev/null +++ b/tox.toml @@ -0,0 +1,10 @@ +requires = ["tox>=4"] +env_list = ["testing"] + +[env.testing] +desciption = "Run pytest and unittest" +deps = ["-r requirements.txt"] +commands = [ + ["python", "-m", "pytest"], + ["python", "-m", "unittest", "discover", "-s", "tests/unit"] +] \ No newline at end of file