libMesh
Loading...
Searching...
No Matches
static_condensation_dof_map.C
Go to the documentation of this file.
1// The libMesh Finite Element Library.
2// Copyright (C) 2002-2026 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner
3
4// This library is free software; you can redistribute it and/or
5// modify it under the terms of the GNU Lesser General Public
6// License as published by the Free Software Foundation; either
7// version 2.1 of the License, or (at your option) any later version.
8
9// This library is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Lesser General Public License for more details.
13
14// You should have received a copy of the GNU Lesser General Public
15// License along with this library; if not, write to the Free Software
16// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
18#include "libmesh/static_condensation_dof_map.h"
19
20#include "libmesh/mesh_base.h"
21#include "libmesh/dof_map.h"
22#include "libmesh/elem.h"
23#include "libmesh/int_range.h"
24#include "libmesh/system.h"
25#include "libmesh/equation_systems.h"
26#include "timpi/parallel_sync.h"
27#include <unordered_set>
28
29namespace libMesh
30{
32 System & system,
33 const DofMap & dof_map)
34 : DofMapBase(dof_map.comm()),
35 _mesh(mesh),
36 _system(system),
37 _dof_map(dof_map),
38 _sc_is_initialized(false)
39{
40 libmesh_experimental();
41}
42
44
46 const dof_id_type full_dof_number,
47 const bool involved_in_constraints,
48 std::unordered_map<dof_id_type, dof_id_type> & uncondensed_global_to_local_map,
49 std::unordered_set<dof_id_type> & local_uncondensed_dofs_set,
50 std::unordered_map<processor_id_type, std::unordered_set<dof_id_type>> &
51 nonlocal_uncondensed_dofs,
52 std::vector<dof_id_type> & elem_uncondensed_dofs,
53 dof_id_type & uncondensed_local_dof_number,
54 std::unordered_set<dof_id_type> & constraint_dofs)
55{
56 if (uncondensed_global_to_local_map.count(full_dof_number))
57 // We've already seen this dof on this element
58 return;
59
60 if (_dof_map.local_index(full_dof_number))
61 local_uncondensed_dofs_set.insert(full_dof_number);
62 else
63 nonlocal_uncondensed_dofs[_dof_map.dof_owner(full_dof_number)].insert(full_dof_number);
64
65 elem_uncondensed_dofs.push_back(full_dof_number);
66 (uncondensed_global_to_local_map)[full_dof_number] = uncondensed_local_dof_number++;
67 if (involved_in_constraints)
68 constraint_dofs.insert(full_dof_number);
69}
70
72 const dof_id_type full_dof_number,
73 bool involved_in_constraints,
74 std::unordered_map<dof_id_type, dof_id_type> & uncondensed_global_to_local_map,
75 std::unordered_set<dof_id_type> & local_uncondensed_dofs_set,
76 std::unordered_map<processor_id_type, std::unordered_set<dof_id_type>> &
77 nonlocal_uncondensed_dofs,
78 std::vector<dof_id_type> & elem_uncondensed_dofs,
79 dof_id_type & uncondensed_local_dof_number,
80 std::unordered_set<dof_id_type> & constraint_dofs)
81{
82 const auto & full_dof_constraints = _dof_map.get_dof_constraints();
83 auto it = full_dof_constraints.find(full_dof_number);
84 const bool is_constrained = it != full_dof_constraints.end();
85 involved_in_constraints = involved_in_constraints || is_constrained;
86
87 this->add_uncondensed_dof(full_dof_number,
88 involved_in_constraints,
89 uncondensed_global_to_local_map,
90 local_uncondensed_dofs_set,
91 nonlocal_uncondensed_dofs,
92 elem_uncondensed_dofs,
93 uncondensed_local_dof_number,
94 constraint_dofs);
95 if (is_constrained)
96 for (const auto & [full_constraining_dof, weight] : it->second)
97 {
98 libmesh_ignore(weight);
99 // Our constraining dofs may themselves be constrained
100 this->add_uncondensed_dof_plus_constraint_dofs(full_constraining_dof,
101 /*involved_in_constraints=*/true,
102 uncondensed_global_to_local_map,
103 local_uncondensed_dofs_set,
104 nonlocal_uncondensed_dofs,
105 elem_uncondensed_dofs,
106 uncondensed_local_dof_number,
107 constraint_dofs);
108 }
109}
110
112{
113 if (this->initialized())
114 this->clear();
115
116 std::vector<dof_id_type> elem_dofs, elem_uncondensed_dofs; // only used to satisfy API
117 dof_id_type condensed_local_dof_number = 0, uncondensed_local_dof_number = 0;
118 std::unordered_map<dof_id_type, dof_id_type> *condensed_global_to_local_map = nullptr,
119 *uncondensed_global_to_local_map = nullptr;
120 std::set<unsigned int> full_vars_present_in_reduced_sys;
121 std::unordered_set<dof_id_type> local_uncondensed_dofs_set, constraint_dofs;
122 std::unordered_map<processor_id_type, std::unordered_set<dof_id_type>> nonlocal_uncondensed_dofs;
123
124 // Handle SCALAR dofs
125 for (const auto vg : make_range(_dof_map.n_variable_groups()))
126 if (const auto & vg_description = _dof_map.variable_group(vg);
127 vg_description.type().family == SCALAR)
128 {
129 std::vector<dof_id_type> scalar_dof_indices;
130 const processor_id_type last_pid = this->comm().size() - 1;
131 for (const auto vg_vn : make_range(vg_description.n_variables()))
132 {
133 const auto vn = vg_description.number(vg_vn);
134 _dof_map.SCALAR_dof_indices(scalar_dof_indices, vn);
135 if (this->comm().rank() == last_pid)
136 local_uncondensed_dofs_set.insert(scalar_dof_indices.begin(),
137 scalar_dof_indices.end());
138 else
139 nonlocal_uncondensed_dofs[last_pid].insert(scalar_dof_indices.begin(),
140 scalar_dof_indices.end());
141 }
142 }
143
144 auto scalar_dofs_functor =
145 [this,
146 &uncondensed_global_to_local_map,
147 &local_uncondensed_dofs_set,
148 &nonlocal_uncondensed_dofs,
149 &elem_uncondensed_dofs,
150 &uncondensed_local_dof_number,
151 &constraint_dofs](const Elem & /*elem*/,
152 std::vector<dof_id_type> & dof_indices,
153 const std::vector<dof_id_type> & scalar_dof_indices) {
154 dof_indices.insert(dof_indices.end(), scalar_dof_indices.begin(), scalar_dof_indices.end());
155 for (const auto global_dof : scalar_dof_indices)
157 false,
158 *uncondensed_global_to_local_map,
159 local_uncondensed_dofs_set,
160 nonlocal_uncondensed_dofs,
161 elem_uncondensed_dofs,
162 uncondensed_local_dof_number,
163 constraint_dofs);
164 };
165
166 auto field_dofs_functor = [this,
167 &condensed_local_dof_number,
168 &condensed_global_to_local_map,
169 &uncondensed_global_to_local_map,
170 &local_uncondensed_dofs_set,
171 &nonlocal_uncondensed_dofs,
172 &elem_uncondensed_dofs,
173 &uncondensed_local_dof_number,
174 &constraint_dofs](const Elem & elem,
175 const unsigned int node_num,
176 const unsigned int var_num,
177 std::vector<dof_id_type> & dof_indices,
178 const dof_id_type field_dof) {
179 dof_indices.push_back(field_dof);
180
181 bool uncondensed_dof = false;
182 if (_uncondensed_vars.count(var_num))
183 {
184 libmesh_assert_msg(
185 node_num == invalid_uint,
186 "Users should not be providing continuous FEM variables to the uncondensed vars API");
187 uncondensed_dof = true;
188 }
189
190 if (node_num != invalid_uint && !elem.is_internal(node_num))
191 uncondensed_dof = true;
192
193 if (uncondensed_dof)
195 false,
196 *uncondensed_global_to_local_map,
197 local_uncondensed_dofs_set,
198 nonlocal_uncondensed_dofs,
199 elem_uncondensed_dofs,
200 uncondensed_local_dof_number,
201 constraint_dofs);
202 else
203 (*condensed_global_to_local_map)[field_dof] = condensed_local_dof_number++;
204 };
205
206 for (auto elem : _mesh.active_local_element_ptr_range())
207 {
208 auto & dof_data = _elem_to_dof_data[elem->id()];
209 condensed_local_dof_number = 0;
210 uncondensed_local_dof_number = 0;
211 condensed_global_to_local_map = &dof_data.condensed_global_to_local_map;
212 uncondensed_global_to_local_map = &dof_data.uncondensed_global_to_local_map;
213
214 const auto sub_id = elem->subdomain_id();
215 for (const auto vg : make_range(_dof_map.n_variable_groups()))
216 {
217 const auto & var_group = _dof_map.variable_group(vg);
218 for (const auto v : make_range(var_group.n_variables()))
219 {
220 const auto var_num = var_group.number(v);
221 dof_data.reduced_space_indices.resize(var_num + 1);
222 if (!var_group.active_on_subdomain(sub_id))
223 continue;
224 elem_uncondensed_dofs.clear();
226 elem_dofs,
227 var_num,
228 scalar_dofs_functor,
229 field_dofs_functor,
230 elem->p_level());
231 if (!elem_uncondensed_dofs.empty())
232 {
233 auto & var_reduced_space_indices = dof_data.reduced_space_indices[var_num];
234 var_reduced_space_indices.insert(var_reduced_space_indices.end(),
235 elem_uncondensed_dofs.begin(),
236 elem_uncondensed_dofs.end());
237 full_vars_present_in_reduced_sys.insert(var_num);
238 }
239 }
240 }
241 }
242
243 //
244 // We've built our local uncondensed dofs container ... but only using local element dof_indices
245 // calls. It can be the case that we own a degree of freedom that is not actually needed by our
246 // local element assembly but is needed by other processes element assembly. One example we've run
247 // into of this is a mid-edge coarse element node holding side hierarchic dofs which is also a
248 // fine element's vertex node. This node may be owned by the process holding the fine element
249 // which doesn't need those side hierarchic dofs for its assembly
250 //
251
252 // Build supported query type. Has to be map to contiguous data for calls to MPI
253 std::unordered_map<processor_id_type, std::vector<dof_id_type>> nonlocal_uncondensed_dofs_mapvec;
254 for (const auto & [pid, set] : nonlocal_uncondensed_dofs)
255 {
256 auto & vec = nonlocal_uncondensed_dofs_mapvec[pid];
257 vec.assign(set.begin(), set.end());
258 }
259 // clear no longer needed memory
260 nonlocal_uncondensed_dofs.clear();
261
262 auto receive_needed_local_dofs =
263 [&local_uncondensed_dofs_set](processor_id_type,
264 const std::vector<dof_id_type> & local_dofs_to_insert) {
265 local_uncondensed_dofs_set.insert(local_dofs_to_insert.begin(), local_dofs_to_insert.end());
266 };
267
269 _mesh.comm(), nonlocal_uncondensed_dofs_mapvec, receive_needed_local_dofs);
270
271 _local_uncondensed_dofs.assign(local_uncondensed_dofs_set.begin(),
272 local_uncondensed_dofs_set.end());
273 local_uncondensed_dofs_set.clear();
274
275 //
276 // Build the reduced system data
277 //
278
279 const dof_id_type n_local = _local_uncondensed_dofs.size();
280 dof_id_type n = n_local;
281 this->comm().sum(n);
282
283 // Get DOF counts on all processors
284 this->compute_dof_info(n_local);
285
286 // Build a map from the full size problem uncondensed dof indices to the reduced problem
287 // (uncondensed) dof indices
288 std::unordered_map<dof_id_type, dof_id_type> full_dof_to_reduced_dof;
289 const auto local_start = _first_df[this->processor_id()];
290 for (const auto i : index_range(_local_uncondensed_dofs))
291 full_dof_to_reduced_dof[_local_uncondensed_dofs[i]] = i + local_start;
292
293 // Build the condensed system sparsity pattern
295 this->_mesh, /*calculate_constrained=*/false, /*use_condensed_system=*/true);
296 const auto & nnz = _reduced_sp->get_n_nz();
297 const auto & noz = _reduced_sp->get_n_oz();
298 libmesh_assert(nnz.size() == noz.size());
299
300 // Optimization for PETSc. This is critical for problems in which there are SCALAR dofs that
301 // introduce dense rows to avoid allocating a dense matrix
304 for (const dof_id_type local_reduced_i : index_range(_local_uncondensed_dofs))
305 {
306 const dof_id_type full_i = _local_uncondensed_dofs[local_reduced_i];
307 const dof_id_type local_full_i = full_i - _dof_map.first_dof();
308 libmesh_assert(local_full_i < nnz.size());
309 _reduced_nnz[local_reduced_i] = nnz[local_full_i];
310 _reduced_noz[local_reduced_i] = noz[local_full_i];
311 }
312
313 //
314 // Now we need to pull our nonlocal data
315 //
316
317 auto gather_functor = [&full_dof_to_reduced_dof](processor_id_type,
318 const std::vector<dof_id_type> & full_dof_ids,
319 std::vector<dof_id_type> & reduced_dof_ids) {
320 reduced_dof_ids.resize(full_dof_ids.size());
321 for (const auto i : index_range(full_dof_ids))
322 reduced_dof_ids[i] = libmesh_map_find(full_dof_to_reduced_dof, full_dof_ids[i]);
323 };
324
325 auto action_functor =
326 [&full_dof_to_reduced_dof](processor_id_type,
327 const std::vector<dof_id_type> & full_dof_ids,
328 const std::vector<dof_id_type> & reduced_dof_ids) {
329 for (const auto i : index_range(full_dof_ids))
330 {
331 libmesh_assert(!full_dof_to_reduced_dof.count(full_dof_ids[i]));
332 full_dof_to_reduced_dof[full_dof_ids[i]] = reduced_dof_ids[i];
333 }
334 };
335
337 nonlocal_uncondensed_dofs_mapvec,
338 gather_functor,
339 action_functor,
341 nonlocal_uncondensed_dofs_mapvec.clear();
342
343 // Determine the variables with any degrees of freedom present in the reduced system
344 _communicator.set_union(full_vars_present_in_reduced_sys);
345 _reduced_vars.reserve(full_vars_present_in_reduced_sys.size());
346 unsigned int first_local_number = 0;
347 for (const auto i : index_range(full_vars_present_in_reduced_sys))
348 {
349 const auto full_var_num = *std::next(full_vars_present_in_reduced_sys.begin(), i);
350 const auto & full_var = _dof_map.variable(full_var_num);
351 _reduced_vars.push_back(Variable{nullptr,
352 full_var.name(),
353 cast_int<unsigned int>(i),
354 first_local_number,
355 full_var.type()});
356 first_local_number += _reduced_vars.back().n_components(_mesh);
357 }
358
359 // Now we can finally set our element reduced dof indices
360 std::vector<dof_id_type> var_full_dof_indices;
361 for (auto & [elem, dof_data] : _elem_to_dof_data)
362 {
363 libmesh_ignore(elem);
364 auto & reduced_space_indices = dof_data.reduced_space_indices;
365 // Keep around only those variables which are present in our reduced system
366 {
367 std::size_t i = 0;
368 reduced_space_indices.erase(
369 std::remove_if(
370 reduced_space_indices.begin(),
371 reduced_space_indices.end(),
372 [&full_vars_present_in_reduced_sys, &i](const std::vector<dof_id_type> &) {
373 return !full_vars_present_in_reduced_sys.count(i++);
374 }),
375 reduced_space_indices.end());
376 }
377 libmesh_assert(reduced_space_indices.size() == full_vars_present_in_reduced_sys.size());
378
379 for (auto & var_dof_indices : reduced_space_indices)
380 {
381 var_full_dof_indices = var_dof_indices;
382 var_dof_indices.clear();
383 for (const auto full_dof : var_full_dof_indices)
384 var_dof_indices.push_back(libmesh_map_find(full_dof_to_reduced_dof, full_dof));
385 }
386 }
387
388 // Build our dof constraints map
389 for (const auto full_dof : constraint_dofs)
391 libmesh_map_find(full_dof_to_reduced_dof, full_dof);
392 constraint_dofs.clear();
393
394 // Prevent querying Nodes for dof indices
395 std::vector<unsigned int> nvpg(_reduced_vars.size());
396 for (auto & elem : nvpg)
397 elem = 1;
398
399 // add_system returns a system if it already exists instead of erroring so there's no harm if
400 // we do this multiple times
403 {
405 for (auto * const nd : _mesh.active_node_ptr_range())
406 {
407 nd->set_n_vars_per_group(_reduced_system->number(), nvpg);
408 for (const auto g : index_range(nvpg))
409 nd->set_n_comp_group(_reduced_system->number(), g, 0);
410 }
411 }
412
413 // We don't want to write the reduced system
415
416 _sc_is_initialized = true;
417}
418
419unsigned int StaticCondensationDofMap::n_variables() const { return _reduced_vars.size(); }
420
421const Variable & StaticCondensationDofMap::variable(const unsigned int c) const
422{
423 return _reduced_vars[c];
424}
425
427 std::vector<dof_id_type> & di,
428 const unsigned int vn,
429 int /*p_level*/) const
430{
431 di.clear();
432 di = libmesh_map_find(_elem_to_dof_data, elem->id()).reduced_space_indices[vn];
433}
434
436 std::vector<dof_id_type> &,
437 const unsigned int) const
438{
439 libmesh_error_msg("StaticCondensationDofMap dof indices are only meant to be queried with "
440 "elements, not nodes");
441}
442
444{
446 _elem_to_dof_data.clear();
447 _uncondensed_vars.clear();
448 _reduced_vars.clear();
449 _reduced_sp = nullptr;
450 _reduced_nnz.clear();
451 _reduced_noz.clear();
452 _sc_is_initialized = false;
453}
454}
processor_id_type size() const
processor_id_type rank() const
void set_union(T &data, const unsigned int root_id) const
This base class provides a minimal set of interfaces for satisfying user requests for.
std::size_t compute_dof_info(dof_id_type n_local_dofs)
compute the key degree of freedom information given the local number of degrees of freedom on this pr...
std::vector< dof_id_type > _first_df
First DOF index on processor p.
dof_id_type first_dof(const processor_id_type proc) const
virtual void clear()
This class handles the numbering of degrees of freedom on a mesh.
Definition dof_map.h:181
processor_id_type dof_owner(const dof_id_type dof) const
Definition dof_map.h:815
unsigned int n_variable_groups() const
Definition dof_map.h:733
const DofConstraints & get_dof_constraints() const
Provide a const accessor to the DofConstraints map.
Definition dof_map.h:1177
void dof_indices(const Elem *const elem, std::vector< dof_id_type > &di) const
Definition dof_map.C:2201
const VariableGroup & variable_group(const unsigned int c) const
Definition dof_map.h:2348
bool local_index(dof_id_type dof_index) const
Definition dof_map.h:967
const Variable & variable(const unsigned int c) const override
Definition dof_map.h:2358
void SCALAR_dof_indices(std::vector< dof_id_type > &di, const unsigned int vn, const bool old_dofs=false) const
Fills the vector di with the global degree of freedom indices corresponding to the SCALAR variable vn...
Definition dof_map.C:2605
std::unique_ptr< SparsityPattern::Build > build_sparsity(const MeshBase &mesh, bool calculate_constrained=false, bool use_condensed_system=false) const
Builds a sparsity pattern for matrices using the current degree-of-freedom numbering and coupling.
Definition dof_map.C:63
static constexpr dof_id_type invalid_id
An invalid id to distinguish an uninitialized DofObject.
Definition dof_object.h:473
dof_id_type id() const
Definition dof_object.h:819
This is the base class from which all geometric element types are derived.
Definition elem.h:96
virtual System & add_system(std::string_view system_type, std::string_view name)
Add the system of type system_type named name to the systems array.
FEFamily family
The type of finite element.
Definition fe_type.h:228
This is the MeshBase class.
Definition mesh_base.h:81
A Node is like a Point, but with more information.
Definition node.h:55
const Parallel::Communicator & _communicator
processor_id_type processor_id() const
const Parallel::Communicator & comm() const
virtual const Variable & variable(const unsigned int c) const override
std::unordered_set< unsigned int > _uncondensed_vars
Variables for which we will keep all dofs.
std::vector< dof_id_type > _local_uncondensed_dofs
All the uncondensed degrees of freedom (numbered in the "full" uncondensed + condensed space).
std::unique_ptr< SparsityPattern::Build > _reduced_sp
Owned storage of the reduced system sparsity pattern.
std::unordered_map< dof_id_type, dof_id_type > _full_to_reduced_constraint_dofs
A small map from full system degrees of freedom to reduced/condensed system degrees of freedom involv...
std::vector< Variable > _reduced_vars
The variables in the reduced system.
std::unordered_map< dof_id_type, DofData > _elem_to_dof_data
A map from element ID to Schur complement data.
void add_uncondensed_dof_plus_constraint_dofs(dof_id_type full_dof_number, bool involved_in_constraints, std::unordered_map< dof_id_type, dof_id_type > &uncondensed_global_to_local_map, std::unordered_set< dof_id_type > &local_uncondensed_dofs_set, std::unordered_map< processor_id_type, std::unordered_set< dof_id_type > > &nonlocal_uncondensed_dofs, std::vector< dof_id_type > &elem_uncondensed_dofs, dof_id_type &uncondensed_local_dof_number, std::unordered_set< dof_id_type > &constraint_dofs)
Add an uncondensed dof potentially along with constraining dofs which themselves must/will also be un...
std::vector< dof_id_type > _reduced_noz
Number of off-diagonal nonzeros per row in the reduced system.
void add_uncondensed_dof(dof_id_type full_dof_number, bool involved_in_constraints, std::unordered_map< dof_id_type, dof_id_type > &uncondensed_global_to_local_map, std::unordered_set< dof_id_type > &local_uncondensed_dofs_set, std::unordered_map< processor_id_type, std::unordered_set< dof_id_type > > &nonlocal_uncondensed_dofs, std::vector< dof_id_type > &elem_uncondensed_dofs, dof_id_type &uncondensed_local_dof_number, std::unordered_set< dof_id_type > &constraint_dofs)
Add an uncondensed dof.
std::vector< dof_id_type > _reduced_nnz
Number of on-diagonal nonzeros per row in the reduced system.
System * _reduced_system
A dummyish system to help with DofObjects.
bool _sc_is_initialized
Whether our object has been initialized.
virtual void dof_indices(const Elem *const elem, std::vector< dof_id_type > &di, const unsigned int vn, int p_level=-12345) const override
Fills the vector di with the global degree of freedom indices for the element.
bool initialized() const
Whether we are initialized.
StaticCondensationDofMap(MeshBase &mesh, System &system, const DofMap &dof_map)
virtual unsigned int n_variables() const override
void reinit()
Build the element global to local index maps.
Manages consistently variables, degrees of freedom, and coefficient vectors.
Definition system.h:100
bool is_initialized() const
Definition system.h:2457
bool & hide_output()
Definition system.h:1852
void init()
Initializes degrees of freedom on the current mesh.
Definition system.C:196
unsigned int number() const
Definition system.h:2393
const EquationSystems & get_equation_systems() const
Definition system.h:767
unsigned int number(unsigned int v) const
Definition variable.h:316
This class defines the notion of a variable in the system.
Definition variable.h:51
const std::string & name() const
Definition variable.h:122
unsigned int n_components() const
Definition variable.C:23
const FEType & type() const
Definition variable.h:144
MeshBase & mesh
void pull_parallel_vector_data(const Communicator &comm, const MapToVectors &queries, GatherFunctor &gather_data, const ActionFunctor &act_on_data, const datum *example)
void push_parallel_vector_data(const Communicator &comm, MapToVectors &&data, const ActionFunctor &act_on_data)
The libMesh namespace provides an interface to certain functionality in the library.
auto index_range(const T &sizable)
Helper function that returns an IntRange<std::size_t> representing all the indices of the passed-in v...
Definition int_range.h:153
void libmesh_ignore(const Args &...)
libmesh_assert(ctx)
const unsigned int invalid_uint
A number which is used quite often to represent an invalid or uninitialized value for an unsigned int...
Definition libmesh.h:303
uint8_t dof_id_type
Definition id_types.h:67
uint8_t processor_id_type
Definition id_types.h:104
IntRange< T > make_range(T beg, T end)
The 2-parameter make_range() helper function returns an IntRange<T> when both input parameters are of...
Definition int_range.h:176