diff --git a/Coverage report.pdf b/Coverage report.pdf new file mode 100644 index 00000000..2d65db3d Binary files /dev/null and b/Coverage report.pdf differ diff --git a/README.md b/README.md index da66993c..a54227ff 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,36 @@ Please follow the instructions in [python_testing_exercise.md](https://github.com/Simulation-Software-Engineering/Lecture-Material/blob/main/05_testing_and_ci/python_testing_exercise.md). ## Test logs (for submission) +For all the tests, the following changes were made : + +**Changing `self.nx = int(w / dx)` to `self.nx = int(h / dx)` in initialize_domain** + +**Changing to `dx2, dy2 = self.dx * self.dy, self.dy * self.dy` in initialize_physical_parameters** + +**Changing to `u = self.T_hot * np.ones((self.nx, self.ny))` in set_initial_condition** + ### pytest log +#### unit + +Screenshot 2025-01-20 at 7 23 06 PM + +Screenshot 2025-01-20 at 7 23 19 PM + +Screenshot 2025-01-20 at 7 23 29 PM + +#### integration + +Screenshot 2025-01-20 at 7 46 05 PM + +Screenshot 2025-01-20 at 7 46 13 PM + + ### unittest log +Screenshot 2025-01-20 at 7 40 00 PM + ## 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/diffusion2d.py b/diffusion2d.py index 51a07f2d..45233db5 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), "Width (w) must be a float" + assert isinstance(h, float), "Height (h) must be a float" + assert isinstance(dx, float), "dx must be a float" + assert isinstance(dy, float), "dy must 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), "Thermal diffusivity (d) must be a float" + assert isinstance(T_cold, float), "T_cold must be a float" + assert isinstance(T_hot, float), "T_hot must 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..070be7e6 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +numpy>=1.26.1 +matplotlib>=3.8.4 \ No newline at end of file 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..ed8a4f4c 100644 --- a/tests/integration/test_diffusion2d.py +++ b/tests/integration/test_diffusion2d.py @@ -10,6 +10,9 @@ def test_initialize_physical_parameters(): Checks function SolveDiffusion2D.initialize_domain """ solver = SolveDiffusion2D() + solver.initialize_domain(w = 20., h = 30., dx = 0.2, dy = 0.4) + solver.initialize_physical_parameters(d=2., T_cold=200., T_hot=500.) + assert round(solver.dt, 3) == 0.008 def test_set_initial_condition(): @@ -17,3 +20,34 @@ def test_set_initial_condition(): Checks function SolveDiffusion2D.get_initial_function """ solver = SolveDiffusion2D() + + # Set domain and physical parameters + w, h = 10.0, 11.0 # Width and height of the domain + dx, dy = 0.1, 0.2 # Grid spacing in x and y directions + T_cold, T_hot = 301.0, 701.0 # Cold and hot temperatures + + # Initialize the domain and physical parameters + solver.initialize_domain(w=w, h=h, dx=dx, dy=dy) + solver.initialize_physical_parameters(T_cold=T_cold, T_hot=T_hot) + + # Set the initial condition + res = solver.set_initial_condition() + + # Test each grid point + radius_squared = 2**2 # Radius of the circle squared + center_x, center_y = 5.0, 5.0 # Center of the hot circle + nx, ny = solver.nx, solver.ny # Number of grid points in x and y directions + + for i in range(nx): + for j in range(ny): + # Calculate the squared distance from the center of the circle + x, y = i * dx, j * dy + distance_squared = (x - center_x) ** 2 + (y - center_y) ** 2 + + # Check if the point is inside or outside the circle + if distance_squared < radius_squared: + # Inside the circle, temperature should be T_hot + assert res[i, j] == T_hot, f"Expected T_hot={T_hot} at ({i},{j}), got {res[i, j]}" + else: + # Outside the circle, temperature should be T_cold + assert res[i, j] == T_cold, f"Expected T_cold={T_cold} at ({i},{j}), got {res[i, j]}" 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..8d5fc28d 100644 --- a/tests/unit/test_diffusion2d_functions.py +++ b/tests/unit/test_diffusion2d_functions.py @@ -10,6 +10,10 @@ def test_initialize_domain(): Check function SolveDiffusion2D.initialize_domain """ solver = SolveDiffusion2D() + solver.initialize_domain(w=20.,h=30.,dx=0.2,dy=0.4) + + assert solver.nx == 100, f"Expected nx=100, but got {solver.nx}" + assert solver.ny == 75, f"Expected ny=75, but got {solver.ny}" def test_initialize_physical_parameters(): @@ -17,6 +21,15 @@ def test_initialize_physical_parameters(): Checks function SolveDiffusion2D.initialize_domain """ solver = SolveDiffusion2D() + solver.w = 20. + solver.h = 30. + solver.dx = 0.2 + solver.dy = 0.4 + solver.initialize_physical_parameters(d=2., T_cold=200., T_hot=500.) + + # Check the calculated dt value (assert with some tolerance) + expected_dt = 0.008 + assert abs(solver.dt - expected_dt) < 1e-4, f"Expected dt={expected_dt}, but got {solver.dt}" def test_set_initial_condition(): @@ -24,3 +37,23 @@ def test_set_initial_condition(): Checks function SolveDiffusion2D.get_initial_function """ solver = SolveDiffusion2D() + solver = SolveDiffusion2D() + solver.nx = 100 + solver.ny = 150 + solver.dx = 0.2 + solver.dy = 0.4 + solver.T_cold = 100. + solver.T_hot = 400. + + u = solver.set_initial_condition() + + # Check the shape of the output matrix u (it should match nx, ny) + assert u.shape == (solver.nx, solver.ny), f"Expected u.shape = ({solver.nx}, {solver.ny}), but got {u.shape}" + + # Check that corners are cold (boundary conditions) + assert u[0, 0] == solver.T_cold, "Top-left corner should be cold." + assert u[0, -1] == solver.T_cold, "Top-right corner should be cold." + assert u[-1, 0] == solver.T_cold, "Bottom-left corner should be cold." + assert u[-1, -1] == solver.T_cold, "Bottom-right corner should be cold." + + diff --git a/tests/unit/test_diffusion2d_functions_unit.py b/tests/unit/test_diffusion2d_functions_unit.py new file mode 100644 index 00000000..8f451050 --- /dev/null +++ b/tests/unit/test_diffusion2d_functions_unit.py @@ -0,0 +1,82 @@ +""" +Tests for functions in class SolveDiffusion2D +""" + +from diffusion2d import SolveDiffusion2D +import unittest + +class TestDiffusion2D(unittest.TestCase): + def setUp(self): + """Initialize the solver with default values for tests.""" + self.solver = SolveDiffusion2D() + + # Set up the domain and physical parameters for the tests + self.solver.w = 20. # Width of the domain + self.solver.h = 30. # Height of the domain + self.solver.dx = 0.2 # Grid step size in x direction + self.solver.dy = 0.4 # Grid step size in y direction + self.solver.T_cold = 100. # Initial cold temperature + self.solver.T_hot = 400. # Initial hot temperature + self.solver.initialize_domain(self.solver.w, self.solver.h, self.solver.dx, self.solver.dy) + + def test_initialize_domain(self): + """ + Check function SolveDiffusion2D.initialize_domain + """ + self.solver.initialize_domain(w=20., h=30., dx=0.2, dy=0.4) + + # nx and ny should be based on w/dx and h/dy + self.assertEqual(self.solver.nx, 100, f"Expected nx=100, but got {self.solver.nx}") + self.assertEqual(self.solver.ny, 75, f"Expected ny=75, but got {self.solver.ny}") + + + + + def test_initialize_physical_parameters(self): + """ + Checks function SolveDiffusion2D.initialize_domain + """ + self.solver.initialize_physical_parameters(d=2., T_cold=200., T_hot=500.) + + # Check the calculated dt value (assert with some tolerance) + expected_dt = 0.008 + self.assertAlmostEqual(self.solver.dt, expected_dt, places=4, msg=f"Expected dt={expected_dt}, but got {self.solver.dt}") + + + + def test_set_initial_condition(self): + """ + Checks function SolveDiffusion2D.get_initial_function + """ + # Initialize physical parameters for this test + self.solver.nx = 100 + self.solver.ny = 150 + self.solver.T_cold = 100. + self.solver.T_hot = 400. + u = self.solver.set_initial_condition() + + # Check the shape of the output matrix u (it should match nx, ny) + self.assertEqual(u.shape, (self.solver.nx, self.solver.ny),f"Expected u.shape = ({self.solver.nx}, {self.solver.ny}), but got {u.shape}") + + # Check that corners are cold (boundary conditions) + self.assertEqual(u[0, 0], self.solver.T_cold, "Top-left corner should be cold.") + self.assertEqual(u[0, -1], self.solver.T_cold, "Top-right corner should be cold.") + self.assertEqual(u[-1, 0], self.solver.T_cold, "Bottom-left corner should be cold.") + self.assertEqual(u[-1, -1], self.solver.T_cold, "Bottom-right corner should be cold.") + + # Check if some of the center values are hot (inside the circle, should be equal to T_hot) + # Based on the implementation, we know that the center will have T_hot values. + r, cx, cy = 2, 5, 5 # Circle parameters used in set_initial_condition + for i in range(self.solver.nx): + for j in range(self.solver.ny): + # Calculate distance from the center (cx, cy) + dist = (i * self.solver.dx - cx) ** 2 + (j * self.solver.dy - cy) ** 2 + if dist < r**2: + # The points within the radius of the circle should have T_hot + self.assertEqual(u[i, j], self.solver.T_hot,f"Expected T_hot={self.solver.T_hot} inside the circle, but got {u[i,j]} at ({i},{j})") + else: + # Points outside the circle should remain at T_cold + self.assertEqual(u[i, j], self.solver.T_cold, f"Expected T_cold={self.solver.T_cold} outside the circle, but got {u[i,j]} at ({i},{j})") + + + diff --git a/tox.toml b/tox.toml new file mode 100644 index 00000000..fa300c42 --- /dev/null +++ b/tox.toml @@ -0,0 +1,11 @@ +env_list = ["pytest", "unittest"] + +[env.pytest] +description = "pytest" +deps = ["pytest", "-r requirements.txt"] +commands = [["python", "-m", "pytest", "tests/integration/test_diffusion2d.py"]] + +[env.unittest] +description = "unittest" +deps = ["-r requirements.txt"] +commands = [["python", "-m", "unittest", "tests/unit/test_diffusion2d_functions_unit.py"]] \ No newline at end of file diff --git a/tox_result.png b/tox_result.png new file mode 100644 index 00000000..d3872675 Binary files /dev/null and b/tox_result.png differ