IRKsome 2026.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- irksome/__init__.py +122 -0
- irksome/backend.py +129 -0
- irksome/backends/__init__.py +0 -0
- irksome/backends/dolfinx.py +184 -0
- irksome/backends/firedrake.py +139 -0
- irksome/base_time_stepper.py +252 -0
- irksome/bcs.py +52 -0
- irksome/constant.py +21 -0
- irksome/dirk_stepper.py +224 -0
- irksome/discontinuous_galerkin_stepper.py +309 -0
- irksome/explicit_stepper.py +21 -0
- irksome/galerkin_stepper.py +440 -0
- irksome/imex.py +647 -0
- irksome/integrated_lagrange.py +39 -0
- irksome/labeling.py +174 -0
- irksome/multistep.py +182 -0
- irksome/nystrom_dirk_stepper.py +281 -0
- irksome/nystrom_stepper.py +222 -0
- irksome/pc.py +170 -0
- irksome/scheme.py +186 -0
- irksome/stage_derivative.py +433 -0
- irksome/stage_value.py +344 -0
- irksome/stepper.py +236 -0
- irksome/tableaux/ButcherTableaux.py +261 -0
- irksome/tableaux/ars_dirk_imex_tableaux.py +111 -0
- irksome/tableaux/dirk_imex_tableaux.py +58 -0
- irksome/tableaux/multistep_tableaux.py +126 -0
- irksome/tableaux/pep_explicit_rk.py +70 -0
- irksome/tableaux/sspk_tableau.py +85 -0
- irksome/tableaux/wso_dirk_tableaux.py +183 -0
- irksome/tools.py +171 -0
- irksome/ufl/__init__.py +0 -0
- irksome/ufl/deriv.py +176 -0
- irksome/ufl/estimate_degrees.py +194 -0
- irksome/ufl/lag.py +12 -0
- irksome/ufl/manipulation.py +277 -0
- irksome-2026.0.0.dist-info/METADATA +54 -0
- irksome-2026.0.0.dist-info/RECORD +41 -0
- irksome-2026.0.0.dist-info/WHEEL +5 -0
- irksome-2026.0.0.dist-info/licenses/LICENSE +13 -0
- irksome-2026.0.0.dist-info/top_level.txt +1 -0
irksome/__init__.py
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
from .tableaux.ButcherTableaux import (
|
|
2
|
+
Alexander,
|
|
3
|
+
BackwardEuler,
|
|
4
|
+
GaussLegendre,
|
|
5
|
+
LobattoIIIA,
|
|
6
|
+
LobattoIIIC,
|
|
7
|
+
PareschiRusso,
|
|
8
|
+
QinZhang,
|
|
9
|
+
RadauIIA,
|
|
10
|
+
)
|
|
11
|
+
from .tableaux.multistep_tableaux import (
|
|
12
|
+
BDF,
|
|
13
|
+
AdamsBashforth,
|
|
14
|
+
AdamsMoulton,
|
|
15
|
+
)
|
|
16
|
+
from .tableaux.pep_explicit_rk import PEPRK
|
|
17
|
+
from .ufl.deriv import Dt, expand_time_derivatives, check_irksome_import_order
|
|
18
|
+
from .ufl.lag import lag
|
|
19
|
+
|
|
20
|
+
check_irksome_import_order()
|
|
21
|
+
|
|
22
|
+
from .tableaux.dirk_imex_tableaux import DIRK_IMEX
|
|
23
|
+
from .tableaux.ars_dirk_imex_tableaux import ARS_DIRK_IMEX
|
|
24
|
+
from .tableaux.sspk_tableau import SSPK_DIRK_IMEX, SSPButcherTableau
|
|
25
|
+
from .tableaux.multistep_tableaux import MultistepTableau
|
|
26
|
+
|
|
27
|
+
from .constant import MeshConstant
|
|
28
|
+
from .tableaux.wso_dirk_tableaux import WSODIRK
|
|
29
|
+
from .scheme import create_time_quadrature
|
|
30
|
+
from .scheme import ContinuousPetrovGalerkinScheme, DiscontinuousGalerkinScheme
|
|
31
|
+
from .scheme import GalerkinCollocationScheme, DiscontinuousGalerkinCollocationScheme
|
|
32
|
+
from .bcs import BoundsConstrainedDirichletBC
|
|
33
|
+
from .dirk_stepper import DIRKTimeStepper
|
|
34
|
+
from .imex import RadauIIAIMEXMethod, DIRKIMEXMethod
|
|
35
|
+
from .stage_derivative import getForm
|
|
36
|
+
from .nystrom_dirk_stepper import DIRKNystromTimeStepper, ExplicitNystromTimeStepper
|
|
37
|
+
from .nystrom_stepper import (
|
|
38
|
+
StageDerivativeNystromTimeStepper,
|
|
39
|
+
ClassicNystrom4Tableau,
|
|
40
|
+
)
|
|
41
|
+
from .stage_value import StageValueTimeStepper
|
|
42
|
+
|
|
43
|
+
from .multistep import MultistepTimeStepper
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
__all__ = [
|
|
47
|
+
"AdamsBashforth",
|
|
48
|
+
"AdamsMoulton",
|
|
49
|
+
"Alexander",
|
|
50
|
+
"ARS_DIRK_IMEX",
|
|
51
|
+
"BackwardEuler",
|
|
52
|
+
"BDF",
|
|
53
|
+
"create_time_quadrature",
|
|
54
|
+
"DIRK_IMEX",
|
|
55
|
+
"DiscontinuousGalerkinCollocationScheme",
|
|
56
|
+
"Dt",
|
|
57
|
+
"expand_time_derivatives",
|
|
58
|
+
"GalerkinCollocationScheme",
|
|
59
|
+
"GaussLegendre",
|
|
60
|
+
"lag",
|
|
61
|
+
"LobattoIIIA",
|
|
62
|
+
"LobattoIIIC",
|
|
63
|
+
"MeshConstant",
|
|
64
|
+
"MultistepTableau",
|
|
65
|
+
"PareschiRusso",
|
|
66
|
+
"PEPRK",
|
|
67
|
+
"QinZhang",
|
|
68
|
+
"RadauIIA",
|
|
69
|
+
"SSPButcherTableau",
|
|
70
|
+
"SSPK_DIRK_IMEX",
|
|
71
|
+
"WSODIRK",
|
|
72
|
+
"DIRKTimeStepper",
|
|
73
|
+
"BoundsConstrainedDirichletBC",
|
|
74
|
+
"getForm",
|
|
75
|
+
"RadauIIAIMEXMethod",
|
|
76
|
+
"DIRKIMEXMethod",
|
|
77
|
+
"DIRKNystromTimeStepper",
|
|
78
|
+
"ExplicitNystromTimeStepper",
|
|
79
|
+
"StageDerivativeNystromTimeStepper",
|
|
80
|
+
"ClassicNystrom4Tableau",
|
|
81
|
+
"StageValueTimeStepper",
|
|
82
|
+
"ContinuousPetrovGalerkinTimeStepper",
|
|
83
|
+
"DiscontinuousGalerkinTimeStepper",
|
|
84
|
+
"MultistepTimeStepper",
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
import importlib
|
|
90
|
+
|
|
91
|
+
importlib.import_module("firedrake")
|
|
92
|
+
from .labeling import TimeQuadratureLabel
|
|
93
|
+
from .discontinuous_galerkin_stepper import DiscontinuousGalerkinTimeStepper
|
|
94
|
+
from .galerkin_stepper import ContinuousPetrovGalerkinTimeStepper
|
|
95
|
+
|
|
96
|
+
from .pc import (
|
|
97
|
+
ClinesBase,
|
|
98
|
+
ClinesLD,
|
|
99
|
+
NystromAuxiliaryOperatorPC,
|
|
100
|
+
RanaBase,
|
|
101
|
+
RanaDU,
|
|
102
|
+
RanaLD,
|
|
103
|
+
IRKAuxiliaryOperatorPC,
|
|
104
|
+
)
|
|
105
|
+
from .stepper import TimeStepper
|
|
106
|
+
|
|
107
|
+
__all__ += [
|
|
108
|
+
"TimeQuadratureLabel",
|
|
109
|
+
"DiscontinuousGalerkinScheme",
|
|
110
|
+
"ContinuousPetrovGalerkinScheme",
|
|
111
|
+
"ClinesBase",
|
|
112
|
+
"ClinesLD",
|
|
113
|
+
"NystromAuxiliaryOperatorPC",
|
|
114
|
+
"RanaBase",
|
|
115
|
+
"RanaDU",
|
|
116
|
+
"RanaLD",
|
|
117
|
+
"IRKAuxiliaryOperatorPC",
|
|
118
|
+
"TimeStepper",
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
except ModuleNotFoundError:
|
|
122
|
+
pass
|
irksome/backend.py
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
from typing import Protocol, Any, Sequence
|
|
2
|
+
import ufl
|
|
3
|
+
from importlib import import_module
|
|
4
|
+
import types
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Backend(Protocol):
|
|
8
|
+
def get_function_space(self, V: ufl.Coefficient) -> ufl.FunctionSpace:
|
|
9
|
+
"""Get a function space from the backend"""
|
|
10
|
+
|
|
11
|
+
def extract_bcs(bcs: Any) -> tuple[Any]:
|
|
12
|
+
"""Extract boundary conditions"""
|
|
13
|
+
|
|
14
|
+
class Function:
|
|
15
|
+
...
|
|
16
|
+
|
|
17
|
+
class DirichletBC:
|
|
18
|
+
...
|
|
19
|
+
|
|
20
|
+
def get_stages(self, V: ufl.FunctionSpace, num_stages: int) -> ufl.Coefficient:
|
|
21
|
+
"""
|
|
22
|
+
Given a function space for a single time-step, get a duplicate of this space,
|
|
23
|
+
repeated `num_stages` times.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
V: Space for single step
|
|
27
|
+
num_stages: Number of stages
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
A coefficient in the new function space
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
class Constant:
|
|
34
|
+
"""MeshLess constant class"""
|
|
35
|
+
|
|
36
|
+
class MeshConstant:
|
|
37
|
+
def __init__(self, msh: ufl.Mesh):
|
|
38
|
+
"""Initialize a mesh constant over a domain"""
|
|
39
|
+
|
|
40
|
+
def Constant(self, val: float = 0.0):
|
|
41
|
+
"""Generate a constant in the backend language with a specific value"""
|
|
42
|
+
|
|
43
|
+
def ConstantOrZero(
|
|
44
|
+
x: float | complex, MC: MeshConstant | None = None
|
|
45
|
+
) -> ufl.core.expr.Expr:
|
|
46
|
+
"""
|
|
47
|
+
Create a constant with backend class if MeshConstant is not supplied.
|
|
48
|
+
Create UFL zero if `x` is sufficiently small
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def get_mesh_constant(MC: MeshConstant | None) -> ufl.core.expr.Expr:
|
|
52
|
+
"""Get a backend class to construct a mesh constant from"""
|
|
53
|
+
|
|
54
|
+
def TestFunction(space: ufl.FunctionSpace, part: int | None = None) -> ufl.Argument:
|
|
55
|
+
"""Return a test-function that can be used by forms in the backend."""
|
|
56
|
+
|
|
57
|
+
def TrialFunction(
|
|
58
|
+
space: ufl.FunctionSpace, part: int | None = None
|
|
59
|
+
) -> ufl.Argument:
|
|
60
|
+
"""Return a trial-function that can be used by forms in the backend."""
|
|
61
|
+
|
|
62
|
+
def create_variational_problem(
|
|
63
|
+
F: ufl.Form,
|
|
64
|
+
u: ufl.Coefficient,
|
|
65
|
+
bcs: DirichletBC | Sequence | None = None,
|
|
66
|
+
**kwargs,
|
|
67
|
+
) -> Any:
|
|
68
|
+
"""Create a variational problem in the backend language."""
|
|
69
|
+
|
|
70
|
+
def create_variational_solver(
|
|
71
|
+
problem: Any,
|
|
72
|
+
solver_parameters: dict | None = None,
|
|
73
|
+
**kwargs,
|
|
74
|
+
):
|
|
75
|
+
"""Create a variational solver in the backend language."""
|
|
76
|
+
|
|
77
|
+
def get_stage_spaces(V: ufl.FunctionSpace, num_stages: int) -> ufl.FunctionSpace:
|
|
78
|
+
"""Create a stage space with M number of components."""
|
|
79
|
+
|
|
80
|
+
def norm(
|
|
81
|
+
v: ufl.core.expr.Expr, norm_type: str = "L2", mesh: ufl.Mesh | None = None
|
|
82
|
+
) -> float:
|
|
83
|
+
"""Compute the norm of a function in the backend language."""
|
|
84
|
+
|
|
85
|
+
def assemble(expr: ufl.core.expr.Expr) -> Any:
|
|
86
|
+
"""Assemble a UFL expression in the backend language."""
|
|
87
|
+
|
|
88
|
+
def derivative(
|
|
89
|
+
form: ufl.Form,
|
|
90
|
+
u: ufl.Coefficient,
|
|
91
|
+
du: ufl.Argument | None = None,
|
|
92
|
+
coefficient_derivatives: dict | None = None,
|
|
93
|
+
) -> ufl.Form:
|
|
94
|
+
"""Compute the derivative of a form with respect to a coefficient in the backend language."""
|
|
95
|
+
|
|
96
|
+
def invalidate_jacobian(solver: Any):
|
|
97
|
+
"""Invalidate the Jacobian matrix in the backend language."""
|
|
98
|
+
|
|
99
|
+
class EquationBCSplit:
|
|
100
|
+
...
|
|
101
|
+
|
|
102
|
+
class EquationBC:
|
|
103
|
+
...
|
|
104
|
+
|
|
105
|
+
def create_bounds_constrained_bc(V, g, sub_domain, bounds, solver_parameters=None) -> DirichletBC:
|
|
106
|
+
...
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def get_backend(backend: str | types.ModuleType) -> Backend:
|
|
110
|
+
"""Get backend class from backend name.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
backend: Name of the backend to get
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
Backend class
|
|
117
|
+
"""
|
|
118
|
+
if isinstance(backend, types.ModuleType):
|
|
119
|
+
return backend
|
|
120
|
+
if backend == "firedrake":
|
|
121
|
+
from .backends import firedrake as fd_backend
|
|
122
|
+
|
|
123
|
+
return fd_backend
|
|
124
|
+
elif backend == "dolfinx":
|
|
125
|
+
from .backends import dolfinx as dx_backend
|
|
126
|
+
|
|
127
|
+
return dx_backend
|
|
128
|
+
else:
|
|
129
|
+
return import_module(backend)
|
|
File without changes
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""DOLFINx backend for Irksome"""
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
from mpi4py import MPI
|
|
5
|
+
from petsc4py import PETSc
|
|
6
|
+
import basix.ufl
|
|
7
|
+
import dolfinx.fem.petsc
|
|
8
|
+
import ufl
|
|
9
|
+
import typing
|
|
10
|
+
import numpy as np
|
|
11
|
+
|
|
12
|
+
def get_stage_space(V: ufl.FunctionSpace, num_stages: int) -> ufl.FunctionSpace:
|
|
13
|
+
if num_stages == 1:
|
|
14
|
+
me = V.ufl_elemet()
|
|
15
|
+
else:
|
|
16
|
+
el = V.ufl_element()
|
|
17
|
+
if el.num_sub_elements > 0:
|
|
18
|
+
me = basix.ufl.mixed_element(
|
|
19
|
+
np.tile(el.sub_elements, num_stages).tolist()
|
|
20
|
+
)
|
|
21
|
+
else:
|
|
22
|
+
me = basix.ufl.blocked_element(el, shape=(num_stages,))
|
|
23
|
+
return dolfinx.fem.functionspace(V.mesh, me)
|
|
24
|
+
|
|
25
|
+
def extract_bcs(bcs: typing.Any) -> tuple[typing.Any]:
|
|
26
|
+
"""Extract boundary conditions"""
|
|
27
|
+
return bcs
|
|
28
|
+
|
|
29
|
+
def create_variational_problem(F, u, bcs=None, aP=None, **kwargs):
|
|
30
|
+
"""Create a variational problem."""
|
|
31
|
+
if len(F.arguments()) == 2:
|
|
32
|
+
a, L = ufl.system(F)
|
|
33
|
+
return dolfinx.fem.petsc.LinearProblem(
|
|
34
|
+
a,
|
|
35
|
+
L,
|
|
36
|
+
u,
|
|
37
|
+
bcs=bcs,
|
|
38
|
+
petsc_options_prefix="IrkSomeLinearSolver",
|
|
39
|
+
P=aP,
|
|
40
|
+
**kwargs,
|
|
41
|
+
)
|
|
42
|
+
else:
|
|
43
|
+
return dolfinx.fem.petsc.NonlinearProblem(
|
|
44
|
+
F,
|
|
45
|
+
u,
|
|
46
|
+
petsc_options_prefix="IrkSomeNonlinearSolver",
|
|
47
|
+
bcs=bcs,
|
|
48
|
+
petsc_options=kwargs.get("solver_parameters"),
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
def create_variational_solver(
|
|
52
|
+
problem: dolfinx.fem.petsc.LinearProblem | dolfinx.fem.petsc.NonlinearProblem,
|
|
53
|
+
**kwargs,
|
|
54
|
+
):
|
|
55
|
+
"""Create a variational solver that uses PETSc SNES or KSP."""
|
|
56
|
+
solver_parameters = kwargs.get("solver_parameters", {})
|
|
57
|
+
solver = problem.solver
|
|
58
|
+
solver_prefix = problem.solver.getOptionsPrefix()
|
|
59
|
+
opts = PETSc.Options(
|
|
60
|
+
)
|
|
61
|
+
opts.prefixPush(solver_prefix)
|
|
62
|
+
for k, v in solver_parameters.items():
|
|
63
|
+
opts.setValue(k, v)
|
|
64
|
+
solver.setFromOptions()
|
|
65
|
+
opts.prefixPop()
|
|
66
|
+
# For some strange reason delValue doesn't respect prefixes
|
|
67
|
+
for k, v in solver_parameters.items():
|
|
68
|
+
opts.delValue(f"{solver_prefix}{k}")
|
|
69
|
+
return problem
|
|
70
|
+
|
|
71
|
+
def get_function_space(u: ufl.Coefficient) -> ufl.FunctionSpace:
|
|
72
|
+
return u.ufl_function_space()
|
|
73
|
+
|
|
74
|
+
def get_stages(V: dolfinx.fem.FunctionSpace, num_stages: int) -> ufl.Coefficient:
|
|
75
|
+
"""
|
|
76
|
+
Given a function space for a single time-step, get a duplicate of this space,
|
|
77
|
+
repeated `num_stages` times.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
V: Space for single step
|
|
81
|
+
num_stages: Number of stages
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
A coefficient in the new function space
|
|
85
|
+
"""
|
|
86
|
+
if V.num_sub_spaces == 0:
|
|
87
|
+
el = basix.ufl.mixed_element([V.ufl_element()] * num_stages)
|
|
88
|
+
else:
|
|
89
|
+
el = basix.ufl.mixed_element(V.ufl_element().sub_elements * num_stages)
|
|
90
|
+
Vbig = dolfinx.fem.functionspace(V.mesh, el)
|
|
91
|
+
return dolfinx.fem.Function(Vbig)
|
|
92
|
+
|
|
93
|
+
class MeshConstant(object):
|
|
94
|
+
def __init__(self, msh):
|
|
95
|
+
self.msh = msh
|
|
96
|
+
try:
|
|
97
|
+
import basix.ufl
|
|
98
|
+
|
|
99
|
+
r_el = basix.ufl.real_element(
|
|
100
|
+
msh.basix_cell(), value_shape=(), dtype=dolfinx.default_scalar_type
|
|
101
|
+
)
|
|
102
|
+
self.V = dolfinx.fem.functionspace(msh, r_el)
|
|
103
|
+
except TypeError:
|
|
104
|
+
try:
|
|
105
|
+
import scifem
|
|
106
|
+
except ModuleNotFoundError:
|
|
107
|
+
raise RuntimeError(
|
|
108
|
+
"DOLFINx with real element support or Scifem is required to make mesh-constants"
|
|
109
|
+
)
|
|
110
|
+
self.V = scifem.create_real_functionspace(msh, ())
|
|
111
|
+
|
|
112
|
+
def Constant(self, val=0.0) -> ufl.Coefficient:
|
|
113
|
+
v = dolfinx.fem.Function(self.V)
|
|
114
|
+
v.value = val
|
|
115
|
+
return v
|
|
116
|
+
|
|
117
|
+
def get_mesh_constant(MC: MeshConstant | None) -> ufl.core.expr.Expr:
|
|
118
|
+
return MC.Constant if MC is not None else ufl.constantvalue.ComplexValue
|
|
119
|
+
|
|
120
|
+
class DirichletBC(dolfinx.fem.DirichletBC):
|
|
121
|
+
pass
|
|
122
|
+
|
|
123
|
+
def norm(
|
|
124
|
+
v: ufl.core.expr.Expr, norm_type: str = "L2", mesh: ufl.Mesh | None = None
|
|
125
|
+
) -> float:
|
|
126
|
+
"""Compute the norm of a function in the backend language."""
|
|
127
|
+
if mesh is not None:
|
|
128
|
+
dx = ufl.Mesure("dx", domain=mesh)
|
|
129
|
+
else:
|
|
130
|
+
dx = ufl.dx
|
|
131
|
+
p = 2
|
|
132
|
+
if norm_type.startswith("L"):
|
|
133
|
+
p = int(norm_type[1:])
|
|
134
|
+
if p < 1:
|
|
135
|
+
raise ValueError(f"Invalid norm type {norm_type}")
|
|
136
|
+
expr = ufl.inner(v, v) ** (p / 2)
|
|
137
|
+
form = dolfinx.fem.form(expr * dx)
|
|
138
|
+
else:
|
|
139
|
+
raise NotImplementedError(f"Norm type {norm_type} not implemented")
|
|
140
|
+
norm_loc = dolfinx.fem.assemble_scalar(form)
|
|
141
|
+
return form.mesh.comm.Allreduce(MPI.IN_PLACE, norm_loc, op=MPI.SUM) ** (1 / p)
|
|
142
|
+
|
|
143
|
+
def assemble(expr: ufl.core.expr.Expr | float):
|
|
144
|
+
"""Assemble a UFL expression in the backend language."""
|
|
145
|
+
if isinstance(expr, float):
|
|
146
|
+
return float
|
|
147
|
+
else:
|
|
148
|
+
form = dolfinx.fem.form(expr)
|
|
149
|
+
if form.rank == 0:
|
|
150
|
+
return dolfinx.fem.assemble_scalar(form)
|
|
151
|
+
elif form.rank == 1:
|
|
152
|
+
return dolfinx.fem.assemble_vector(form)
|
|
153
|
+
elif form.rank == 2:
|
|
154
|
+
return dolfinx.fem.assemble_matrix(form)
|
|
155
|
+
else:
|
|
156
|
+
raise ValueError(f"Cannot assemble form of rank {form.rank}")
|
|
157
|
+
|
|
158
|
+
derivative = ufl.derivative
|
|
159
|
+
TrialFunction = ufl.TrialFunction
|
|
160
|
+
Function = dolfinx.fem.Function
|
|
161
|
+
TestFunction = ufl.TestFunction
|
|
162
|
+
|
|
163
|
+
class Constant(ufl.constantvalue.ScalarValue):
|
|
164
|
+
# NOTE: If dolfinx ever get's meshless constants we should change this
|
|
165
|
+
def assign(self, value):
|
|
166
|
+
self._value = value
|
|
167
|
+
|
|
168
|
+
class EquationBCSplit:
|
|
169
|
+
def __init__(self, *args, **kwargs):
|
|
170
|
+
raise NotImplementedError("DOLFINx does not support EquationBCSplit")
|
|
171
|
+
|
|
172
|
+
class EquationBC:
|
|
173
|
+
def __init__(self, *args, **kwargs):
|
|
174
|
+
raise NotImplementedError("DOLFINx does not support EquationBC")
|
|
175
|
+
|
|
176
|
+
def invalidate_jacobian(solver: dolfinx.fem.petsc.LinearProblem):
|
|
177
|
+
"""Invalidate the Jacobian matrix in the backend language."""
|
|
178
|
+
raise RuntimeError("DOLFINx does not support Jacobian invalidation")
|
|
179
|
+
|
|
180
|
+
def create_bounds_constrained_bc(V, g, sub_domain, bounds, solver_parameters=None):
|
|
181
|
+
raise NotImplementedError("Bounds-constrained BCs are not implemented for DOLFINx")
|
|
182
|
+
|
|
183
|
+
except ModuleNotFoundError:
|
|
184
|
+
pass
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""Firedrake backend for Irksome"""
|
|
2
|
+
|
|
3
|
+
import firedrake
|
|
4
|
+
import ufl
|
|
5
|
+
import typing
|
|
6
|
+
from functools import reduce
|
|
7
|
+
from operator import mul
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
assemble = firedrake.assemble
|
|
11
|
+
derivative = firedrake.derivative
|
|
12
|
+
norm = firedrake.norm
|
|
13
|
+
Function = firedrake.Function
|
|
14
|
+
TestFunction = firedrake.TestFunction
|
|
15
|
+
TrialFunction = firedrake.TrialFunction
|
|
16
|
+
DirichletBC = firedrake.DirichletBC
|
|
17
|
+
EquationBC = firedrake.bcs.EquationBC
|
|
18
|
+
EquationBCSplit = firedrake.bcs.EquationBCSplit
|
|
19
|
+
Constant = firedrake.Constant
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_stage_space(V: ufl.FunctionSpace, num_stages: int) -> ufl.FunctionSpace:
|
|
23
|
+
# NOTE: Should use firedrake.MixedFunctionSpace, but there is a bug for vector spaces.
|
|
24
|
+
return reduce(mul, (V for _ in range(num_stages)))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def extract_bcs(bcs: typing.Any) -> tuple[typing.Any]:
|
|
28
|
+
"""Return an iterable of boundary conditions on the residual form"""
|
|
29
|
+
return tuple(bc.extract_form("F") for bc in firedrake.solving._extract_bcs(bcs))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_function_space(u: ufl.Coefficient) -> firedrake.FunctionSpace:
|
|
33
|
+
return u.function_space()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_stages(V: firedrake.FunctionSpace, num_stages: int) -> firedrake.Function:
|
|
37
|
+
"""
|
|
38
|
+
Given a function space for a single time-step, get a duplicate of this space,
|
|
39
|
+
repeated `num_stages` times.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
V: Space for single step
|
|
43
|
+
num_stages: Number of stages
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
A coefficient in the new function space
|
|
47
|
+
"""
|
|
48
|
+
Vbig = get_stage_space(V, num_stages)
|
|
49
|
+
return firedrake.Function(Vbig)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class MeshConstant(object):
|
|
53
|
+
def __init__(self, msh: ufl.Mesh):
|
|
54
|
+
self.msh = ufl.domain.as_domain(msh)
|
|
55
|
+
self.V = firedrake.FunctionSpace(self.msh, "R", 0)
|
|
56
|
+
|
|
57
|
+
def Constant(self, val=0.0) -> ufl.Coefficient:
|
|
58
|
+
return firedrake.Function(self.V).assign(val)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def get_mesh_constant(MC: MeshConstant | None):
|
|
62
|
+
return MC.Constant if MC else firedrake.Constant
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def create_variational_problem(F, u, bcs=None, J=None, Jp=None, **kwargs):
|
|
66
|
+
if len(F.arguments()) == 2:
|
|
67
|
+
a, L = ufl.system(F)
|
|
68
|
+
kwargs.pop("is_linear", None)
|
|
69
|
+
problem = firedrake.LinearVariationalProblem(a, L, u, bcs=bcs, aP=Jp, **kwargs)
|
|
70
|
+
else:
|
|
71
|
+
constant_jacobian = kwargs.pop("constant_jacobian", False)
|
|
72
|
+
problem = firedrake.NonlinearVariationalProblem(F, u, bcs=bcs, J=J, Jp=Jp, **kwargs)
|
|
73
|
+
if constant_jacobian:
|
|
74
|
+
problem._constant_jacobian = constant_jacobian
|
|
75
|
+
return problem
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def create_variational_solver(problem, **kwargs):
|
|
79
|
+
if isinstance(problem, firedrake.LinearVariationalProblem):
|
|
80
|
+
return firedrake.LinearVariationalSolver(problem, **kwargs)
|
|
81
|
+
else:
|
|
82
|
+
return firedrake.NonlinearVariationalSolver(problem, **kwargs)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def invalidate_jacobian(solver):
|
|
86
|
+
return firedrake.LinearVariationalSolver.invalidate_jacobian(solver)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class BoundsConstrainedDirichletBC(firedrake.DirichletBC):
|
|
90
|
+
def __init__(
|
|
91
|
+
self,
|
|
92
|
+
V,
|
|
93
|
+
g,
|
|
94
|
+
sub_domain,
|
|
95
|
+
bounds,
|
|
96
|
+
solver_parameters=None,
|
|
97
|
+
):
|
|
98
|
+
self.g = g
|
|
99
|
+
self.solver_parameters = solver_parameters
|
|
100
|
+
self.bounds = bounds
|
|
101
|
+
self.gnew = Function(V)
|
|
102
|
+
|
|
103
|
+
F = ufl.inner(self.gnew - g, TestFunction(V)) * ufl.dx
|
|
104
|
+
|
|
105
|
+
if solver_parameters is None:
|
|
106
|
+
solver_parameters = {
|
|
107
|
+
"snes_type": "vinewtonrsls",
|
|
108
|
+
"snes_max_it": 300,
|
|
109
|
+
"snes_atol": 1.0e-8,
|
|
110
|
+
"ksp_type": "preonly",
|
|
111
|
+
"mat_type": "aij",
|
|
112
|
+
}
|
|
113
|
+
problem = create_variational_problem(F, self.gnew)
|
|
114
|
+
self.solver = create_variational_solver(
|
|
115
|
+
problem, solver_parameters=solver_parameters
|
|
116
|
+
)
|
|
117
|
+
super().__init__(V, g, sub_domain)
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def function_arg(self):
|
|
121
|
+
"""The value of this boundary condition."""
|
|
122
|
+
self.solver.solve(bounds=self.bounds)
|
|
123
|
+
return self.gnew
|
|
124
|
+
|
|
125
|
+
@function_arg.setter
|
|
126
|
+
def function_arg(self, g):
|
|
127
|
+
"""Set the value of this boundary condition."""
|
|
128
|
+
self.solver.solve(bounds=self.bounds)
|
|
129
|
+
return self.gnew
|
|
130
|
+
|
|
131
|
+
def reconstruct(self, V=None, g=None, sub_domain=None):
|
|
132
|
+
V = V or self.function_space()
|
|
133
|
+
g = g or self.g
|
|
134
|
+
sub_domain = sub_domain or self.sub_domain
|
|
135
|
+
return type(self)(V, g, sub_domain, self.bounds, self.solver_parameters)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def create_bounds_constrained_bc(V, g, sub_domain, bounds, solver_parameters=None):
|
|
139
|
+
return BoundsConstrainedDirichletBC(V, g, sub_domain, bounds, solver_parameters=solver_parameters)
|