diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..932bbfb --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +venv/ +__pycache__/ +*.pyc +.pytest_cache/ +.tox/ +.coverage +htmlcov/ diff --git a/coverage-report.pdf b/coverage-report.pdf new file mode 100644 index 0000000..9fbcf8d Binary files /dev/null and b/coverage-report.pdf differ diff --git a/diffusion2d.py b/diffusion2d.py index 51a07f2..89f32e3 100644 --- a/diffusion2d.py +++ b/diffusion2d.py @@ -38,6 +38,10 @@ def __init__(self): self.dt = None def initialize_domain(self, w=10., h=10., dx=0.1, dy=0.1): + assert isinstance(w, float), 'w should be a float' + assert isinstance(h, float), 'h should be a float' + assert isinstance(dx, float), 'dx should be a float' + assert isinstance(dy, float), 'dy should be a float' self.w = w self.h = h self.dx = dx @@ -45,7 +49,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 isinstance(d, float), 'd should be a float' + assert isinstance(T_cold, float), 'T_cold should be a float' + assert isinstance(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 0000000..13a998b --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +numpy==1.24.4 +matplotlib==3.7.5 +pytest==8.3.5 +coverage==7.6.1 +tox>=4 diff --git a/tests/integration/test_diffusion2d.py b/tests/integration/test_diffusion2d.py index fd026b4..293cf9a 100644 --- a/tests/integration/test_diffusion2d.py +++ b/tests/integration/test_diffusion2d.py @@ -2,18 +2,69 @@ 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 that dt is computed correctly when initialize_domain + and initialize_physical_parameters are called together. """ solver = SolveDiffusion2D() + + # Integration: call initialize_domain first + w = 10. + h = 10. + dx = 0.5 + dy = 0.5 + solver.initialize_domain(w=w, h=h, dx=dx, dy=dy) + + # Call initialize_physical_parameters + d = 2. + T_cold = 100. + T_hot = 500. + solver.initialize_physical_parameters(d=d, T_cold=T_cold, T_hot=T_hot) + + # Manual calculation of expected dt + # dt = dx2 * dy2 / (2 * D * (dx2 + dy2)) + # dx2 = 0.25, dy2 = 0.25 + # dt = 0.0625 / (2 * 2 * 0.5) = 0.0625 / 2 = 0.03125 + expected_dt = 0.03125 + + assert np.isclose(solver.dt, expected_dt) def test_set_initial_condition(): """ - Checks function SolveDiffusion2D.get_initial_function + Checks that the initial condition u is computed correctly + when all setup functions are called together. """ solver = SolveDiffusion2D() + + # Integration: call setup functions + w = 10. + h = 10. + dx = 1. + dy = 1. + solver.initialize_domain(w=w, h=h, dx=dx, dy=dy) + solver.initialize_physical_parameters(d=4., T_cold=300., T_hot=700.) + + # Call set_initial_condition + u = solver.set_initial_condition() + + # Manual calculation of expected u + nx = int(w / dx) + ny = int(h / dy) + expected_u = 300. * np.ones((nx, ny)) + + # Circle of radius r=2 centered at (cx=5, cy=5) + r, cx, cy = 2, 5, 5 + r2 = r ** 2 + for i in range(nx): + for j in range(ny): + p2 = (i * dx - cx) ** 2 + (j * dy - cy) ** 2 + if p2 < r2: + expected_u[i, j] = 700. + + np.testing.assert_array_equal(u, expected_u) diff --git a/tests/unit/test_diffusion2d_functions.py b/tests/unit/test_diffusion2d_functions.py index c4277ff..e3f8f7c 100644 --- a/tests/unit/test_diffusion2d_functions.py +++ b/tests/unit/test_diffusion2d_functions.py @@ -2,25 +2,69 @@ Tests for functions in class SolveDiffusion2D """ +import unittest +import numpy as np from diffusion2d import SolveDiffusion2D -def test_initialize_domain(): - """ - Check function SolveDiffusion2D.initialize_domain - """ - solver = SolveDiffusion2D() +class TestDiffusion2D(unittest.TestCase): + def setUp(self): + self.solver = SolveDiffusion2D() + def test_initialize_domain(self): + """ + Check function SolveDiffusion2D.initialize_domain + """ + w = 20. + h = 10. + dx = 0.5 + dy = 2. + self.solver.initialize_domain(w=w, h=h, dx=dx, dy=dy) + + self.assertEqual(self.solver.w, w) + self.assertEqual(self.solver.h, h) + self.assertEqual(self.solver.dx, dx) + self.assertEqual(self.solver.dy, dy) + self.assertEqual(self.solver.nx, 40) + self.assertEqual(self.solver.ny, 5) -def test_initialize_physical_parameters(): - """ - Checks function SolveDiffusion2D.initialize_domain - """ - solver = SolveDiffusion2D() + def test_initialize_physical_parameters(self): + """ + Checks function SolveDiffusion2D.initialize_physical_parameters + """ + self.solver.dx = 0.2 + self.solver.dy = 0.2 + d = 5. + T_cold = 200. + T_hot = 600. + self.solver.initialize_physical_parameters(d=d, T_cold=T_cold, T_hot=T_hot) + + self.assertEqual(self.solver.D, d) + self.assertEqual(self.solver.T_cold, T_cold) + self.assertEqual(self.solver.T_hot, T_hot) + + # Expected dt = dx2 * dy2 / (2 * D * (dx2 + dy2)) + # dx2 = 0.04, dy2 = 0.04 + # dt = 0.0016 / (2 * 5 * 0.08) = 0.0016 / 0.8 = 0.002 + self.assertAlmostEqual(self.solver.dt, 0.002) - -def test_set_initial_condition(): - """ - Checks function SolveDiffusion2D.get_initial_function - """ - solver = SolveDiffusion2D() + def test_set_initial_condition(self): + """ + Checks function SolveDiffusion2D.set_initial_condition + """ + self.solver.initialize_domain(w=10., h=10., dx=1., dy=1.) + self.solver.initialize_physical_parameters(d=4., T_cold=300., T_hot=700.) + + u = self.solver.set_initial_condition() + + # Check shape + self.assertEqual(u.shape, (10, 10)) + + # Check T_cold outside (0, 0) -> (0-5)^2 + (0-5)^2 = 50 > 4 + self.assertEqual(u[0, 0], 300.) + + # Check T_hot inside (5, 5) -> (5-5)^2 + (5-5)^2 = 0 < 4 + # Index corresponding to x=5, y=5. + # i * dx = x => i = 5/1 = 5 + # j * dy = y => j = 5/1 = 5 + self.assertEqual(u[5, 5], 700.) diff --git a/tox.toml b/tox.toml new file mode 100644 index 0000000..236651c --- /dev/null +++ b/tox.toml @@ -0,0 +1,9 @@ +requires = ["tox>=4"] +env_list = ["py"] + +[env_run_base] +description = "Run tests using pytest" +skip_install = true +deps = ["-rrequirements.txt"] +set_env = { PYTHONPATH = "{tox_root}" } +commands = [["pytest", "tests", "-v"]]