From fdfc908e2026112428dc96a84e2eb27db59e739c Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Fri, 6 Dec 2024 09:54:01 +0000 Subject: [PATCH] Begin using Mac runners (#3881) * Add GitHub actions for macOS using `firedrake-install` and `pip install firedrake`. These will only run on PRs labelled "macOS" (or on pushes to master). * Fixups to pyproject.toml --------- Co-authored-by: Josh Hope-Collins Co-authored-by: David A. Ham --- .github/workflows/build-mac.yml | 58 ++++++++ .github/workflows/pip-mac.yml | 127 ++++++++++++++++++ .github/workflows/pyop2.yml | 5 +- docs/source/download.rst | 4 +- pyproject.toml | 12 +- setup.py | 21 ++- .../firedrake/regression/test_dg_advection.py | 12 +- .../regression/test_poisson_strong_bcs.py | 10 +- 8 files changed, 229 insertions(+), 20 deletions(-) create mode 100644 .github/workflows/build-mac.yml create mode 100644 .github/workflows/pip-mac.yml diff --git a/.github/workflows/build-mac.yml b/.github/workflows/build-mac.yml new file mode 100644 index 0000000000..51aa9f5104 --- /dev/null +++ b/.github/workflows/build-mac.yml @@ -0,0 +1,58 @@ +name: Install and test Firedrake (macOS) + +on: + push: + branches: + - master + pull_request: + # By default this workflow is run on the "opened", "synchronize" and + # "reopened" events. We add "labelled" so it will run if the PR is given a label. + types: [opened, synchronize, reopened, labeled] + +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 Firedrake (macOS) + runs-on: [self-hosted, macOS] + # Only run this action if we are pushing to master or the PR is labelled "macOS" + if: ${{ (github.ref == 'refs/heads/master') || contains(github.event.pull_request.labels.*.name, 'macOS') }} + env: + FIREDRAKE_CI_TESTS: 1 # needed to symlink the checked out branch into the venv + OMP_NUM_THREADS: 1 + OPENBLAS_NUM_THREADS: 1 + steps: + - name: Add homebrew to PATH + # https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions#adding-a-system-path + run: echo "/opt/homebrew/bin" >> "$GITHUB_PATH" + - uses: actions/checkout@v4 + - name: Pre-run cleanup + if: ${{ always() }} + run: | + cd .. + rm -rf firedrake_venv + - name: Install Python + run: brew install python python-setuptools + - name: Build Firedrake + run: | + cd .. + "$(brew --prefix)/bin/python3" \ + firedrake/scripts/firedrake-install \ + --venv-name firedrake_venv \ + --disable-ssh \ + || (cat firedrake-install.log && /bin/false) + - name: Run smoke tests + run: | + . ../firedrake_venv/bin/activate + python -m pytest -v tests/firedrake/regression/ -k "poisson_strong or stokes_mini or dg_advection" + timeout-minutes: 30 + - name: Post-run cleanup + if: ${{ always() }} + run: | + cd .. + rm -rf firedrake_venv diff --git a/.github/workflows/pip-mac.yml b/.github/workflows/pip-mac.yml new file mode 100644 index 0000000000..c8d1fbfe2e --- /dev/null +++ b/.github/workflows/pip-mac.yml @@ -0,0 +1,127 @@ +name: Pip install Firedrake (macOS) + +on: + push: + branches: + - master + pull_request: + # By default this workflow is run on the "opened", "synchronize" and + # "reopened" events. We add "labelled" so it will run if the PR is given a label. + types: [opened, synchronize, reopened, labeled] + +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 Firedrake using pip (macOS)" + runs-on: [self-hosted, macOS] + # Only run this action if we are pushing to master or the PR is labelled "macOS" + if: ${{ (github.ref == 'refs/heads/master') || contains(github.event.pull_request.labels.*.name, 'macOS') }} + env: + OMP_NUM_THREADS: 1 + OPENBLAS_NUM_THREADS: 1 + steps: + - name: Add homebrew to PATH + # https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions#adding-a-system-path + run: echo "/opt/homebrew/bin" >> "$GITHUB_PATH" + + - name: Install homebrew packages + run: brew install gcc autoconf pkg-config make automake cmake ninja libtool boost openblas python python-setuptools mpich + + - name: Cleanup (pre) + if: ${{ always() }} + run: | + rm -rf pip_venv + "$(brew --prefix)/bin/python3" -m pip cache purge + + - name: Create a virtual environment + run: | + "$(brew --prefix)/bin/python3" -m venv pip_venv + mkdir pip_venv/src + + - name: Install PETSc + run: | + cd pip_venv/src + git clone https://github.com/firedrakeproject/petsc.git + cd petsc + ./configure PETSC_DIR="$PWD" PETSC_ARCH=default \ + --with-shared-libraries=1 \ + --with-mpi-dir=/opt/homebrew \ + --with-zlib \ + --download-bison \ + --download-hdf5 \ + --download-hwloc \ + --download-hypre \ + --download-metis \ + --download-mumps \ + --download-netcdf \ + --download-pastix \ + --download-pnetcdf \ + --download-ptscotch \ + --download-scalapack \ + --download-suitesparse \ + --download-superlu_dist + make + + - name: Install libsupermesh + run: | + source pip_venv/bin/activate + python -m pip install 'rtree>=1.2' + cd pip_venv/src + git clone https://github.com/firedrakeproject/libsupermesh.git + mkdir -p libsupermesh/build + cd libsupermesh/build + cmake .. \ + -DBUILD_SHARED_LIBS=ON \ + -DCMAKE_INSTALL_PREFIX="$VIRTUAL_ENV" \ + -DMPI_C_COMPILER=/opt/homebrew/bin/mpicc \ + -DMPI_CXX_COMPILER=/opt/homebrew/bin/mpicxx \ + -DMPI_Fortran_COMPILER=/opt/homebrew/bin/mpif90 \ + -DCMAKE_Fortran_COMPILER=/opt/homebrew/bin/mpif90 \ + -DMPIEXEC_EXECUTABLE=/opt/homebrew/bin/mpiexec + make + make install + + - uses: actions/checkout@v4 + with: + path: pip_venv/src/firedrake + + - name: Pip install + run: | + export PETSC_DIR="$PWD/pip_venv/src/petsc" + export PETSC_ARCH=default + export HDF5_DIR="$PETSC_DIR/$PETSC_ARCH" + export HDF5_MPI=ON + export CC=/opt/homebrew/bin/mpicc + export CXX=/opt/homebrew/bin/mpicxx + export MPICC="$CC" + source pip_venv/bin/activate + cd pip_venv/src + python -m pip install \ + --log=firedrake-install.log \ + --no-binary h5py \ + -v -e './firedrake[test]' + + - name: Install CI-specific test dependencies + run: | + source pip_venv/bin/activate + python -m pip install -U pytest-timeout + + - name: Run Firedrake smoke tests + run: | + source pip_venv/bin/activate + cd pip_venv/src/firedrake + python -m pytest --timeout=1800 -v tests/firedrake/regression \ + -k "poisson_strong or stokes_mini or dg_advection" + timeout-minutes: 30 + + - name: Cleanup (post) + if: ${{ always() }} + run: | + rm -rf pip_venv + "$(brew --prefix)/bin/python3" -m pip cache purge diff --git a/.github/workflows/pyop2.yml b/.github/workflows/pyop2.yml index 36065accf1..0eb2ef2813 100644 --- a/.github/workflows/pyop2.yml +++ b/.github/workflows/pyop2.yml @@ -106,7 +106,10 @@ jobs: working-directory: PyOP2 run: | source ../venv/bin/activate - python -m pip install -v ".[test]" + export CC=mpicc + export HDF5_DIR="$PETSC_DIR/$PETSC_ARCH" + export HDF5_MPI=ON + python -m pip install --no-binary h5py -v ".[test]" - name: Run tests shell: bash diff --git a/docs/source/download.rst b/docs/source/download.rst index 3865e8b175..74645fc682 100644 --- a/docs/source/download.rst +++ b/docs/source/download.rst @@ -230,10 +230,10 @@ type:: You should now be able to run ``firedrake-update``. -Installing Firedrake with pip (experimental, Linux only) +Installing Firedrake with pip (experimental) -------------------------------------------------------- -Firedrake has experimental support for installing using ``pip``, avoiding the need for the ``firedrake-install`` script. At present only Linux is tested using this install method. +Firedrake has experimental support for installing using ``pip``, avoiding the need for the ``firedrake-install`` script. Requirements ~~~~~~~~~~~~ diff --git a/pyproject.toml b/pyproject.toml index 5cd6608241..0dc84087af 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,8 +16,13 @@ requires-python = ">=3.10" dependencies = [ "cachetools", "decorator<=4.4.2", - "mpi4py", - "h5py", + "mpi4py>3; python_version >= '3.13'", + "mpi4py; python_version < '3.13'", + # TODO: We are only using our fork here because the most recent PyPI release + # does not yet work with build isolation for Python 3.13. Once a version + # newer than 3.12.1 is released we can revert to simply using "h5py". + "h5py @ git+https://github.com/firedrakeproject/h5py.git ; python_version >= '3.13'", + "h5py; python_version < '3.13'", "petsc4py", "numpy", "packaging", @@ -91,7 +96,8 @@ requires = [ "pybind11", "pkgconfig", "numpy", - "mpi4py", + "mpi4py>3; python_version >= '3.13'", + "mpi4py; python_version < '3.13'", "petsc4py", "rtree>=1.2", ] diff --git a/setup.py b/setup.py index 36a5fe5082..cf970c1712 100644 --- a/setup.py +++ b/setup.py @@ -1,9 +1,5 @@ -from dataclasses import dataclass, field -from setuptools import setup, find_packages, Extension -from glob import glob -from pathlib import Path -from Cython.Build import cythonize import os +import platform import sys import site import numpy as np @@ -11,6 +7,11 @@ import petsc4py import rtree import pkgconfig +from dataclasses import dataclass, field +from setuptools import setup, find_packages, Extension +from glob import glob +from pathlib import Path +from Cython.Build import cythonize # Define the compilers to use if not already set if "CC" not in os.environ: @@ -87,7 +88,14 @@ def __getitem__(self, key): # Pybind11 # example: # gcc -I/pyind11/include ... -pybind11_ = ExternalDependency(include_dirs=[pybind11.get_include()]) +pybind11_extra_compile_args = [] +if platform.uname().system == "Darwin": + # Clang needs to specify at least C++11 + pybind11_extra_compile_args.append("-std=c++11") +pybind11_ = ExternalDependency( + include_dirs=[pybind11.get_include()], + extra_compile_args=pybind11_extra_compile_args, +) # numpy # example: @@ -225,7 +233,6 @@ def extensions(): ## PYBIND11 EXTENSIONS pybind11_list = [] # tinyasm/tinyasm.cpp: petsc, pybind11 - # tinyasm/tinyasm.cpp: petsc, pybind11 pybind11_list.append(Extension( name="tinyasm._tinyasm", language="c++", diff --git a/tests/firedrake/regression/test_dg_advection.py b/tests/firedrake/regression/test_dg_advection.py index a655176c24..0559f2f159 100644 --- a/tests/firedrake/regression/test_dg_advection.py +++ b/tests/firedrake/regression/test_dg_advection.py @@ -38,8 +38,16 @@ def run_test(mesh): t = 0.0 T = 10*dt - problem = LinearVariationalProblem(a_mass, action(arhs, D1), dD1) - solver = LinearVariationalSolver(problem, solver_parameters={'ksp_type': 'cg'}) + problem = LinearVariationalProblem(a_mass, action(arhs, D1), dD1, + constant_jacobian=True) + solver = LinearVariationalSolver( + problem, + solver_parameters={ + 'ksp_type': 'preonly', + 'pc_type': 'bjacobi', + 'sub_pc_type': 'ilu' + } + ) L2_0 = norm(D) Dbar_0 = assemble(D*dx) diff --git a/tests/firedrake/regression/test_poisson_strong_bcs.py b/tests/firedrake/regression/test_poisson_strong_bcs.py index 2655f98b91..208260554d 100644 --- a/tests/firedrake/regression/test_poisson_strong_bcs.py +++ b/tests/firedrake/regression/test_poisson_strong_bcs.py @@ -18,7 +18,7 @@ from firedrake import * -def run_test(r, degree, parameters={}, quadrilateral=False): +def run_test(r, degree, parameters, quadrilateral=False): # Create mesh and define function space mesh = UnitSquareMesh(2 ** r, 2 ** r, quadrilateral=quadrilateral) x = SpatialCoordinate(mesh) @@ -41,7 +41,7 @@ def run_test(r, degree, parameters={}, quadrilateral=False): return sqrt(assemble(inner(u - f, u - f) * dx)) -def run_test_linear(r, degree, parameters={}, quadrilateral=False): +def run_test_linear(r, degree, parameters, quadrilateral=False): # Create mesh and define function space mesh = UnitSquareMesh(2 ** r, 2 ** r, quadrilateral=quadrilateral) x = SpatialCoordinate(mesh) @@ -86,7 +86,7 @@ def test_poisson_analytic_linear(params, degree, quadrilateral): @pytest.mark.parallel(nprocs=2) def test_poisson_analytic_linear_parallel(): - from mpi4py import MPI - error = run_test_linear(1, 1) - print('[%d]' % MPI.COMM_WORLD.rank, 'error:', error) + # specify superlu_dist as MUMPS fails in parallel on MacOS + solver_parameters = {'pc_factor_mat_solver_type': 'superlu_dist'} + error = run_test_linear(1, 1, solver_parameters) assert error < 5e-6