libMesh
Loading...
Searching...
No Matches
nonmanifold_coupling_test.C
Go to the documentation of this file.
1// libMesh includes
2#include <libmesh/dof_map.h>
3#include <libmesh/elem.h>
4#include <libmesh/equation_systems.h>
5#include <libmesh/linear_implicit_system.h>
6#include <libmesh/non_manifold_coupling.h>
7#include <libmesh/partitioner.h>
8#include <libmesh/replicated_mesh.h>
9#include <libmesh/sides_to_elem_map.h>
10
11// Unit test includes
12#include "test_comm.h"
13#include "libmesh_cppunit.h"
14
15// C++ includes
16#include <memory>
17
18using namespace libMesh;
19
20// We use a custom partioner for this test to help ensure we're
21// testing the cases we want. In particular, we're going to ensure
22// that elements attached to the "shared" side are on different
23// processors from each other so we know ghosting will be required for
24// algebraic and coupling ghosting between the two subdomains.
26{
27public:
28
34 virtual ~NonManifoldTestPartitioner() = default;
35
39 virtual std::unique_ptr<Partitioner> clone () const override
40 {
41 return std::make_unique<NonManifoldTestPartitioner>(*this);
42 }
43
44 virtual bool partitions_from_scratch () const override { return true; }
45
46protected:
47
51 virtual void _do_partition (MeshBase & mesh,
52 const unsigned int n) override
53 {
54 // If we're on one partition, then everyone gets to be on that partition
55 if (n == 1)
56 this->single_partition_range (mesh.active_elements_begin(), mesh.active_elements_end());
57 else
58 {
59 // Assign a round-robin processor id to each Elem attached to
60 // the "non-manifold" edge in this Mesh. In this case, the
61 // test Meshes were created so that they contain a single
62 // non-manifold edge, so we can stop partitioning once we have
63 // found that one edge.
64 libmesh_assert_greater (n, 0);
65
67
68 auto success = [&]() -> bool
69 {
70 for (const auto & elem : mesh.element_ptr_range())
71 for (auto s : elem->side_index_range())
72 {
73 const auto [side_neighbors_begin, side_neighbors_end] =
74 stem.get_connected_elems(elem, s);
75
76 if (std::distance(side_neighbors_begin, side_neighbors_end) == 5)
77 {
78 for (auto [e, it] = std::make_tuple(0u, side_neighbors_begin);
79 it != side_neighbors_end; ++e, ++it)
80 {
81 // If there are more Elems attached to the
82 // non-manifold edge than processors, just wrap
83 // around back to 0. Setting the processor id is a
84 // bit convoluted because the SidesToElemMap only
85 // has const pointers, but since we have a
86 // non-const reference to the mesh, we can work
87 // around that.
88 Elem * elem = mesh.elem_ptr((*it)->id());
89 elem->processor_id() = e % n;
90
91 // Debugging
92 // libMesh::out << "Partitioning Elem " << elem->id()
93 // << " onto proc " << elem->processor_id()
94 // << std::endl;
95 }
96
97 // Stop once we have partitioned elements attached to
98 // the non-manifold Side with 5 neighbors. This should
99 // effectively leave all other Elems assigned to
100 // processor 0.
101 return true;
102 }
103 }
104
105 // If we made it here, then we didn't find a Side shared by
106 // 5 elements and we'll throw an error later
107 return false;
108 }();
109
110 libmesh_error_msg_if(!success, "Did not find expected non-manifold edge.");
111 }
112 }
113};
114
115
116// Common functionality for all the different tests we'll be running
118{
119protected:
120
121 std::unique_ptr<MeshBase> _mesh;
122
123 std::unique_ptr<EquationSystems> _es;
124
125 void read_mesh(const std::string & mesh_filename)
126 {
127 // We are making assumptions in various places about the presence
128 // of the elements on the current processor so we're restricting to
129 // ReplicatedMesh for now.
130 _mesh = std::make_unique<ReplicatedMesh>(*TestCommWorld);
131 _mesh->read(mesh_filename);
132
133 // Use our custom partitioner that assigns non-manifold edge
134 // neighbors to different processors.
135 _mesh->partitioner() = std::make_unique<NonManifoldTestPartitioner>();
136
137 _mesh->prepare_for_use();
138
139 // Debugging: print processor ids determined by partitioner
140 // for (const auto & elem : _mesh->element_ptr_range())
141 // libMesh::out << "Elem " << elem->id() << " is on processor " << elem->processor_id() << std::endl;
142 }
143
144 void init_es()
145 {
146 _es = std::make_unique<EquationSystems>(*_mesh);
147
148 // Add System with a single linear Lagrange variable on it.
149 LinearImplicitSystem & sys = _es->add_system<LinearImplicitSystem> ("SimpleSystem");
150 sys.add_variable("u", FIRST, LAGRANGE);
151
152 // Attach ghosting functor to the System. We use the std::shared_ptr interface
153 // so that the DofMap takes ownership of the object. Note: if you comment out
154 // this line, then the test _can_ fail when run in parallel, but it depends on
155 // the number of processors used. For small numbers of procs, sometimes we can
156 // get "lucky" and wind up with all the non-manifold neighbor DOFs either local
157 // to or ghosted on all procs where they are needed. In practice, this test should
158 // be run on 3 or more procs to make it fail when the NonManifoldGhostingFunctor
159 // is not used.
160 auto ghosting_functor = std::make_shared<NonManifoldGhostingFunctor>(*_mesh);
161 sys.get_dof_map().add_coupling_functor(ghosting_functor, /*to_mesh=*/true);
162
163 // Initialize the DofMap, etc. for all Systems
164 _es->init();
165 }
166};
167
168// This class defines the actual unit tests
169class NonManifoldGhostingFunctorTest : public CppUnit::TestCase,
171{
172public:
173
175
176 // These tests all require Exodus
177#if defined(LIBMESH_HAVE_EXODUS_API)
182#endif
183
185
186public:
187 void setUp()
188 {
189 }
190
191 void tearDown()
192 {
193 }
194
195 void verifySendListEntries0() { this->verify_send_list_entries_helper("meshes/non_manifold_junction0.exo"); }
196 void verifySendListEntries1() { this->verify_send_list_entries_helper("meshes/non_manifold_junction1.exo"); }
197 void verifySendListEntries2() { this->verify_send_list_entries_helper("meshes/non_manifold_junction2.exo"); }
198 void verifySendListEntries3() { this->verify_send_list_entries_helper("meshes/non_manifold_junction3.exo"); }
199
200 // We call this helper function (which can take an argument) from
201 // the CPPUNIT_TEST macro functions, (which I don't think can take an argument?)
202 void verify_send_list_entries_helper(const std::string & mesh_filename)
203 {
204 // Call base class implementations
205 this->read_mesh(mesh_filename);
206 this->init_es();
207
208 // Get reference to the System, corresponding DofMap, and send_list for this processor
209 System & system = _es->get_system("SimpleSystem");
210 DofMap & dof_map = system.get_dof_map();
211 const std::vector<dof_id_type> & send_list = dof_map.get_send_list();
212
214
215 // Use the SidesToElemMap to get a range of Elem pointers attached
216 // to the non-manifold edge that we intentionally partitioned onto
217 // different processors.
219 auto side_neighbors_found = [&]() -> bool
220 {
221 for (const auto & elem : _mesh->element_ptr_range())
222 for (auto s : elem->side_index_range())
223 {
224 auto range = stem.get_connected_elems(elem, s);
225 if (std::distance(range.first, range.second) == 5)
226 {
227 beg = range.first;
228 end = range.second;
229 return true;
230 }
231 }
232
233 // If we made it here, then we didn't find a Side shared by
234 // 5 elements and we'll throw an error later
235 return false;
236 }();
237
238 // Require that neighbors were found
239 CPPUNIT_ASSERT(side_neighbors_found);
240
241 // For every pair of elements (e,f) on this edge owned by processors (proc_e, proc_f) respectively, check that:
242 // 1.) The DOFs of e are either local to, or in the send-list of, proc_f
243 // 2.) The DOFs of f are either local to, or in the send-list of, proc_e
244 for (auto it_e = beg; it_e != end; ++it_e)
245 for (auto it_f = std::next(it_e); it_f != end; ++it_f)
246 {
247 // Lambda to be used for error checking
248 auto check_dofs = [&](const Elem * elem)
249 {
250 std::vector<dof_id_type> dof_indices;
251 dof_map.dof_indices(elem, dof_indices);
252
253 for (const auto & dof : dof_indices)
254 {
255 bool is_local = (dof >= dof_map.first_dof()) && (dof < dof_map.end_dof());
256 bool is_in_send_list = (Utility::binary_find(send_list.begin(), send_list.end(), dof) != send_list.end());
257 CPPUNIT_ASSERT(is_local || is_in_send_list);
258 }
259 };
260
261 const Elem * elem_e = *it_e;
262 const Elem * elem_f = *it_f;
263
264 if (_mesh->comm().rank() == elem_e->processor_id())
265 check_dofs(elem_f);
266
267 if (_mesh->comm().rank() == elem_f->processor_id())
268 check_dofs(elem_e);
269 }
270 }
271};
272
void read_mesh(const std::string &mesh_filename)
std::unique_ptr< MeshBase > _mesh
std::unique_ptr< EquationSystems > _es
CPPUNIT_TEST(verifySendListEntries1)
void verify_send_list_entries_helper(const std::string &mesh_filename)
LIBMESH_CPPUNIT_TEST_SUITE(NonManifoldGhostingFunctorTest)
CPPUNIT_TEST(verifySendListEntries0)
CPPUNIT_TEST(verifySendListEntries3)
CPPUNIT_TEST(verifySendListEntries2)
virtual ~NonManifoldTestPartitioner()=default
NonManifoldTestPartitioner()=default
virtual std::unique_ptr< Partitioner > clone() const override
NonManifoldTestPartitioner(NonManifoldTestPartitioner &&)=default
NonManifoldTestPartitioner & operator=(const NonManifoldTestPartitioner &)=default
virtual void _do_partition(MeshBase &mesh, const unsigned int n) override
Partition the MeshBase onto n processors.
virtual bool partitions_from_scratch() const override
NonManifoldTestPartitioner(const NonManifoldTestPartitioner &)=default
dof_id_type first_dof(const processor_id_type proc) const
dof_id_type end_dof(const processor_id_type proc) const
This class handles the numbering of degrees of freedom on a mesh.
Definition dof_map.h:181
const std::vector< dof_id_type > & get_send_list() const
Definition dof_map.h:540
void dof_indices(const Elem *const elem, std::vector< dof_id_type > &di) const
Definition dof_map.C:2203
void add_coupling_functor(GhostingFunctor &coupling_functor, bool to_mesh=true)
Adds a functor which can specify coupling requirements for creation of sparse matrices.
Definition dof_map.C:2007
processor_id_type processor_id() const
Definition dof_object.h:895
This is the base class from which all geometric element types are derived.
Definition elem.h:96
Manages consistently variables, degrees of freedom, coefficient vectors, matrices and linear solvers ...
This is the MeshBase class.
Definition mesh_base.h:81
virtual const Elem * elem_ptr(const dof_id_type i) const =0
This class implements a generalization of the MeshTools::build_nodes_to_elem_map() function,...
static SidesToElemMap build(const MeshBase &mesh)
Static build function.
std::vector< constElem * >::const_iterator ElemIter
Typedef for the iterator type returned by the SidesToeElemMap::get_connected_elems() function.
std::pair< ElemIter, ElemIter > get_connected_elems(const Elem *elem, unsigned int side) const
Return an iterator pair defining the range of Elems connected to "side" of "elem".
The Partitioner class provides a uniform interface for partitioning algorithms.
Definition partitioner.h:52
bool single_partition_range(MeshBase::element_iterator it, MeshBase::element_iterator end)
Slightly generalized version of single_partition which acts on a range of elements defined by the pai...
Manages consistently variables, degrees of freedom, and coefficient vectors.
Definition system.h:100
unsigned int add_variable(std::string_view var, const FEType &type, const std::set< subdomain_id_type > *const active_subdomains=nullptr)
Adds the variable var to the list of variables for this system.
Definition system.C:1344
const DofMap & get_dof_map() const
Definition system.h:2417
Communicator * TestCommWorld
MeshBase & mesh
ForwardIterator binary_find(ForwardIterator first, ForwardIterator last, const T &value)
The STL provides std::binary_search() which returns true or false depending on whether the searched-f...
Definition utility.h:233
The libMesh namespace provides an interface to certain functionality in the library.
CPPUNIT_TEST_SUITE_REGISTRATION(NonManifoldGhostingFunctorTest)