Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions docs/notebooks/08-composable-solvers.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@

# %%
import matplotlib.pyplot as plt
import petsctools

# %%
from firedrake import *
Expand Down Expand Up @@ -89,7 +90,7 @@
# Since we're going to look at a bunch of different solver options, let's have a function that builds a solver with the provided options.

# %%
def create_solver(solver_parameters, *, pmat=None, appctx=None):
def create_solver(solver_parameters, *, pmat=None):
p = {}
if solver_parameters is not None:
p.update(solver_parameters)
Expand All @@ -98,7 +99,7 @@ def create_solver(solver_parameters, *, pmat=None, appctx=None):
p.setdefault("ksp_rtol", 1e-7)
problem = NonlinearVariationalProblem(F, w, bcs=bcs, Jp=pmat)
solver = NonlinearVariationalSolver(problem, nullspace=nullspace, options_prefix="",
solver_parameters=p, appctx=appctx)
solver_parameters=p)
return solver


Expand Down Expand Up @@ -300,8 +301,9 @@ class MassMatrix(AuxiliaryOperatorPC):
def form(self, pc, test, trial):
# Extract the original form and bcs
a, bcs = super().form(pc, test, trial)
# Grab the definition of nu from the user application context (a dict)
nu = self.get_appctx(pc)["nu"]
# Grab the definition of nu from the options database
prefix = pc.getOptionsPrefix() or ""
nu = petsctools.Options(prefix)["nu"]
return (-1/nu * test*trial*dx, bcs)


Expand All @@ -321,15 +323,15 @@ def form(self, pc, test, trial):
"fieldsplit_1": {
"ksp_type": "preonly",
"pc_type": "python",
"pc_python_type": "__main__.MassMatrix",
"pc_python_type": f"{__name__}.MassMatrix",
"mass_pc_type": "lu",
"mass_nu": nu,
}
}

# %%
appctx = {"nu": nu} # arbitrary user data that is available inside the user PC object
w.zero()
solver = create_solver(mass_parameters, appctx=appctx)
solver = create_solver(mass_parameters)
solver.solve()
convergence(solver)

Expand Down Expand Up @@ -363,16 +365,16 @@ def form(self, pc, test, trial):
"pc_type": "python",
"pc_python_type": f"{__name__}.MassMatrix",
"mass_pc_type": "sor",
"mass_nu": nu,
}
}

# %% [markdown]
# Now, when the solver runs, PETSc will call back in to Firedrake for restriction and prolongation, as well as rediscretising $A$ on the coarser levels.

# %%
appctx = {"nu": nu} # arbitrary user data that is available inside the user PC object
w.zero()
solver = create_solver(fieldsplit_mg_parameters, appctx=appctx)
solver = create_solver(fieldsplit_mg_parameters)
solver.solve()
convergence(solver)

Expand Down
4 changes: 2 additions & 2 deletions firedrake/preconditioners/assembled.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ def initialize(self, pc):
if pc.type != "python":
raise ValueError("Expecting PC type python")
opc = pc
appctx = self.get_appctx(pc)
fcp = appctx.get("form_compiler_parameters")
prefix = pc.getOptionsPrefix() or ""
fcp = dmhooks.get_appctx(pc.getDM()).get_python_option(prefix, "form_compiler_parameters", None)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We store a handle to these on both the snescontext and in the appctx. It's essentially global data so we should just get it from the snescontext.

Suggested change
fcp = dmhooks.get_appctx(pc.getDM()).get_python_option(prefix, "form_compiler_parameters", None)
fcp = dmhooks.get_appctx(pc.getDM()).fcp


V = get_function_space(pc.getDM()).collapse()
test = TestFunction(V)
Expand Down
10 changes: 5 additions & 5 deletions firedrake/preconditioners/massinv.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from firedrake.preconditioners.assembled import AssembledPC
from firedrake import inner, dx
from firedrake.dmhooks import get_appctx
from firedrake.preconditioners.assembled import AssembledPC

__all__ = ("MassInvPC", )

