From efa79f530bea4df3f6e7a842a741397a911a8c61 Mon Sep 17 00:00:00 2001 From: Josh Hope-Collins Date: Fri, 22 Nov 2024 10:59:16 +0000 Subject: [PATCH 01/11] Create dependabot.yml Automatically open PRs to update github actions --- .github/dependabot.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..0d08e261 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "github-actions" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" From d413c6097f01ee2ec3cba3a4c463a1f58dcfccef Mon Sep 17 00:00:00 2001 From: Josh Hope-Collins Date: Thu, 28 Nov 2024 17:05:51 +0000 Subject: [PATCH 02/11] reorganise tests into unit and integration --- .../preconditioners/test_allatonce_pcs.py | 0 .../preconditioners/test_auxiliarypc.py | 0 .../test_allatoncesolver_integration.py} | 100 ++------- tests/{ => integration}/test_paradiag.py | 0 .../utils/test_hybridisation.py | 0 .../allatonce/test_allatonceform.py | 0 .../allatonce/test_allatoncefunction.py | 0 .../allatonce/test_allatoncejacobian.py | 0 .../allatonce/test_allatoncesolver_unit.py | 198 ++++++++++++++++++ .../complex_proxy/test_complex_mixed.py | 0 .../complex_proxy/test_complex_vector.py | 0 tests/{ => unit}/test_parallel_arrays.py | 0 tests/{ => unit}/utils/test_diagnostics.py | 0 tests/{ => unit}/utils/test_williamson.py | 0 14 files changed, 219 insertions(+), 79 deletions(-) rename tests/{ => integration}/preconditioners/test_allatonce_pcs.py (100%) rename tests/{ => integration}/preconditioners/test_auxiliarypc.py (100%) rename tests/{allatonce/test_allatoncesolver.py => integration/test_allatoncesolver_integration.py} (72%) rename tests/{ => integration}/test_paradiag.py (100%) rename tests/{ => integration}/utils/test_hybridisation.py (100%) rename tests/{ => unit}/allatonce/test_allatonceform.py (100%) rename tests/{ => unit}/allatonce/test_allatoncefunction.py (100%) rename tests/{ => unit}/allatonce/test_allatoncejacobian.py (100%) create mode 100644 tests/unit/allatonce/test_allatoncesolver_unit.py rename tests/{ => unit}/complex_proxy/test_complex_mixed.py (100%) rename tests/{ => unit}/complex_proxy/test_complex_vector.py (100%) rename tests/{ => unit}/test_parallel_arrays.py (100%) rename tests/{ => unit}/utils/test_diagnostics.py (100%) rename tests/{ => unit}/utils/test_williamson.py (100%) diff --git a/tests/preconditioners/test_allatonce_pcs.py b/tests/integration/preconditioners/test_allatonce_pcs.py similarity index 100% rename from tests/preconditioners/test_allatonce_pcs.py rename to tests/integration/preconditioners/test_allatonce_pcs.py diff --git a/tests/preconditioners/test_auxiliarypc.py b/tests/integration/preconditioners/test_auxiliarypc.py similarity index 100% rename from tests/preconditioners/test_auxiliarypc.py rename to tests/integration/preconditioners/test_auxiliarypc.py diff --git a/tests/allatonce/test_allatoncesolver.py b/tests/integration/test_allatoncesolver_integration.py similarity index 72% rename from tests/allatonce/test_allatoncesolver.py rename to tests/integration/test_allatoncesolver_integration.py index 41335de8..73fc8610 100644 --- a/tests/allatonce/test_allatoncesolver.py +++ b/tests/integration/test_allatoncesolver_integration.py @@ -3,82 +3,9 @@ import pytest -@pytest.mark.parallel(nprocs=6) -def test_solve_heat_equation_nopc(): - """ - Tests the basic solver setup using the heat equation. - Solves using unpreconditioned GMRES and checks that the - residual of the all-at-once-form is below tolerance. - """ - - # set up space-time parallelism - - nslices = fd.COMM_WORLD.size//2 - slice_length = 2 - - time_partition = tuple((slice_length for _ in range(nslices))) - ensemble = asQ.create_ensemble(time_partition, comm=fd.COMM_WORLD) - - mesh = fd.UnitSquareMesh(6, 6, comm=ensemble.comm) - V = fd.FunctionSpace(mesh, "CG", 1) - - # all-at-once function and initial conditions - - x, y = fd.SpatialCoordinate(mesh) - ics = fd.Function(V).interpolate(fd.exp(-((x - 0.5)**2 + (y - 0.5)**2) / 0.5**2)) - - aaofunc = asQ.AllAtOnceFunction(ensemble, time_partition, V) - aaofunc.assign(ics) - - # all-at-once form - - dt = 0.01 - theta = 1.0 - - def form_function(u, v, t): - return fd.inner(fd.grad(u), fd.grad(v))*fd.dx - - def form_mass(u, v): - return fd.inner(u, v)*fd.dx - - aaoform = asQ.AllAtOnceForm(aaofunc, dt, theta, - form_mass, form_function) - - # solver and options - - atol = 1.0e-10 - solver_parameters = { - 'snes_type': 'ksponly', - 'snes': { - 'monitor': None, - 'converged_reason': None, - }, - 'pc_type': 'none', - 'ksp_type': 'gmres', - 'mat_type': 'matfree', - 'ksp': { - 'monitor': None, - 'converged_rate': None, - 'atol': atol, - 'rtol': 1.0e-100, - 'stol': 1.0e-100, - } - } - - aaosolver = asQ.AllAtOnceSolver(aaoform, aaofunc, - solver_parameters=solver_parameters) - - aaosolver.solve() - - # check residual - - aaoform.assemble(func=aaofunc) - residual = fd.norm(aaoform.F.function) - - assert residual < atol, "GMRES should converge to prescribed tolerance even without preconditioning" - - -def test_solve_heat_equation_circulantpc(): +@pytest.mark.parallel(nprocs=4) +@pytest.mark.parametrize('partition', ['serial', 't-parallel', 'st-parallel']) +def test_solve_heat_equation_circulantpc(partition): """ Tests the basic solver setup using the heat equation. Solves using GMRES preconditioned with the CirculantPC @@ -89,8 +16,22 @@ def test_solve_heat_equation_circulantpc(): # set up space-time parallelism window_length = 4 + nprocs = fd.COMM_WORLD.size + assert window_length % nprocs == 0, "test setup incorrectly" + + if partition == 'serial': + time_partition = window_length + elif partition == 't-parallel': + nslices = nprocs + slice_length = window_length//nslices + time_partition = [slice_length for _ in range(nslices)] + elif partition == 'st-parallel': + nslices = nprocs//2 + slice_length = window_length//nslices + time_partition = [slice_length for _ in range(nslices)] + else: + assert False, "Unrecognised partition type" - time_partition = window_length ensemble = asQ.create_ensemble(time_partition, comm=fd.COMM_WORLD) mesh = fd.UnitSquareMesh(6, 6, comm=ensemble.comm) @@ -166,7 +107,7 @@ def form_mass(u, v): @pytest.mark.parallel(nprocs=4) @pytest.mark.parametrize("extrude", extruded) @pytest.mark.parametrize("cpx_type", cpx_types) -def test_solve_mixed_wave_equation(extrude, cpx_type): +def test_solve_mixed_wave_equation_circulantpc(extrude, cpx_type): """ Tests the solver setup using a nonlinear wave equation. Solves using GMRES preconditioned with CirculantPC and checks @@ -249,10 +190,11 @@ def form_mass(uu, up, vu, vp): 'stol': 1e-100, }, 'mat_type': 'matfree', - 'ksp_type': 'preonly', + 'ksp_type': 'gmres', 'ksp': { 'monitor': None, 'converged_reason': None, + 'rtol': 1e-3, }, 'pc_type': 'python', 'pc_python_type': 'asQ.CirculantPC', diff --git a/tests/test_paradiag.py b/tests/integration/test_paradiag.py similarity index 100% rename from tests/test_paradiag.py rename to tests/integration/test_paradiag.py diff --git a/tests/utils/test_hybridisation.py b/tests/integration/utils/test_hybridisation.py similarity index 100% rename from tests/utils/test_hybridisation.py rename to tests/integration/utils/test_hybridisation.py diff --git a/tests/allatonce/test_allatonceform.py b/tests/unit/allatonce/test_allatonceform.py similarity index 100% rename from tests/allatonce/test_allatonceform.py rename to tests/unit/allatonce/test_allatonceform.py diff --git a/tests/allatonce/test_allatoncefunction.py b/tests/unit/allatonce/test_allatoncefunction.py similarity index 100% rename from tests/allatonce/test_allatoncefunction.py rename to tests/unit/allatonce/test_allatoncefunction.py diff --git a/tests/allatonce/test_allatoncejacobian.py b/tests/unit/allatonce/test_allatoncejacobian.py similarity index 100% rename from tests/allatonce/test_allatoncejacobian.py rename to tests/unit/allatonce/test_allatoncejacobian.py diff --git a/tests/unit/allatonce/test_allatoncesolver_unit.py b/tests/unit/allatonce/test_allatoncesolver_unit.py new file mode 100644 index 00000000..157d5fc9 --- /dev/null +++ b/tests/unit/allatonce/test_allatoncesolver_unit.py @@ -0,0 +1,198 @@ +import asQ +import firedrake as fd +import pytest + + +@pytest.mark.parallel(nprocs=2) +@pytest.mark.parametrize('partition', ['serial', 'parallel']) +def test_solve_heat_equation_nopc(partition): + """ + Tests the basic solver setup using the heat equation. + Solves using unpreconditioned GMRES and checks that the + residual of the all-at-once-form is below tolerance. + """ + + # set up space-time parallelism + + window_length = 6 + + nprocs = fd.COMM_WORLD.size + assert window_length % nprocs == 0, "test setup incorrectly" + + if partition == 'serial': + time_partition = window_length + elif partition == 'parallel': + slice_length = window_length//nprocs + time_partition = [slice_length for _ in range(nprocs)] + else: + assert False, "Unrecognised partition type" + + ensemble = asQ.create_ensemble(time_partition, comm=fd.COMM_WORLD) + + mesh = fd.UnitSquareMesh(6, 6, comm=ensemble.comm) + V = fd.FunctionSpace(mesh, "CG", 1) + + # all-at-once function and initial conditions + + x, y = fd.SpatialCoordinate(mesh) + ics = fd.Function(V).interpolate(fd.exp(-((x - 0.5)**2 + (y - 0.5)**2) / 0.5**2)) + + aaofunc = asQ.AllAtOnceFunction(ensemble, time_partition, V) + aaofunc.assign(ics) + + # all-at-once form + + dt = 0.01 + theta = 1.0 + + def form_function(u, v, t): + return fd.inner(fd.grad(u), fd.grad(v))*fd.dx + + def form_mass(u, v): + return fd.inner(u, v)*fd.dx + + aaoform = asQ.AllAtOnceForm(aaofunc, dt, theta, + form_mass, form_function) + + # solver and options + + atol = 1.0e-10 + solver_parameters = { + 'snes_type': 'ksponly', + 'snes': { + 'monitor': None, + 'converged_reason': None, + }, + 'pc_type': 'none', + 'ksp_type': 'gmres', + 'mat_type': 'matfree', + 'ksp': { + 'monitor': None, + 'converged_rate': None, + 'atol': atol, + 'rtol': 1.0e-100, + 'stol': 1.0e-100, + } + } + + aaosolver = asQ.AllAtOnceSolver(aaoform, aaofunc, + solver_parameters=solver_parameters) + + aaosolver.solve() + + # check residual + + aaoform.assemble(func=aaofunc) + residual = fd.norm(aaoform.F.function) + + assert residual < atol, "GMRES should converge to prescribed tolerance even without preconditioning" + + +extruded = [pytest.param(False, id="standard_mesh"), + pytest.param(True, id="extruded_mesh")] + + +@pytest.mark.parallel(nprocs=4) +@pytest.mark.parametrize("extrude", extruded) +def test_solve_mixed_wave_equation_nopc(extrude): + """ + Tests the solver setup using a nonlinear wave equation. + Solves using GMRES preconditioned with CirculantPC and checks + that the residual of the all-at-once-form is below tolerance. + """ + + # space-time parallelism + nslices = fd.COMM_WORLD.size//2 + slice_length = 2 + + time_partition = tuple((slice_length for _ in range(nslices))) + ensemble = asQ.create_ensemble(time_partition, comm=fd.COMM_WORLD) + + # mesh and function spaces + nx = 6 + if extrude: + mesh1D = fd.UnitIntervalMesh(nx, comm=ensemble.comm) + mesh = fd.ExtrudedMesh(mesh1D, nx, layer_height=1./nx) + + horizontal_degree = 1 + vertical_degree = 1 + S1 = fd.FiniteElement("CG", fd.interval, horizontal_degree+1) + S2 = fd.FiniteElement("DG", fd.interval, horizontal_degree) + + # vertical base spaces + T0 = fd.FiniteElement("CG", fd.interval, vertical_degree+1) + T1 = fd.FiniteElement("DG", fd.interval, vertical_degree) + + # build spaces V2, V3 + V2h_elt = fd.HDiv(fd.TensorProductElement(S1, T1)) + V2v_elt = fd.HDiv(fd.TensorProductElement(S2, T0)) + V3_elt = fd.TensorProductElement(S2, T1) + V2_elt = V2h_elt + V2v_elt + + V = fd.FunctionSpace(mesh, V2_elt, name="HDiv") + Q = fd.FunctionSpace(mesh, V3_elt, name="DG") + else: + mesh = fd.UnitSquareMesh(nx, nx, comm=ensemble.comm) + V = fd.FunctionSpace(mesh, "BDM", 1) + Q = fd.FunctionSpace(mesh, "DG", 0) + + W = V * Q + + # all-at-once function and ics + x, y = fd.SpatialCoordinate(mesh) + ics = fd.Function(W) + u0, p0 = ics.subfunctions + p0.interpolate(fd.exp(-((x-0.5)**2 + (y-0.5)**2)/0.5**2)) + + aaofunc = asQ.AllAtOnceFunction(ensemble, time_partition, W) + aaofunc.assign(ics) + + # all-at-once form + + dt = 0.01 + theta = 0.5 + c = fd.Constant(10) + eps = fd.Constant(0.001) + + def form_function(uu, up, vu, vp, t): + return (fd.div(vu) * up + c * fd.sqrt(fd.inner(uu, uu) + eps) * fd.inner(uu, vu) + - fd.div(uu) * vp) * fd.dx + + def form_mass(uu, up, vu, vp): + return (fd.inner(uu, vu) + up * vp) * fd.dx + + aaoform = asQ.AllAtOnceForm(aaofunc, dt, theta, + form_mass, form_function) + + # solver and options + + atol = 1e-8 + solver_parameters = { + 'snes': { + 'linesearch_type': 'basic', + 'monitor': None, + 'converged_reason': None, + 'atol': atol, + 'rtol': 1e-100, + 'stol': 1e-100, + }, + 'mat_type': 'matfree', + 'pc_type': 'none', + 'ksp_type': 'gmres', + 'ksp': { + 'monitor': None, + 'converged_rate': None, + 'rtol': 1e-3, + }, + } + + aaosolver = asQ.AllAtOnceSolver(aaoform, aaofunc, + solver_parameters=solver_parameters) + + aaosolver.solve() + + # check residual + aaoform.assemble(func=aaofunc) + residual = fd.norm(aaoform.F.function) + + assert (residual < atol), "GMRES should converge to prescribed tolerance with CirculantPC" diff --git a/tests/complex_proxy/test_complex_mixed.py b/tests/unit/complex_proxy/test_complex_mixed.py similarity index 100% rename from tests/complex_proxy/test_complex_mixed.py rename to tests/unit/complex_proxy/test_complex_mixed.py diff --git a/tests/complex_proxy/test_complex_vector.py b/tests/unit/complex_proxy/test_complex_vector.py similarity index 100% rename from tests/complex_proxy/test_complex_vector.py rename to tests/unit/complex_proxy/test_complex_vector.py diff --git a/tests/test_parallel_arrays.py b/tests/unit/test_parallel_arrays.py similarity index 100% rename from tests/test_parallel_arrays.py rename to tests/unit/test_parallel_arrays.py diff --git a/tests/utils/test_diagnostics.py b/tests/unit/utils/test_diagnostics.py similarity index 100% rename from tests/utils/test_diagnostics.py rename to tests/unit/utils/test_diagnostics.py diff --git a/tests/utils/test_williamson.py b/tests/unit/utils/test_williamson.py similarity index 100% rename from tests/utils/test_williamson.py rename to tests/unit/utils/test_williamson.py From 31b605ba1aa6f42ef919de55efd5cf62ee1d4cc8 Mon Sep 17 00:00:00 2001 From: Josh Hope-Collins Date: Thu, 28 Nov 2024 17:06:26 +0000 Subject: [PATCH 03/11] scheduled CI, and run unit and integration tests separately --- .github/workflows/test.yml | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 704ff95e..596807f7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,6 +7,10 @@ on: - master # And all pull requests pull_request: + schedule: + # * is a special character in YAML so you have to quote this string + # Scheduled run over the weekend to detect any upstream breaks. + - cron: '00 1 * * 7' # Allows you to run this workflow manually from the Actions tab workflow_dispatch: @@ -46,19 +50,35 @@ jobs: . /home/firedrake/firedrake/bin/activate python -m pip install pytest-timeout python -m pip install pytest-cov + # try and reduce the number of warnings to sift through + python -m pip install siphash24 - name: Install run: | . /home/firedrake/firedrake/bin/activate python -m pip install -e . + python --version + python -m pytest --version + flake8 --version + firedrake-status - name: Lint run: | . /home/firedrake/firedrake/bin/activate - flake8 --version flake8 . - - name: Test + - name: Test - unit tests run: | . /home/firedrake/firedrake/bin/activate - python --version - python -m pytest --version - firedrake-status - python -m pytest -v -n 6 --durations=40 --timeout=600 --cov=asQ --cov-report=term tests/ + python -m pytest \ + -n 12 --dist worksteal \ + --durations=40 \ + --timeout=300 \ + --cov=asQ --cov-report=term \ + -v tests/unit + - name: Test - integration tests + run: | + . /home/firedrake/firedrake/bin/activate + python -m pytest \ + -n 4 --dist worksteal \ + --durations=40 \ + --timeout=600 \ + --cov=asQ --cov-report=term --cov-append \ + -v tests/integration From 54df2aa7afb339f3edb4308d7299b8cf1a1170d0 Mon Sep 17 00:00:00 2001 From: Josh Hope-Collins Date: Thu, 28 Nov 2024 17:19:41 +0000 Subject: [PATCH 04/11] ci workflow debug --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 596807f7..a52fcc9c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,7 +10,7 @@ on: schedule: # * is a special character in YAML so you have to quote this string # Scheduled run over the weekend to detect any upstream breaks. - - cron: '00 1 * * 7' + - cron: '0 1 * * 0' # Allows you to run this workflow manually from the Actions tab workflow_dispatch: @@ -44,7 +44,7 @@ jobs: # (copied from https://help.github.com/en/actions/migrating-to-github-actions/migrating-from-circleci-to-github-actions) run: | sudo chmod -R 777 $GITHUB_WORKSPACE /github /__w/_temp - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Install test dependencies run: | . /home/firedrake/firedrake/bin/activate From 6b4af9697ebeafe298d8900b373c5b79da11a8e4 Mon Sep 17 00:00:00 2001 From: Josh Hope-Collins Date: Mon, 10 Feb 2025 17:28:52 +0000 Subject: [PATCH 05/11] update tests for aaoform.F now being AllAtOnceCofunction --- tests/unit/allatonce/test_allatoncejacobian.py | 8 -------- tests/unit/allatonce/test_allatoncesolver_unit.py | 6 ++++-- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/tests/unit/allatonce/test_allatoncejacobian.py b/tests/unit/allatonce/test_allatoncejacobian.py index cdf4d070..b4b0214f 100644 --- a/tests/unit/allatonce/test_allatoncejacobian.py +++ b/tests/unit/allatonce/test_allatoncejacobian.py @@ -6,10 +6,6 @@ from operator import mul -def assemble(form, *args, **kwargs): - return fd.assemble(form, *args, **kwargs).riesz_representation(riesz_map='l2') - - bc_options = ["no_bcs", "homogeneous_bcs", "inhomogeneous_bcs"] @@ -295,7 +291,3 @@ def form_mass(u, p, v, q): yparallel = y[step].subfunctions[cpt] for pdat, sdat in zip(yparallel.dat, yserial.dat): assert np.allclose(pdat.data, sdat.data), f"Timestep {step}, component {cpt}, of AllAtOnceJacobian action doesn't match component of monolithic residual calculated locally" - - -if __name__ == '__main__': - pass diff --git a/tests/unit/allatonce/test_allatoncesolver_unit.py b/tests/unit/allatonce/test_allatoncesolver_unit.py index 157d5fc9..30198d3c 100644 --- a/tests/unit/allatonce/test_allatoncesolver_unit.py +++ b/tests/unit/allatonce/test_allatoncesolver_unit.py @@ -83,7 +83,8 @@ def form_mass(u, v): # check residual aaoform.assemble(func=aaofunc) - residual = fd.norm(aaoform.F.function) + with aaoform.F.global_vec_ro() as fvec: + residual = fvec.norm() assert residual < atol, "GMRES should converge to prescribed tolerance even without preconditioning" @@ -193,6 +194,7 @@ def form_mass(uu, up, vu, vp): # check residual aaoform.assemble(func=aaofunc) - residual = fd.norm(aaoform.F.function) + with aaoform.F.global_vec_ro() as fvec: + residual = fvec.norm() assert (residual < atol), "GMRES should converge to prescribed tolerance with CirculantPC" From 865d2158ac3c767199726828bd72ccad099cb372 Mon Sep 17 00:00:00 2001 From: Josh Hope-Collins Date: Mon, 10 Feb 2025 17:50:48 +0000 Subject: [PATCH 06/11] Update .github/workflows/test.yml --- .github/workflows/test.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4c70ab87..67e7b4ae 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -53,6 +53,14 @@ jobs: python -m pip install pytest-cov # try and reduce the number of warnings to sift through python -m pip install siphash24 + cd /home/firedrake/firedrake/src/fiat + git fetch + git checkout connorjward/fix-quadrature-hash + cd - + cd /home/firedrake/firedrake/src/firedrake + git fetch + git checkout connorjward/more-cache-fixes + cd - - name: Install run: | . /home/firedrake/firedrake/bin/activate From 9d9a386e37268cb2975bb5d4c5ba16c4efad90bc Mon Sep 17 00:00:00 2001 From: Josh Hope-Collins Date: Mon, 10 Feb 2025 18:23:38 +0000 Subject: [PATCH 07/11] Update .github/workflows/test.yml --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 67e7b4ae..0d93c9c7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -77,7 +77,7 @@ jobs: run: | . /home/firedrake/firedrake/bin/activate python -m pytest \ - -n 12 --dist worksteal \ + -n 6 --dist worksteal \ --durations=40 \ --timeout=300 \ --cov=asQ --cov-report=term \ From c2d9069ef320e68524f5ac0ac66a9858c9e9a284 Mon Sep 17 00:00:00 2001 From: Josh Hope-Collins Date: Wed, 12 Feb 2025 14:46:08 +0000 Subject: [PATCH 08/11] Update .github/workflows/test.yml --- .github/workflows/test.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0d93c9c7..ddf3c12b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -53,14 +53,6 @@ jobs: python -m pip install pytest-cov # try and reduce the number of warnings to sift through python -m pip install siphash24 - cd /home/firedrake/firedrake/src/fiat - git fetch - git checkout connorjward/fix-quadrature-hash - cd - - cd /home/firedrake/firedrake/src/firedrake - git fetch - git checkout connorjward/more-cache-fixes - cd - - name: Install run: | . /home/firedrake/firedrake/bin/activate From 0fd5e2658349799a51db2023e2fe4651ee03d30c Mon Sep 17 00:00:00 2001 From: Josh Hope-Collins Date: Wed, 12 Feb 2025 15:41:03 +0000 Subject: [PATCH 09/11] Apply suggestions from code review --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ddf3c12b..34031b9b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -69,7 +69,7 @@ jobs: run: | . /home/firedrake/firedrake/bin/activate python -m pytest \ - -n 6 --dist worksteal \ + -n 3 --dist worksteal \ --durations=40 \ --timeout=300 \ --cov=asQ --cov-report=term \ @@ -78,7 +78,7 @@ jobs: run: | . /home/firedrake/firedrake/bin/activate python -m pytest \ - -n 4 --dist worksteal \ + -n 3 --dist worksteal \ --durations=40 \ --timeout=600 \ --cov=asQ --cov-report=term --cov-append \ From 8b0087aaac024da0887b72cfde9aed3da1579785 Mon Sep 17 00:00:00 2001 From: Josh Hope-Collins Date: Tue, 17 Jun 2025 15:50:07 +0100 Subject: [PATCH 10/11] Test using pip install Firedrake (#215) * Use pip installed firedrake on CI * firedrake container now already has siphash package * firedrake container doesn't have flake8 already * ci - fix test path * die firedrake-status die die die??? * remove pytest coverage for now * ci - always run tests(nprocs=1), and lint only asQ repo * ci fixup * CI workflow tidy * remove old ci workflow * ci: one pytest call per job * Some small updates for the most recent Firedrake et al versions. (#216) * import OptionsManager from petsctools * FunctionSpace.subspaces not subfunctions * pc.getOptionsPrefix now defaults to None not '' * splitting TensorProductElements in unmixed spaces seems to work again now! * pass quadrature_degree properly to project --- .github/workflows/test-install.yml | 166 ++++++++++++++++++ .github/workflows/test.yml | 85 --------- asQ/allatonce/function.py | 2 +- asQ/allatonce/solver.py | 3 +- asQ/complex_proxy/vector_impl.py | 2 +- asQ/preconditioners/base.py | 2 +- tests/integration/test_paradiag.py | 7 +- .../unit/complex_proxy/test_complex_mixed.py | 8 +- .../unit/complex_proxy/test_complex_vector.py | 4 +- utils/hybridisation.py | 4 +- utils/shallow_water/miniapp.py | 6 +- 11 files changed, 188 insertions(+), 101 deletions(-) create mode 100644 .github/workflows/test-install.yml delete mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test-install.yml b/.github/workflows/test-install.yml new file mode 100644 index 00000000..5c3c6acb --- /dev/null +++ b/.github/workflows/test-install.yml @@ -0,0 +1,166 @@ +name: Install and test asQ + +on: + # Run on pushes to master + push: + branches: + - master + # And all pull requests + pull_request: + schedule: + # Scheduled run over at 0217 UTC Sunday to detect any upstream breaks. + # * is a special character in YAML so you have to quote this string + - cron: '17 2 * * 0' + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +concurrency: + # Cancels jobs running if new commits are pushed + group: > + ${{ github.workflow }}- + ${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + build: + name: "Build and test asQ" + # Use Firedrake's Linux runners + runs-on: [self-hosted, Linux] + # Use the firedrake container without asQ installed - we want to run the repo version. + container: + image: firedrakeproject/firedrake-vanilla-default:latest + env: + # Sometimes we want to determine if tests are running on CI + ASQ_CI_TESTS: 1 + # Tell pyop2 to complain if SPMD assumptions are broken + PYOP2_SPMD_STRICT: 1 + # Make sure tests with >8 processes are not silently skipped + PYTEST_MPI_MAX_NPROCS: 8 + # Common arguments for pytest. TODO: Is pytest coverage possible firedrake-run-split-tests? + PYTEST_ARGS: --durations=50 --timeout=500 --timeout-method=thread -o faulthandler_timeout=600 --verbose asQ-repo/tests/ + # venv activation script + ACTIVATE: venv-asQ/bin/activate + steps: + - name: Fix HOME + # For unknown reasons GitHub actions overwrite HOME to /github/home + # which will break everything unless fixed + # (https://github.com/actions/runner/issues/863) + run: echo "HOME=/home/firedrake" >> "$GITHUB_ENV" + + - name: Pre-cleanup + # TODO: Why do we need to do this? + run: | + : # Wipe everything away in the current directory + find . -delete + firedrake-clean + + - uses: actions/checkout@v4 + with: + # Download asQ into a subdirectory not called 'asQ' to make sure + # that the package installs correctly. Otherwise 'import asQ' may + # work even if the installation failed because it is a subdirectory. + path: asQ-repo + + - name: Create virtual environment + # pass '--system-site-packages' so Firedrake can be found + run: python3 -m venv --system-site-packages venv-asQ + + - name: Install asQ + id: install + run: | + . $ACTIVATE + python --version + pip --version + pip install ./asQ-repo + pip list + python -m pytest --version + # TODO: firedrake-status doesn't work without $VIRTUAL_ENV/src. Is it obsolete now? + # firedrake-status + + - name: Lint + run: | + . $ACTIVATE + python -m pip install flake8 + cd ./asQ-repo + flake8 . + + - name: Run tests (nprocs = 1) + # Run even if earlier tests failed + if: success() || steps.install.conclusion == 'success' + run: | + . $ACTIVATE + : # Use pytest-xdist here so we can have a single collated output (not possible + : # for parallel tests) + firedrake-run-split-tests 1 1 -n 12 --dist worksteal "$PYTEST_ARGS" + timeout-minutes: 20 + + - name: Run tests (nprocs = 2) + # Run even if earlier tests failed + if: success() || steps.install.conclusion == 'success' + run: | + . $ACTIVATE + firedrake-run-split-tests 2 6 "$PYTEST_ARGS" + timeout-minutes: 20 + + - name: Run tests (nprocs = 3) + # Run even if earlier tests failed + if: success() || steps.install.conclusion == 'success' + run: | + . $ACTIVATE + firedrake-run-split-tests 3 4 "$PYTEST_ARGS" + timeout-minutes: 20 + + - name: Run tests (nprocs = 4) + # Run even if earlier tests failed + if: success() || steps.install.conclusion == 'success' + run: | + . $ACTIVATE + firedrake-run-split-tests 4 3 "$PYTEST_ARGS" + timeout-minutes: 20 + + - name: Run tests (nprocs = 5) + # Run even if earlier tests failed + if: success() || steps.install.conclusion == 'success' + run: | + . $ACTIVATE + firedrake-run-split-tests 5 2 "$PYTEST_ARGS" + timeout-minutes: 20 + + - name: Run tests (nprocs = 6) + # Run even if earlier tests failed + if: success() || steps.install.conclusion == 'success' + run: | + . $ACTIVATE + firedrake-run-split-tests 6 2 "$PYTEST_ARGS" + timeout-minutes: 20 + + - name: Run tests (nprocs = 7) + # Run even if earlier tests failed + if: success() || steps.install.conclusion == 'success' + run: | + . $ACTIVATE + firedrake-run-split-tests 7 1 "$PYTEST_ARGS" + timeout-minutes: 20 + + - name: Run tests (nprocs = 8) + # Run even if earlier tests failed + if: success() || steps.install.conclusion == 'success' + run: | + . $ACTIVATE + firedrake-run-split-tests 8 1 "$PYTEST_ARGS" + timeout-minutes: 20 + + - name: Upload pytest log files + uses: actions/upload-artifact@v4 + if: success() || steps.install.conclusion == 'success' + with: + name: pytest-logs + path: pytest_*.log + retention-days: 5 + + - name: Post-cleanup + if: always() + run: | + find . -delete + firedrake-clean diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 34031b9b..00000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,85 +0,0 @@ -name: Lint and test - -on: - # Run on pushes to master - push: - branches: - - master - # And all pull requests - pull_request: - schedule: - # * is a special character in YAML so you have to quote this string - # Scheduled run over the weekend to detect any upstream breaks. - - cron: '0 1 * * 0' - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -concurrency: - # Cancels jobs running if new commits are pushed - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -jobs: - build: - name: "Build and test asQ" - # The type of runner that the job will run on - # runs-on: ubuntu-latest - runs-on: [self-hosted, Linux] - # The docker container to use. - container: - image: firedrakeproject/firedrake-vanilla:latest - env: - ASQ_CI_TESTS: 1 - OMP_NUM_THREADS: 1 - OPENBLAS_NUM_THREADS: 1 - PYOP2_SPMD_STRICT: 1 - # Steps represent a sequence of tasks that will be executed as - # part of the jobs - steps: - - name: Fix permissions - # Firedrake's Dockerfile sets USER to firedrake instead of - # using the default user, so we need to update file - # permissions for this image to work on GH Actions. - # See https://help.github.com/en/actions/reference/virtual-environments-for-github-hosted-runners#docker-container-filesystem - # (copied from https://help.github.com/en/actions/migrating-to-github-actions/migrating-from-circleci-to-github-actions) - run: | - sudo chmod -R 777 $GITHUB_WORKSPACE /github /__w/_temp - - uses: actions/checkout@v4 - - name: Install test dependencies - run: | - . /home/firedrake/firedrake/bin/activate - python -m pip install pytest-timeout - python -m pip install pytest-cov - # try and reduce the number of warnings to sift through - python -m pip install siphash24 - - name: Install - run: | - . /home/firedrake/firedrake/bin/activate - python -m pip install -e . - python --version - python -m pytest --version - flake8 --version - firedrake-status - - name: Lint - run: | - . /home/firedrake/firedrake/bin/activate - flake8 . - - name: Test - unit tests - run: | - . /home/firedrake/firedrake/bin/activate - python -m pytest \ - -n 3 --dist worksteal \ - --durations=40 \ - --timeout=300 \ - --cov=asQ --cov-report=term \ - -v tests/unit - - name: Test - integration tests - run: | - . /home/firedrake/firedrake/bin/activate - python -m pytest \ - -n 3 --dist worksteal \ - --durations=40 \ - --timeout=600 \ - --cov=asQ --cov-report=term --cov-append \ - -v tests/integration diff --git a/asQ/allatonce/function.py b/asQ/allatonce/function.py index 42dd3a6b..1d31b986 100644 --- a/asQ/allatonce/function.py +++ b/asQ/allatonce/function.py @@ -67,7 +67,7 @@ def __init__(self, ensemble, time_partition, function_space): self.function_space = reduce(mul, (self.field_function_space for _ in range(self.nlocal_timesteps))) - self.ncomponents = len(self.field_function_space.subfunctions) + self.ncomponents = len(self.field_function_space.subspaces) # this will be renamed either self.function or self.cofunction self._fbuf = fd.Function(self.function_space) diff --git a/asQ/allatonce/solver.py b/asQ/allatonce/solver.py index 0df08826..4237d5d0 100644 --- a/asQ/allatonce/solver.py +++ b/asQ/allatonce/solver.py @@ -1,4 +1,5 @@ -from firedrake.petsc import PETSc, OptionsManager, flatten_parameters +from firedrake.petsc import PETSc +from petsctools import OptionsManager, flatten_parameters from asQ.profiling import profiler from asQ.allatonce import (AllAtOnceCofunction, AllAtOnceFunction, diff --git a/asQ/complex_proxy/vector_impl.py b/asQ/complex_proxy/vector_impl.py index 1f0bf39c..6247e55a 100644 --- a/asQ/complex_proxy/vector_impl.py +++ b/asQ/complex_proxy/vector_impl.py @@ -96,7 +96,7 @@ def split(u, i): us = fd.split(u) - ncomponents = len(u.function_space().subfunctions) + ncomponents = len(u.function_space().subspaces) if ncomponents == 1: return tuple((us[i],)) diff --git a/asQ/preconditioners/base.py b/asQ/preconditioners/base.py index f503c16b..08c41092 100644 --- a/asQ/preconditioners/base.py +++ b/asQ/preconditioners/base.py @@ -48,7 +48,7 @@ def initialize(self, pc, final_initialize=True): raise ValueError("Expecting PC type python") # grab aao objects off petsc mat python context - prefix = pc.getOptionsPrefix() + prefix = pc.getOptionsPrefix() or "" self.full_prefix = prefix + self.prefix if hasattr(self, "deprecated_prefix"): self.deprecated_prefix = prefix + self.deprecated_prefix diff --git a/tests/integration/test_paradiag.py b/tests/integration/test_paradiag.py index 72fd336a..527b502e 100644 --- a/tests/integration/test_paradiag.py +++ b/tests/integration/test_paradiag.py @@ -469,9 +469,10 @@ def test_steady_swe_miniapp(): bc_opts = ["no_bcs", "homogeneous_bcs", "inhomogeneous_bcs"] -extruded_mixed = [pytest.param(False, id="standard_mesh"), - pytest.param(True, id="extruded_mesh", - marks=pytest.mark.xfail(reason="fd.split for TensorProductElements in unmixed spaces broken by ufl PR#122."))] +extruded_mixed = [ + pytest.param(False, id="standard_mesh"), + pytest.param(True, id="extruded_mesh"), +] cpx_types = [pytest.param('vector', id="vector_cpx"), pytest.param('mixed', id="mixed_cpx")] diff --git a/tests/unit/complex_proxy/test_complex_mixed.py b/tests/unit/complex_proxy/test_complex_mixed.py index f8d7f502..5f000b21 100644 --- a/tests/unit/complex_proxy/test_complex_mixed.py +++ b/tests/unit/complex_proxy/test_complex_mixed.py @@ -119,15 +119,15 @@ def test_mixed_function_space(mesh, mixed_element): V = fd.FunctionSpace(mesh, mixed_element) W = cpx.FunctionSpace(V) - assert len(W.subfunctions) == 2*len(V.subfunctions), "The complex space should have twice the number of components as the real space" + assert len(W.subspaces) == 2*len(V.subspaces), "The complex space should have twice the number of components as the real space" for i in range(V.ufl_element().num_sub_elements): idx_real = 2*i+0 idx_imag = 2*i+1 - real_elem = W.subfunctions[idx_real].ufl_element() - imag_elem = W.subfunctions[idx_imag].ufl_element() - orig_elem = V.subfunctions[i].ufl_element() + real_elem = W.subspaces[idx_real].ufl_element() + imag_elem = W.subspaces[idx_imag].ufl_element() + orig_elem = V.subspaces[i].ufl_element() assert real_elem == orig_elem, "The complex function space should have the cpx Element corresponding to the cpx Element of the real space" assert imag_elem == orig_elem, "The complex function space should have the cpx Element corresponding to the cpx Element of the real space" diff --git a/tests/unit/complex_proxy/test_complex_vector.py b/tests/unit/complex_proxy/test_complex_vector.py index a0e7c13c..e8622a53 100644 --- a/tests/unit/complex_proxy/test_complex_vector.py +++ b/tests/unit/complex_proxy/test_complex_vector.py @@ -127,9 +127,9 @@ def test_mixed_function_space(mesh, mixed_element): V = fd.FunctionSpace(mesh, mixed_element) W = cpx.FunctionSpace(V) - assert len(W.subfunctions) == len(V.subfunctions), "The complex space should have the same number of components as the real space" + assert len(W.subspaces) == len(V.subspaces), "The complex space should have the same number of components as the real space" - for wcpt, vcpt in zip(W.subfunctions, V.subfunctions): + for wcpt, vcpt in zip(W.subspaces, V.subspaces): assert wcpt == cpx.FunctionSpace(vcpt), "Each component of the complex space should be the complex space of the component of the real space" diff --git a/utils/hybridisation.py b/utils/hybridisation.py index ee2ebf33..355b591e 100644 --- a/utils/hybridisation.py +++ b/utils/hybridisation.py @@ -103,7 +103,7 @@ def _break_function_space(V, appctx=None): raise ValueError(msg) # hybridisable space - broken HDiv and Trace - Vs = V.subfunctions + Vs = V.subspaces Vu = Vs[iu] @@ -204,7 +204,7 @@ def initialize(self, pc): # the trace bit n = fd.FacetNormal(self.mesh) - ncpts = len(V.subfunctions) + ncpts = len(V.subspaces) def form_trace(*args): trls = args[:ncpts+1] diff --git a/utils/shallow_water/miniapp.py b/utils/shallow_water/miniapp.py index 6b259e2b..0c37b724 100644 --- a/utils/shallow_water/miniapp.py +++ b/utils/shallow_water/miniapp.py @@ -104,7 +104,11 @@ def form_mass(u, h, v, q): u0 = w0.subfunctions[self.velocity_index] h0 = w0.subfunctions[self.depth_index] - u0.project(velocity_expression(*x), quadrature_degree=6) + # limit the projection degree to a 2*V.degree+2 in + # case we get a non-polynomial expression (e.g. Galewsky) + u0.project( + velocity_expression(*x), + form_compiler_parameters={"quadrature_degree": 6}) h0.interpolate(depth_expression(*x)) if reference_state: From d40b6b345a6cbd3ab773f4edec385da2e68a84ff Mon Sep 17 00:00:00 2001 From: Josh Hope-Collins Date: Wed, 2 Jul 2025 10:35:01 +0100 Subject: [PATCH 11/11] import guard around petsctools for master vs release --- asQ/allatonce/solver.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/asQ/allatonce/solver.py b/asQ/allatonce/solver.py index 4237d5d0..8ab98412 100644 --- a/asQ/allatonce/solver.py +++ b/asQ/allatonce/solver.py @@ -1,5 +1,9 @@ from firedrake.petsc import PETSc -from petsctools import OptionsManager, flatten_parameters +# TODO: remove this check once petsctools is included in firedrake release +try: # Firedrake master + from petsctools import OptionsManager, flatten_parameters +except ImportError: # Firedrake release + from firedrake.petsc import OptionsManager, flatten_parameters from asQ.profiling import profiler from asQ.allatonce import (AllAtOnceCofunction, AllAtOnceFunction,