Skip to content

Commit 4f7bc42

Browse files
majosmalexfiklinducer
authored
Recast partition_mesh in terms of generic part identifiers (#308)
* simplify partition_mesh interface * add get_connected_partitions back to docs * use opaque partition ID instead of number in partition_mesh * use self/other instead of local/nonlocal * fix compatibility * remove unnecessary import Co-authored-by: Alex Fikl <alexfikl@gmail.com> * eliminate fun Co-authored-by: Alex Fikl <alexfikl@gmail.com> * stamp out remaining traces of fun * rename membership_list_to_sets -> membership_list_to_map and store index sets as numpy arrays instead of python sets * remove return_sets option from get_partition_by_pymetis * fix bugs in MPIBoundaryCommSetupHelper * flake8 * fix bug * add a couple of fixmes * handle groupless mesh case in dim/ambient_dim * Revert "handle groupless mesh case in dim/ambient_dim" not a good solution * disable removal of empty mesh groups in partitioning * fix some issues with partitioning docs * remove empty-group filtering * clarify part vs. partition terminology * change some asserts to exceptions * add a couple of FIXMEs * cosmetic change Co-authored-by: Andreas Klöckner <inform@tiker.net> * detect elements that belong to multiple parts * add return_parts argument to partition_mesh * add type hints to mesh partitioning * Fix some annotations in mesh.processing * add explicit part_id attribute to InterPartAdjacencyGroup * cosmetic change * fix some type annotations * Dict -> Mapping * fix outdated argument reference in docstring * try using just dtype dtype[Any] requires python 3.9 Co-authored-by: Alex Fikl <alexfikl@gmail.com> Co-authored-by: Andreas Klöckner <inform@tiker.net>
1 parent 163acec commit 4f7bc42

File tree

5 files changed

+447
-381
lines changed

5 files changed

+447
-381
lines changed

meshmode/discretization/connection/opposite_face.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -521,20 +521,20 @@ def make_opposite_face_connection(actx, volume_to_bdry_conn):
521521

522522
# {{{ make_partition_connection
523523

524+
# FIXME: Consider adjusting terminology from local/remote to self/other.
524525
def make_partition_connection(actx, *, local_bdry_conn,
525526
remote_bdry_discr, remote_group_infos):
526527
"""
527-
Connects ``local_bdry_conn`` to a neighboring partition.
528+
Connects ``local_bdry_conn`` to a neighboring part.
528529
529-
:arg local_bdry_conn: A :class:`DiscretizationConnection` of the local
530-
partition.
530+
:arg local_bdry_conn: A :class:`DiscretizationConnection` of the local part.
531531
:arg remote_bdry_discr: A :class:`~meshmode.discretization.Discretization`
532-
of the boundary of the remote partition.
532+
of the boundary of the remote part.
533533
:arg remote_group_infos: An array of
534534
:class:`meshmode.distributed.RemoteGroupInfo` instances, one per remote
535535
volume element group.
536536
:returns: A :class:`DirectDiscretizationConnection` that performs data
537-
exchange across faces from the remote partition to the local partition.
537+
exchange across faces from the remote part to the local part.
538538
539539
.. versionadded:: 2017.1
540540
@@ -556,15 +556,15 @@ def make_partition_connection(actx, *, local_bdry_conn,
556556
# The code assumes that there is the same number of volume and surface groups.
557557
#
558558
# A weak reason to choose remote as the outer loop is because
559-
# InterPartitionAdjacency refers to neighbors by global volume element
559+
# InterPartAdjacency refers to neighbors by global volume element
560560
# numbers, and we only have enough information to resolve those to (group,
561561
# group_local_el_nr) for local elements (whereas we have no information
562562
# about remote volume elements).
563563
#
564564
# (See the find_group_indices below.)
565565

566566
for rgi in remote_group_infos:
567-
rem_ipags = rgi.inter_partition_adj_groups
567+
rem_ipags = rgi.inter_part_adj_groups
568568

569569
for rem_ipag in rem_ipags:
570570
i_local_grps = find_group_indices(local_vol_groups, rem_ipag.neighbors)

meshmode/distributed.py

Lines changed: 84 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
.. autoclass:: MPIBoundaryCommSetupHelper
55
66
.. autofunction:: get_partition_by_pymetis
7-
.. autofunction:: get_inter_partition_tags
7+
.. autofunction:: membership_list_to_map
8+
.. autofunction:: get_connected_parts
89
910
.. autoclass:: RemoteGroupInfo
1011
.. autoclass:: make_remote_group_infos
@@ -37,7 +38,7 @@
3738

3839
from dataclasses import dataclass
3940
import numpy as np
40-
from typing import List, Set, Union, Mapping, cast, Sequence, TYPE_CHECKING, Hashable
41+
from typing import List, Set, Union, Mapping, cast, Sequence, TYPE_CHECKING
4142

4243
from arraycontext import ArrayContext
4344
from meshmode.discretization.connection import (
@@ -46,9 +47,8 @@
4647
from meshmode.mesh import (
4748
Mesh,
4849
InteriorAdjacencyGroup,
49-
InterPartitionAdjacencyGroup,
50-
BoundaryTag,
51-
BTAG_PARTITION,
50+
InterPartAdjacencyGroup,
51+
PartID,
5252
)
5353

5454
from meshmode.discretization import ElementGroupFactory
@@ -94,31 +94,32 @@ def send_mesh_parts(self, mesh, part_per_element, num_parts):
9494
:arg part_per_element: A :class:`numpy.ndarray` containing one
9595
integer per element of *mesh* indicating which part of the
9696
partitioned mesh the element is to become a part of.
97-
:arg num_parts: The number of partitions to divide the mesh into.
97+
:arg num_parts: The number of parts to divide the mesh into.
9898
99-
Sends each partition to a different rank.
100-
Returns one partition that was not sent to any other rank.
99+
Sends each part to a different rank.
100+
Returns one part that was not sent to any other rank.
101101
"""
102102
mpi_comm = self.mpi_comm
103103
rank = mpi_comm.Get_rank()
104104
assert num_parts <= mpi_comm.Get_size()
105105

106106
assert self.is_mananger_rank()
107107

108+
part_num_to_elements = membership_list_to_map(part_per_element)
109+
108110
from meshmode.mesh.processing import partition_mesh
109-
parts = [partition_mesh(mesh, part_per_element, i)[0]
110-
for i in range(num_parts)]
111+
parts = partition_mesh(mesh, part_num_to_elements)
111112

112113
local_part = None
113114

114115
reqs = []
115-
for r, part in enumerate(parts):
116+
for r, part in parts.items():
116117
if r == self.manager_rank:
117118
local_part = part
118119
else:
119120
reqs.append(mpi_comm.isend(part, dest=r, tag=TAG_DISTRIBUTE_MESHES))
120121

121-
logger.info("rank %d: sent all mesh partitions", rank)
122+
logger.info("rank %d: sent all mesh parts", rank)
122123
for req in reqs:
123124
req.wait()
124125

@@ -147,16 +148,20 @@ def receive_mesh_part(self):
147148

148149
# {{{ remote group info
149150

151+
# FIXME: "Remote" is perhaps not the best naming convention for this. For example,
152+
# in a multi-volume context it may be used when constructing inter-part connections
153+
# between two parts on the same rank.
150154
@dataclass
151155
class RemoteGroupInfo:
152-
inter_partition_adj_groups: List[InterPartitionAdjacencyGroup]
156+
inter_part_adj_groups: List[InterPartAdjacencyGroup]
153157
vol_elem_indices: np.ndarray
154158
bdry_elem_indices: np.ndarray
155159
bdry_faces: np.ndarray
156160

157161

158162
def make_remote_group_infos(
159-
actx: ArrayContext, local_btag: BoundaryTag,
163+
actx: ArrayContext,
164+
remote_part_id: PartID,
160165
bdry_conn: DirectDiscretizationConnection
161166
) -> Sequence[RemoteGroupInfo]:
162167
local_vol_mesh = bdry_conn.from_discr.mesh
@@ -165,10 +170,10 @@ def make_remote_group_infos(
165170

166171
return [
167172
RemoteGroupInfo(
168-
inter_partition_adj_groups=[
173+
inter_part_adj_groups=[
169174
fagrp for fagrp in local_vol_mesh.facial_adjacency_groups[igrp]
170-
if isinstance(fagrp, InterPartitionAdjacencyGroup)
171-
and fagrp.boundary_tag == local_btag],
175+
if isinstance(fagrp, InterPartAdjacencyGroup)
176+
and fagrp.part_id == remote_part_id],
172177
vol_elem_indices=np.concatenate([
173178
actx.to_numpy(batch.from_element_indices)
174179
for batch in bdry_conn.groups[igrp].batches]),
@@ -188,17 +193,13 @@ def make_remote_group_infos(
188193
@dataclass(init=True, frozen=True)
189194
class InterRankBoundaryInfo:
190195
"""
191-
.. attribute:: local_btag
192-
193-
A boundary tag for the local boundary towards the remote partition.
194-
195196
.. attribute:: local_part_id
196197
197-
An opaque, hashable, picklable identifier for the local partition.
198+
An opaque, hashable, picklable identifier for the local part.
198199
199200
.. attribute:: remote_part_id
200201
201-
An opaque, hashable, picklable identifier for the remote partition.
202+
An opaque, hashable, picklable identifier for the remote part.
202203
203204
.. attribute:: remote_rank
204205
@@ -207,22 +208,21 @@ class InterRankBoundaryInfo:
207208
.. attribute:: local_boundary_connection
208209
209210
A :class:`~meshmode.discretization.connection.DirectDiscretizationConnection`
210-
from the volume onto the boundary described by :attr:`local_btag`.
211+
from the volume onto the boundary described by
212+
``BTAG_PARTITION(remote_part_id)``.
211213
212214
.. automethod:: __init__
213215
"""
214216

215-
# FIXME better names?
216-
local_btag: BoundaryTag
217-
local_part_id: Hashable
218-
remote_part_id: Hashable
217+
local_part_id: PartID
218+
remote_part_id: PartID
219219
remote_rank: int
220220
local_boundary_connection: DirectDiscretizationConnection
221221

222222

223223
class MPIBoundaryCommSetupHelper:
224224
"""
225-
Helper for setting up inter-partition facial data exchange.
225+
Helper for setting up inter-part facial data exchange.
226226
227227
.. automethod:: __init__
228228
.. automethod:: __enter__
@@ -240,11 +240,11 @@ def __init__(self,
240240
],
241241
bdry_grp_factory: ElementGroupFactory):
242242
"""
243-
:arg local_bdry_conns: A :class:`dict` mapping remote partition to
243+
:arg local_bdry_conns: A :class:`dict` mapping remote part to
244244
`local_bdry_conn`, where `local_bdry_conn` is a
245245
:class:`~meshmode.discretization.connection.DirectDiscretizationConnection`
246-
that performs data exchange from
247-
the volume to the faces adjacent to partition `i_remote_part`.
246+
that performs data exchange from the volume to the faces adjacent to
247+
part `i_remote_part`.
248248
:arg bdry_grp_factory: Group factory to use when creating the remote-to-local
249249
boundary connections
250250
"""
@@ -265,9 +265,8 @@ def __init__(self,
265265

266266
inter_rank_bdry_info = [
267267
InterRankBoundaryInfo(
268-
local_btag=BTAG_PARTITION(remote_rank),
269-
local_part_id=remote_rank,
270-
remote_part_id=self.i_local_rank,
268+
local_part_id=self.i_local_rank,
269+
remote_part_id=remote_rank,
271270
remote_rank=remote_rank,
272271
local_boundary_connection=conn
273272
)
@@ -292,7 +291,7 @@ def __enter__(self):
292291

293292
# to know when we're done
294293
self.pending_recv_identifiers = {
295-
(irbi.remote_rank, irbi.remote_part_id)
294+
(irbi.local_part_id, irbi.remote_part_id)
296295
for irbi in self.inter_rank_bdry_info}
297296

298297
self.send_reqs = [
@@ -302,7 +301,7 @@ def __enter__(self):
302301
irbi.remote_part_id,
303302
irbi.local_boundary_connection.to_discr.mesh,
304303
make_remote_group_infos(
305-
self.array_context, irbi.local_btag,
304+
self.array_context, irbi.remote_part_id,
306305
irbi.local_boundary_connection)),
307306
dest=irbi.remote_rank)
308307
for irbi in self.inter_rank_bdry_info]
@@ -314,13 +313,12 @@ def __exit__(self, type, value, traceback):
314313

315314
def complete_some(self):
316315
"""
317-
Returns a :class:`dict` mapping a subset of remote partitions to
316+
Returns a :class:`dict` mapping a subset of remote parts to
318317
remote-to-local boundary connections, where a remote-to-local boundary
319318
connection is a
320319
:class:`~meshmode.discretization.connection.DirectDiscretizationConnection`
321-
that performs data exchange across faces from partition `i_remote_part`
322-
to the local mesh. When an empty dictionary is returned, setup is
323-
complete.
320+
that performs data exchange across faces from part `i_remote_part` to the
321+
local mesh. When an empty dictionary is returned, setup is complete.
324322
"""
325323
from mpi4py import MPI
326324

@@ -341,36 +339,41 @@ def complete_some(self):
341339

342340
remote_to_local_bdry_conns = {}
343341

344-
local_part_id_to_irbi = {
345-
irbi.local_part_id: irbi for irbi in self.inter_rank_bdry_info}
346-
assert len(local_part_id_to_irbi) == len(self.inter_rank_bdry_info)
342+
part_ids_to_irbi = {
343+
(irbi.local_part_id, irbi.remote_part_id): irbi
344+
for irbi in self.inter_rank_bdry_info}
345+
if len(part_ids_to_irbi) < len(self.inter_rank_bdry_info):
346+
raise ValueError(
347+
"duplicate local/remote part pair in inter_rank_bdry_info")
347348

348-
for i_src_rank, recvd in zip(
349-
source_ranks, data):
350-
(recvd_remote_part_id, recvd_local_part_id,
349+
for i_src_rank, recvd in zip(source_ranks, data):
350+
(remote_part_id, local_part_id,
351351
remote_bdry_mesh, remote_group_infos) = recvd
352352

353353
logger.debug("rank %d: Received part id '%s' data from rank %d",
354-
self.i_local_rank, recvd_local_part_id, i_src_rank)
354+
self.i_local_rank, remote_part_id, i_src_rank)
355355

356356
# Connect local_mesh to remote_mesh
357357
from meshmode.discretization.connection import make_partition_connection
358-
irbi = local_part_id_to_irbi[recvd_local_part_id]
358+
irbi = part_ids_to_irbi[local_part_id, remote_part_id]
359359
assert i_src_rank == irbi.remote_rank
360-
assert recvd_remote_part_id == irbi.remote_part_id
361360

362-
remote_to_local_bdry_conns[recvd_local_part_id] \
363-
= make_partition_connection(
364-
self.array_context,
365-
local_bdry_conn=irbi.local_boundary_connection,
366-
remote_bdry_discr=(
367-
irbi.local_boundary_connection.to_discr.copy(
368-
actx=self.array_context,
369-
mesh=remote_bdry_mesh,
370-
group_factory=self.bdry_grp_factory)),
371-
remote_group_infos=remote_group_infos)
361+
if self._using_old_timey_interface:
362+
key = remote_part_id
363+
else:
364+
key = (remote_part_id, local_part_id)
365+
366+
remote_to_local_bdry_conns[key] = (
367+
make_partition_connection(
368+
self.array_context,
369+
local_bdry_conn=irbi.local_boundary_connection,
370+
remote_bdry_discr=irbi.local_boundary_connection.to_discr.copy(
371+
actx=self.array_context,
372+
mesh=remote_bdry_mesh,
373+
group_factory=self.bdry_grp_factory),
374+
remote_group_infos=remote_group_infos))
372375

373-
self.pending_recv_identifiers.remove((i_src_rank, recvd_remote_part_id))
376+
self.pending_recv_identifiers.remove((local_part_id, remote_part_id))
374377

375378
if not self.pending_recv_identifiers:
376379
MPI.Request.waitall(self.send_reqs)
@@ -381,6 +384,7 @@ def complete_some(self):
381384
# }}}
382385

383386

387+
# FIXME: Move somewhere else, since it's not strictly limited to distributed?
384388
def get_partition_by_pymetis(mesh, num_parts, *, connectivity="facial", **kwargs):
385389
"""Return a mesh partition created by :mod:`pymetis`.
386390
@@ -390,7 +394,7 @@ def get_partition_by_pymetis(mesh, num_parts, *, connectivity="facial", **kwargs
390394
``"facial"`` or ``"nodal"`` (based on vertices).
391395
:arg kwargs: Passed unmodified to :func:`pymetis.part_graph`.
392396
:returns: a :class:`numpy.ndarray` with one entry per element indicating
393-
to which partition each element belongs, with entries between ``0`` and
397+
to which part each element belongs, with entries between ``0`` and
394398
``num_parts-1``.
395399
396400
.. versionchanged:: 2020.2
@@ -429,39 +433,33 @@ def get_partition_by_pymetis(mesh, num_parts, *, connectivity="facial", **kwargs
429433
return np.array(p)
430434

431435

432-
def get_connected_partitions(mesh: Mesh) -> "Set[int]":
433-
"""For a local mesh part in *mesh*, determine the set of boundary
434-
tags for connections to other parts, cf.
435-
:class:`meshmode.mesh.InterPartitionAdjacencyGroup`.
436+
def membership_list_to_map(membership_list):
437+
"""
438+
Convert a :class:`numpy.ndarray` that maps an index to a key into a
439+
:class:`dict` that maps a key to a set of indices (with each set of indices
440+
stored as a sorted :class:`numpy.ndarray`).
436441
"""
437-
assert mesh.facial_adjacency_groups is not None
438-
# internal and deprecated, remove in July 2022
439-
440-
def _get_neighbor_part_nr(btag):
441-
if isinstance(btag, BTAG_PARTITION):
442-
return btag.part_nr
443-
else:
444-
raise ValueError("unexpected inter-partition boundary tag type found")
445-
446442
return {
447-
_get_neighbor_part_nr(grp.boundary_tag)
448-
for fagrp_list in mesh.facial_adjacency_groups
449-
for grp in fagrp_list
450-
if isinstance(grp, InterPartitionAdjacencyGroup)}
443+
entry: np.where(membership_list == entry)[0]
444+
for entry in set(membership_list)}
451445

452446

453-
def get_inter_partition_tags(mesh: Mesh) -> "Set[BoundaryTag]":
454-
"""For a local mesh part in *mesh*, determine the set of boundary
455-
tags for connections to other parts, cf.
456-
:class:`meshmode.mesh.InterPartitionAdjacencyGroup`.
457-
"""
447+
# FIXME: Move somewhere else, since it's not strictly limited to distributed?
448+
def get_connected_parts(mesh: Mesh) -> "Set[PartID]":
449+
"""For a local mesh part in *mesh*, determine the set of connected parts."""
458450
assert mesh.facial_adjacency_groups is not None
459451

460452
return {
461-
grp.boundary_tag
453+
grp.part_id
462454
for fagrp_list in mesh.facial_adjacency_groups
463455
for grp in fagrp_list
464-
if isinstance(grp, InterPartitionAdjacencyGroup)}
456+
if isinstance(grp, InterPartAdjacencyGroup)}
457+
465458

459+
def get_connected_partitions(mesh: Mesh) -> "Set[PartID]":
460+
warn(
461+
"get_connected_partitions is deprecated and will stop working in June 2023. "
462+
"Use get_connected_parts instead.", DeprecationWarning, stacklevel=2)
463+
return get_connected_parts(mesh)
466464

467465
# vim: foldmethod=marker

0 commit comments

Comments
 (0)