Expand All @@ -18,17 +19,16 @@ class MassInvPC(AssembledPC):
For Stokes problems, to be spectrally equivalent to the Schur
complement, the mass matrix should be weighted by the viscosity.
This can be provided (defaulting to constant viscosity) by
providing a field defining the viscosity in the application
context, keyed on ``"mu"``.
providing a field defining the viscosity, keyed on ``"mu"``.
"""

_prefix = "Mp_"

def form(self, pc, test, trial):
_, bcs = super(MassInvPC, self).form(pc)

appctx = self.get_appctx(pc)
mu = appctx.get("mu", 1.0)
prefix = pc.getOptionsPrefix() or ""
mu = get_appctx(pc.getDM()).get_python_option(prefix, "mu", 1.0)
a = inner((1/mu) * trial, test) * dx
return a, bcs

Expand Down
12 changes: 7 additions & 5 deletions firedrake/preconditioners/patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -658,7 +658,7 @@ def select_entity(p, dm=None, exclude=None):
return dm.getLabelValue(exclude, p) == -1


class PlaneSmoother(object):
class PlaneSmoother:
@staticmethod
def coords(dm, p, coordinates):
coordinatesV = coordinates.function_space()
Expand Down Expand Up @@ -759,9 +759,10 @@ def __call__(self, pc):
axis = int(sweep_split[0])
except ValueError:
try:
axis = context.appctx[sweep_split[0]]
axis = context.get_python_option(prefix, sweep_split[0])
except KeyError:
raise KeyError("PlaneSmoother axis key %s not provided" % sweep_split[0])
breakpoint()
raise KeyError(f"PlaneSmoother axis key {sweep_split[0]} not provided")

dir = {'+': +1, '-': -1}[sweep_split[1]]
# Either use equispaced bins for relaxation or get from appctx
Expand All @@ -770,10 +771,11 @@ def __call__(self, pc):
entities = self.sort_entities(dm, axis, dir, ndiv=ndiv)
except ValueError:
try:
divisions = context.appctx[sweep_split[2]]
divisions = context.get_python_option(prefix, sweep_split[2])
entities = self.sort_entities(dm, axis, dir, divisions=divisions)
except KeyError:
raise KeyError("PlaneSmoother division key %s not provided" % sweep_split[2:])
breakpoint()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
breakpoint()

raise KeyError(f"PlaneSmoother division key {sweep_split[2:]} not provided")

for patch in entities:
if not patch:
Expand Down
57 changes: 51 additions & 6 deletions firedrake/solving_utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import warnings
from itertools import chain
from typing import Any

import numpy
import petsctools
import ufl

from pyop2 import op2
Expand Down Expand Up @@ -131,7 +134,11 @@ def check_snes_convergence(snes):
%s""" % (snes.getIterationNumber(), msg))


class _SNESContext(object):
_missing = object()
"""Sentinel value used as a default for when 'None' is potentially meaningful."""


