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
3738
3839from dataclasses import dataclass
3940import 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
4243from arraycontext import ArrayContext
4344from meshmode .discretization .connection import (
4647from meshmode .mesh import (
4748 Mesh ,
4849 InteriorAdjacencyGroup ,
49- InterPartitionAdjacencyGroup ,
50- BoundaryTag ,
51- BTAG_PARTITION ,
50+ InterPartAdjacencyGroup ,
51+ PartID ,
5252)
5353
5454from 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
151155class 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
158162def 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 )
189194class 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
223223class 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?
384388def 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