LCOV - code coverage report
Current view: top level - src/actions - AddPeriodicBCAction.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: 2bf808 Lines: 142 162 87.7 %
Date: 2025-07-17 01:28:37 Functions: 6 6 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       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 "AddPeriodicBCAction.h"
      11             : 
      12             : // MOOSE includes
      13             : #include "DisplacedProblem.h"
      14             : #include "FEProblem.h"
      15             : #include "FunctionPeriodicBoundary.h"
      16             : #include "GeneratedMesh.h"
      17             : #include "InputParameters.h"
      18             : #include "MooseMesh.h"
      19             : #include "MooseVariableFE.h"
      20             : #include "NonlinearSystem.h"
      21             : #include "RelationshipManager.h"
      22             : 
      23             : #include "libmesh/periodic_boundary.h" // translation PBCs provided by libmesh
      24             : 
      25             : using namespace libMesh;
      26             : 
      27             : registerMooseAction("MooseApp", AddPeriodicBCAction, "add_periodic_bc");
      28             : registerMooseAction("MooseApp", AddPeriodicBCAction, "add_geometric_rm");
      29             : registerMooseAction("MooseApp", AddPeriodicBCAction, "add_algebraic_rm");
      30             : 
      31             : InputParameters
      32         879 : AddPeriodicBCAction::validParams()
      33             : {
      34         879 :   InputParameters params = Action::validParams();
      35         879 :   params.addParam<std::vector<std::string>>("auto_direction",
      36             :                                             "If using a generated mesh, you can "
      37             :                                             "specify just the dimension(s) you "
      38             :                                             "want to mark as periodic");
      39             : 
      40         879 :   params.addParam<BoundaryName>("primary", "Boundary ID associated with the primary boundary.");
      41         879 :   params.addParam<BoundaryName>("secondary", "Boundary ID associated with the secondary boundary.");
      42         879 :   params.addParam<RealVectorValue>("translation",
      43             :                                    "Vector that translates coordinates on the "
      44             :                                    "primary boundary to coordinates on the "
      45             :                                    "secondary boundary.");
      46         879 :   params.addParam<std::vector<std::string>>("transform_func",
      47             :                                             "Functions that specify the transformation");
      48         879 :   params.addParam<std::vector<std::string>>("inv_transform_func",
      49             :                                             "Functions that specify the inverse transformation");
      50             : 
      51         879 :   params.addParam<std::vector<VariableName>>(
      52             :       "variable", {}, "Variable for the periodic boundary condition");
      53         879 :   params.addClassDescription("Action that adds periodic boundary conditions");
      54         879 :   return params;
      55           0 : }
      56             : 
      57         676 : AddPeriodicBCAction::AddPeriodicBCAction(const InputParameters & params)
      58         676 :   : Action(params), _mesh(nullptr)
      59             : {
      60             :   // Check for inconsistent parameters
      61         676 :   if (isParamValid("auto_direction"))
      62             :   {
      63         893 :     if (isParamValid("primary") || isParamValid("secondary") || isParamValid("translation") ||
      64         893 :         isParamValid("transform_func") || isParamValid("inv_transform_func"))
      65           4 :       paramError(
      66             :           "auto_direction",
      67             :           "Using the automatic periodic boundary detection does not require additional parameters");
      68             :   }
      69         377 :   else if (!isParamValid("primary") || !isParamValid("secondary"))
      70           0 :     paramError("primary", "Both a primary and secondary boundary must be specified");
      71         672 : }
      72             : 
      73             : void
      74        1024 : AddPeriodicBCAction::setPeriodicVars(libMesh::PeriodicBoundaryBase & p,
      75             :                                      const std::vector<VariableName> & var_names)
      76             : {
      77             :   // TODO: multi-system
      78        1024 :   if (_problem->numSolverSystems() > 1)
      79           4 :     mooseError("Multiple solver systems currently not supported");
      80             : 
      81        1020 :   NonlinearSystemBase & nl = _problem->getNonlinearSystemBase(/*nl_sys_num=*/0);
      82             :   const std::vector<VariableName> * var_names_ptr;
      83             : 
      84             :   // If var_names is empty - then apply this periodic condition to all variables in the system
      85        1020 :   if (var_names.empty())
      86         268 :     var_names_ptr = &nl.getVariableNames();
      87             :   else
      88         752 :     var_names_ptr = &var_names;
      89             : 
      90             :   // Helper function to apply periodic BC for a given variable number
      91        1060 :   auto applyPeriodicBC = [&](unsigned int var_num, const std::string & var_name)
      92             :   {
      93        1060 :     p.set_variable(var_num);
      94        1060 :     if (_mesh->isRegularOrthogonal())
      95         946 :       _mesh->addPeriodicVariable(var_num, p.myboundary, p.pairedboundary);
      96             :     else
      97         114 :       mooseInfoRepeated("Periodicity information for variable '" + var_name +
      98             :                         "' will only be stored in the system's DoF map, not on the MooseMesh");
      99        1056 :   };
     100             : 
     101             :   // If is an array variable, loop over all of components
     102        2052 :   for (const auto & var_name : *var_names_ptr)
     103             :   {
     104             :     // Exclude scalar variables for which periodic boundary conditions dont make sense
     105        1036 :     if (!nl.hasScalarVariable(var_name))
     106             :     {
     107        1024 :       const auto & var = nl.getVariable(0, var_name);
     108        1024 :       unsigned int var_num = var.number();
     109             : 
     110        1024 :       if (var.fieldType() == Moose::VarFieldType::VAR_FIELD_ARRAY)
     111             :       {
     112          60 :         for (const auto component : make_range(var.count()))
     113          48 :           applyPeriodicBC(var_num + component, var_name + "_" + std::to_string(component));
     114             :       }
     115             :       else
     116        1012 :         applyPeriodicBC(var_num, var_name);
     117             :     }
     118             :   }
     119        1016 : }
     120             : 
     121             : bool
     122         656 : AddPeriodicBCAction::autoTranslationBoundaries()
     123             : {
     124         656 :   auto displaced_problem = _problem->getDisplacedProblem();
     125             : 
     126         656 :   if (isParamValid("auto_direction"))
     127             :   {
     128             :     // If we are working with a parallel mesh then we're going to ghost all the boundaries
     129             :     // everywhere because we don't know what we need...
     130         295 :     if (_mesh->isDistributedMesh())
     131             :     {
     132         107 :       bool is_orthogonal_mesh = _mesh->detectOrthogonalDimRanges();
     133             : 
     134             :       // If we can't detect the orthogonal dimension ranges for this
     135             :       // Mesh, then auto_direction periodicity isn't going to work.
     136         107 :       if (!is_orthogonal_mesh)
     137           0 :         mooseError("Could not detect orthogonal dimension ranges for DistributedMesh.");
     138             :     }
     139             : 
     140         295 :     std::vector<std::string> auto_dirs = getParam<std::vector<std::string>>("auto_direction");
     141             : 
     142         295 :     int dim_offset = _mesh->dimension() - 2;
     143         861 :     for (const auto & dir : auto_dirs)
     144             :     {
     145         566 :       int component = -1;
     146         566 :       if (dir == "X" || dir == "x")
     147         295 :         component = 0;
     148         271 :       else if (dir == "Y" || dir == "y")
     149             :       {
     150         233 :         if (dim_offset < 0)
     151           0 :           mooseError("Cannot wrap 'Y' direction when using a 1D mesh");
     152         233 :         component = 1;
     153             :       }
     154          38 :       else if (dir == "Z" || dir == "z")
     155             :       {
     156          38 :         if (dim_offset <= 0)
     157           0 :           mooseError("Cannot wrap 'Z' direction when using a 1D or 2D mesh");
     158          38 :         component = 2;
     159             :       }
     160             : 
     161         566 :       if (component >= 0)
     162             :       {
     163             :         const std::pair<BoundaryID, BoundaryID> * boundary_ids =
     164         566 :             _mesh->getPairedBoundaryMapping(component);
     165         566 :         RealVectorValue v;
     166         566 :         v(component) = _mesh->dimensionWidth(component);
     167         566 :         PeriodicBoundary p(v);
     168             : 
     169         566 :         if (boundary_ids == nullptr)
     170           0 :           mooseError("Couldn't auto-detect a paired boundary for use with periodic boundary "
     171           0 :                      "conditions in the '" +
     172           0 :                      dir + "' direction");
     173             : 
     174         566 :         p.myboundary = boundary_ids->first;
     175         566 :         p.pairedboundary = boundary_ids->second;
     176         566 :         setPeriodicVars(p, getParam<std::vector<VariableName>>("variable"));
     177         566 :         auto & eq = _problem->es();
     178        1698 :         for (const auto i : make_range(eq.n_systems()))
     179        1132 :           eq.get_system(i).get_dof_map().add_periodic_boundary(p);
     180         566 :         if (displaced_problem)
     181             :         {
     182           0 :           auto & deq = displaced_problem->es();
     183           0 :           for (const auto i : make_range(deq.n_systems()))
     184           0 :             deq.get_system(i).get_dof_map().add_periodic_boundary(p);
     185             :         }
     186         566 :       }
     187             :     }
     188         295 :     return true;
     189         295 :   }
     190         361 :   return false;
     191         656 : }
     192             : 
     193             : void
     194        1964 : AddPeriodicBCAction::act()
     195             : {
     196        1964 :   if (_current_task == "add_geometric_rm")
     197             :     // Tell the mesh to hold off on deleting remote elements because we need to wait for our
     198             :     // periodic boundaries to be added
     199         672 :     Action::_mesh->allowRemoteElementRemoval(false);
     200             : 
     201        1964 :   if (_current_task == "add_algebraic_rm")
     202             :   {
     203         636 :     auto rm_params = _factory.getValidParams("ElementSideNeighborLayers");
     204             : 
     205         636 :     rm_params.set<std::string>("for_whom") = "PeriodicBCs";
     206         636 :     if (!_mesh)
     207           0 :       mooseError("We should have added periodic boundaries and consequently we should have set the "
     208             :                  "_mesh by now");
     209             : 
     210         636 :     rm_params.set<MooseMesh *>("mesh") = _mesh;
     211             :     // The default GhostPointNeighbors ghosting functor in libMesh handles the geometric ghosting
     212             :     // of periodic boundaries for us, so we only need to handle the algebraic ghosting here
     213         636 :     rm_params.set<Moose::RelationshipManagerType>("rm_type") =
     214             :         Moose::RelationshipManagerType::ALGEBRAIC;
     215             : 
     216         636 :     if (rm_params.areAllRequiredParamsValid())
     217             :     {
     218         636 :       auto rm_obj = _factory.create<RelationshipManager>(
     219         636 :           "ElementSideNeighborLayers", "periodic_bc_ghosting_" + name(), rm_params);
     220             : 
     221         636 :       if (!_app.addRelationshipManager(rm_obj))
     222         186 :         _factory.releaseSharedObjects(*rm_obj);
     223         636 :     }
     224             :     else
     225           0 :       mooseError("Invalid initialization of ElementSideNeighborLayers");
     226         636 :   }
     227             : 
     228        1964 :   if (_current_task == "add_periodic_bc")
     229             :   {
     230         656 :     auto & nl = _problem->getNonlinearSystemBase(/*nl_sys_num=*/0);
     231         656 :     _mesh = &_problem->mesh();
     232         656 :     auto displaced_problem = _problem->getDisplacedProblem();
     233             : 
     234         656 :     if (!autoTranslationBoundaries())
     235             :     {
     236             :       // Check that the boundaries exist in the mesh
     237         361 :       const auto & primary_name = getParam<BoundaryName>("primary");
     238         361 :       const auto & secondary_name = getParam<BoundaryName>("secondary");
     239         361 :       if (!MooseMeshUtils::hasBoundaryName(*_mesh, primary_name))
     240           4 :         paramError("primary", "Boundary '" + primary_name + "' does not exist in the mesh");
     241         357 :       if (!MooseMeshUtils::hasBoundaryName(*_mesh, secondary_name))
     242           4 :         paramError("secondary", "Boundary '" + secondary_name + "' does not exist in the mesh");
     243             : 
     244         353 :       if (_pars.isParamValid("translation"))
     245             :       {
     246         244 :         RealVectorValue translation = getParam<RealVectorValue>("translation");
     247             : 
     248         244 :         PeriodicBoundary p(translation);
     249         244 :         p.myboundary = _mesh->getBoundaryID(primary_name);
     250         244 :         p.pairedboundary = _mesh->getBoundaryID(secondary_name);
     251         244 :         setPeriodicVars(p, getParam<std::vector<VariableName>>("variable"));
     252             : 
     253         240 :         auto & eq = _problem->es();
     254         720 :         for (const auto i : make_range(eq.n_systems()))
     255         480 :           eq.get_system(i).get_dof_map().add_periodic_boundary(p);
     256         240 :         if (displaced_problem)
     257             :         {
     258          96 :           auto & deq = displaced_problem->es();
     259         288 :           for (const auto i : make_range(deq.n_systems()))
     260         192 :             deq.get_system(i).get_dof_map().add_periodic_boundary(p);
     261             :         }
     262         240 :       }
     263         109 :       else if (getParam<std::vector<std::string>>("transform_func") != std::vector<std::string>())
     264             :       {
     265             :         std::vector<std::string> inv_fn_names =
     266         109 :             getParam<std::vector<std::string>>("inv_transform_func");
     267         109 :         std::vector<std::string> fn_names = getParam<std::vector<std::string>>("transform_func");
     268             : 
     269             :         // If the user provided a forward transformation, they must also provide an inverse -- we
     270             :         // can't form the inverse of an arbitrary function automatically...
     271         109 :         if (inv_fn_names == std::vector<std::string>())
     272           0 :           mooseError("You must provide an inv_transform_func for FunctionPeriodicBoundary!");
     273             : 
     274         109 :         FunctionPeriodicBoundary pb(*_problem, fn_names);
     275         109 :         pb.myboundary = _mesh->getBoundaryID(primary_name);
     276         109 :         pb.pairedboundary = _mesh->getBoundaryID(secondary_name);
     277         109 :         setPeriodicVars(pb, getParam<std::vector<VariableName>>("variable"));
     278             : 
     279         105 :         FunctionPeriodicBoundary ipb(*_problem, inv_fn_names);
     280         105 :         ipb.myboundary = _mesh->getBoundaryID(secondary_name);   // these are swapped
     281         105 :         ipb.pairedboundary = _mesh->getBoundaryID(primary_name); // these are swapped
     282         105 :         setPeriodicVars(ipb, getParam<std::vector<VariableName>>("variable"));
     283             : 
     284             :         // Add the pair of periodic boundaries to the dof map
     285         105 :         auto & eq = _problem->es();
     286         315 :         for (const auto i : make_range(eq.n_systems()))
     287         210 :           eq.get_system(i).get_dof_map().add_periodic_boundary(pb, ipb);
     288         105 :         if (displaced_problem)
     289             :         {
     290           0 :           auto & deq = displaced_problem->es();
     291           0 :           for (const auto i : make_range(deq.n_systems()))
     292           0 :             deq.get_system(i).get_dof_map().add_periodic_boundary(pb, ipb);
     293             :         }
     294         105 :       }
     295             :       else
     296             :       {
     297           0 :         mooseError(
     298             :             "You have to specify either 'auto_direction', 'translation' or 'trans_func' in your "
     299           0 :             "period boundary section '" +
     300           0 :             _name + "'");
     301             :       }
     302             :     }
     303             : 
     304             :     // Now make sure that the mesh default ghosting functor has its periodic bcs set
     305             :     // TODO: multi-system
     306        1280 :     _mesh->getMesh().default_ghosting().set_periodic_boundaries(
     307         640 :         nl.dofMap().get_periodic_boundaries());
     308         640 :     if (displaced_problem)
     309         192 :       displaced_problem->mesh().getMesh().default_ghosting().set_periodic_boundaries(
     310          96 :           displaced_problem->solverSys(/*nl_sys_num=*/0).dofMap().get_periodic_boundaries());
     311         640 :   }
     312        1948 : }

Generated by: LCOV version 1.14