LCOV - code coverage report
Current view: top level - src/base - reduced_dof_map.C (source / functions) Hit Total Coverage
Test: libMesh/libmesh: #4624 (b6265e) with base 1fb77f Lines: 206 225 91.6 %
Date: 2026-11-03 16:49:57 Functions: 41 50 82.0 %
Legend: Lines: hit not hit

          Line data    Source code
       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/reduced_dof_map.h"
      19             : 
      20             : #include "libmesh/dof_map.h"
      21             : #include "libmesh/elem.h"
      22             : #include "libmesh/equation_systems.h"
      23             : #include "libmesh/int_range.h"
      24             : #include "libmesh/mesh_base.h"
      25             : #include "libmesh/system.h"
      26             : #include "timpi/parallel_sync.h"
      27             : 
      28             : #include <algorithm>
      29             : #include <set>
      30             : 
      31             : namespace libMesh
      32             : {
      33          96 : ReducedDofMap::ReducedDofMap(MeshBase & mesh,
      34             :                              System & system,
      35             :                              const DofMap & dof_map,
      36          96 :                              std::string reduced_system_name)
      37             :   : DofMapBase(dof_map.comm()),
      38          32 :     _mesh(mesh),
      39          32 :     _system(system),
      40          32 :     _dof_map(dof_map),
      41          64 :     _reduced_system_name(std::move(reduced_system_name)),
      42          32 :     _parent(nullptr),
      43          32 :     _initialized(false),
      44          96 :     _reduced_system(nullptr)
      45             : {
      46          96 : }
      47             : 
      48          96 : ReducedDofMap::~ReducedDofMap() = default;
      49             : 
      50        4338 : void ReducedDofMap::retained_dof_added(const Elem &, dof_id_type) {}
      51             : 
      52         780 : void ReducedDofMap::unretained_dof_found(const Elem &, dof_id_type) {}
      53             : 
      54     2483979 : bool ReducedDofMap::retains_field_dof(const Elem & elem,
      55             :                                       const unsigned int node_num,
      56             :                                       const unsigned int var_num,
      57             :                                       const dof_id_type field_dof) const
      58             : {
      59     4967628 :   return (!_parent || _parent->retains_field_dof(elem, node_num, var_num, field_dof)) &&
      60     3311642 :          this->retain_field_dof(elem, node_num, var_num, field_dof);
      61             : }
      62             : 
      63         108 : bool ReducedDofMap::retains_scalar_dof(const dof_id_type scalar_dof) const
      64             : {
      65         216 :   return (!_parent || _parent->retains_scalar_dof(scalar_dof)) &&
      66         144 :          this->retain_scalar_dof(scalar_dof);
      67             : }
      68             : 
      69      594801 : void ReducedDofMap::retain_dof(const dof_id_type full_dof)
      70             : {
      71      594801 :   if (_dof_map.local_index(full_dof))
      72      193075 :     _local_retained_set.insert(full_dof);
      73             :   else
      74       25960 :     _nonlocal_retained[_dof_map.dof_owner(full_dof)].insert(full_dof);
      75      594801 : }
      76             : 
      77      670080 : void ReducedDofMap::retain_dof_and_constraining_dofs(const Elem * const elem,
      78             :                                                      const dof_id_type full_dof,
      79             :                                                      std::unordered_set<dof_id_type> & elem_seen,
      80             :                                                      std::vector<dof_id_type> & elem_var_dofs)
      81             : {
      82      670080 :   if (!elem_seen.insert(full_dof).second)
      83       25097 :     return;
      84             : 
      85             :   // A constraining dof belongs to this space only if it belongs to the parent space; one the
      86             :   // parent eliminates still passes its own constraining dofs on
      87      594789 :   if (_parent && _parent->find_reduced_dof(full_dof) == DofObject::invalid_id)
      88             :     {
      89           0 :       this->retain_constraining_dofs(elem, full_dof, elem_seen, elem_var_dofs);
      90           0 :       return;
      91             :     }
      92             : 
      93      594789 :   this->retain_dof(full_dof);
      94      594789 :   elem_var_dofs.push_back(full_dof);
      95      594789 :   this->retained_dof_added(*elem, full_dof);
      96             : 
      97      594789 :   this->retain_constraining_dofs(elem, full_dof, elem_seen, elem_var_dofs);
      98             : }
      99             : 
     100      727599 : void ReducedDofMap::retain_constraining_dofs(const Elem * const elem,
     101             :                                              const dof_id_type full_dof,
     102             :                                              std::unordered_set<dof_id_type> & elem_seen,
     103             :                                              std::vector<dof_id_type> & elem_var_dofs)
     104             : {
     105             : #ifdef LIBMESH_ENABLE_CONSTRAINTS
     106      727599 :   const auto & constraints = _dof_map.get_dof_constraints();
     107      727599 :   if (const auto it = constraints.find(full_dof); it != constraints.end())
     108      236835 :     for (const auto & [constraining_dof, weight] : it->second)
     109             :       {
     110       53127 :         libmesh_ignore(weight);
     111             :         // Constraining dofs may themselves be constrained
     112      159381 :         this->retain_dof_and_constraining_dofs(elem, constraining_dof, elem_seen, elem_var_dofs);
     113             :       }
     114             : #else
     115             :   libmesh_ignore(elem, full_dof, elem_seen, elem_var_dofs);
     116             : #endif
     117      727599 : }
     118             : 
     119         402 : void ReducedDofMap::reinit()
     120             : {
     121         402 :   if (this->initialized())
     122         312 :     this->clear();
     123             : 
     124             :   // SCALAR dofs are always retained. They are owned by the last rank
     125         894 :   for (const auto vg : make_range(_dof_map.n_variable_groups()))
     126         492 :     if (const auto & vg_description = _dof_map.variable_group(vg);
     127         492 :         vg_description.type().family == SCALAR)
     128             :       {
     129           8 :         std::vector<dof_id_type> scalar_dof_indices;
     130          24 :         for (const auto vg_vn : make_range(vg_description.n_variables()))
     131             :           {
     132          16 :             _dof_map.SCALAR_dof_indices(scalar_dof_indices, vg_description.number(vg_vn));
     133          24 :             for (const auto dof : scalar_dof_indices)
     134          12 :               if (this->retains_scalar_dof(dof))
     135          12 :                 this->retain_dof(dof);
     136             :           }
     137             :       }
     138             : 
     139         268 :   std::vector<dof_id_type> elem_dofs; // only used to satisfy the dof_indices API
     140         402 :   std::vector<dof_id_type> * elem_var_dofs = nullptr;
     141         268 :   std::unordered_set<dof_id_type> elem_seen;
     142         268 :   std::set<unsigned int> full_vars_present;
     143             : 
     144          16 :   auto scalar_dofs_functor = [this, &elem_seen, &elem_var_dofs](
     145             :                                  const Elem & elem,
     146             :                                  std::vector<dof_id_type> & dof_indices,
     147         160 :                                  const std::vector<dof_id_type> & scalar_dof_indices) {
     148          64 :     dof_indices.insert(dof_indices.end(), scalar_dof_indices.begin(), scalar_dof_indices.end());
     149          96 :     for (const auto dof : scalar_dof_indices)
     150          48 :       if (this->retains_scalar_dof(dof))
     151          48 :         this->retain_dof_and_constraining_dofs(&elem, dof, elem_seen, *elem_var_dofs);
     152             :       else
     153             :         {
     154           0 :           if (!_dof_map.local_index(dof))
     155           0 :             _nonlocal_unretained.insert(dof);
     156           0 :           this->retain_constraining_dofs(&elem, dof, elem_seen, *elem_var_dofs);
     157             :         }
     158         316 :   };
     159             : 
     160      214487 :   auto field_dofs_functor = [this, &elem_seen, &elem_var_dofs](const Elem & elem,
     161             :                                                               const unsigned int node_num,
     162             :                                                               const unsigned int var_num,
     163             :                                                               std::vector<dof_id_type> & dof_indices,
     164     2587487 :                                                               const dof_id_type field_dof) {
     165      643461 :     dof_indices.push_back(field_dof);
     166      643461 :     if (this->retains_field_dof(elem, node_num, var_num, field_dof))
     167      510651 :       this->retain_dof_and_constraining_dofs(&elem, field_dof, elem_seen, *elem_var_dofs);
     168             :     else
     169             :       {
     170             :         // Only dofs of the parent space belong to this space's problem
     171      132810 :         if (!_parent || _parent->retains_field_dof(elem, node_num, var_num, field_dof))
     172      132645 :           this->unretained_dof_found(elem, field_dof);
     173      132810 :         if (!_dof_map.local_index(field_dof))
     174           6 :           _nonlocal_unretained.insert(field_dof);
     175      132810 :         this->retain_constraining_dofs(&elem, field_dof, elem_seen, *elem_var_dofs);
     176             :       }
     177      643863 :   };
     178             : 
     179       80040 :   for (const auto * const elem : _mesh.active_local_element_ptr_range())
     180             :     {
     181       47622 :       auto & elem_dofs_by_var = _elem_reduced_dofs[elem->id()];
     182       47622 :       elem_dofs_by_var.resize(_dof_map.n_variables());
     183       15874 :       elem_seen.clear();
     184             : 
     185       31748 :       const auto sub_id = elem->subdomain_id();
     186       95925 :       for (const auto vg : make_range(_dof_map.n_variable_groups()))
     187             :         {
     188       48303 :           const auto & var_group = _dof_map.variable_group(vg);
     189       32202 :           if (!var_group.active_on_subdomain(sub_id))
     190           0 :             continue;
     191       97182 :           for (const auto v : make_range(var_group.n_variables()))
     192             :             {
     193       48879 :               const auto var_num = var_group.number(v);
     194       48879 :               elem_var_dofs = &elem_dofs_by_var[var_num];
     195       81465 :               _dof_map.dof_indices(
     196       32586 :                   elem, elem_dofs, var_num, scalar_dofs_functor, field_dofs_functor, elem->p_level());
     197       65172 :               if (!elem_var_dofs->empty())
     198       32074 :                 full_vars_present.insert(var_num);
     199             :             }
     200             :         }
     201         134 :     }
     202             : 
     203             :   //
     204             :   // A rank may own a retained dof that none of its local elements use, for instance a mid-edge
     205             :   // node of a coarse element that is a vertex of a finer element owned elsewhere. Every rank that
     206             :   // retains a nonlocal dof therefore tells the dof's owner
     207             :   //
     208         268 :   std::unordered_map<processor_id_type, std::vector<dof_id_type>> nonlocal_retained;
     209         672 :   for (const auto & [pid, set] : _nonlocal_retained)
     210         180 :     nonlocal_retained[pid].assign(set.begin(), set.end());
     211         134 :   _nonlocal_retained.clear();
     212             : 
     213         670 :   TIMPI::push_parallel_vector_data(
     214         268 :       this->comm(),
     215             :       nonlocal_retained,
     216         314 :       [this](processor_id_type, const std::vector<dof_id_type> & dofs) {
     217         270 :         _local_retained_set.insert(dofs.begin(), dofs.end());
     218         180 :       });
     219             : 
     220         402 :   _local_retained_dofs.assign(_local_retained_set.begin(), _local_retained_set.end());
     221         134 :   _local_retained_set.clear();
     222         402 :   std::sort(_local_retained_dofs.begin(), _local_retained_dofs.end());
     223             : 
     224             :   //
     225             :   // Number the reduced space
     226             :   //
     227         536 :   this->compute_dof_info(_local_retained_dofs.size());
     228             : 
     229         402 :   const auto full_first = _dof_map.first_dof();
     230         536 :   _local_full_to_reduced.assign(_dof_map.n_local_dofs(), DofObject::invalid_id);
     231      258483 :   for (const auto i : index_range(_local_retained_dofs))
     232      430135 :     _local_full_to_reduced[_local_retained_dofs[i] - full_first] = this->first_dof() + i;
     233             : 
     234             :   // Every nonlocal dof on a local element gets its reduced index, which is invalid_id for an
     235             :   // eliminated dof, so that find_reduced_dof() can tell eliminated dofs from unknown ones
     236         420 :   for (const auto dof : _nonlocal_unretained)
     237          18 :     nonlocal_retained[_dof_map.dof_owner(dof)].push_back(dof);
     238         134 :   _nonlocal_unretained.clear();
     239             : 
     240          90 :   auto gather_reduced = [this](processor_id_type,
     241             :                                const std::vector<dof_id_type> & full_dofs,
     242        9879 :                                std::vector<dof_id_type> & reduced_dofs) {
     243         360 :     reduced_dofs.resize(full_dofs.size());
     244        9969 :     for (const auto i : index_range(full_dofs))
     245       12932 :       reduced_dofs[i] = this->find_reduced_dof(full_dofs[i]);
     246         538 :   };
     247          90 :   auto store_reduced = [this](processor_id_type,
     248             :                               const std::vector<dof_id_type> & full_dofs,
     249        6646 :                               const std::vector<dof_id_type> & reduced_dofs) {
     250        9969 :     for (const auto i : index_range(full_dofs))
     251       12932 :       _nonlocal_full_to_reduced[full_dofs[i]] = reduced_dofs[i];
     252         448 :   };
     253         402 :   TIMPI::pull_parallel_vector_data(this->comm(),
     254             :                                    nonlocal_retained,
     255             :                                    gather_reduced,
     256             :                                    store_reduced,
     257             :                                    &DofObject::invalid_id);
     258             : 
     259             :   // The parent indices of the local retained dofs, and the ghosts of a vector on this space
     260         536 :   _local_retained_parent_dofs.resize(_local_retained_dofs.size());
     261      258483 :   for (const auto i : index_range(_local_retained_dofs))
     262      344108 :     _local_retained_parent_dofs[i] = this->parent_dof(_local_retained_dofs[i]);
     263       10101 :   for (const auto & [full_dof, reduced] : _nonlocal_full_to_reduced)
     264             :     {
     265        3233 :       libmesh_ignore(full_dof);
     266        9699 :       if (reduced != DofObject::invalid_id)
     267        9681 :         _send_list.push_back(reduced);
     268             :     }
     269         402 :   std::sort(_send_list.begin(), _send_list.end());
     270             : 
     271             :   //
     272             :   // Determine the variables with any dofs in the reduced space
     273             :   //
     274         402 :   this->comm().set_union(full_vars_present);
     275         536 :   _reduced_vars.reserve(full_vars_present.size());
     276         134 :   unsigned int first_local_number = 0;
     277         870 :   for (const auto full_var_num : full_vars_present)
     278             :     {
     279         468 :       const auto & full_var = _dof_map.variable(full_var_num);
     280         936 :       _reduced_vars.push_back(Variable{nullptr,
     281         156 :                                        full_var.name(),
     282             :                                        cast_int<unsigned int>(_reduced_vars.size()),
     283             :                                        first_local_number,
     284             :                                        full_var.type()});
     285         468 :       first_local_number += _reduced_vars.back().n_components(_mesh);
     286             :     }
     287             : 
     288             :   // Renumber the element dofs, keeping only the variables in the reduced space
     289       48024 :   for (auto & [elem_id, elem_dofs_by_var] : _elem_reduced_dofs)
     290             :     {
     291       15874 :       libmesh_ignore(elem_id);
     292       47622 :       std::vector<std::vector<dof_id_type>> reduced_dofs_by_var;
     293       47622 :       reduced_dofs_by_var.reserve(full_vars_present.size());
     294       95733 :       for (const auto full_var_num : full_vars_present)
     295             :         {
     296       16037 :           auto & var_dofs = reduced_dofs_by_var.emplace_back(
     297       64148 :               std::move(elem_dofs_by_var[full_var_num]));
     298      642900 :           for (auto & dof : var_dofs)
     299      594789 :             dof = this->reduced_dof(dof);
     300             :         }
     301       15874 :       elem_dofs_by_var = std::move(reduced_dofs_by_var);
     302       15874 :     }
     303             : 
     304             :   // The sparsity pattern of the reduced space, with rows indexed by the local full dofs
     305         670 :   _reduced_sp = _dof_map.build_sparsity(_mesh, /*calculate_constrained=*/false, this);
     306         134 :   const auto & nnz = _reduced_sp->get_n_nz();
     307         134 :   const auto & noz = _reduced_sp->get_n_oz();
     308         134 :   libmesh_assert_equal_to(nnz.size(), noz.size());
     309             : 
     310             :   // Exact per-row preallocation counts. These are critical when SCALAR dofs introduce dense rows,
     311             :   // since the maximum row count would then allocate a dense matrix
     312         536 :   _reduced_nnz.resize(_local_retained_dofs.size());
     313         536 :   _reduced_noz.resize(_local_retained_dofs.size());
     314      258483 :   for (const auto i : index_range(_local_retained_dofs))
     315             :     {
     316      258081 :       const dof_id_type local_full_i = _local_retained_dofs[i] - full_first;
     317       86027 :       libmesh_assert_less(local_full_i, nnz.size());
     318      344108 :       _reduced_nnz[i] = nnz[local_full_i];
     319      430135 :       _reduced_noz[i] = noz[local_full_i];
     320             :     }
     321             : 
     322             :   //
     323             :   // The stand-in system gives the reduced variables a system number. Nodes hold no reduced dofs,
     324             :   // since reduced dofs are queried by element only
     325             :   //
     326         536 :   const std::vector<unsigned int> nvpg(_reduced_vars.size(), 1);
     327             : 
     328             :   // add_system returns the existing system if there is one, so this is safe on every reinit
     329         402 :   _reduced_system = &_system.get_equation_systems().add_system("Basic", _reduced_system_name);
     330         402 :   if (!_reduced_system->is_initialized())
     331             :     {
     332          84 :       _reduced_system->init();
     333       34076 :       for (auto * const nd : _mesh.active_node_ptr_range())
     334             :         {
     335       25452 :           nd->set_n_vars_per_group(_reduced_system->number(), nvpg);
     336       53946 :           for (const auto g : index_range(nvpg))
     337       28494 :             nd->set_n_comp_group(_reduced_system->number(), g, 0);
     338          28 :         }
     339             :     }
     340             : 
     341             :   // The stand-in system holds no solution data worth writing
     342         402 :   _reduced_system->hide_output() = true;
     343             : 
     344         402 :   _initialized = true;
     345         402 : }
     346             : 
     347      934674 : dof_id_type ReducedDofMap::find_reduced_dof(const dof_id_type full_dof) const
     348             : {
     349      934674 :   if (_dof_map.local_index(full_dof))
     350     1187824 :     return _local_full_to_reduced[full_dof - _dof_map.first_dof()];
     351       43806 :   return libmesh_map_find(_nonlocal_full_to_reduced, full_dof);
     352             : }
     353             : 
     354      980397 : dof_id_type ReducedDofMap::parent_dof(const dof_id_type full_dof) const
     355             : {
     356      980397 :   return _parent ? _parent->find_reduced_dof(full_dof) : full_dof;
     357             : }
     358             : 
     359         720 : dof_id_type ReducedDofMap::parent_n_dofs() const
     360             : {
     361         720 :   return _parent ? _parent->n_dofs() : _dof_map.n_dofs();
     362             : }
     363             : 
     364         720 : dof_id_type ReducedDofMap::parent_n_local_dofs() const
     365             : {
     366         720 :   return _parent ? _parent->n_local_dofs() : _dof_map.n_local_dofs();
     367             : }
     368             : 
     369           0 : dof_id_type ReducedDofMap::parent_first_dof() const
     370             : {
     371           0 :   return _parent ? _parent->first_dof() : _dof_map.first_dof();
     372             : }
     373             : 
     374           0 : dof_id_type ReducedDofMap::parent_end_dof() const
     375             : {
     376           0 :   return _parent ? _parent->end_dof() : _dof_map.end_dof();
     377             : }
     378             : 
     379         360 : const std::vector<dof_id_type> & ReducedDofMap::parent_send_list() const
     380             : {
     381         360 :   return _parent ? _parent->send_list() : _dof_map.get_send_list();
     382             : }
     383             : 
     384      595344 : dof_id_type ReducedDofMap::reduced_dof(const dof_id_type full_dof) const
     385             : {
     386      595344 :   const auto reduced = this->find_reduced_dof(full_dof);
     387      198448 :   libmesh_assert_msg(reduced != DofObject::invalid_id,
     388             :                      "Full dof " << full_dof << " is not retained in the reduced space");
     389      595344 :   return reduced;
     390             : }
     391             : 
     392             : const std::vector<std::vector<dof_id_type>> &
     393      178113 : ReducedDofMap::elem_dof_indices(const dof_id_type elem_id) const
     394             : {
     395      178113 :   return libmesh_map_find(_elem_reduced_dofs, elem_id);
     396             : }
     397             : 
     398           0 : unsigned int ReducedDofMap::n_variables() const { return _reduced_vars.size(); }
     399             : 
     400           0 : const Variable & ReducedDofMap::variable(const unsigned int c) const
     401             : {
     402           0 :   libmesh_assert_less(c, _reduced_vars.size());
     403           0 :   return _reduced_vars[c];
     404             : }
     405             : 
     406           0 : void ReducedDofMap::dof_indices(const Elem * const elem,
     407             :                                 std::vector<dof_id_type> & di,
     408             :                                 const unsigned int vn,
     409             :                                 int /*p_level*/) const
     410             : {
     411           0 :   di = libmesh_map_find(_elem_reduced_dofs, elem->id())[vn];
     412           0 : }
     413             : 
     414           0 : void ReducedDofMap::dof_indices(const Node * const, std::vector<dof_id_type> &, const unsigned int) const
     415             : {
     416           0 :   libmesh_error_msg("ReducedDofMap dof indices are only meant to be queried with elements, not "
     417             :                     "nodes");
     418             : }
     419             : 
     420         408 : void ReducedDofMap::clear()
     421             : {
     422         408 :   DofMapBase::clear();
     423         136 :   _local_retained_dofs.clear();
     424         136 :   _local_retained_parent_dofs.clear();
     425         136 :   _send_list.clear();
     426         136 :   _local_full_to_reduced.clear();
     427         136 :   _nonlocal_full_to_reduced.clear();
     428         136 :   _local_retained_set.clear();
     429         136 :   _nonlocal_retained.clear();
     430         136 :   _nonlocal_unretained.clear();
     431         136 :   _elem_reduced_dofs.clear();
     432         272 :   _reduced_vars.clear();
     433         136 :   _reduced_sp = nullptr;
     434         136 :   _reduced_nnz.clear();
     435         136 :   _reduced_noz.clear();
     436         408 :   _initialized = false;
     437         408 : }
     438             : }

Generated by: LCOV version 1.14