class _SNESContext:
"""Context holding information for SNES callbacks.

Parameters
Expand All @@ -153,7 +160,7 @@ class _SNESContext(object):
Indicates the matrix type for the sparse blocks in the preconditioner
if pmat_type='nest', ignored otherwise.
appctx
Any extra information used in the assembler. For the
(Deprecated) Any extra information used in the assembler. For the
matrix-free case this will contain the Newton state in ``"state"``.
pre_jacobian_callback
User-defined function called immediately before Jacobian assembly.
Expand Down Expand Up @@ -226,7 +233,7 @@ def __init__(self, problem,
appctx.setdefault("state", self._x)
appctx.setdefault("form_compiler_parameters", self.fcp)

self.appctx = appctx
self._appctx = appctx
self.matfree = matfree
self.pmatfree = pmatfree
self.F = problem.F
Expand Down Expand Up @@ -277,6 +284,44 @@ def __init__(self, problem,
self._coefficient_mapping = None
self._transfer_manager = transfer_manager

@property
def appctx(self) -> dict:
# Raise a 'DeprecationWarning' here instead of a 'FutureWarning' because
# this in an internal detail, not user facing
warnings.warn(
"'appctx' is now deprecated. Pass Python objects into the "
"PETSc options directly.",
DeprecationWarning,
)
return self._appctx

def get_python_option(
self,
prefix: str,
option: str,
default: Any = _missing,
) -> Any:
opts = petsctools.Options(prefix)
try:
value = opts[option]
except KeyError:
try:
value = self._appctx[option]
except KeyError:
if default is not _missing:
value = default
else:
raise KeyError
else:
warnings.warn(
"Passing Python objects to preconditioners via the 'appctx' kwarg "
"is now deprecated. Pass the objects into the PETSc options "
"directly instead.",
FutureWarning,
)

return value

def reconstruct(self, problem=None, mat_type=None, pmat_type=None, **kwargs):
"""Reconstruct this _SNESContext instance with new arguments."""
problem = problem or self._problem
Expand All @@ -286,7 +331,7 @@ def reconstruct(self, problem=None, mat_type=None, pmat_type=None, **kwargs):
default_options = {
"sub_mat_type": self.sub_mat_type,
"sub_pmat_type": self.sub_pmat_type,
"appctx": self.appctx,
"appctx": self._appctx,
"options_prefix": self.options_prefix,
"transfer_manager": self.transfer_manager,
"pre_apply_bcs": self.pre_apply_bcs,
Expand Down Expand Up @@ -572,7 +617,7 @@ def _assembler_jac(self):
from firedrake.assemble import get_assembler
return get_assembler(self.J, bcs=self.bcs_J, form_compiler_parameters=self.fcp,
mat_type=self.mat_type, sub_mat_type=self.sub_mat_type,
options_prefix=self.options_prefix, appctx=self.appctx)
options_prefix=self.options_prefix, appctx=self._appctx)

@cached_property
def _jac(self):
Expand All @@ -592,7 +637,7 @@ def _assembler_pjac(self):
if self.mat_type != self.pmat_type or self._problem.Jp is not None:
return get_assembler(self.Jp, bcs=self.bcs_Jp, form_compiler_parameters=self.fcp,
mat_type=self.pmat_type, sub_mat_type=self.sub_pmat_type,
options_prefix=self.options_prefix, appctx=self.appctx)
options_prefix=self.options_prefix, appctx=self._appctx)
else:
return self._assembler_jac

Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ dependencies = [
"packaging",
# TODO RELEASE
# "petsc4py==3.25.0",
"petsctools>=2026.0",
# UNDO ME
"petsctools @ git+https://github.com/firedrakeproject/petsctools.git@connorjward/noappctx",
"pkgconfig",
"progress",
"pyadjoint-ad>=2026.4.0",
Expand Down
3 changes: 2 additions & 1 deletion requirements-build.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ mpi4py>3; python_version >= '3.13'
mpi4py; python_version < '3.13'
numpy
pkgconfig
petsctools
# UNDO ME
petsctools @ git+https://github.com/firedrakeproject/petsctools.git@connorjward/noappctx
pybind11
setuptools>=77.0.3

Expand Down
5 changes: 3 additions & 2 deletions tests/firedrake/regression/test_nullspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ def test_nullspace_mixed_multiple_components():
assert schur_ksp.getIterationNumber() < 6


@pytest.mark.parallel(nprocs=2)
@pytest.mark.parallel(2)
@pytest.mark.parametrize("aux_pc", [False, True], ids=["PC(mu)", "PC(DG0-mu)"])
@pytest.mark.parametrize("rhs", ["form_rhs", "cofunc_rhs"])
def test_near_nullspace_mixed(aux_pc, rhs):
Expand Down Expand Up @@ -369,14 +369,15 @@ def test_near_nullspace_mixed(aux_pc, rhs):
'Mp_pc_type': 'ksp',
'Mp_ksp_ksp_type': 'cg',
'Mp_ksp_pc_type': 'sor',
'Mp_mu': mu0,
'ksp_rtol': '1e-5',
'ksp_monitor': None,
}
}

problem = LinearVariationalProblem(a, L, w, bcs=bcs, aP=aP)
solver = LinearVariationalSolver(
problem, appctx={'mu': mu0},
problem,
nullspace=pressure_nullspace,
transpose_nullspace=pressure_nullspace,
near_nullspace=near_nullmodes_W,
Expand Down
Loading
Loading