From 5dcdbd5725a2ac9dbe87b16e4f63ab1898565281 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Fri, 1 May 2026 11:39:10 +0100 Subject: [PATCH 1/7] stderr in firedrake-configure and Python version check (#5072) * Pipe firedrake-configure errors to stderr and check min Python version * stdout too --- scripts/check-config | 6 +++- scripts/firedrake-configure | 60 +++++++++++++++++++++++++++++-------- 2 files changed, 53 insertions(+), 13 deletions(-) diff --git a/scripts/check-config b/scripts/check-config index 281e0d0afb..948bd8e2f4 100755 --- a/scripts/check-config +++ b/scripts/check-config @@ -32,9 +32,13 @@ def check_min_python_version() -> None: "requires-python = \">=(.*)\"" ) check_file_contains_pattern( - REPO_ROOT / "docs/source/install.rst", + REPO_ROOT / "docs" / "source" / "install.rst", f"Python \\({min_python_version} or greater\\)" ) + check_file_contains_pattern( + REPO_ROOT / "scripts" / "firedrake-configure", + f"MINIMUM_PYTHON_VERSION = \"{min_python_version}\"" + ) def check_petsc_version() -> None: diff --git a/scripts/firedrake-configure b/scripts/firedrake-configure index abcca15926..c854cdc5e6 100755 --- a/scripts/firedrake-configure +++ b/scripts/firedrake-configure @@ -10,6 +10,36 @@ # As a matter of policy, new package managers and archs should only be added to # this file if they are *tested in CI*. +# Start by checking the Python version, older Pythons may not be able to run +# this script. +import sys + + +def stdout(msg: str, **kwargs) -> None: + """Write to stdout.""" + print(msg, file=sys.stdout, **kwargs) + + +def stderr(msg: str, **kwargs) -> None: + """Write to stderr. + + To make it distinct in the terminal output, messages are prefixed with 'ERROR: '. + + """ + print(f"ERROR: {msg}", file=sys.stderr, **kwargs) + + +def abort() -> None: + """Terminate the script.""" + exit(1) + + +MINIMUM_PYTHON_VERSION = "3.10" +if sys.version < MINIMUM_PYTHON_VERSION: + stderr(f"Firedrake requires Python {MINIMUM_PYTHON_VERSION} or greater") + abort() + + import argparse import enum import os @@ -164,34 +194,37 @@ Please see https://firedrakeproject.org/install for more information.""" gpu_arch = GPUArch(args.gpu_arch) if gpu_arch != NO_GPU and package_manager == MACOS_HOMEBREW_ARM64: - raise RuntimeError( + stderr( "GPU-compatible PETSc builds are currently only supported" "on Linux" ) + abort() if args.show_system_packages: if package_manager is None: - raise RuntimeError( + stderr( "Cannot install Firedrake dependencies without a package manager, " "please install them manually" ) - print(" ".join(SYSTEM_PACKAGES[package_manager, arch, gpu_arch]), end="") + abort() + stdout(" ".join(SYSTEM_PACKAGES[package_manager, arch, gpu_arch]), end="") elif args.show_minimal_system_packages: if package_manager is None: - raise RuntimeError( + stderr( "Cannot install Firedrake dependencies without a package manager, " "please install them manually" ) - print(" ".join(MINIMAL_SYSTEM_PACKAGES[package_manager]), end="") + abort() + stdout(" ".join(MINIMAL_SYSTEM_PACKAGES[package_manager]), end="") elif args.show_petsc_configure_options: - print(" ".join(PETSC_CONFIGURE_OPTIONS[package_manager, arch, gpu_arch]), end="") + stdout(" ".join(PETSC_CONFIGURE_OPTIONS[package_manager, arch, gpu_arch]), end="") elif args.show_petsc_version: - print(SUPPORTED_PETSC_VERSION, end="") + stdout(SUPPORTED_PETSC_VERSION, end="") elif args.show_extra_repo_pkg_url: - print(EXTRA_LINUX_APT_PKG_URL[gpu_arch], end="") + stdout(EXTRA_LINUX_APT_PKG_URL[gpu_arch], end="") else: assert args.show_env - print(" ".join(ENVIRONMENT_VARS[package_manager, arch, gpu_arch]), end="") + stdout(" ".join(ENVIRONMENT_VARS[package_manager, arch, gpu_arch]), end="") def sniff_package_manager() -> Optional[PackageManager]: @@ -205,10 +238,11 @@ def sniff_package_manager() -> Optional[PackageManager]: if has_homebrew(): if platform.machine() == "arm64": return MACOS_HOMEBREW_ARM64 - raise RuntimeError( + stderr( "No package manager detected, if you want to configure Firedrake without " "any system packages please pass '--no-package-manager'" ) + abort() def has_apt() -> bool: @@ -218,7 +252,8 @@ def has_apt() -> bool: except FileNotFoundError: return False except: # noqa: E722 - raise RuntimeError("Unexpected error occurred searching for apt") + stderr("Unexpected error occurred searching for apt") + abort() def has_homebrew() -> bool: @@ -228,7 +263,8 @@ def has_homebrew() -> bool: except FileNotFoundError: return False except: # noqa: E722 - raise RuntimeError("Unexpected error occurred searching for homebrew") + stderr("Unexpected error occurred searching for homebrew") + abort() MINIMAL_LINUX_APT_PACKAGES = ( From 5de9276c7099010157aa0415f0569df991d60b85 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Fri, 1 May 2026 16:16:51 +0100 Subject: [PATCH 2/7] Add rough agenda to Firedrake26 (#5075) --- docs/source/firedrake_26.rst | 102 ++++++++++++++++++++++++++++------- 1 file changed, 83 insertions(+), 19 deletions(-) diff --git a/docs/source/firedrake_26.rst b/docs/source/firedrake_26.rst index cea537b4fc..1030179fa2 100644 --- a/docs/source/firedrake_26.rst +++ b/docs/source/firedrake_26.rst @@ -27,14 +27,6 @@ Participants may register for either part of the week, but we hope that many will take the opportunity to learn more about these two related projects by staying for the whole week. -Key dates ---------- - -* Abstract deadline 20 April 2026. -* Registration deadline 27 April 2026. -* 1-3 June PETSc user meeting 2026. -* 3-5 June Firedrake '26. - Venue & registration -------------------- @@ -63,16 +55,88 @@ venue `__ or booking a hotel by London Paddington, as it is a convenient midpoint between London Heathrow and Oxford. -`Register here! `__ - -Abstract submissions --------------------- +Programme +--------- -Submissions for either part of the week for either oral or poster presentation -can be made `on EasyChair -`__. In the spirit of -supporting the whole community to communicate their results, we anticipate that -all submissions will be accepted for presentation in some form. +Abstract submissions are now closed. The full programme will be made available +soon but the agenda for the week is as follows (note that timings may not be +exact): + +**Monday** + +.. table:: + :align: left + + =========== ============ + 10:00-10:30 PETSc talks + 10:30-11:00 coffee break + 11:00-11:45 PETSc talks + 11:45-13:30 lunch + 13:30-14:45 PETSc talks + 14:45-15:30 coffee break + 15:30-16:45 PETSc talks + =========== ============ + +**Tuesday** + +.. table:: + :align: left + + =========== ============ + 09:00-10:00 PETSc talks + 10:00-10:45 coffee break + 10:45-11:45 PETSc talks + 11:45-13:30 lunch + 13:30-14:45 PETSc talks + 14:45-15:30 coffee break + 15:30-16:45 PETSc talks + =========== ============ + +**Wednesday** + +.. table:: + :align: left + + =========== ==================== + 09:00-10:15 PETSc tutorial 1 + 10:15-11:00 coffee break + 11:00-12:15 PETSc tutorial 2 + 12:15-13:30 lunch + 13:30-14:45 Firedrake tutorial 1 + 14:45-15:30 coffee break + 15:30-16:45 Firedrake tutorial 2 + 16:45-17:30 poster session + =========== ==================== + +**Thursday** + +.. table:: + :align: left + + =========== =============== + 09:00-10:00 Firedrake talks + 10:00-10:45 coffee break + 10:45-11:45 Firedrake talks + 11:45-13:30 lunch + 13:30-14:45 Firedrake talks + 14:45-15:30 coffee break + 15:30-16:45 Firedrake talks + =========== =============== + +**Friday** + +.. table:: + :align: left + + =========== =============== + 09:00-10:00 Firedrake talks + 10:00-10:45 coffee break + 10:45-11:45 Firedrake talks + 11:45-13:30 lunch + 13:30-14:15 Firedrake talks + 14:15-15:00 coffee break + 15:00-16:00 Firedrake talks + =========== =============== Travel ------ @@ -86,13 +150,13 @@ The venue is: | United Kingdom From Oxford -........... +~~~~~~~~~~~ The venue is on the number 3 and 3A bus routes from Oxford Station. Buses are frequent and contactless credit card payments are accepted on the bus. By air -...... +~~~~~~ Oxford lies between London and Birmingham. The closest London airport is London Heathrow. From 3da1522aa845bffbdb0c0cce1d5f0c8bf5088118 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Fri, 1 May 2026 18:26:33 +0100 Subject: [PATCH 3/7] Fix for quadrature rule with mixed dimension macroelements (#5074) --- .../submesh/test_submesh_assemble.py | 30 +++++++++++++++++++ tsfc/kernel_interface/common.py | 4 +++ 2 files changed, 34 insertions(+) diff --git a/tests/firedrake/submesh/test_submesh_assemble.py b/tests/firedrake/submesh/test_submesh_assemble.py index af317a8ec9..8692857cad 100644 --- a/tests/firedrake/submesh/test_submesh_assemble.py +++ b/tests/firedrake/submesh/test_submesh_assemble.py @@ -567,3 +567,33 @@ def expr(m): A_ref.petscmat.axpy(-1, A.petscmat) assert np.isclose(A_ref.petscmat.norm(PETSc.NormType.NORM_FROBENIUS), 0) + + +def test_submesh_assemble_facet_macroelement(): + # Test that the macro quadrature rule is correctly selected + # for macroelements whose restriction is also a macroelement + rg = RandomGenerator(PCG64(seed=0)) + mesh = UnitSquareMesh(4, 4) + DGT = FunctionSpace(mesh, "DGT", 0) + marker = Function(DGT) + DirichletBC(DGT, 1, "on_boundary").apply(marker) + label = 111 + mesh = RelabeledMesh(mesh, [marker], [label]) + submesh = Submesh(mesh, mesh.topological_dimension-1, label) + + V = FunctionSpace(mesh, "CG", 1, variant="iso") + Vsub = FunctionSpace(submesh, "CG", 1) + Z = V * Vsub + z = rg.uniform(Z, -1, 1) + u, usub = split(z) + v, vsub = TestFunctions(Z) + + ds_sub = Measure("ds", domain=mesh, intersect_measures=[dx(submesh)]) + + a1 = assemble(inner(1-u, vsub)*ds_sub(degree=2)) + a2 = assemble(inner(u, vsub)*ds_sub(degree=4)) + a = Function(a1.function_space()).assign(a1+a2) + + vsub = TestFunction(Vsub) + aref = assemble(inner(1, vsub)*dx(submesh)) + assert np.allclose(a.dat[1].data_ro, aref.dat.data_ro) diff --git a/tsfc/kernel_interface/common.py b/tsfc/kernel_interface/common.py index 40d585786f..5d61a916aa 100644 --- a/tsfc/kernel_interface/common.py +++ b/tsfc/kernel_interface/common.py @@ -346,6 +346,10 @@ def set_quad_rule(params, cell, integral_type, functions): finat_elements = set(create_element(e) for e in elements if e.family() != "Real") fiat_cells = [fiat_cell] + [finat_el.complex for finat_el in finat_elements] if any(c.is_macrocell() for c in fiat_cells): + if len(set(c.get_spatial_dimension() for c in fiat_cells)) > 1: + dimension = fiat_cell.get_dimension() + fiat_cells = [c if c.get_dimension() == dimension else + c.construct_subcomplex(dimension) for c in fiat_cells] fiat_cell = max_complex(fiat_cells) integration_dim, _ = lower_integral_type(fiat_cell, integral_type) quad_rule = fem.get_quadrature_rule(fiat_cell, integration_dim, quadrature_degree, scheme) From 5d6607730a918ea807968954d4458596405e8716 Mon Sep 17 00:00:00 2001 From: Josh Hope-Collins Date: Fri, 1 May 2026 21:49:14 +0100 Subject: [PATCH 4/7] bugfix: MixedCovariance.function_space property (#5070) --- firedrake/adjoint/covariance_operator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firedrake/adjoint/covariance_operator.py b/firedrake/adjoint/covariance_operator.py index 3c1b58a479..171cd10136 100644 --- a/firedrake/adjoint/covariance_operator.py +++ b/firedrake/adjoint/covariance_operator.py @@ -669,7 +669,7 @@ def rng(self): return self._rngs def sample(self, rng=None, tensor=None): - tensor = tensor or Function(self.function_space) + tensor = tensor or Function(self.function_space()) for cov, tsub, rsub in zip(self.subcovariances, tensor.subfunctions, rng or self.rng()): From 249dee905a92b6b0081a08e088cf5f363b4daa93 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Tue, 5 May 2026 16:49:33 +0100 Subject: [PATCH 5/7] Combat Zenodo rate limiting in firedrake-zenodo (#5032) --- firedrake/scripts/firedrake-zenodo | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/firedrake/scripts/firedrake-zenodo b/firedrake/scripts/firedrake-zenodo index e03cabe861..a8d6852fbc 100755 --- a/firedrake/scripts/firedrake-zenodo +++ b/firedrake/scripts/firedrake-zenodo @@ -287,35 +287,27 @@ variable FIREDRAKE_GITHUB_TOKEN to a github personal access token.""") def zenodo_records(): - """Grab all zenodo records of tagged Firedrake component releases. + """Grab the 100 most recent zenodo records of tagged Firedrake component releases. :returns: An iterable of zenodo records. - :raises LookupError: if we were not able find any records.""" - result = None - i = 1 - while True: - if i > 8000//25: - raise RuntimeError("More than 8000 uploads on zenodo?") + :raises LookupError: if we were not able find any records. + + We only load the 100 most recent tags to avoid running into rate-limiter issues + from Zenodo. + + """ + result = [] + for i in range(1, 5): response = requests.get(f"{ZENODO_URL}/records", params={"q": 'owners:19586 OR owners:19587', "all_versions": True, "size": 25, "sort": "mostrecent", "page": i}) if response.status_code == 200: - if result is None: - result = response.json() - else: - tmp = response.json() - result["hits"]["hits"].extend(tmp["hits"]["hits"]) - n = len(result["hits"]["hits"]) - expect = result["hits"]["total"] - if expect == n: - return result["hits"]["hits"] - elif expect < n: - raise LookupError("Have more hits than Zenodo reports in total") - i += 1 + result.extend(response.json()["hits"]["hits"]) else: raise LookupError("Unable to get zenodo records: %s" % response.json()) + return result def zenodo_metarecords(): From 3e3c6f6ff1d827c624a33a140c5f6bd78851ceab Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Wed, 6 May 2026 11:33:36 +0100 Subject: [PATCH 6/7] Refactor firedrake configure (#5073) --- .github/workflows/core.yml | 6 +- docs/Makefile | 15 +- docs/source/install.rst | 11 +- scripts/firedrake-configure | 1286 ++++++++++++++++++++--------------- 4 files changed, 734 insertions(+), 584 deletions(-) diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml index 245e1d1afb..516cd386dc 100644 --- a/.github/workflows/core.yml +++ b/.github/workflows/core.yml @@ -89,7 +89,8 @@ jobs: arch: [default, complex] runs-on: [self-hosted, Linux] container: - image: ubuntu:latest + # TODO: set to 'ubuntu:latest' + image: ubuntu:noble outputs: sdist_conclusion: ${{ steps.report_sdist.outputs.conclusion }} env: @@ -477,7 +478,8 @@ jobs: name: Build and test Firedrake (Linux CUDA) runs-on: [self-hosted, Linux, gpu] container: - image: ubuntu:latest + # TODO: set to 'ubuntu:latest' + image: ubuntu:noble options: --gpus all if: inputs.test_gpu env: diff --git a/docs/Makefile b/docs/Makefile index 409e73fe86..da681bcad0 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -43,7 +43,7 @@ help: TARGETS = html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext -GENERATED_FILES = source/team.rst source/demos source/element_list.csv source/apt_deps.txt source/homebrew_deps.txt source/minimal_apt_deps.txt source/petsc_configure_options.txt +GENERATED_FILES = source/team.rst source/demos source/element_list.csv source/apt_deps.txt source/homebrew_deps.txt source/petsc_configure_options.txt publishpdf: env FIREDRAKE_MANUAL_RELEASE=`date +%Y-%m` $(MAKE) latexpdf @@ -86,26 +86,19 @@ source/team.rst: source/team2.py source/apt_deps.txt: python3 ../scripts/firedrake-configure \ - --package-manager apt-x86_64 \ + --os ubuntu24.04-x86_64 \ --show-system-packages > source/apt_deps.tmp mv source/apt_deps.tmp source/apt_deps.txt source/homebrew_deps.txt: python3 ../scripts/firedrake-configure \ - --package-manager brew-arm64 \ + --os macos-arm64 \ --show-system-packages > source/homebrew_deps.tmp mv source/homebrew_deps.tmp source/homebrew_deps.txt -source/minimal_apt_deps.txt: - python3 ../scripts/firedrake-configure \ - --package-manager apt-x86_64 \ - --show-minimal-system-packages > source/minimal_apt_deps.tmp - mv source/minimal_apt_deps.tmp source/minimal_apt_deps.txt - - source/petsc_configure_options.txt: python3 ../scripts/firedrake-configure \ - --no-package-manager \ + --os unknown \ --show-petsc-configure-options > source/petsc_configure_options.tmp mv source/petsc_configure_options.tmp source/petsc_configure_options.txt diff --git a/docs/source/install.rst b/docs/source/install.rst index a589196516..424880764a 100644 --- a/docs/source/install.rst +++ b/docs/source/install.rst @@ -119,10 +119,9 @@ ensure that these system dependencies are in place. Some of the dependencies downloaded by PETSc ``configure`` by passing additional flags like ``--download-mpich`` or ``--download-openblas`` (run ``./configure --help | less`` to see what is available). To give you a guide as to what system dependencies are -needed, on Ubuntu they are: +needed, on Ubuntu they are:: -.. literalinclude:: minimal_apt_deps.txt - :language: text + build-essential flex gfortran git ninja-build pkg-config python3-dev python3-pip .. _install_petsc: @@ -157,15 +156,15 @@ do the following steps: If you are using one of the :ref:`officially supported distributions` then these configure options will include paths to system packages so PETSc can correctly find and -link against them. If you are not then you should pass the ``--no-package-manager`` +link against them. If you are not then you should pass the ``--os unknown`` flag to obtain a set of configure options where ``firedrake-configure`` pessimistically assumes that no external packages are available, and hence need to be downloaded and compiled from source:: - $ python3 ../firedrake-configure --no-package-manager --show-petsc-configure-options | xargs -L1 ./configure + $ python3 ../firedrake-configure --os unknown --show-petsc-configure-options | xargs -L1 ./configure For the default build, running ``firedrake-configure`` with -``--no-package-manager`` will produce the flags: +``--os unknown`` will produce the flags: .. literalinclude:: petsc_configure_options.txt :language: text diff --git a/scripts/firedrake-configure b/scripts/firedrake-configure index c854cdc5e6..865dd8eed6 100755 --- a/scripts/firedrake-configure +++ b/scripts/firedrake-configure @@ -10,94 +10,111 @@ # As a matter of policy, new package managers and archs should only be added to # this file if they are *tested in CI*. -# Start by checking the Python version, older Pythons may not be able to run -# this script. import sys +import time -def stdout(msg: str, **kwargs) -> None: - """Write to stdout.""" - print(msg, file=sys.stdout, **kwargs) +def print_and_stop(msg: str) -> None: + """Print a message to stdout and stop the script.""" + print(msg, file=sys.stdout, end="") + exit(0) -def stderr(msg: str, **kwargs) -> None: - """Write to stderr. +def error(msg: str): + """Print an error message to stderr and stop the script. To make it distinct in the terminal output, messages are prefixed with 'ERROR: '. """ - print(f"ERROR: {msg}", file=sys.stderr, **kwargs) + print(f"ERROR: {msg}", file=sys.stderr) + # wait so users have a chance to see the message before it gets swamped by stdout + time.sleep(3) + exit(1) -def abort() -> None: - """Terminate the script.""" - exit(1) +def warn(msg: str): + """Print a warning message to stderr. + + To make it distinct in the terminal output, messages are prefixed with 'WARNING: '. + + """ + print(f"WARNING: {msg}", file=sys.stderr) + # wait so users have a chance to see the message before it gets swamped by stdout + time.sleep(1) +# Start by checking the Python version, older Pythons may not be able to run +# this script. MINIMUM_PYTHON_VERSION = "3.10" if sys.version < MINIMUM_PYTHON_VERSION: - stderr(f"Firedrake requires Python {MINIMUM_PYTHON_VERSION} or greater") - abort() + error(f"Firedrake requires Python {MINIMUM_PYTHON_VERSION} or greater") import argparse +import dataclasses import enum import os import platform -import subprocess -from collections.abc import Sequence -from typing import Optional - +from collections.abc import Mapping, Sequence -class PackageManager(enum.Enum): - LINUX_APT_X86_64 = "apt-x86_64" - LINUX_APT_AARCH64 = "apt-aarch64" - MACOS_HOMEBREW_ARM64 = "brew-arm64" - - -LINUX_APT_X86_64 = PackageManager.LINUX_APT_X86_64 -LINUX_APT_AARCH64 = PackageManager.LINUX_APT_AARCH64 -MACOS_HOMEBREW_ARM64 = PackageManager.MACOS_HOMEBREW_ARM64 - - -class GPUArch(enum.Enum): - NO_GPU = "none" - CUDA = "cuda" - - -NO_GPU = GPUArch.NO_GPU -CUDA = GPUArch.CUDA - - -class FiredrakeArch(enum.Enum): - DEFAULT = "default" - COMPLEX = "complex" - - -ARCH_DEFAULT = FiredrakeArch.DEFAULT -ARCH_COMPLEX = FiredrakeArch.COMPLEX +# Attributes that we expect to change occasionally SUPPORTED_PETSC_VERSION = "v3.25.0" # CUDA 13.1 is currently not supported by GPU drivers on Firedrake CI systems. SUPPORTED_CUDA_VERSION = "13.0" -CUDA_ARCH_MAP = { - "aarch64": "sbsa" -} +def main(): + parser = make_parser() + args = parser.parse_args() -# Contains the URL to a deb package that will enable vendor-specific software development -# repositories, or an empty string if none are required. -EXTRA_LINUX_APT_PKG_URL = { - NO_GPU: "", - CUDA: f"https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2404/{CUDA_ARCH_MAP.get(platform.machine(), platform.machine())}/cuda-keyring_1.1-1_all.deb", -} + if args.no_package_manager: + warn( + "Passing '--no-package-manager' is deprecated, please set '--os unknown' instead" + ) + args.os = OS.UNKNOWN + os_ = args.os if args.os else detect_os() + + arch_key = (os_, args.scalar_type, args.gpu_arch) + arch_key_str = f"({', '.join(f'{k.__class__.__name__}: {k.name}' for k in arch_key)})" + if arch_key in OFFICIAL_ARCHS: + arch = OFFICIAL_ARCHS[arch_key] + elif arch_key in COMMUNITY_ARCHS: + arch = COMMUNITY_ARCHS[arch_key] + warn( + "You have selected a community-maintained Firedrake configuration, " + f"if you encounter issues please contact {arch.maintainer}" + ) + else: + error( + f"Firedrake configuration not found for option set '{arch_key_str}', " + "please consider passing '--os unknown' or building manually" + ) + + if args.show_system_packages: + try: + arch.show_system_packages() + except InvalidConfigurationError: + error( + "Don't know what system dependencies to install for this arch " + f"'{arch_key_str}', please install them manually" + ) + elif args.show_petsc_configure_options: + arch.show_petsc_configure_options() + elif args.show_petsc_version: + print_and_stop(SUPPORTED_PETSC_VERSION) + elif args.show_extra_repo_pkg_url: + arch.show_url() + else: + assert args.show_env + arch.show_env() -def main(): +def make_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser( - description="""Print out the configuration options needed to install Firedrake + description="""\ +Print out the configuration options needed to install Firedrake. This script takes in arguments declaring the operating system, target configuration (termed 'ARCH') and install step (installing system packages, @@ -110,585 +127,724 @@ that are used inside PETSc 'configure' instead of building from source. Please see https://firedrakeproject.org/install for more information.""" ) + package_manager_group = parser.add_mutually_exclusive_group() package_manager_group.add_argument( - "--package-manager", - choices=[pm.value for pm in PackageManager], + "--os", + "--package-manager", # old alias + choices=list(OS), + type=OS, required=False, - help="The system package manager, if not provided 'firedrake-configure' " + help="The operating system, if not provided 'firedrake-configure' " "will attempt to guess it.", ) + # old alias for '--os unknown' package_manager_group.add_argument( "--no-package-manager", action="store_true", required=False, - help="Do not attempt to use a system package manager.", + help="(Deprecated) Equivalent to '--os unknown'.", ) + parser.add_argument( - "--arch", - choices=[arch.value for arch in FiredrakeArch], - default=ARCH_DEFAULT, - help="The target configuration to install.", + "--scalar-type", + "--arch", # old alias + choices=list(ScalarType), + type=ScalarType, + default=ScalarType.DEFAULT, + help="PETSc scalar type.", ) parser.add_argument( "--gpu-arch", - choices=[arch.value for arch in GPUArch], + choices=list(GPUPlatform), + type=GPUPlatform, default="none", help=( "Target GPU architecture. WARNING: This is an experimental feature. " "GPU support in Firedrake is currently very limited." ), ) + cmd_group = parser.add_mutually_exclusive_group(required=True) cmd_group.add_argument( "--show-system-packages", - "--sysdeps", # alias action="store_true", help=( "Print out the system packages Firedrake needs including those " "expected by PETSc." ), ) - cmd_group.add_argument( - "--show-minimal-system-packages", - action="store_true", - help=( - "Print out the minimal system packages Firedrake needs to build. " - "The user must install these." - ), - ) cmd_group.add_argument( "--show-petsc-configure-options", - "--petscconf", # alias action="store_true", help="Print out arguments to pass to PETSc configure.", ) cmd_group.add_argument( "--show-petsc-version", - "--petscver", # alias action="store_true", help="Print out the officially supported PETSc version tag.", ) cmd_group.add_argument( "--show-env", - "--env", # alias action="store_true", help="Print out the environment variables that need to be exported to install Firedrake.", ) + # TODO: This is specific to particular archs, probably not the right design + # choice to enable for all. cmd_group.add_argument( "--show-extra-repo-pkg-url", - "--repopkgurl", action="store_true", help="Print out the URL of any package required to enable non-OS repo access for this build", ) - args = parser.parse_args() - if args.package_manager is not None: - assert not args.no_package_manager - package_manager = PackageManager(args.package_manager) - elif args.no_package_manager: - package_manager = None - else: - package_manager = sniff_package_manager() - arch = FiredrakeArch(args.arch) - - gpu_arch = GPUArch(args.gpu_arch) - if gpu_arch != NO_GPU and package_manager == MACOS_HOMEBREW_ARM64: - stderr( - "GPU-compatible PETSc builds are currently only supported" - "on Linux" - ) - abort() - - if args.show_system_packages: - if package_manager is None: - stderr( - "Cannot install Firedrake dependencies without a package manager, " - "please install them manually" - ) - abort() - stdout(" ".join(SYSTEM_PACKAGES[package_manager, arch, gpu_arch]), end="") - elif args.show_minimal_system_packages: - if package_manager is None: - stderr( - "Cannot install Firedrake dependencies without a package manager, " - "please install them manually" - ) - abort() - stdout(" ".join(MINIMAL_SYSTEM_PACKAGES[package_manager]), end="") - elif args.show_petsc_configure_options: - stdout(" ".join(PETSC_CONFIGURE_OPTIONS[package_manager, arch, gpu_arch]), end="") - elif args.show_petsc_version: - stdout(SUPPORTED_PETSC_VERSION, end="") - elif args.show_extra_repo_pkg_url: - stdout(EXTRA_LINUX_APT_PKG_URL[gpu_arch], end="") - else: - assert args.show_env - stdout(" ".join(ENVIRONMENT_VARS[package_manager, arch, gpu_arch]), end="") + return parser -def sniff_package_manager() -> Optional[PackageManager]: +def detect_os() -> "OS": if platform.system() == "Linux": - if has_apt(): - if platform.machine() == "x86_64": - return LINUX_APT_X86_64 - elif platform.machine() == "aarch64": - return LINUX_APT_AARCH64 + try: + os_info = platform.freedesktop_os_release() + except OSError: + pass + else: + if os_info["NAME"] == "Ubuntu": + if os_info["VERSION_ID"] == "24.04": + if platform.machine() == "x86_64": + return OS.UBUNTU_2404_X86_64 + elif platform.machine() == "aarch64": + return OS.UBUNTU_2404_AARCH64 + elif platform.system() == "Darwin": - if has_homebrew(): - if platform.machine() == "arm64": - return MACOS_HOMEBREW_ARM64 - stderr( - "No package manager detected, if you want to configure Firedrake without " - "any system packages please pass '--no-package-manager'" + if platform.machine() == "arm64": + return OS.MACOS_ARM64 + + error( + "Cannot identify operating system, if you want to configure Firedrake " + "without any system packages please pass '--os unknown'" ) - abort() - - -def has_apt() -> bool: - try: - subprocess.run(["apt", "--version"], capture_output=True) - return True - except FileNotFoundError: - return False - except: # noqa: E722 - stderr("Unexpected error occurred searching for apt") - abort() - - -def has_homebrew() -> bool: - try: - subprocess.run(["brew", "--version"], capture_output=True) - return True - except FileNotFoundError: - return False - except: # noqa: E722 - stderr("Unexpected error occurred searching for homebrew") - abort() - - -MINIMAL_LINUX_APT_PACKAGES = ( - "build-essential", - "flex", - "gfortran", - "git", - "ninja-build", - "pkg-config", - "python3-dev", - "python3-pip", -) - -BASE_LINUX_APT_PACKAGES = ( - MINIMAL_LINUX_APT_PACKAGES + ("bison", "cmake", "libopenblas-dev", "libopenmpi-dev") -) - -PETSC_EXTRAS_COMMON_APT_PACKAGES = ( - "libfftw3-dev", - "libfftw3-mpi-dev", - "libhwloc-dev", - "libhdf5-mpi-dev", - "libmumps-ptscotch-dev", - "libmetis-dev", - "libnetcdf-dev", - "libpnetcdf-dev", - "libptscotch-dev", - "libscalapack-openmpi-dev", -) - -PETSC_EXTRAS_LINUX_APT_NOGPU_PACKAGES = PETSC_EXTRAS_COMMON_APT_PACKAGES + ( - "libsuitesparse-dev", - "libsuperlu-dev", - "libsuperlu-dist-dev", -) - -cuda_ver_str = SUPPORTED_CUDA_VERSION.replace(".", "-") - -PETSC_EXTRAS_LINUX_APT_CUDA_PACKAGES = PETSC_EXTRAS_COMMON_APT_PACKAGES + ( - f"cuda-compat-{cuda_ver_str}", - f"cuda-nvtx-{cuda_ver_str}", - f"cuda-cudart-dev-{cuda_ver_str}", - f"cuda-command-line-tools-{cuda_ver_str}", - f"cuda-minimal-build-{cuda_ver_str}", - f"cuda-libraries-dev-{cuda_ver_str}", - f"cuda-nvml-dev-{cuda_ver_str}", - f"libnpp-dev-{cuda_ver_str}", - f"libcusparse-dev-{cuda_ver_str}", - f"libcublas-dev-{cuda_ver_str}", -) - -LINUX_APT_PACKAGES_NOGPU = BASE_LINUX_APT_PACKAGES + PETSC_EXTRAS_LINUX_APT_NOGPU_PACKAGES - -LINUX_APT_PACKAGES_CUDA = BASE_LINUX_APT_PACKAGES + PETSC_EXTRAS_LINUX_APT_CUDA_PACKAGES - -MINIMAL_MACOS_HOMEBREW_PACKAGES = ( - "autoconf", - "automake", - "boost", - "gcc", - "libtool", - "make", - "ninja", - "pkg-config", - "python", -) - -BASE_MACOS_HOMEBREW_PACKAGES = ( - MINIMAL_MACOS_HOMEBREW_PACKAGES + ("cmake", "openblas", "openmpi") -) - -PETSC_EXTRAS_MACOS_HOMEBREW_PACKAGES = ( - "fftw", - "hwloc", - "hdf5-mpi", - "metis", - "pnetcdf", - "scalapack", - "suitesparse", - "zlib", -) - -MACOS_HOMEBREW_PACKAGES = ( - BASE_MACOS_HOMEBREW_PACKAGES + PETSC_EXTRAS_MACOS_HOMEBREW_PACKAGES -) - -MINIMAL_SYSTEM_PACKAGES = { - LINUX_APT_X86_64: MINIMAL_LINUX_APT_PACKAGES, - LINUX_APT_AARCH64: MINIMAL_LINUX_APT_PACKAGES, - MACOS_HOMEBREW_ARM64: MINIMAL_MACOS_HOMEBREW_PACKAGES, -} -SYSTEM_PACKAGES = { - (LINUX_APT_X86_64, ARCH_DEFAULT, NO_GPU): LINUX_APT_PACKAGES_NOGPU, - (LINUX_APT_X86_64, ARCH_COMPLEX, NO_GPU): LINUX_APT_PACKAGES_NOGPU, - (LINUX_APT_AARCH64, ARCH_DEFAULT, NO_GPU): LINUX_APT_PACKAGES_NOGPU, - (LINUX_APT_AARCH64, ARCH_COMPLEX, NO_GPU): LINUX_APT_PACKAGES_NOGPU, - (MACOS_HOMEBREW_ARM64, ARCH_DEFAULT, NO_GPU): MACOS_HOMEBREW_PACKAGES, - (MACOS_HOMEBREW_ARM64, ARCH_COMPLEX, NO_GPU): MACOS_HOMEBREW_PACKAGES, - (LINUX_APT_X86_64, ARCH_DEFAULT, CUDA): LINUX_APT_PACKAGES_CUDA, - (LINUX_APT_AARCH64, ARCH_DEFAULT, CUDA): LINUX_APT_PACKAGES_CUDA, -} -COMMON_PETSC_CONFIGURE_OPTIONS = ( - "--with-c2html=0", - "--with-debugging=0", - "--with-fortran-bindings=0", - "--with-shared-libraries=1", - "--with-strict-petscerrorcode", -) - - -class PetscPackageAction(enum.IntEnum): - PETSC_AUTODETECT = enum.auto() - PETSC_DOWNLOAD = enum.auto() - - -# Placeholder value to use when we want PETSc to autodetect the package -PETSC_AUTODETECT = PetscPackageAction.PETSC_AUTODETECT - -# Placeholder value to use when we want PETSc to download the package -PETSC_DOWNLOAD = PetscPackageAction.PETSC_DOWNLOAD - - -# For each package and architecture there are a number of different types of input: -# 1. PETSC_AUTODETECT - PETSc will be able to find the package itself -# 2. PETSC_DOWNLOAD - a suitable system package is not available, PETSc configure -# will download and install it -# 3. str - the location of the package root directory (containing 'include' and -# 'lib' subdirectories) -# 4. tuple[str, tuple[str, ...]] - a 2-tuple consisting of the includes directory -# (location of the header files) and a collection of library files that PETSc needs. -PetscSpecValueType = PetscPackageAction | str | tuple[str | None, tuple[str, ...]] -PetscSpecsDictType = dict[str, dict[PackageManager, PetscSpecValueType]] - -PETSC_EXTERNAL_PACKAGE_SPECS_COMMON: PetscSpecsDictType = { - "bison": { - LINUX_APT_X86_64: PETSC_AUTODETECT, - LINUX_APT_AARCH64: PETSC_AUTODETECT, - MACOS_HOMEBREW_ARM64: PETSC_DOWNLOAD, - }, - "fftw": { - LINUX_APT_X86_64: PETSC_AUTODETECT, - LINUX_APT_AARCH64: PETSC_AUTODETECT, - MACOS_HOMEBREW_ARM64: "/opt/homebrew", - }, - "hdf5": { - LINUX_APT_X86_64: PETSC_AUTODETECT, - LINUX_APT_AARCH64: PETSC_AUTODETECT, - MACOS_HOMEBREW_ARM64: "/opt/homebrew", - }, - "hwloc": { - LINUX_APT_X86_64: PETSC_AUTODETECT, - LINUX_APT_AARCH64: PETSC_AUTODETECT, - MACOS_HOMEBREW_ARM64: "/opt/homebrew", - }, - "hypre": { - LINUX_APT_X86_64: PETSC_DOWNLOAD, - LINUX_APT_AARCH64: PETSC_DOWNLOAD, - MACOS_HOMEBREW_ARM64: PETSC_DOWNLOAD, - }, - "metis": { - LINUX_APT_X86_64: PETSC_AUTODETECT, - LINUX_APT_AARCH64: PETSC_AUTODETECT, - MACOS_HOMEBREW_ARM64: "/opt/homebrew", - }, - "mumps": { - LINUX_APT_X86_64: PETSC_AUTODETECT, - LINUX_APT_AARCH64: PETSC_AUTODETECT, - MACOS_HOMEBREW_ARM64: PETSC_DOWNLOAD, - }, - "netcdf": { - LINUX_APT_X86_64: PETSC_AUTODETECT, - LINUX_APT_AARCH64: PETSC_AUTODETECT, - MACOS_HOMEBREW_ARM64: PETSC_DOWNLOAD, - }, - "pnetcdf": { - LINUX_APT_X86_64: PETSC_AUTODETECT, - LINUX_APT_AARCH64: PETSC_AUTODETECT, - MACOS_HOMEBREW_ARM64: "/opt/homebrew", - }, - "ptscotch": { - LINUX_APT_X86_64: PETSC_AUTODETECT, - LINUX_APT_AARCH64: PETSC_AUTODETECT, - MACOS_HOMEBREW_ARM64: PETSC_DOWNLOAD, - }, - "scalapack": { - LINUX_APT_X86_64: (None, ("-lscalapack-openmpi",)), - LINUX_APT_AARCH64: (None, ("-lscalapack-openmpi",)), - MACOS_HOMEBREW_ARM64: "/opt/homebrew", - }, - "zlib": { - LINUX_APT_X86_64: PETSC_AUTODETECT, - LINUX_APT_AARCH64: PETSC_AUTODETECT, - MACOS_HOMEBREW_ARM64: "/opt/homebrew/opt/zlib", - }, -} +class OS(enum.Enum): + UBUNTU_2404_X86_64 = "ubuntu24.04-x86_64" + UBUNTU_2404_AARCH64 = "ubuntu24.04-aarch64" + MACOS_ARM64 = "macos-arm64" + UNKNOWN = "unknown" -PETSC_EXTERNAL_PACKAGE_SPECS_NOGPU: PetscSpecsDictType = ( - PETSC_EXTERNAL_PACKAGE_SPECS_COMMON - | { - "suitesparse": { - LINUX_APT_X86_64: PETSC_AUTODETECT, - LINUX_APT_AARCH64: PETSC_AUTODETECT, - MACOS_HOMEBREW_ARM64: "/opt/homebrew", - }, - "superlu_dist": { - LINUX_APT_X86_64: PETSC_AUTODETECT, - LINUX_APT_AARCH64: PETSC_AUTODETECT, - MACOS_HOMEBREW_ARM64: PETSC_DOWNLOAD, - }, - } -) - -PETSC_EXTERNAL_PACKAGE_SPECS_CUDA: PetscSpecsDictType = ( - PETSC_EXTERNAL_PACKAGE_SPECS_COMMON - | { - "suitesparse": { - LINUX_APT_X86_64: PETSC_DOWNLOAD, - LINUX_APT_AARCH64: PETSC_DOWNLOAD, - MACOS_HOMEBREW_ARM64: "/opt/homebrew", - }, - "superlu_dist": { - LINUX_APT_X86_64: PETSC_DOWNLOAD, - LINUX_APT_AARCH64: PETSC_DOWNLOAD, - MACOS_HOMEBREW_ARM64: PETSC_DOWNLOAD, - }, - "umpire": { - LINUX_APT_X86_64: PETSC_DOWNLOAD, - LINUX_APT_AARCH64: PETSC_DOWNLOAD, - MACOS_HOMEBREW_ARM64: PETSC_DOWNLOAD, - }, - } -) - -COMMON_PETSC_EXTERNAL_PACKAGES = ( - "bison", - "fftw", - "hdf5", - "hwloc", - "metis", - "mumps", - "netcdf", - "pnetcdf", - "ptscotch", - "scalapack", - "suitesparse", - "superlu_dist", - "zlib", -) - -PETSC_EXTRA_EXTERNAL_PACKAGES_CUDA = ("umpire",) - - -def prepare_external_package_configure_options( - external_packages: Sequence[str], - package_manager: PackageManager | None = None, - gpu_arch: GPUArch = NO_GPU, -) -> tuple[str, ...]: - configure_options = [] - for external_package in external_packages: - if package_manager is None: - # Don't know anything about the system, download everything - package_spec = PETSC_DOWNLOAD - else: - if gpu_arch == NO_GPU: - package_spec = PETSC_EXTERNAL_PACKAGE_SPECS_NOGPU[external_package][package_manager] - elif gpu_arch == CUDA: - package_spec = PETSC_EXTERNAL_PACKAGE_SPECS_CUDA[external_package][package_manager] - - if package_spec == PETSC_AUTODETECT: - # PETSc will find the package for us - configure_options.append(f"--with-{external_package}") - elif package_spec == PETSC_DOWNLOAD: - # Package not provided by package manager, download it instead - configure_options.append(f"--download-{external_package}") - elif isinstance(package_spec, str): - # Package is installed in a 'sensible' way to /include and - # /lib but PETSc does not know so we have to provide it - configure_options.append(f"--with-{external_package}-dir={package_spec}") - else: - # Package is installed but not findable, have to provide paths - # to headers and libraries - (include_dir, libs) = package_spec - if include_dir is not None: - configure_options.append(f"--with-{external_package}-include={include_dir}") - if len(libs) >= 1: - configure_options.append(f"--with-{external_package}-lib={','.join(libs)}") - return tuple(configure_options) - - -def get_petsc_arch(arch: FiredrakeArch, gpu_arch: GPUArch) -> str: - arr = ["arch", "firedrake", arch.value] - if gpu_arch != NO_GPU: - arr.append(gpu_arch.value) - return "-".join(arr) - - -def prepare_configure_options( - package_manager: Optional[PackageManager], - arch: FiredrakeArch, - gpu_arch: GPUArch, -) -> tuple[str, ...]: - configure_options = list(COMMON_PETSC_CONFIGURE_OPTIONS) - configure_options.append(f"PETSC_ARCH={get_petsc_arch(arch, gpu_arch)}") - - # include/link flags - if package_manager in (LINUX_APT_X86_64, LINUX_APT_AARCH64): - incdir = "/usr/include" - if package_manager == LINUX_APT_X86_64: - libdir = "/usr/lib/x86_64-linux-gnu" - elif package_manager == LINUX_APT_AARCH64: - libdir = "/usr/lib/aarch64-linux-gnu" - - includes = ( - f"{incdir}/hdf5/openmpi", - f"{incdir}/scotch", - ) - if gpu_arch == NO_GPU: - includes = includes + ( - f"{incdir}/superlu", - f"{incdir}/superlu-dist", - ) +class ScalarType(enum.Enum): + DEFAULT = "default" + COMPLEX = "complex" - libraries = ( - f"{libdir}/hdf5/openmpi", - ) - incflags = " " + " ".join([f"-I{inc}" for inc in includes]) - libflags = " " + " ".join([f"-L{lib}" for lib in libraries]) +class GPUPlatform(enum.Enum): + NO_GPU = "none" + CUDA = "cuda" - else: - incflags = "" - libflags = "" - - configure_options.extend([ - f"--COPTFLAGS='-O3 -march=native -mtune=native{incflags}{libflags}'", - f"--CXXOPTFLAGS='-O3 -march=native -mtune=native{incflags}{libflags}'", - ]) - # Fortran flags - if package_manager == MACOS_HOMEBREW_ARM64: - # -march=native and -mtune=native not available for FOPTFLAGS on macOS - configure_options.append(f"--FOPTFLAGS='-O3{incflags}{libflags}'") - # Avoid macos + openmpi + mumps segmentation violation issue; - # see https://github.com/firedrakeproject/firedrake/issues/4102 and https://github.com/firedrakeproject/firedrake/issues/4113. - configure_options.append("-download-mumps-avoid-mpi-in-place") - else: - configure_options.append(f"--FOPTFLAGS='-O3 -march=native -mtune=native{incflags}{libflags}'") - if arch == ARCH_COMPLEX: - configure_options.append("--with-scalar-type=complex") +class InvalidConfigurationError(Exception): + pass - if gpu_arch == CUDA: - configure_options.extend( - ["--with-cuda=1", "--with-openmp=1", "--with-cxx-dialect=c++17"] - ) - external_packages = list(COMMON_PETSC_EXTERNAL_PACKAGES) - if arch != ARCH_COMPLEX: - external_packages.append("hypre") - if gpu_arch == CUDA: - external_packages.extend(PETSC_EXTRA_EXTERNAL_PACKAGES_CUDA) - configure_options.extend( - prepare_external_package_configure_options( - external_packages, package_manager, gpu_arch - ) - ) - return tuple(configure_options) - - -PETSC_VALID_BUILD_COMBINATIONS = ( - (LINUX_APT_X86_64, ARCH_DEFAULT, NO_GPU), - (LINUX_APT_X86_64, ARCH_COMPLEX, NO_GPU), - (LINUX_APT_AARCH64, ARCH_DEFAULT, NO_GPU), - (LINUX_APT_AARCH64, ARCH_COMPLEX, NO_GPU), - (MACOS_HOMEBREW_ARM64, ARCH_DEFAULT, NO_GPU), - (MACOS_HOMEBREW_ARM64, ARCH_COMPLEX, NO_GPU), - (None, ARCH_DEFAULT, NO_GPU), - (None, ARCH_COMPLEX, NO_GPU), - (LINUX_APT_X86_64, ARCH_DEFAULT, CUDA), - (LINUX_APT_AARCH64, ARCH_DEFAULT, CUDA), - (None, ARCH_DEFAULT, CUDA), -) - - -PETSC_CONFIGURE_OPTIONS = { - (package_manager, arch, gpu_arch): prepare_configure_options( - package_manager, arch, gpu_arch - ) - for (package_manager, arch, gpu_arch) in PETSC_VALID_BUILD_COMBINATIONS -} +@dataclasses.dataclass(frozen=True, kw_only=True) +class Arch: + name: str + """Name of the configuration. + This will be prefixed by 'arch-firedrake-' when generating the PETSc arch name. -def prepare_environment_vars( - package_manager: Optional[PackageManager], - arch: FiredrakeArch, - gpu_arch: GPUArch, -) -> tuple[str, ...]: - vars = { - "PETSC_DIR": f"{os.getcwd()}/petsc", - "PETSC_ARCH": get_petsc_arch(arch, gpu_arch), - "HDF5_MPI": "ON", - } - if gpu_arch == CUDA: - vars["PATH"] = f"/usr/local/cuda/bin:{os.environ.get('PATH', '')}" - - if package_manager == MACOS_HOMEBREW_ARM64: - # On macOS h5py cannot find the HDF5 library without help - vars["HDF5_DIR"] = "/opt/homebrew" - elif package_manager is None: - # If package manager is 'None' then we expect PETSc to build HDF5 - vars["HDF5_DIR"] = f"{vars['PETSC_DIR']}/{vars['PETSC_ARCH']}" - - return tuple(f"{var}={value}" for var, value in vars.items()) - - -ENVIRONMENT_VARS = { - (package_manager, arch, gpu_arch): prepare_environment_vars( - package_manager, arch, gpu_arch - ) - for (package_manager, arch, gpu_arch) in PETSC_VALID_BUILD_COMBINATIONS + """ + system_packages: Sequence[str] | None = None + """System packages.""" + extra_petsc_configure_options: Sequence[str] = () + """Extra options passed to PETSc configure.""" + cflags: Sequence[str] = () + """CFLAGS used to compile PETSc and external packages.""" + cxxflags: Sequence[str] = () + """CXXFLAGS used to compile PETSc and external packages.""" + fflags: Sequence[str] = () + """FFLAGS used to compile PETSc and external packages.""" + include_dirs: Sequence[str] = () + """Extra include directories used during PETSc configure and install""" + library_dirs: Sequence[str] = () + """Extra library directories used during PETSc configure and install""" + extra_env_vars: Mapping[str, str] = dataclasses.field(default_factory=dict) + """Extra environment variables that need setting during Firedrake install. + + If the special strings '$PETSC_DIR', '$PETSC_ARCH' or '$PATH' are used then + these will be replaced. + + """ + extra_url: str | None = None + """The URL to a deb package that will enable vendor-specific software development + repositories, or None if not required. + """ + + common_petsc_configure_options = [ + "--with-c2html=0", + "--with-debugging=0", + "--with-fortran-bindings=0", + "--with-shared-libraries=1", + "--with-strict-petscerrorcode", + ] + + def show_system_packages(self) -> None: + if self.system_packages is None: + raise InvalidConfigurationError + else: + print_and_stop(" ".join(self.system_packages)) + + def show_petsc_configure_options(self) -> None: + include_dirs = [f"-I{idir}" for idir in self.include_dirs] + library_dirs = [f"-L{ldir}" for ldir in self.library_dirs] + cflags = " ".join((*self.cflags, *include_dirs, *library_dirs)) + cxxflags = " ".join((*self.cxxflags, *include_dirs, *library_dirs)) + fflags = " ".join((*self.fflags, *include_dirs, *library_dirs)) + opts = [ + f"PETSC_ARCH=arch-firedrake-{self.name}", + f"--COPTFLAGS='{cflags}'", + f"--CXXOPTFLAGS='{cxxflags}'", + f"--FOPTFLAGS='{fflags}'", + *self.common_petsc_configure_options, + *self.extra_petsc_configure_options, + ] + print_and_stop(" ".join(opts)) + + def show_url(self) -> None: + assert self.extra_url + print_and_stop(self.extra_url) + + def show_env(self) -> None: + # TODO: inspect the current directory and error if PETSc isn't found + petsc_dir = f"{os.getcwd()}/petsc" + petsc_arch = f"arch-firedrake-{self.name}" + + env = { + "PETSC_DIR": petsc_dir, + "PETSC_ARCH": petsc_arch, + "HDF5_MPI": "ON", + } + env |= self.extra_env_vars + + replace_mapping = { + "$PETSC_DIR": petsc_dir, + "$PETSC_ARCH": petsc_arch, + "$PATH": os.environ.get("PATH", ""), + } + + def replace_vars(value: str): + for from_, to in replace_mapping.items(): + value = value.replace(from_, to) + return value + + env_str = " ".join(f"{name}={replace_vars(value)}" for name, value in env.items()) + print_and_stop(env_str) + + +@dataclasses.dataclass(frozen=True, kw_only=True) +class CommunityArch(Arch): + maintainer: str + """Name of the person or team maintaining a particular arch.""" + last_modified: str + + +ArchKey = tuple[OS, ScalarType, GPUPlatform] + +# Configurations with official support that are tested in CI +OFFICIAL_ARCHS: Mapping[ArchKey, Arch] = { + + # Ubuntu 24.04 x86 + + (OS.UBUNTU_2404_X86_64, ScalarType.DEFAULT, GPUPlatform.NO_GPU): Arch( + name="default", + system_packages=[ + "bison", + "build-essential", + "cmake", + "flex", + "gfortran", + "git", + "ninja-build", + "pkg-config", + "python3-dev", + "python3-pip", + + # PETSc external packages + "libfftw3-dev", + "libfftw3-mpi-dev", + "libhwloc-dev", + "libhdf5-mpi-dev", + "libmumps-ptscotch-dev", + "libmetis-dev", + "libnetcdf-dev", + "libopenblas-dev", + "libopenmpi-dev", + "libpnetcdf-dev", + "libptscotch-dev", + "libscalapack-openmpi-dev", + "libsuitesparse-dev", + "libsuperlu-dev", + "libsuperlu-dist-dev", + ], + extra_petsc_configure_options=[ + "--with-bison", + "--with-fftw", + "--with-hdf5", + "--with-hwloc", + "--with-metis", + "--with-mumps", + "--with-netcdf", + "--with-pnetcdf", + "--with-ptscotch", + "--with-scalapack-lib=-lscalapack-openmpi", + "--with-suitesparse", + "--with-superlu_dist", + "--with-zlib", + "--download-hypre", + ], + cflags=["-O3", "-march=native", "-mtune=native"], + cxxflags=["-O3", "-march=native", "-mtune=native"], + fflags=["-O3", "-march=native", "-mtune=native"], + include_dirs=[ + "/usr/include/hdf5/openmpi", + "/usr/include/scotch", + "/usr/include/superlu", + "/usr/include/superlu-dist", + ], + library_dirs=[ + "/usr/lib/x86_64-linux-gnu/hdf5/openmpi", + ], + ), + + (OS.UBUNTU_2404_X86_64, ScalarType.DEFAULT, GPUPlatform.CUDA): Arch( + name="default-cuda", + system_packages=[ + "bison", + "build-essential", + "cmake", + "flex", + "gfortran", + "git", + "ninja-build", + "pkg-config", + "python3-dev", + "python3-pip", + + # PETSc external packages + "libfftw3-dev", + "libfftw3-mpi-dev", + "libhwloc-dev", + "libhdf5-mpi-dev", + "libmumps-ptscotch-dev", + "libmetis-dev", + "libnetcdf-dev", + "libopenblas-dev", + "libopenmpi-dev", + "libpnetcdf-dev", + "libptscotch-dev", + "libscalapack-openmpi-dev", + + # CUDA packages + f"cuda-compat-{SUPPORTED_CUDA_VERSION.replace('.', '-')}", + f"cuda-nvtx-{SUPPORTED_CUDA_VERSION.replace('.', '-')}", + f"cuda-cudart-dev-{SUPPORTED_CUDA_VERSION.replace('.', '-')}", + f"cuda-command-line-tools-{SUPPORTED_CUDA_VERSION.replace('.', '-')}", + f"cuda-minimal-build-{SUPPORTED_CUDA_VERSION.replace('.', '-')}", + f"cuda-libraries-dev-{SUPPORTED_CUDA_VERSION.replace('.', '-')}", + f"cuda-nvml-dev-{SUPPORTED_CUDA_VERSION.replace('.', '-')}", + f"libnpp-dev-{SUPPORTED_CUDA_VERSION.replace('.', '-')}", + f"libcusparse-dev-{SUPPORTED_CUDA_VERSION.replace('.', '-')}", + f"libcublas-dev-{SUPPORTED_CUDA_VERSION.replace('.', '-')}", + ], + extra_petsc_configure_options=[ + "--with-bison", + "--with-fftw", + "--with-hdf5", + "--with-hwloc", + "--with-metis", + "--with-mumps", + "--with-netcdf", + "--with-pnetcdf", + "--with-ptscotch", + "--with-scalapack-lib=-lscalapack-openmpi", + "--with-zlib", + "--download-hypre", + "--download-suitesparse", + "--download-superlu_dist", + + # CUDA options + "--with-cuda=1", + "--with-openmp=1", + "--with-cxx-dialect=c++17", + "--download-umpire", + ], + cflags=["-O3", "-march=native", "-mtune=native"], + cxxflags=["-O3", "-march=native", "-mtune=native"], + fflags=["-O3", "-march=native", "-mtune=native"], + include_dirs=[ + "/usr/include/hdf5/openmpi", + "/usr/include/scotch", + "/usr/include/superlu", + "/usr/include/superlu-dist", + ], + library_dirs=[ + "/usr/lib/x86_64-linux-gnu/hdf5/openmpi", + ], + extra_env_vars={ + "PATH": "/usr/local/cuda/bin:$PATH", + }, + extra_url="https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2404/x86_64/cuda-keyring_1.1-1_all.deb", + ), + + (OS.UBUNTU_2404_X86_64, ScalarType.COMPLEX, GPUPlatform.NO_GPU): Arch( + name="complex", + system_packages=[ + "bison", + "build-essential", + "cmake", + "flex", + "gfortran", + "git", + "ninja-build", + "pkg-config", + "python3-dev", + "python3-pip", + + # PETSc external packages + "libfftw3-dev", + "libfftw3-mpi-dev", + "libhwloc-dev", + "libhdf5-mpi-dev", + "libmumps-ptscotch-dev", + "libmetis-dev", + "libnetcdf-dev", + "libopenblas-dev", + "libopenmpi-dev", + "libpnetcdf-dev", + "libptscotch-dev", + "libscalapack-openmpi-dev", + "libsuitesparse-dev", + "libsuperlu-dev", + "libsuperlu-dist-dev", + ], + extra_petsc_configure_options=[ + "--with-scalar-type=complex", + "--with-bison", + "--with-fftw", + "--with-hdf5", + "--with-hwloc", + "--with-metis", + "--with-mumps", + "--with-netcdf", + "--with-pnetcdf", + "--with-ptscotch", + "--with-scalapack-lib=-lscalapack-openmpi", + "--with-suitesparse", + "--with-superlu_dist", + "--with-zlib", + ], + cflags=["-O3", "-march=native", "-mtune=native"], + cxxflags=["-O3", "-march=native", "-mtune=native"], + fflags=["-O3", "-march=native", "-mtune=native"], + include_dirs=[ + "/usr/include/hdf5/openmpi", + "/usr/include/scotch", + "/usr/include/superlu", + "/usr/include/superlu-dist", + ], + library_dirs=[ + "/usr/lib/x86_64-linux-gnu/hdf5/openmpi", + ], + ), + + # Ubuntu 24.04 aarch64 + + (OS.UBUNTU_2404_AARCH64, ScalarType.DEFAULT, GPUPlatform.NO_GPU): Arch( + name="default", + system_packages=[ + "bison", + "build-essential", + "cmake", + "flex", + "gfortran", + "git", + "ninja-build", + "pkg-config", + "python3-dev", + "python3-pip", + + # PETSc external packages + "libfftw3-dev", + "libfftw3-mpi-dev", + "libhwloc-dev", + "libhdf5-mpi-dev", + "libmumps-ptscotch-dev", + "libmetis-dev", + "libnetcdf-dev", + "libopenblas-dev", + "libopenmpi-dev", + "libpnetcdf-dev", + "libptscotch-dev", + "libscalapack-openmpi-dev", + "libsuitesparse-dev", + "libsuperlu-dev", + "libsuperlu-dist-dev", + ], + extra_petsc_configure_options=[ + "--with-bison", + "--with-fftw", + "--with-hdf5", + "--with-hwloc", + "--with-metis", + "--with-mumps", + "--with-netcdf", + "--with-pnetcdf", + "--with-ptscotch", + "--with-scalapack-lib=-lscalapack-openmpi", + "--with-suitesparse", + "--with-superlu_dist", + "--with-zlib", + "--download-hypre", + ], + cflags=["-O3", "-march=native", "-mtune=native"], + cxxflags=["-O3", "-march=native", "-mtune=native"], + fflags=["-O3", "-march=native", "-mtune=native"], + include_dirs=[ + "/usr/include/hdf5/openmpi", + "/usr/include/scotch", + "/usr/include/superlu", + "/usr/include/superlu-dist", + ], + library_dirs=[ + "/usr/lib/aarch64-linux-gnu/hdf5/openmpi", + ], + ), + + (OS.UBUNTU_2404_AARCH64, ScalarType.COMPLEX, GPUPlatform.NO_GPU): Arch( + name="complex", + system_packages=[ + "bison", + "build-essential", + "cmake", + "flex", + "gfortran", + "git", + "ninja-build", + "pkg-config", + "python3-dev", + "python3-pip", + + # PETSc external packages + "libfftw3-dev", + "libfftw3-mpi-dev", + "libhwloc-dev", + "libhdf5-mpi-dev", + "libmumps-ptscotch-dev", + "libmetis-dev", + "libnetcdf-dev", + "libopenblas-dev", + "libopenmpi-dev", + "libpnetcdf-dev", + "libptscotch-dev", + "libscalapack-openmpi-dev", + "libsuitesparse-dev", + "libsuperlu-dev", + "libsuperlu-dist-dev", + ], + extra_petsc_configure_options=[ + "--with-scalar-type=complex", + "--with-bison", + "--with-fftw", + "--with-hdf5", + "--with-hwloc", + "--with-metis", + "--with-mumps", + "--with-netcdf", + "--with-pnetcdf", + "--with-ptscotch", + "--with-scalapack-lib=-lscalapack-openmpi", + "--with-suitesparse", + "--with-superlu_dist", + "--with-zlib", + ], + cflags=["-O3", "-march=native", "-mtune=native"], + cxxflags=["-O3", "-march=native", "-mtune=native"], + fflags=["-O3", "-march=native", "-mtune=native"], + include_dirs=[ + "/usr/include/hdf5/openmpi", + "/usr/include/scotch", + "/usr/include/superlu", + "/usr/include/superlu-dist", + ], + library_dirs=[ + "/usr/lib/aarch64-linux-gnu/hdf5/openmpi", + ], + ), + + # macOS arm64 + + (OS.MACOS_ARM64, ScalarType.DEFAULT, GPUPlatform.NO_GPU): Arch( + name="default", + system_packages=[ + "autoconf", + "automake", + "boost", + "gcc", + "libtool", + "make", + "ninja", + "pkg-config", + "python", + + # PETSc external packages + "cmake", + "fftw", + "hwloc", + "hdf5-mpi", + "metis", + "openblas", + "openmpi", + "pnetcdf", + "scalapack", + "suitesparse", + "zlib", + ], + extra_petsc_configure_options=[ + # External packages + "--with-fftw-dir=/opt/homebrew", + "--with-hdf5-dir=/opt/homebrew", + "--with-hwloc-dir=/opt/homebrew", + "--with-metis-dir=/opt/homebrew", + "--with-pnetcdf-dir=/opt/homebrew", + "--with-scalapack-dir=/opt/homebrew", + "--with-suitesparse-dir=/opt/homebrew", + "--with-zlib-dir=/opt/homebrew", + "--download-bison", + "--download-hypre", + "--download-mumps", + "--download-netcdf", + "--download-ptscotch", + "--download-superlu_dist", + + # Avoid macos + openmpi + mumps segmentation violation issue; + # see https://github.com/firedrakeproject/firedrake/issues/4102 and https://github.com/firedrakeproject/firedrake/issues/4113. + "-download-mumps-avoid-mpi-in-place", + ], + cflags=["-O3", "-march=native", "-mtune=native"], + cxxflags=["-O3", "-march=native", "-mtune=native"], + fflags=["-O3"], + extra_env_vars={"HDF5_DIR": "/opt/homebrew"}, + ), + + (OS.MACOS_ARM64, ScalarType.COMPLEX, GPUPlatform.NO_GPU): Arch( + name="complex", + system_packages=[ + "autoconf", + "automake", + "boost", + "gcc", + "libtool", + "make", + "ninja", + "pkg-config", + "python", + + # PETSc external packages + "cmake", + "fftw", + "hwloc", + "hdf5-mpi", + "metis", + "openblas", + "openmpi", + "pnetcdf", + "scalapack", + "suitesparse", + "zlib", + ], + extra_petsc_configure_options=[ + "--with-scalar-type=complex", + + # External packages + "--with-fftw-dir=/opt/homebrew", + "--with-hdf5-dir=/opt/homebrew", + "--with-hwloc-dir=/opt/homebrew", + "--with-metis-dir=/opt/homebrew", + "--with-pnetcdf-dir=/opt/homebrew", + "--with-scalapack-dir=/opt/homebrew", + "--with-suitesparse-dir=/opt/homebrew", + "--with-zlib-dir=/opt/homebrew", + "--download-bison", + "--download-mumps", + "--download-netcdf", + "--download-ptscotch", + "--download-superlu_dist", + + # Avoid macos + openmpi + mumps segmentation violation issue; + # see https://github.com/firedrakeproject/firedrake/issues/4102 and https://github.com/firedrakeproject/firedrake/issues/4113. + "-download-mumps-avoid-mpi-in-place", + ], + cflags=["-O3", "-march=native", "-mtune=native"], + cxxflags=["-O3", "-march=native", "-mtune=native"], + fflags=["-O3"], + extra_env_vars={"HDF5_DIR": "/opt/homebrew"}, + ), + + # Sane default configurations for unknown platforms + + (OS.UNKNOWN, ScalarType.DEFAULT, GPUPlatform.NO_GPU): Arch( + name="default", + extra_petsc_configure_options=[ + "--download-bison", + "--download-fftw", + "--download-hdf5", + "--download-hwloc", + "--download-hypre", + "--download-metis", + "--download-mumps", + "--download-netcdf", + "--download-pnetcdf", + "--download-ptscotch", + "--download-scalapack", + "--download-suitesparse", + "--download-superlu_dist", + "--download-zlib", + ], + cflags=["-O3", "-march=native", "-mtune=native"], + cxxflags=["-O3", "-march=native", "-mtune=native"], + fflags=["-O3", "-march=native", "-mtune=native"], + extra_env_vars={"HDF5_DIR": "$PETSC_DIR/$PETSC_ARCH"}, + ), + + (OS.UNKNOWN, ScalarType.COMPLEX, GPUPlatform.NO_GPU): Arch( + name="complex", + extra_petsc_configure_options=[ + "--with-scalar-type=complex", + "--download-bison", + "--download-fftw", + "--download-hdf5", + "--download-hwloc", + "--download-hypre", + "--download-metis", + "--download-mumps", + "--download-netcdf", + "--download-pnetcdf", + "--download-ptscotch", + "--download-scalapack", + "--download-suitesparse", + "--download-superlu_dist", + "--download-zlib", + ], + cflags=["-O3", "-march=native", "-mtune=native"], + cxxflags=["-O3", "-march=native", "-mtune=native"], + fflags=["-O3", "-march=native", "-mtune=native"], + extra_env_vars={"HDF5_DIR": "$PETSC_DIR/$PETSC_ARCH"}, + ), } +# 'Best effort' configurations that are not tested in CI +COMMUNITY_ARCHS: Mapping[ArchKey, CommunityArch] = {} + + if __name__ == "__main__": main() From 23df95616b638bf9015878461ef1d3cbfda771f9 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Wed, 6 May 2026 16:38:37 +0100 Subject: [PATCH 7/7] AssembledPC: fix sub_mat_type (#5079) * AssembledPC: fix sub_mat_type * get default (sub_)mat_type from parent _SNESContext * Use the assembler from _SNESContext --- firedrake/preconditioners/assembled.py | 14 +++++++------- firedrake/solving_utils.py | 20 +++++++++++++------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/firedrake/preconditioners/assembled.py b/firedrake/preconditioners/assembled.py index d0e225e813..a0234048d5 100644 --- a/firedrake/preconditioners/assembled.py +++ b/firedrake/preconditioners/assembled.py @@ -17,7 +17,6 @@ class AssembledPC(PCBase): _prefix = "assembled_" def initialize(self, pc): - from firedrake.assemble import get_assembler A, P = pc.getOperators() if pc.type != "python": @@ -45,7 +44,13 @@ def initialize(self, pc): (a, bcs) = self.form(pc, test, trial) - form_assembler = get_assembler(a, bcs=bcs, form_compiler_parameters=fcp, mat_type=mat_type, options_prefix=options_prefix) + self._ctx_ref = self.new_snes_ctx(opc, a, bcs, mat_type, + fcp=fcp, + sub_mat_type=sub_mat_type, + options_prefix=options_prefix) + + form_assembler = self._ctx_ref._assembler_jac + self.P = form_assembler.allocate() self._assemble_P = form_assembler.assemble self._assemble_P(tensor=self.P) @@ -62,11 +67,6 @@ def initialize(self, pc): # We set a DM and an appropriate SNESContext on the constructed PC so one # can do e.g. multigrid or patch solves. dm = opc.getDM() - self._ctx_ref = self.new_snes_ctx(opc, a, bcs, mat_type, - fcp=fcp, - sub_mat_type=sub_mat_type, - options_prefix=options_prefix) - pc.setDM(dm) pc.setOptionsPrefix(options_prefix) pc.setOperators(A, self.P.petscmat) diff --git a/firedrake/solving_utils.py b/firedrake/solving_utils.py index 5efecc2e20..b1090d1111 100644 --- a/firedrake/solving_utils.py +++ b/firedrake/solving_utils.py @@ -279,15 +279,21 @@ def __init__(self, problem, def reconstruct(self, problem=None, mat_type=None, pmat_type=None, **kwargs): """Reconstruct this _SNESContext instance with new arguments.""" - problem = problem or self.problem + problem = problem or self._problem mat_type = mat_type or self.mat_type pmat_type = pmat_type or self.pmat_type - kwargs.setdefault("sub_mat_type", self.sub_mat_type) - kwargs.setdefault("sub_pmat_type", self.sub_pmat_type) - kwargs.setdefault("appctx", self.appctx) - kwargs.setdefault("options_prefix", self.options_prefix) - kwargs.setdefault("transfer_manager", self.transfer_manager) - kwargs.setdefault("pre_apply_bcs", self.pre_apply_bcs) + + default_options = { + "sub_mat_type": self.sub_mat_type, + "sub_pmat_type": self.sub_pmat_type, + "appctx": self.appctx, + "options_prefix": self.options_prefix, + "transfer_manager": self.transfer_manager, + "pre_apply_bcs": self.pre_apply_bcs, + } + for k, v in default_options.items(): + if kwargs.get(k) is None: + kwargs[k] = v return _SNESContext(problem, mat_type, pmat_type, **kwargs) @property