diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..03e61fc --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.pyc + +# Virtual environments +venv/ +.tox/ + +# Testing and coverage +.coverage +htmlcov/ +.pytest_cache/ \ No newline at end of file diff --git a/coverage-report.pdf b/coverage-report.pdf new file mode 100644 index 0000000..04d9b84 Binary files /dev/null and b/coverage-report.pdf differ diff --git a/diffusion2d.py b/diffusion2d.py index 51a07f2..42625fc 100644 --- a/diffusion2d.py +++ b/diffusion2d.py @@ -42,13 +42,25 @@ def initialize_domain(self, w=10., h=10., dx=0.1, dy=0.1): self.h = h self.dx = dx self.dy = dy + + # Assertion statements to check for float inputs + assert isinstance(self.w, float), "w should be a float" + assert isinstance(self.h, float), "h should be a float" + assert isinstance(self.dx, float), "dx should be a float" + assert isinstance(self.dy, float), "dy should be a float" + 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.): self.D = d self.T_cold = T_cold self.T_hot = T_hot + + # Assertion statements to check for float inputs + assert isinstance(self.D, float), "D should be a float" + assert isinstance(self.T_cold, float), "T_cold should be a float" + assert isinstance(self.T_hot, float), "T_hot should be a float" # Computing a stable time step dx2, dy2 = self.dx * self.dx, self.dy * self.dy @@ -136,4 +148,4 @@ def main(): if __name__ == "__main__": - main() + main() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..8f87fb4 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +numpy +matplotlib +pytest +coverage \ No newline at end of file diff --git a/tests/integration/test_diffusion2d.py b/tests/integration/test_diffusion2d.py index fd026b4..100b262 100644 --- a/tests/integration/test_diffusion2d.py +++ b/tests/integration/test_diffusion2d.py @@ -1,19 +1,85 @@ """ Tests for functionality checks in class SolveDiffusion2D """ +import unittest +import numpy as np +import sys +import os + +# Fix import path to allow importing diffusion2d from the root directory +sys.path.append(os.getcwd()) from diffusion2d import SolveDiffusion2D -def test_initialize_physical_parameters(): - """ - Checks function SolveDiffusion2D.initialize_domain - """ - solver = SolveDiffusion2D() +class TestDiffusion2D(unittest.TestCase): + + def setUp(self): + """ + Fixture that runs before every test. + """ + self.solver = SolveDiffusion2D() + + def test_initialize_physical_parameters(self): + """ + Checks function SolveDiffusion2D.initialize_physical_parameters + running AFTER initialize_domain. + """ + # 1. Define inputs + w = 10.0 + h = 10.0 + dx = 1.0 + dy = 1.0 + d = 4.0 + T_cold = 300.0 + T_hot = 700.0 + + # 2. Call the functions in sequence (Integration) + self.solver.initialize_domain(w, h, dx, dy) + self.solver.initialize_physical_parameters(d, T_cold, T_hot) + + # 3. Calculate expected dt manually + # dt = dx^2 * dy^2 / (2 * D * (dx^2 + dy^2)) + # dt = (1*1) / (2 * 4 * (1+1)) = 1 / 16 = 0.0625 + expected_dt = 0.0625 + + # 4. Assertion + self.assertAlmostEqual(self.solver.dt, expected_dt, places=4) + + def test_set_initial_condition(self): + """ + Checks function SolveDiffusion2D.set_initial_condition + running AFTER domain and physical parameters are set. + """ + # 1. Define inputs for the whole chain + w = 10.0 + h = 10.0 + dx = 1.0 + dy = 1.0 + d = 4.0 + T_cold = 300.0 + T_hot = 700.0 + + # 2. Call the functions in sequence + self.solver.initialize_domain(w, h, dx, dy) + self.solver.initialize_physical_parameters(d, T_cold, T_hot) + actual_u = self.solver.set_initial_condition() + # 3. Manually compute expected_u + # We know initialize_domain with these params creates a 10x10 grid (w/dx, h/dy) + nx = 10 + ny = 10 + expected_u = T_cold * np.ones((nx, ny)) -def test_set_initial_condition(): - """ - Checks function SolveDiffusion2D.get_initial_function - """ - solver = SolveDiffusion2D() + # Circle logic from the problem statement: r=2, 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] = T_hot + + # 4. Assertion + np.testing.assert_array_equal(actual_u, expected_u) \ No newline at end of file diff --git a/tests/unit/test_diffusion2d_functions.py b/tests/unit/test_diffusion2d_functions.py index c4277ff..67af5d2 100644 --- a/tests/unit/test_diffusion2d_functions.py +++ b/tests/unit/test_diffusion2d_functions.py @@ -1,26 +1,105 @@ """ Tests for functions in class SolveDiffusion2D """ +import unittest +import numpy as np +import sys +import os + +# Fix import path: Add the root directory to sys.path so we can import diffusion2d +# This assumes you run the test from the root of the repository +sys.path.append(os.getcwd()) from diffusion2d import SolveDiffusion2D -def test_initialize_domain(): - """ - Check function SolveDiffusion2D.initialize_domain - """ - solver = SolveDiffusion2D() +class TestDiffusion2D(unittest.TestCase): + + def setUp(self): + """ + Fixture that runs before every test. + """ + self.solver = SolveDiffusion2D() + + def test_initialize_domain(self): + """ + Check function SolveDiffusion2D.initialize_domain + """ + # 1. Define input parameters different from defaults + w = 20.0 + h = 40.0 + dx = 2.0 + dy = 4.0 + + # 2. Expected values (manually calculated) + # nx = 20 / 2 = 10 + # ny = 40 / 4 = 10 + expected_nx = 10 + expected_ny = 10 + + # 3. Call the function + self.solver.initialize_domain(w, h, dx, dy) + + # 4. Assertions + 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 + """ + # 1. Manually define the required internal variables (dx, dy) + # We must do this because we cannot call initialize_domain here. + self.solver.dx = 1.0 + self.solver.dy = 1.0 + + # 2. Define inputs + d = 5.0 + T_cold = 200.0 + T_hot = 600.0 + + # 3. Calculate expected dt manually + # dt = dx^2 * dy^2 / (2 * D * (dx^2 + dy^2)) + # dt = 1*1 / (2*5 * (1+1)) = 1 / 20 = 0.05 + expected_dt = 0.05 + # 4. Call the function + self.solver.initialize_physical_parameters(d, T_cold, T_hot) -def test_initialize_physical_parameters(): - """ - Checks function SolveDiffusion2D.initialize_domain - """ - solver = SolveDiffusion2D() + # 5. Assertions + self.assertAlmostEqual(self.solver.dt, expected_dt, places=5) + self.assertEqual(self.solver.T_cold, T_cold) + self.assertEqual(self.solver.T_hot, T_hot) + def test_set_initial_condition(self): + """ + Checks function SolveDiffusion2D.set_initial_condition + """ + # 1. Manually set all member variables required for calculation + self.solver.nx = 3 + self.solver.ny = 3 + self.solver.dx = 1.0 + self.solver.dy = 1.0 + self.solver.T_cold = 300.0 + self.solver.T_hot = 700.0 + + # 2. Call the function + actual_u = self.solver.set_initial_condition() -def test_set_initial_condition(): - """ - Checks function SolveDiffusion2D.get_initial_function - """ - solver = SolveDiffusion2D() + # 3. Manually compute the expected array + # We replicate the logic for a small 3x3 grid + expected_u = 300.0 * np.ones((3, 3)) + + # Circle parameters from the code: r=2, cx=5, cy=5 + r, cx, cy = 2, 5, 5 + r2 = r ** 2 + + for i in range(3): + for j in range(3): + p2 = (i * 1.0 - cx) ** 2 + (j * 1.0 - cy) ** 2 + if p2 < r2: + expected_u[i, j] = 700.0 + + # 4. Assertions + # Use numpy.testing to compare arrays + np.testing.assert_array_equal(actual_u, expected_u) \ No newline at end of file diff --git a/tox.toml b/tox.toml new file mode 100644 index 0000000..1eab694 --- /dev/null +++ b/tox.toml @@ -0,0 +1,12 @@ +[tox] +requires = ["tox>=4"] +env_list = ["test_env"] + +[testenv] +description = "run unit tests" +deps = ["-rrequirements.txt"] +commands = [ + ["pytest"], + ["coverage", "run", "-m", "pytest"], + ["coverage", "xml"], +] \ No newline at end of file