https://mooseframework.inl.gov
Loading...
Searching...
No Matches
CopyMeshPartitioner.C
Go to the documentation of this file.
1//* This file is part of the MOOSE framework
2//* https://mooseframework.inl.gov
3//*
4//* All rights reserved, see COPYRIGHT for full restrictions
5//* https://github.com/idaholab/moose/blob/master/COPYRIGHT
6//*
7//* Licensed under LGPL 2.1, please see LICENSE for details
8//* https://www.gnu.org/licenses/lgpl-2.1.html
9
10#include "CopyMeshPartitioner.h"
11
12#include "MooseApp.h"
13#include "MooseMesh.h"
14#include "MooseRandom.h"
15
16#include "libmesh/elem.h"
17#include "libmesh/parallel_algebra.h"
18
19// TIMPI includes
20#include "timpi/communicator.h"
21#include "timpi/parallel_sync.h"
22
24
27{
29 params.addClassDescription("Assigns element to match the partitioning of another mesh. If in a "
30 "child application, defaults to the parent app mesh if the other mesh "
31 "is not specified programmatically.");
32 params.addPrivateParam<MooseMesh *>("source_mesh");
33
34 return params;
35}
36
38{
39 if (isParamValid("source_mesh"))
40 _base_mesh = getParam<MooseMesh *>("source_mesh");
41 else if (!_app.isUltimateMaster() && _app.masterMesh())
43 else
44 mooseError("Expecting either a parent app with a mesh to copy the partitioning from, a "
45 "'source_mesh' (private) parameter to be set programmatically.");
46}
47
48std::unique_ptr<Partitioner>
50{
51 return _app.getFactory().clone(*this);
52}
53
54void
55CopyMeshPartitioner::_do_partition(MeshBase & mesh, const unsigned int /*n*/)
56{
57 mooseAssert(_base_mesh, "Should have a base mesh to copy the partitioning from");
58
59 // First check that it makes sense to copy the partitioning
60 // If we use the same number of procs to partition this mesh than to partition the source mesh, we
61 // are effectively matching regions to processes the same way in both meshes. Makes sense.
62 // If we use more procs, this will leave some processes with no elements. It is not ideal, let's
63 // just give a warning. This does not happen in practice with MultiApps.
64 // If we use fewer procs (N_source > N_current), we error. If we could avoid erroring, then
65 // either:
66 // - the elements on our mesh always get matched to elements from only N_current processes among
67 // the N_source processes. We can accomodate that
68 // - the elements on our mesh get matched to more processes than we have. We would
69 // need a heuristic (for example a nested partitioner) to re-distribute these elements. We won't
70 // support it
71 std::set<unsigned int> pids_used;
72
73 // We cannot get the point locator only on some ranks of the base mesh, so we must error here
74 if (_base_mesh->comm().size() > mesh.comm().size())
75 mooseError("This partitioner does not support using fewer ranks to partition the mesh than are "
76 "used to partition the source mesh (the mesh we are copying the partitioning from)");
77 mooseAssert(_base_mesh->comm().rank() == mesh.comm().rank(),
78 "Should be the same rank on both mesh MPI communicators");
79
80 // Get point locator
81 auto pl = _base_mesh->getPointLocator();
82 if (!_base_mesh->getMesh().is_serial())
83 pl->enable_out_of_mesh_mode();
84
85 // We use the pull_parallel_vector_data algorithm.
86 // This code is very similar to partitioner::assign_partitioning in libMesh partitioner.C
87 // We need to define three things:
88 // - the list of requests: the elements in our local mesh we are trying to assign pids to
89 // - how to gather results: not all processors know which element belongs where, they will each
90 // process the requests sent to them and send back the elem pids if they know them.
91 // - how to fill the requests: from the data gathered and sent back, we need to decide which data
92 // is good and save it into the output map
93
94 // For each processor id, all the elements in our mesh that will request to know their new
95 // processor id. If the target mesh is replicated, that's all the elements from that mesh, but the
96 // source mesh may be distributed so we still need the communication. If a distributed mesh, then
97 // that's only the local and ghosted elements.
98 // We send the target element vertex average, enough to find it in the source mesh.
99 // We send the target element pid, to know where to save the result in the filled requests
100 // We send an index for the ordering of the requests to facilitate retrieving the results
101 // NOTE: in partitioner.C they manage to unpack the filled requests in the same order that
102 // the requests are sent, thus saving the need for that last index. Possible rework..
103 std::map<processor_id_type, std::vector<std::tuple<Point, processor_id_type, unsigned int>>>
104 requested_elements;
105 const bool distributed_mesh = !_base_mesh->getMesh().is_serial();
106 unsigned int request_i = 0;
107 for (auto & elem : mesh.active_element_ptr_range())
108 {
109 // we don't know which processor owns this point.
110 // As a first try, we add this to every processor id
111 // TODO: a simple bounding box heuristic would do fine!
112 const auto elem_pt = elem->vertex_average();
113 if (distributed_mesh)
114 for (const auto pid : make_range(mesh.comm().size()))
115 requested_elements[pid].push_back({elem_pt, elem->processor_id(), request_i});
116 // source mesh is replicated, let's just ask the sending processor what the processor id is
117 else
118 requested_elements[processor_id()].push_back({elem_pt, processor_id(), request_i});
119 request_i++;
120 }
121
122 // For each requests, find which process is able to fill the request
123 // Every processor fills these requests as best it can.
124 auto gather_functor =
125 [&pl](
126 processor_id_type /*pid*/,
127 const std::vector<std::tuple<Point, processor_id_type, unsigned int>> & incoming_elements,
128 std::vector<processor_id_type> & outgoing_pids)
129 {
130 outgoing_pids.resize(incoming_elements.size(), libMesh::invalid_uint);
131
132 // Try the pl on the incoming element
133 for (const auto i : index_range(incoming_elements))
134 {
135 const auto & elem = (*pl)(Point(std::get<0>(incoming_elements[i])));
136 if (elem)
137 outgoing_pids[i] = elem->processor_id();
138 }
139 };
140
141 // Results to gather from each processor
142 // For each processor id, we have a vector indexed by element with the new processor id
143 // Note that the filled_requests should match the ordering of the requested_elements
144 std::map<processor_id_type, std::vector<processor_id_type>> filled_request;
145 for (const auto i : make_range(mesh.comm().size()))
146 filled_request[i].resize(requested_elements.count(i) ? requested_elements[i].size() : 0);
147
148 // How to act on the exchanged data, and fill the filled_request (output we sought)
149 auto action_functor =
150 [&filled_request](
151 processor_id_type,
152 const std::vector<std::tuple<Point, processor_id_type, unsigned int>> & elems,
153 const std::vector<processor_id_type> & new_procids)
154 {
155 for (const auto i : index_range(new_procids))
156 if (new_procids[i] != libMesh::invalid_uint)
157 {
158 const auto elem_pid = std::get<1>(elems[i]);
159 const auto request_i = std::get<2>(elems[i]);
160 filled_request[elem_pid][request_i] = new_procids[i];
161 }
162 };
163
164 // Trade requests with other processors
165 // NOTE: We could try to use base mesh communicator because that's where we gather the information
166 // However, if that mesh communicator has more processes than we do, that would be trouble.
167 const processor_id_type * ex = nullptr;
168 Parallel::pull_parallel_vector_data(
169 mesh.comm(), requested_elements, gather_functor, action_functor, ex);
170
171 // Assign the partitioning.
172 request_i = 0;
173 for (auto & elem : mesh.active_element_ptr_range())
174 {
175 const processor_id_type current_pid = elem->processor_id();
176 const auto lookup_pid = distributed_mesh ? current_pid : processor_id();
177 const processor_id_type elem_procid = filled_request[lookup_pid][request_i++];
178
179 elem->processor_id() = elem_procid;
180 // Keep track of processor ids used in case we need to do a pass at re-assigning
181 pids_used.insert(elem_procid);
182 }
183
184 // Synchronize the pids used across all ranks
185 // NOTE: we could have gathered this earlier
186 std::vector<unsigned int> pids_used_vec(pids_used.begin(), pids_used.end());
187 mesh.comm().allgather(pids_used_vec);
188 pids_used.insert(pids_used_vec.begin(), pids_used_vec.end());
189
190 // Check the pids used
191 // We cannot use more process ids to partition the mesh than the current app is using
192 const auto max_pid = mesh.comm().size();
193 if (pids_used.size() > max_pid)
194 mooseError("Partitioning copy used more regions (" + std::to_string(pids_used.size()) +
195 ") than the number of parallel processes (" + std::to_string(mesh.comm().size()) +
196 ")");
197 if (pids_used.size() < max_pid)
199 "Some parallel (MPI) processes were not assigned any element during partitioning. These "
200 "processes will not perform any work.");
201
202 // The pids are not too many, but their numbering is not contiguous, renumber the process id
203 // assignment
204 if (*pids_used.rbegin() > max_pid)
205 {
206 std::unordered_map<unsigned int, unsigned int> source_to_current_pid_map;
207
208 // TODO: This is a naive remapping. There might be remapping that have optimal locality. E.g.
209 // the mpi ranks are on the same node, limiting communications. Once we can have multiple
210 // partitioners, we should look into having a nested partitioner handle the outliers
211 unsigned int i = 0;
212 for (const auto pid_set : pids_used)
213 source_to_current_pid_map[pid_set] = i++;
214
215 for (auto & elem_ptr : mesh.active_element_ptr_range())
216 elem_ptr->processor_id() = source_to_current_pid_map[elem_ptr->processor_id()];
217 }
218}
registerMooseObject("MooseApp", CopyMeshPartitioner)
Partitions a mesh based on the partitioning of the other mesh.
static InputParameters validParams()
virtual std::unique_ptr< Partitioner > clone() const override
virtual void _do_partition(MeshBase &mesh, const unsigned int n) override
const MooseMesh * _base_mesh
A pointer to the mesh to copy the partitioning from.
CopyMeshPartitioner(const InputParameters &params)
std::unique_ptr< T > clone(const T &object)
Clones the object object.
Definition Factory.h:323
The main MOOSE class responsible for handling user-defined parameters in almost every MOOSE system.
void addPrivateParam(const std::string &name, const T &value)
These method add a parameter to the InputParameters object which can be retrieved like any other para...
void addClassDescription(const std::string &doc_string)
This method adds a description of the class that will be displayed in the input file syntax dump.
const MooseMesh * masterMesh() const
Returns a pointer to the master mesh.
Definition MooseApp.h:876
Factory & getFactory()
Retrieve a writable reference to the Factory associated with this App.
Definition MooseApp.h:407
bool isUltimateMaster() const
Whether or not this app is the ultimate master app.
Definition MooseApp.h:866
void mooseError(Args &&... args) const
Emits an error prefixed with object name and type and optionally a file path to the top-level block p...
Definition MooseBase.h:271
bool isParamValid(const std::string &name) const
Test if the supplied parameter is valid.
Definition MooseBase.h:199
MooseMesh wraps a libMesh::Mesh object and enhances its capabilities by caching additional data and s...
Definition MooseMesh.h:95
MeshBase & getMesh()
Accessor for the underlying libMesh Mesh object.
Definition MooseMesh.C:3549
virtual std::unique_ptr< libMesh::PointLocatorBase > getPointLocator() const
Proxy function to get a (sub)PointLocator from either the underlying libMesh mesh (default),...
Definition MooseMesh.C:3838
MooseApp & _app
The MOOSE application this is associated with.
Definition MooseBase.h:375
Base class for MOOSE partitioner.
static InputParameters validParams()
void mooseWarning(Args &&... args) const
processor_id_type size() const
processor_id_type rank() const
processor_id_type processor_id() const
const Parallel::Communicator & comm() const
MeshBase & mesh
const unsigned int invalid_uint