Skip to content

Commit 5ac6441

Browse files
Firedrake coupling (#513)
* Added coupling of pySDC with Firedrake. So far only simple heat equation example with short tutorial. * Pipeline fix * Implemented @tlunet's suggestion * Linting
1 parent c472a48 commit 5ac6441

File tree

12 files changed

+804
-11
lines changed

12 files changed

+804
-11
lines changed

.github/workflows/ci_pipeline.yml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,58 @@ jobs:
169169
path: |
170170
data_libpressio
171171
coverage_libpressio.dat
172+
173+
user_firedrake_tests:
174+
runs-on: ubuntu-latest
175+
container:
176+
image: firedrakeproject/firedrake-vanilla:latest
177+
options: --user root
178+
volumes:
179+
- ${{ github.workspace }}:/repositories
180+
defaults:
181+
run:
182+
shell: bash -l {0}
183+
steps:
184+
- name: Checkout pySDC
185+
uses: actions/checkout@v4
186+
with:
187+
path: ./pySDC
188+
- name: Checkout gusto
189+
uses: actions/checkout@v4
190+
with:
191+
repository: firedrakeproject/gusto
192+
path: ./gusto_repo
193+
- name: Install pySDC
194+
run: |
195+
. /home/firedrake/firedrake/bin/activate
196+
python -m pip install --no-deps -e /repositories/pySDC
197+
python -m pip install qmat
198+
- name: Install gusto
199+
run: |
200+
. /home/firedrake/firedrake/bin/activate
201+
python -m pip install -e /repositories/gusto_repo
202+
- name: run pytest
203+
run: |
204+
. /home/firedrake/firedrake/bin/activate
205+
firedrake-clean
206+
cd ./pySDC
207+
coverage run -m pytest --continue-on-collection-errors -v --durations=0 /repositories/pySDC/pySDC/tests -m firedrake
208+
timeout-minutes: 120
209+
- name: Make coverage report
210+
run: |
211+
. /home/firedrake/firedrake/bin/activate
212+
213+
cd ./pySDC
214+
mv data ../data_firedrake
215+
coverage combine
216+
mv .coverage ../coverage_firedrake.dat
217+
- name: Upload artifacts
218+
uses: actions/upload-artifact@v4
219+
with:
220+
name: test-artifacts-firedrake
221+
path: |
222+
data_firedrake
223+
coverage_firedrake.dat
172224
173225
user_monodomain_tests_linux:
174226
runs-on: ubuntu-latest
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
from mpi4py import MPI
2+
import firedrake as fd
3+
import numpy as np
4+
5+
6+
class FiredrakeEnsembleCommunicator:
7+
"""
8+
Ensemble communicator for performing multiple similar distributed simulations with Firedrake, see https://www.firedrakeproject.org/firedrake/parallelism.html
9+
This is intended to do space-time parallelism in pySDC.
10+
This class wraps the time communicator. All requests that are not overloaded are passed to the time communicator. For instance, `ensemble.rank` will return the rank in the time communicator.
11+
Some operations are overloaded to use the interface of the MPI communicator but handles communication with the ensemble communicator instead.
12+
"""
13+
14+
def __init__(self, comm, space_size):
15+
"""
16+
Args:
17+
comm (MPI.Intracomm): MPI communicator, which will be split into time and space communicators
18+
space_size (int): Size of the spatial communicators
19+
20+
Attributes:
21+
ensemble (firedrake.Ensemble): Ensemble communicator
22+
"""
23+
self.ensemble = fd.Ensemble(comm, space_size)
24+
25+
@property
26+
def space_comm(self):
27+
return self.ensemble.comm
28+
29+
@property
30+
def time_comm(self):
31+
return self.ensemble.ensemble_comm
32+
33+
def __getattr__(self, name):
34+
return getattr(self.time_comm, name)
35+
36+
def Reduce(self, sendbuf, recvbuf, op=MPI.SUM, root=0):
37+
if type(sendbuf) in [np.ndarray]:
38+
self.ensemble.ensemble_comm.Reduce(sendbuf, recvbuf, op, root)
39+
else:
40+
assert op == MPI.SUM
41+
self.ensemble.reduce(sendbuf, recvbuf, root=root)
42+
43+
def Allreduce(self, sendbuf, recvbuf, op=MPI.SUM):
44+
if type(sendbuf) in [np.ndarray]:
45+
self.ensemble.ensemble_comm.Allreduce(sendbuf, recvbuf, op)
46+
else:
47+
assert op == MPI.SUM
48+
self.ensemble.allreduce(sendbuf, recvbuf)
49+
50+
def Bcast(self, buf, root=0):
51+
if type(buf) in [np.ndarray]:
52+
self.ensemble.ensemble_comm.Bcast(buf, root)
53+
else:
54+
self.ensemble.bcast(buf, root=root)
55+
56+
57+
def get_ensemble(comm, space_size):
58+
return fd.Ensemble(comm, space_size)

pySDC/implementations/datatype_classes/fenics_mesh.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ def __rmul__(self, other):
8080
Args:
8181
other (float): factor
8282
Raises:
83-
DataError: is other is not a float
83+
DataError: if other is not a float
8484
Returns:
8585
fenics_mesh: copy of original values scaled by factor
8686
"""
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import firedrake as fd
2+
3+
from pySDC.core.errors import DataError
4+
5+
6+
class firedrake_mesh(object):
7+
"""
8+
Wrapper for firedrake function data.
9+
10+
Attributes:
11+
functionspace (firedrake.Function): firedrake data
12+
"""
13+
14+
def __init__(self, init, val=0.0):
15+
if fd.functionspaceimpl.WithGeometry in type(init).__mro__:
16+
self.functionspace = fd.Function(init)
17+
self.functionspace.assign(val)
18+
elif fd.Function in type(init).__mro__:
19+
self.functionspace = fd.Function(init)
20+
elif type(init) == firedrake_mesh:
21+
self.functionspace = init.functionspace.copy(deepcopy=True)
22+
else:
23+
raise DataError('something went wrong during %s initialization' % type(init))
24+
25+
def __getattr__(self, key):
26+
return getattr(self.functionspace, key)
27+
28+
@property
29+
def asnumpy(self):
30+
"""
31+
Get a numpy array of the values associated with this data
32+
"""
33+
return self.functionspace.dat._numpy_data
34+
35+
def __add__(self, other):
36+
if isinstance(other, type(self)):
37+
me = firedrake_mesh(other)
38+
me.functionspace.assign(self.functionspace + other.functionspace)
39+
return me
40+
else:
41+
raise DataError("Type error: cannot add %s to %s" % (type(other), type(self)))
42+
43+
def __sub__(self, other):
44+
if isinstance(other, type(self)):
45+
me = firedrake_mesh(other)
46+
me.functionspace.assign(self.functionspace - other.functionspace)
47+
return me
48+
else:
49+
raise DataError("Type error: cannot add %s to %s" % (type(other), type(self)))
50+
51+
def __rmul__(self, other):
52+
"""
53+
Overloading the right multiply by scalar factor
54+
55+
Args:
56+
other (float): factor
57+
Raises:
58+
DataError: if other is not a float
59+
Returns:
60+
fenics_mesh: copy of original values scaled by factor
61+
"""
62+
63+
try:
64+
me = firedrake_mesh(self)
65+
me.functionspace.assign(other * self.functionspace)
66+
return me
67+
except TypeError as e:
68+
raise DataError("Type error: cannot multiply %s to %s" % (type(other), type(self))) from e
69+
70+
def __abs__(self):
71+
"""
72+
Overloading the abs operator for mesh types
73+
74+
Returns:
75+
float: L2 norm
76+
"""
77+
78+
return fd.norm(self.functionspace, 'L2')
79+
80+
81+
class IMEX_firedrake_mesh(object):
82+
"""
83+
Datatype for IMEX integration with firedrake data.
84+
85+
Attributes:
86+
impl (firedrake_mesh): implicit part
87+
expl (firedrake_mesh): explicit part
88+
"""
89+
90+
def __init__(self, init, val=0.0):
91+
if type(init) == type(self):
92+
self.impl = firedrake_mesh(init.impl)
93+
self.expl = firedrake_mesh(init.expl)
94+
else:
95+
self.impl = firedrake_mesh(init, val=val)
96+
self.expl = firedrake_mesh(init, val=val)
97+
98+
def __add__(self, other):
99+
me = IMEX_firedrake_mesh(self)
100+
me.impl = self.impl + other.impl
101+
me.expl = self.expl + other.expl
102+
return me
103+
104+
def __sub__(self, other):
105+
me = IMEX_firedrake_mesh(self)
106+
me.impl = self.impl - other.impl
107+
me.expl = self.expl - other.expl
108+
return me
109+
110+
def __rmul__(self, other):
111+
me = IMEX_firedrake_mesh(self)
112+
me.impl = other * self.impl
113+
me.expl = other * self.expl
114+
return me

0 commit comments

Comments
 (0)