From 2dd08e676af5d8b22ae885a60096ea7d4151d8e6 Mon Sep 17 00:00:00 2001 From: Davor Dundovic <33790330+ddundo@users.noreply.github.com> Date: Fri, 28 Mar 2025 17:23:28 +0000 Subject: [PATCH 1/4] Replace parallel_dynamic markers in conftest.py --- test/conftest.py | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/test/conftest.py b/test/conftest.py index 52714c2..7c2a4fa 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -1,9 +1,9 @@ """ Global pytest configuration. - -**Disclaimer: some functions copied from firedrake/src/tests/conftest.py """ +import pytest +from pyop2.mpi import COMM_WORLD import numpy as np np.random.seed(0) @@ -12,10 +12,29 @@ def pytest_configure(config): """ Register an additional marker. - - **Disclaimer: copied from firedrake/src/tests/conftest.py """ config.addinivalue_line( - "markers", - "slow: mark test as slow to run", + "markers", + "parallel_dynamic: mark test to run with nprocs equal to the current MPI size" ) + +def pytest_collection_modifyitems(config, items): + """ + Replace ``parallel_dynamic`` markers with mpi-pytest's ``parallel(nprocs=N)`` + marker, where N is the current MPI size determined from the ``mpiexec -n N`` command + line argument. + """ + rank_size = COMM_WORLD.Get_size() + + for item in items: + # Check if a test is marked with the parallel_dynamic marker + markers = [marker for marker in item.own_markers + if marker.name == 'parallel_dynamic'] + + if markers: + # Remove parallel_dynamic markers + for marker in markers: + item.own_markers.remove(marker) + + # Add mpi-pytest's parallel marker with current process count + item.add_marker(pytest.mark.parallel(nprocs=rank_size)) From cfa4c3e57fb2dd63a555285312748489483b75ec Mon Sep 17 00:00:00 2001 From: Davor Dundovic <33790330+ddundo@users.noreply.github.com> Date: Fri, 28 Mar 2025 17:24:05 +0000 Subject: [PATCH 2/4] Mark some `test_monge_ampere.py` tests with `parallel_dynamic` --- test/test_monge_ampere.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/test/test_monge_ampere.py b/test/test_monge_ampere.py index 3920bb7..a5e3bdc 100644 --- a/test/test_monge_ampere.py +++ b/test/test_monge_ampere.py @@ -26,6 +26,7 @@ ) from monitors import const_monitor, ring_monitor from parameterized import parameterized +import pytest from movement.monge_ampere import MongeAmpereMover, MongeAmpereMover_Relaxation @@ -58,16 +59,19 @@ class TestExceptions(BaseClasses.TestMongeAmpere): Unit tests for exceptions raised by Monge-Ampère movers. """ + @pytest.mark.parallel_dynamic def test_method_valueerror(self): with self.assertRaises(ValueError) as cm: MongeAmpereMover(self.dummy_mesh, self.dummy_monitor, method="method") self.assertEqual(str(cm.exception), "Method 'method' not recognised.") + @pytest.mark.parallel_dynamic def test_no_monitor_valueerror(self): with self.assertRaises(ValueError) as cm: MongeAmpereMover(self.dummy_mesh, None) self.assertEqual(str(cm.exception), "Please supply a monitor function.") + @pytest.mark.parallel_dynamic def test_1d_quasi_newton_valueerror(self): mesh = self.mesh(dim=1) with self.assertRaises(NotImplementedError) as cm: @@ -88,6 +92,7 @@ def test_maxiter_convergenceerror(self, method): self.assertEqual(str(cm.exception), "Solver failed to converge in 1 iteration.") @parameterized.expand([(True,), (False,)]) + @pytest.mark.parallel_dynamic def test_divergence_convergenceerror(self, raise_errors): """ Test that divergence of the mesh mover raises a :class:`~.ConvergenceError` if @@ -102,6 +107,7 @@ def test_divergence_convergenceerror(self, raise_errors): self.assertEqual(str(cm.exception), "Solver diverged after 1 iteration.") @parameterized.expand([("phi",), ("H",)]) + @pytest.mark.parallel_dynamic def test_initial_guess_valueerror(self, var): mesh = self.mesh(n=2) fs_constructor = {"phi": FunctionSpace, "H": TensorFunctionSpace}[var] @@ -110,6 +116,7 @@ def test_initial_guess_valueerror(self, var): MongeAmpereMover(mesh, ring_monitor, **kwargs) self.assertEqual(str(cm.exception), "Need to initialise both phi *and* H.") + # @pytest.mark.parallel_dynamic def test_non_straight_boundary_valueerror(self): mesh = self.mesh(dim=2, n=3) mesh.coordinates.dat.data_with_halos[1][0] -= 0.25 @@ -119,6 +126,7 @@ def test_non_straight_boundary_valueerror(self): msg = "Boundary segment '1' is not linear." self.assertEqual(str(cm.exception), msg) + # @pytest.mark.parallel_dynamic def test_non_flat_plane_valueerror(self): mesh = self.mesh(dim=3, n=3) mesh.coordinates.dat.data_with_halos[1][0] -= 0.25 @@ -128,6 +136,7 @@ def test_non_flat_plane_valueerror(self): msg = "Boundary segment '1' is not planar." self.assertEqual(str(cm.exception), msg) + @pytest.mark.parallel_dynamic def test_invalid_plane_valuerror(self): mesh = self.mesh(dim=3, n=1) mesh.coordinates.dat.data_with_halos[:][:] = 0.0 @@ -137,6 +146,7 @@ def test_invalid_plane_valuerror(self): msg = "Could not determine a plane for the provided points." self.assertEqual(str(cm.exception), msg) + # @pytest.mark.parallel_dynamic def test_curved_notimplementederror(self): coords = Function(VectorFunctionSpace(UnitTriangleMesh(), "CG", 2)) coords.interpolate(coords.function_space().mesh().coordinates) @@ -145,6 +155,7 @@ def test_curved_notimplementederror(self): msg = "MongeAmpereMover_Relaxation not implemented on curved meshes." self.assertEqual(str(cm.exception), msg) + # @pytest.mark.parallel_dynamic def test_tangling_valueerror(self): mover = MongeAmpereMover(self.mesh(2, n=3), ring_monitor) mover.xi.dat.data[3] += 0.2 @@ -152,6 +163,7 @@ def test_tangling_valueerror(self): mover.move() self.assertEqual(str(cm.exception), "Mesh has 1 tangled element.") + @pytest.mark.parallel_dynamic def test_periodic_plex_valueerror(self): mover = MongeAmpereMover(self.mesh(1, n=3, periodic=True), const_monitor) with self.assertRaises(ValueError) as cm: @@ -159,6 +171,7 @@ def test_periodic_plex_valueerror(self): msg = "Cannot update DMPlex coordinates for periodic meshes." self.assertEqual(str(cm.exception), msg) + @pytest.mark.parallel_dynamic def test_fix_invalid_segment_valueerror(self): with self.assertRaises(ValueError) as cm: MongeAmpereMover(self.mesh(1), const_monitor, fixed_boundary_segments=[-1]) @@ -180,6 +193,7 @@ class TestMonitor(BaseClasses.TestMongeAmpere): (3, "quasi_newton"), ] ) + @pytest.mark.parallel_dynamic def test_uniform_monitor(self, dim, method): """ Test that the mesh mover converges in one iteration for a constant monitor @@ -204,6 +218,7 @@ def test_uniform_monitor(self, dim, method): @parameterized.expand( [(2, "relaxation"), (2, "quasi_newton"), (3, "relaxation"), (3, "quasi_newton")] ) + @pytest.mark.parallel_dynamic def test_continue(self, dim, method): """ Test that providing a good initial guess benefits the solver. @@ -230,6 +245,7 @@ def test_continue(self, dim, method): @parameterized.expand( [(2, "relaxation"), (2, "quasi_newton"), (3, "relaxation"), (3, "quasi_newton")] ) + # @pytest.mark.parallel_dynamic def test_change_monitor(self, dim, method): """ Test that the mover can handle changes to the monitor function, such as would @@ -259,6 +275,7 @@ class TestBCs(BaseClasses.TestMongeAmpere): Unit tests for boundary conditions of Monge-Ampère movers. """ + @pytest.mark.parallel_dynamic def _test_boundary_preservation(self, mesh, method, fixed_boundaries): bnd = assemble(Constant(1.0) * ufl.ds(domain=mesh)) coord_space = mesh.coordinates.function_space() @@ -293,6 +310,7 @@ def _test_boundary_preservation(self, mesh, method, fixed_boundaries): (3, "quasi_newton"), ] ) + @pytest.mark.parallel_dynamic def test_periodic(self, dim, method): """ Test that periodic unit domains are not given boundary conditions by the @@ -310,6 +328,7 @@ def test_periodic(self, dim, method): # Check that the variational problem does not have boundary conditions self.assertTrue(len(mover._l2_projector._problem.bcs) == 0) + @pytest.mark.parallel_dynamic def test_initial_guess_valueerror(self): mesh = self.mesh(2, n=2) phi_init = Function(FunctionSpace(mesh, "CG", 1)) @@ -336,6 +355,7 @@ def test_initial_guess_valueerror(self): (3, "quasi_newton", []), ] ) + # @pytest.mark.parallel_dynamic def test_boundary_preservation_axis_aligned(self, dim, method, fixed_boundaries): """ Test that boundaries of unit domains are preserved by the Monge-Ampère movers. @@ -370,6 +390,7 @@ def test_boundary_preservation_axis_aligned(self, dim, method, fixed_boundaries) (3, "quasi_newton", []), ] ) + # @pytest.mark.parallel_dynamic def test_boundary_preservation_non_axis_aligned( self, dim, method, fixed_boundaries ): @@ -451,6 +472,7 @@ class TestMisc(BaseClasses.TestMongeAmpere): (3, "quasi_newton"), ] ) + @pytest.mark.parallel_dynamic def test_continue(self, dim, method): """ Test that providing a good initial guess benefits the solver. @@ -485,6 +507,7 @@ def test_continue(self, dim, method): (3, "quasi_newton"), ] ) + @pytest.mark.parallel_dynamic def test_coordinate_update(self, dim, method): mesh = self.mesh(dim=dim, n=2) xyz = list(ufl.SpatialCoordinate(mesh)) From b4860f6434ab233ea51c0690aa4ee62672cb02ac Mon Sep 17 00:00:00 2001 From: Davor Dundovic <33790330+ddundo@users.noreply.github.com> Date: Fri, 28 Mar 2025 17:28:08 +0000 Subject: [PATCH 3/4] Point to docs/parallel_dynamic_tests branch in test_suite.yml --- .github/workflows/test_suite.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_suite.yml b/.github/workflows/test_suite.yml index 7fa6e3d..a809bfe 100644 --- a/.github/workflows/test_suite.yml +++ b/.github/workflows/test_suite.yml @@ -25,4 +25,4 @@ on: jobs: test_suite: - uses: mesh-adaptation/docs/.github/workflows/reusable_test_suite.yml@main + uses: mesh-adaptation/docs/.github/workflows/reusable_test_suite.yml@parallel_dynamic_tests From 7c3c4d62b01fb76315d1be4dfe15dd057fb32fed Mon Sep 17 00:00:00 2001 From: Davor Dundovic <33790330+ddundo@users.noreply.github.com> Date: Fri, 28 Mar 2025 17:34:13 +0000 Subject: [PATCH 4/4] Linting --- test/conftest.py | 18 ++++++++++-------- test/test_monge_ampere.py | 2 +- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/test/conftest.py b/test/conftest.py index 7c2a4fa..fbdce13 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -2,9 +2,9 @@ Global pytest configuration. """ +import numpy as np import pytest from pyop2.mpi import COMM_WORLD -import numpy as np np.random.seed(0) @@ -14,10 +14,11 @@ def pytest_configure(config): Register an additional marker. """ config.addinivalue_line( - "markers", - "parallel_dynamic: mark test to run with nprocs equal to the current MPI size" + "markers", + "parallel_dynamic: mark test to run with nprocs equal to the current MPI size", ) + def pytest_collection_modifyitems(config, items): """ Replace ``parallel_dynamic`` markers with mpi-pytest's ``parallel(nprocs=N)`` @@ -25,16 +26,17 @@ def pytest_collection_modifyitems(config, items): line argument. """ rank_size = COMM_WORLD.Get_size() - + for item in items: # Check if a test is marked with the parallel_dynamic marker - markers = [marker for marker in item.own_markers - if marker.name == 'parallel_dynamic'] - + markers = [ + marker for marker in item.own_markers if marker.name == "parallel_dynamic" + ] + if markers: # Remove parallel_dynamic markers for marker in markers: item.own_markers.remove(marker) - + # Add mpi-pytest's parallel marker with current process count item.add_marker(pytest.mark.parallel(nprocs=rank_size)) diff --git a/test/test_monge_ampere.py b/test/test_monge_ampere.py index a5e3bdc..01d0938 100644 --- a/test/test_monge_ampere.py +++ b/test/test_monge_ampere.py @@ -2,6 +2,7 @@ from unittest.mock import MagicMock import numpy as np +import pytest import ufl from firedrake.assemble import assemble from firedrake.bcs import DirichletBC, EquationBC @@ -26,7 +27,6 @@ ) from monitors import const_monitor, ring_monitor from parameterized import parameterized -import pytest from movement.monge_ampere import MongeAmpereMover, MongeAmpereMover_Relaxation