Skip to content

Commit f5b60f2

Browse files
authored
Utilise PetscSection to constrain essential boundary conditions (#2844)
- Left over TODOs but will be addressed on the `petsc` branch
1 parent a91bdaf commit f5b60f2

26 files changed

Lines changed: 2066 additions & 241 deletions

.github/workflows/docker-petsc.yml

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ permissions:
66
on:
77
push:
88
branches:
9-
- petsc # Push events on petsc branch
9+
- petsc
1010

1111
jobs:
1212
build-and-push:
@@ -16,6 +16,9 @@ jobs:
1616
# Use buildkit
1717
DOCKER_BUILDKIT: "1"
1818

19+
PETSC_REPO: https://gitlab.com/ZoeLeibowitz/petsc.git
20+
PETSC_BRANCH: zoe/feature-da-section-sf
21+
1922
steps:
2023
- name: Checkout devito
2124
uses: actions/checkout@v5
@@ -38,10 +41,13 @@ jobs:
3841
context: .
3942
file: docker/Dockerfile.petsc
4043
push: true
44+
platforms: linux/amd64
4145
tags: |
4246
devitocodes/devito-petsc:latest
43-
build-args: base=devitocodes/devito:gcc-dev-amd64
44-
platforms: linux/amd64
47+
build-args: |
48+
base=devitocodes/devito:gcc-dev-amd64
49+
PETSC_REPO=${{ env.PETSC_REPO }}
50+
PETSC_BRANCH=${{ env.PETSC_BRANCH }}
4551
4652
- name: Remove dangling layers
4753
run: docker system prune -f

.github/workflows/pytest-petsc.yml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ concurrency:
55
cancel-in-progress: true
66

77
on:
8-
# Trigger the workflow on push or pull request,
9-
# but only for the master branch
108
push:
119
branches:
1210
- main
@@ -50,7 +48,11 @@ jobs:
5048

5149
- name: Build docker image
5250
run: |
53-
docker build -f docker/Dockerfile.petsc --tag devito_petsc_image:test .
51+
docker build \
52+
-f docker/Dockerfile.petsc \
53+
--build-arg PETSC_REPO=https://gitlab.com/ZoeLeibowitz/petsc.git \
54+
--build-arg PETSC_BRANCH=zoe/feature-da-section-sf \
55+
--tag devito_petsc_image:test .
5456
5557
- name: Set run prefix
5658
run: |

devito/data/decomposition.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,39 @@ def index_loc_to_glb(self, *args):
450450
else:
451451
raise TypeError("Expected 1 arguments, found %d" % len(args))
452452

453+
def index_glb_to_loc_unsafe(self, glb_idx, rel=True):
454+
"""
455+
Convert a global index to a local index even if not owned.
456+
WARNING: Must not be used to index data as there are no guard
457+
rails against returning out of bound indices.
458+
"""
459+
if not self.loc_empty:
460+
loc_abs_min = self.loc_abs_min - self.glb_min
461+
loc_abs_max = self.loc_abs_max - self.glb_min
462+
glb_max = self.glb_max - self.glb_min
463+
else:
464+
loc_abs_min = self.loc_abs_min
465+
loc_abs_max = self.loc_abs_max
466+
glb_max = self.glb_max
467+
468+
glb_min = 0
469+
470+
base = loc_abs_min if rel else 0
471+
472+
# index_glb_to_loc(index)
473+
# -> Base case, empty local subdomain
474+
if self.loc_empty:
475+
return None
476+
# -> Handle negative index
477+
if glb_idx < 0:
478+
glb_idx = glb_max + glb_idx + 1
479+
# -> Do the actual conversion
480+
if loc_abs_min <= glb_idx <= loc_abs_max or glb_min <= glb_idx <= glb_max:
481+
return glb_idx - base
482+
else:
483+
# This should raise an exception when used to access a numpy.array
484+
return glb_idx
485+
453486
def reshape(self, *args):
454487
"""
455488
Create a new Decomposition with extended or reduced boundary subdomains.

devito/ir/cgen/printer.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from devito.symbolics.inspection import has_integer_args, sympy_dtype
1919
from devito.symbolics.queries import q_leaf
2020
from devito.types.basic import AbstractFunction
21+
from devito.types.misc import PostIncrementIndex
2122
from devito.tools import ctypes_to_cstr, dtype_to_ctype, ctypes_vector_mapper
2223

2324
__all__ = ['BasePrinter', 'ccode']
@@ -148,8 +149,13 @@ def _print_Indexed(self, expr):
148149
--------
149150
U[t,x,y,z] -> U[t][x][y][z]
150151
"""
151-
inds = ''.join(['[' + self._print(x) + ']' for x in expr.indices])
152-
return f'{self._print(expr.base.label)}{inds}'
152+
inds = []
153+
for i in expr.indices:
154+
if isinstance(i, PostIncrementIndex):
155+
inds.append(f"[{self._print(i)}++]")
156+
else:
157+
inds.append(f"[{self._print(i)}]")
158+
return f"{self._print(expr.base.label)}{''.join(inds)}"
153159

154160
def _print_FIndexed(self, expr):
155161
"""

devito/ir/equations/equation.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,10 @@ def detect(cls, expr):
107107
ReduceMin: OpMin,
108108
PetscEq: OpPetsc
109109
}
110-
try:
111-
return reduction_mapper[type(expr)]
112-
except KeyError:
113-
pass
110+
111+
for expr_type, op in reduction_mapper.items():
112+
if isinstance(expr, expr_type):
113+
return op
114114

115115
# NOTE: in the future we might want to track down other kinds
116116
# of operations here (e.g., memcpy). However, we don't care for

devito/ir/iet/nodes.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1148,8 +1148,8 @@ def expr_symbols(self):
11481148
ret.extend([self.pointer._C_symbol, self.pointee._C_symbol])
11491149
else:
11501150
ret.extend([self.pointer, self.pointee.indexed])
1151-
ret.extend(flatten(i.free_symbols
1152-
for i in self.pointee.symbolic_shape[1:]))
1151+
ret.extend(flatten(i.free_symbols
1152+
for i in self.pointee.symbolic_shape[1:]))
11531153
else:
11541154
assert False, f"Unexpected pointer type {type(self.pointer)}"
11551155

devito/operator/operator.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
from devito.types.dimension import Thickness
4343
from devito.petsc.iet.passes import lower_petsc
4444
from devito.petsc.clusters import petsc_preprocess
45+
from devito.petsc.equations import lower_exprs_petsc
4546

4647
__all__ = ['Operator']
4748

@@ -368,6 +369,8 @@ def _lower_exprs(cls, expressions, **kwargs):
368369
# in particular uniqueness across expressions is ensured
369370
expressions = concretize_subdims(expressions, **kwargs)
370371

372+
expressions = lower_exprs_petsc(expressions, **kwargs)
373+
371374
processed = [LoweredEq(i) for i in expressions]
372375

373376
return processed

devito/petsc/config.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,19 @@ def core_metadata():
4444
petsc_lib = tuple([arch / 'lib' for arch in petsc_dir])
4545

4646
return {
47-
'includes': ('petscsnes.h', 'petscdmda.h'),
47+
# TODO: Only add petscsection header when needed
48+
'includes': ('petscsnes.h', 'petscdmda.h', 'petscsection.h'),
4849
'include_dirs': petsc_include,
4950
'libs': ('petsc'),
5051
'lib_dirs': petsc_lib,
5152
'ldflags': tuple([f"-Wl,-rpath,{lib}" for lib in petsc_lib])
5253
}
5354

5455

56+
# Maximum number of bytes for a KSPType string in the profiler struct.
57+
KSPTYPE_MAX_LEN = 64
58+
59+
5560
def get_petsc_type_mappings():
5661
try:
5762
petsc_precision = petsc_variables['PETSC_PRECISION']
@@ -73,7 +78,8 @@ def get_petsc_type_mappings():
7378
petsc_type_to_ctype = {v: k for k, v in printer_mapper.items()}
7479
# Add other PETSc types
7580
petsc_type_to_ctype.update({
76-
'KSPType': ctypes.c_char_p,
81+
# Store a copy so it doens't segfault after SNESDestroy
82+
'KSPType': ctypes.c_char * KSPTYPE_MAX_LEN,
7783
'KSPConvergedReason': petsc_type_to_ctype['PetscInt'],
7884
'KSPNormType': petsc_type_to_ctype['PetscInt'],
7985
})

devito/petsc/equations.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
from devito.symbolics import retrieve_indexed, retrieve_dimensions, uxreplace
2+
from devito.types.dimension import SpaceDimension, CustomDimension
3+
from devito import Min, Max
4+
5+
from devito.petsc.types.equation import ConstrainBC
6+
from devito.petsc.types.dimension import (
7+
SubDimMax, SubDimMin,
8+
SpaceDimMax, SpaceDimMin,
9+
)
10+
11+
12+
def lower_exprs_petsc(expressions, **kwargs):
13+
14+
# Process `ConstrainBC` equations
15+
expressions = constrain_essential_bcs(expressions, **kwargs)
16+
17+
return expressions
18+
19+
20+
def constrain_essential_bcs(expressions, **kwargs):
21+
"""
22+
Expand loop bounds for `ConstrainBC` expressions so that each MPI rank
23+
iterates over all locally visible constrained points, including those in
24+
the halo. PETSc requires each rank to report all constrained nodes in its
25+
local data region. The loops are not used for data access — only to
26+
identify which local indices are constrained.
27+
"""
28+
constrain_expressions = [e for e in expressions if isinstance(e, ConstrainBC)]
29+
if not constrain_expressions:
30+
return expressions
31+
32+
sregistry = kwargs.get('sregistry')
33+
new_exprs = []
34+
35+
# TODO: rethink
36+
halo_size = {e.target.function._size_halo for e in constrain_expressions}
37+
assert len(halo_size) == 1
38+
halo_size = halo_size.pop()
39+
40+
all_dims = {d for e in constrain_expressions for d in extract_dims(e)}
41+
subdims = [d for d in all_dims if d.is_Sub and not d.local]
42+
space_dims = [d for d in all_dims if isinstance(d, SpaceDimension)]
43+
44+
mapper = {}
45+
46+
for d in subdims:
47+
halo = halo_size[d]
48+
49+
subdim_max = SubDimMax(
50+
sregistry.make_name(prefix=f"{d.name}_max"), subdim=d
51+
)
52+
subdim_min = SubDimMin(
53+
sregistry.make_name(prefix=f"{d.name}_min"), subdim=d
54+
)
55+
56+
mapper[d] = CustomDimension(
57+
name=d.name,
58+
symbolic_min=Max(subdim_min, d.parent.symbolic_min - halo.left),
59+
symbolic_max=Min(subdim_max, d.parent.symbolic_max + halo.right),
60+
)
61+
62+
for d in space_dims:
63+
halo = halo_size[d]
64+
space_dim_max = SpaceDimMax(
65+
sregistry.make_name(prefix=f"{d.name}_max"), space_dim=d
66+
)
67+
space_dim_min = SpaceDimMin(
68+
sregistry.make_name(prefix=f"{d.name}_min"), space_dim=d
69+
)
70+
71+
mapper[d] = CustomDimension(
72+
name=sregistry.make_name(prefix=f"{d.name}_expanded"),
73+
symbolic_min=Max(space_dim_min, d.symbolic_min - halo.left),
74+
symbolic_max=Min(space_dim_max, d.symbolic_max + halo.right),
75+
)
76+
77+
# Apply mapper to expressions
78+
for e in expressions:
79+
if not isinstance(e, ConstrainBC):
80+
new_exprs.append(e)
81+
continue
82+
83+
dims = extract_dims(e)
84+
if not dims:
85+
new_exprs.append(e)
86+
continue
87+
88+
new_e = uxreplace(e, mapper)
89+
90+
if e.implicit_dims:
91+
new_e = new_e._rebuild(
92+
implicit_dims=tuple(mapper.get(d, d) for d in e.implicit_dims)
93+
)
94+
new_exprs.append(new_e)
95+
return new_exprs
96+
97+
98+
def extract_dims(expr):
99+
indexeds = retrieve_indexed(expr)
100+
dims = retrieve_dimensions(
101+
[i for j in indexeds for i in j.indices],
102+
mode="unique",
103+
)
104+
dims.update(expr.implicit_dims)
105+
return dims

0 commit comments

Comments
 (0)