diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..408e5324 --- /dev/null +++ b/.gitignore @@ -0,0 +1,239 @@ +# Created by https://www.toptal.com/developers/gitignore/api/macos,windows,lithium,python +# Edit at https://www.toptal.com/developers/gitignore?templates=macos,windows,lithium,python + +### Lithium ### +libraries/* +resources/tmp/* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +### Python Patch ### +# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration +poetry.toml + +# ruff +.ruff_cache/ + +# LSP config files +pyrightconfig.json + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/macos,windows,lithium,python \ No newline at end of file diff --git a/README.md b/README.md index da66993c..cfc8dc4f 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,243 @@ Please follow the instructions in [python_testing_exercise.md](https://github.co ### pytest log +``` +====================================================================== FAILURES ====================================================================== +_______________________________________________________________ test_initialize_domain _______________________________________________________________ + + def test_initialize_domain(): + """ + Check function SolveDiffusion2D.initialize_domain + """ + solver = SolveDiffusion2D() + + # Fixture + w = 20. + h = 30. + dx = 0.3 + dy = 0.5 + + # Actual Result + solver.initialize_domain(w, h, dx, dy) + + # Expected Result + expected_nx = 66 + expected_ny = 60 + + assert w == solver.w + assert h == solver.h + assert dx == solver.dx + assert dy == solver.dy + assert expected_nx == solver.nx +> assert expected_ny == solver.ny +E assert 60 == 40 +E + where 40 = .ny + +tests/unit/test_diffusion2d_functions.py:35: AssertionError +________________________________________________________ test_initialize_physical_parameters _________________________________________________________ + + def test_initialize_physical_parameters(): + """ + Checks function SolveDiffusion2D.initialize_physical_parameters + """ + solver = SolveDiffusion2D() + + # Fixture + solver.dx = 0.3 + solver.dy = 0.5 + + d = 5. + T_cold = 100. + T_hot = 300. + + # Actual Result + solver.initialize_physical_parameters(d, T_cold, T_hot) + + # Expected Result + expected_dt = pytest.approx(0.00661764705882353, abs=1e-4) + + assert solver.D == d + assert solver.T_cold == T_cold + assert solver.T_hot == T_hot +> assert solver.dt == expected_dt +E assert 0.0125 == 0.00661764705882353 ± 1.0e-04 +E +E comparison failed +E Obtained: 0.0125 +E Expected: 0.00661764705882353 ± 1.0e-04 + +tests/unit/test_diffusion2d_functions.py:61: AssertionError +---------------------------------------------------------------- Captured stdout call ---------------------------------------------------------------- +dt = 0.0125 +_____________________________________________________________ test_set_initial_condition _____________________________________________________________ + + def test_set_initial_condition(): + """ + Checks function SolveDiffusion2D.get_initial_function + """ + solver = SolveDiffusion2D() + + # Fixture + solver.dx = 1 + solver.dy = 0.9 + solver.nx = 8 + solver.ny = 10 + solver.T_cold = 150. + solver.T_hot = 1000. + + # Actual Result + actual_result = solver.set_initial_condition() + + # Expected Result + expected_result = np.array([ + [ 150., 150., 150., 150., 150., 150., 150., 150., 150., 150.], + [ 150., 150., 150., 150., 150., 150., 150., 150., 150., 150.], + [ 150., 150., 150., 150., 150., 150., 150., 150., 150., 150.], + [ 150., 150., 150., 150., 150., 150., 150., 150., 150., 150.], + [ 150., 150., 150., 150., 1000., 1000., 1000., 1000., 150., 150.], + [ 150., 150., 150., 150., 1000., 1000., 1000., 1000., 150., 150.], + [ 150., 150., 150., 150., 1000., 1000., 1000., 1000., 150., 150.], + [ 150., 150., 150., 150., 150., 150., 150., 150., 150., 150.]]) + + +> assert (actual_result == expected_result).all() +E assert False +E + where False = () +E + where = array([[ 150.... 150.]]) == array([[ 150.... 150.]]) +E +E Use -v to get more diff.all + +tests/unit/test_diffusion2d_functions.py:93: AssertionError +============================================================== short test summary info =============================================================== +FAILED tests/unit/test_diffusion2d_functions.py::test_initialize_domain - assert 60 == 40 +FAILED tests/unit/test_diffusion2d_functions.py::test_initialize_physical_parameters - assert 0.0125 == 0.00661764705882353 ± 1.0e-04 +FAILED tests/unit/test_diffusion2d_functions.py::test_set_initial_condition - assert False +================================================================= 3 failed in 0.32s ================================================================== +``` + ### unittest log +``` +====================================================================== +FAIL: test_initialize_domain (tests.unit.test_diffusion2d_functions.TestOperations.test_initialize_domain) +Check function SolveDiffusion2D.initialize_domain +---------------------------------------------------------------------- +Traceback (most recent call last): + File "/Users/kevin/Desktop/uni-stuttgart/2024W/SSE/Exercise/Exercise_01_15/testing-python-exercise-wt2425/tests/unit/test_diffusion2d_functions.py", line 127, in test_initialize_domain + self.assertEqual(self.solver.ny, expected_ny) +AssertionError: 40 != 60 + +====================================================================== +FAIL: test_initialize_physical_parameters (tests.unit.test_diffusion2d_functions.TestOperations.test_initialize_physical_parameters) +Checks function SolveDiffusion2D.initialize_physical_parameters +---------------------------------------------------------------------- +Traceback (most recent call last): + File "/Users/kevin/Desktop/uni-stuttgart/2024W/SSE/Exercise/Exercise_01_15/testing-python-exercise-wt2425/tests/unit/test_diffusion2d_functions.py", line 151, in test_initialize_physical_parameters + self.assertAlmostEqual(self.solver.dt, expected_dt, 5) +AssertionError: 0.0125 != 0.00661764705882353 within 5 places (0.0058823529411764705 difference) + +====================================================================== +FAIL: test_set_initial_condition (tests.unit.test_diffusion2d_functions.TestOperations.test_set_initial_condition) +Checks function SolveDiffusion2D.get_initial_function +---------------------------------------------------------------------- +Traceback (most recent call last): + File "/Users/kevin/Desktop/uni-stuttgart/2024W/SSE/Exercise/Exercise_01_15/testing-python-exercise-wt2425/tests/unit/test_diffusion2d_functions.py", line 181, in test_set_initial_condition + self.assertTrue((actual_result == expected_result).all()) +AssertionError: False is not true + +---------------------------------------------------------------------- +Ran 3 tests in 0.001s + +FAILED (failures=3) +``` + +### Integration test log + +``` +================================================================ test session starts ================================================================= +platform darwin -- Python 3.12.7, pytest-8.3.3, pluggy-1.5.0 +rootdir: /Users/kevin/Desktop/uni-stuttgart/2024W/SSE/Exercise/Exercise_01_15/testing-python-exercise-wt2425 +collected 2 items + +tests/integration/test_diffusion2d.py FF [100%] + +====================================================================== FAILURES ====================================================================== +________________________________________________________ test_initialize_physical_parameters _________________________________________________________ + + def test_initialize_physical_parameters(): + """ + Checks function SolveDiffusion2D.initialize_domain + """ + solver = SolveDiffusion2D() + + # Fixture + w, h = 20., 30. + dx, dy = 0.3, 0.5 + d = 5. + T_cold, T_hot = 100., 300. + + # Actual Result + solver.initialize_domain(w, h, dx, dy) + solver.initialize_physical_parameters(d, T_cold, T_hot) + + # Expected Result + expected_dt = pytest.approx(0.00661764705882353, abs=1e-5) + +> assert solver.dt == expected_dt +E assert 0.0125 == 0.00661764705882353 ± 1.0e-05 +E +E comparison failed +E Obtained: 0.0125 +E Expected: 0.00661764705882353 ± 1.0e-05 + +tests/integration/test_diffusion2d.py:28: AssertionError +---------------------------------------------------------------- Captured stdout call ---------------------------------------------------------------- +dt = 0.0125 +_____________________________________________________________ test_set_initial_condition _____________________________________________________________ + + def test_set_initial_condition(): + """ + Checks function SolveDiffusion2D.get_initial_function + """ + solver = SolveDiffusion2D() + + # Fixture + w, h = 20., 30. + dx, dy = 0.3, 0.5 + d = 5. + T_cold, T_hot = 100., 300. + + # Actual Result + solver.initialize_domain(w, h, dx, dy) + solver.initialize_physical_parameters(d, T_cold, T_hot) + actual_result = solver.set_initial_condition() + + # Expected Result + def get_expected_result(): + u = 100. * np.ones((66, 60)) + + r, cx, cy = 2, 5, 5 + r2 = r ** 2 + for i in range(66): + for j in range(60): + p2 = (i * 0.3 - cx) ** 2 + (j * 0.5 - cy) ** 2 + if p2 < r2: + u[i, j] = 300. + + return u + +> assert (actual_result == get_expected_result()).all() +E ValueError: operands could not be broadcast together with shapes (66,40) (66,60) + +tests/integration/test_diffusion2d.py:63: ValueError +---------------------------------------------------------------- Captured stdout call ---------------------------------------------------------------- +dt = 0.0125 +============================================================== short test summary info =============================================================== +FAILED tests/integration/test_diffusion2d.py::test_initialize_physical_parameters - assert 0.0125 == 0.00661764705882353 ± 1.0e-05 +FAILED tests/integration/test_diffusion2d.py::test_set_initial_condition - ValueError: operands could not be broadcast together with shapes (66,40) (66,60) +================================================================= 2 failed in 0.29s ================================================================== +``` + ## 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..e51df078 Binary files /dev/null and b/coverage-report.pdf differ diff --git a/diffusion2d.py b/diffusion2d.py index 51a07f2d..4d1521ed 100644 --- a/diffusion2d.py +++ b/diffusion2d.py @@ -38,6 +38,12 @@ def __init__(self): self.dt = None def initialize_domain(self, w=10., h=10., dx=0.1, dy=0.1): + # check input type + assert type(w) == float, "w should be a float" + assert type(h) == float, "h should be a float" + assert type(dx) == float, "dx should be a float" + assert type(dy) == float, "dy should be a float" + self.w = w self.h = h self.dx = dx @@ -45,7 +51,12 @@ 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.): + # check input type + assert type(d) == float, "d should be a float" + assert type(T_cold) == float, "T_cold should be a float" + assert type(T_hot) == float, "T_hot should be a float" + 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..7552c4ec --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +numpy +pytest +matplotlib \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/integration/test_diffusion2d.py b/tests/integration/test_diffusion2d.py index fd026b40..b10e6c1d 100644 --- a/tests/integration/test_diffusion2d.py +++ b/tests/integration/test_diffusion2d.py @@ -3,7 +3,8 @@ """ from diffusion2d import SolveDiffusion2D - +import pytest +import numpy as np def test_initialize_physical_parameters(): """ @@ -11,9 +12,54 @@ def test_initialize_physical_parameters(): """ solver = SolveDiffusion2D() + # Fixture + w, h = 20., 30. + dx, dy = 0.3, 0.5 + d = 5. + T_cold, T_hot = 100., 300. + + # Actual Result + solver.initialize_domain(w, h, dx, dy) + solver.initialize_physical_parameters(d, T_cold, T_hot) + + # Expected Result + expected_dt = pytest.approx(0.00661764705882353, abs=1e-5) + + assert solver.dt == expected_dt + + def test_set_initial_condition(): """ Checks function SolveDiffusion2D.get_initial_function """ solver = SolveDiffusion2D() + + # Fixture + w, h = 20., 30. + dx, dy = 0.3, 0.5 + d = 5. + T_cold, T_hot = 100., 300. + + # Actual Result + solver.initialize_domain(w, h, dx, dy) + solver.initialize_physical_parameters(d, T_cold, T_hot) + actual_result = solver.set_initial_condition() + + # Expected Result + def get_expected_result(): + u = 100. * np.ones((66, 60)) + + r, cx, cy = 2, 5, 5 + r2 = r ** 2 + for i in range(66): + for j in range(60): + p2 = (i * 0.3 - cx) ** 2 + (j * 0.5 - cy) ** 2 + if p2 < r2: + u[i, j] = 300. + + return u + + assert (actual_result == get_expected_result()).all() + + diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/unit/test_diffusion2d_functions.py b/tests/unit/test_diffusion2d_functions.py index c4277ffd..a7b1147a 100644 --- a/tests/unit/test_diffusion2d_functions.py +++ b/tests/unit/test_diffusion2d_functions.py @@ -4,6 +4,10 @@ from diffusion2d import SolveDiffusion2D +from unittest import TestCase +import pytest +import numpy as np + def test_initialize_domain(): """ @@ -11,16 +15,165 @@ def test_initialize_domain(): """ solver = SolveDiffusion2D() + # Fixture + w = 20. + h = 30. + dx = 0.3 + dy = 0.5 + + # Actual Result + solver.initialize_domain(w, h, dx, dy) + + # Expected Result + expected_nx = 66 + expected_ny = 60 + + assert w == solver.w + assert h == solver.h + assert dx == solver.dx + assert dy == solver.dy + assert expected_nx == solver.nx + assert expected_ny == solver.ny + def test_initialize_physical_parameters(): """ - Checks function SolveDiffusion2D.initialize_domain + Checks function SolveDiffusion2D.initialize_physical_parameters """ solver = SolveDiffusion2D() + # Fixture + solver.dx = 0.3 + solver.dy = 0.5 + + d = 5. + T_cold = 100. + T_hot = 300. + + # Actual Result + solver.initialize_physical_parameters(d, T_cold, T_hot) + + # Expected Result + expected_dt = pytest.approx(0.00661764705882353, abs=1e-5) + + assert solver.D == d + assert solver.T_cold == T_cold + assert solver.T_hot == T_hot + assert solver.dt == expected_dt + def test_set_initial_condition(): """ Checks function SolveDiffusion2D.get_initial_function """ solver = SolveDiffusion2D() + + # Fixture + solver.dx = 1 + solver.dy = 0.9 + solver.nx = 8 + solver.ny = 10 + solver.T_cold = 150. + solver.T_hot = 1000. + + # Actual Result + actual_result = solver.set_initial_condition() + + # Expected Result + expected_result = np.array([ + [ 150., 150., 150., 150., 150., 150., 150., 150., 150., 150.], + [ 150., 150., 150., 150., 150., 150., 150., 150., 150., 150.], + [ 150., 150., 150., 150., 150., 150., 150., 150., 150., 150.], + [ 150., 150., 150., 150., 150., 150., 150., 150., 150., 150.], + [ 150., 150., 150., 150., 1000., 1000., 1000., 1000., 150., 150.], + [ 150., 150., 150., 150., 1000., 1000., 1000., 1000., 150., 150.], + [ 150., 150., 150., 150., 1000., 1000., 1000., 1000., 150., 150.], + [ 150., 150., 150., 150., 150., 150., 150., 150., 150., 150.]]) + + + assert (actual_result == expected_result).all() + + +class TestOperations(TestCase): + + def setUp(self): + self.solver = SolveDiffusion2D() + + def test_initialize_domain(self): + """ + Check function SolveDiffusion2D.initialize_domain + """ + # Fixture + w = 20. + h = 30. + dx = 0.3 + dy = 0.5 + + # Actual Result + self.solver.initialize_domain(w, h, dx, dy) + + # Expected Result + expected_nx = 66 + expected_ny = 60 + + self.assertAlmostEqual(self.solver.w, w, 3) + self.assertAlmostEqual(self.solver.h, h, 3) + self.assertAlmostEqual(self.solver.dx, dx, 3) + self.assertAlmostEqual(self.solver.dy, dy, 3) + self.assertEqual(self.solver.nx, expected_nx) + self.assertEqual(self.solver.ny, expected_ny) + + + def test_initialize_physical_parameters(self): + """ + Checks function SolveDiffusion2D.initialize_physical_parameters + """ + # Fixture + self.solver.dx = 0.3 + self.solver.dy = 0.5 + + d = 5. + T_cold = 100. + T_hot = 300. + + # Actual Result + self.solver.initialize_physical_parameters(d, T_cold, T_hot) + + # Expected Result + expected_dt = 0.00661764705882353 + + self.assertAlmostEqual(self.solver.D, d, 3) + self.assertAlmostEqual(self.solver.T_cold, T_cold, 3) + self.assertAlmostEqual(self.solver.T_hot, T_hot, 3) + self.assertAlmostEqual(self.solver.dt, expected_dt, 5) + + + def test_set_initial_condition(self): + """ + Checks function SolveDiffusion2D.get_initial_function + """ + # Fixture + self.solver.dx = 1 + self.solver.dy = 0.9 + self.solver.nx = 8 + self.solver.ny = 10 + self.solver.T_cold = 150. + self.solver.T_hot = 1000. + + # Actual Result + actual_result = self.solver.set_initial_condition() + + # Expected Result + expected_result = np.array([ + [ 150., 150., 150., 150., 150., 150., 150., 150., 150., 150.], + [ 150., 150., 150., 150., 150., 150., 150., 150., 150., 150.], + [ 150., 150., 150., 150., 150., 150., 150., 150., 150., 150.], + [ 150., 150., 150., 150., 150., 150., 150., 150., 150., 150.], + [ 150., 150., 150., 150., 1000., 1000., 1000., 1000., 150., 150.], + [ 150., 150., 150., 150., 1000., 1000., 1000., 1000., 150., 150.], + [ 150., 150., 150., 150., 1000., 1000., 1000., 1000., 150., 150.], + [ 150., 150., 150., 150., 150., 150., 150., 150., 150., 150.]]) + + + self.assertTrue((actual_result == expected_result).all()) + \ No newline at end of file diff --git a/tox.toml b/tox.toml new file mode 100644 index 00000000..aa8bb0a1 --- /dev/null +++ b/tox.toml @@ -0,0 +1,7 @@ +requires = ["tox>=4"] +env_list = ["testing"] + +[env.testing] +description = "Run pytest" +deps = ["-r requirements.txt"] +commands = [["pytest"], ["python", "-m", "unittest"]] \ No newline at end of file