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 935 : AddPeriodicBCAction::validParams() 33 : { 34 935 : InputParameters params = Action::validParams(); 35 935 : 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 935 : params.addParam<BoundaryName>("primary", "Boundary ID associated with the primary boundary."); 41 935 : params.addParam<BoundaryName>("secondary", "Boundary ID associated with the secondary boundary."); 42 935 : params.addParam<RealVectorValue>("translation", 43 : "Vector that translates coordinates on the " 44 : "primary boundary to coordinates on the " 45 : "secondary boundary."); 46 935 : params.addParam<std::vector<std::string>>("transform_func", 47 : "Functions that specify the transformation"); 48 935 : params.addParam<std::vector<std::string>>("inv_transform_func", 49 : "Functions that specify the inverse transformation"); 50 : 51 935 : params.addParam<std::vector<VariableName>>( 52 : "variable", {}, "Variable for the periodic boundary condition"); 53 935 : params.addClassDescription("Action that adds periodic boundary conditions"); 54 935 : return params; 55 0 : } 56 : 57 732 : AddPeriodicBCAction::AddPeriodicBCAction(const InputParameters & params) 58 732 : : Action(params), _mesh(nullptr) 59 : { 60 : // Check for inconsistent parameters 61 732 : if (isParamValid("auto_direction")) 62 : { 63 971 : if (isParamValid("primary") || isParamValid("secondary") || isParamValid("translation") || 64 971 : 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 407 : else if (!isParamValid("primary") || !isParamValid("secondary")) 70 0 : paramError("primary", "Both a primary and secondary boundary must be specified"); 71 728 : } 72 : 73 : void 74 1113 : AddPeriodicBCAction::setPeriodicVars(libMesh::PeriodicBoundaryBase & p, 75 : const std::vector<VariableName> & var_names) 76 : { 77 : // TODO: multi-system 78 1113 : if (_problem->numSolverSystems() > 1) 79 4 : mooseError("Multiple solver systems currently not supported"); 80 : 81 1109 : 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 1109 : if (var_names.empty()) 86 291 : var_names_ptr = &nl.getVariableNames(); 87 : else 88 818 : var_names_ptr = &var_names; 89 : 90 : // Helper function to apply periodic BC for a given variable number 91 1152 : auto applyPeriodicBC = [&](unsigned int var_num, const std::string & var_name) 92 : { 93 1152 : p.set_variable(var_num); 94 1152 : if (_mesh->isRegularOrthogonal()) 95 1028 : _mesh->addPeriodicVariable(var_num, p.myboundary, p.pairedboundary); 96 : else 97 124 : mooseInfoRepeated("Periodicity information for variable '" + var_name + 98 : "' will only be stored in the system's DoF map, not on the MooseMesh"); 99 1148 : }; 100 : 101 : // If is an array variable, loop over all of components 102 2231 : for (const auto & var_name : *var_names_ptr) 103 : { 104 : // Exclude scalar variables for which periodic boundary conditions dont make sense 105 1126 : if (!nl.hasScalarVariable(var_name)) 106 : { 107 1113 : const auto & var = nl.getVariable(0, var_name); 108 1113 : unsigned int var_num = var.number(); 109 : 110 1113 : if (var.fieldType() == Moose::VarFieldType::VAR_FIELD_ARRAY) 111 : { 112 65 : for (const auto component : make_range(var.count())) 113 52 : applyPeriodicBC(var_num + component, var_name + "_" + std::to_string(component)); 114 : } 115 : else 116 1100 : applyPeriodicBC(var_num, var_name); 117 : } 118 : } 119 1105 : } 120 : 121 : bool 122 712 : AddPeriodicBCAction::autoTranslationBoundaries() 123 : { 124 712 : auto displaced_problem = _problem->getDisplacedProblem(); 125 : 126 712 : 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 321 : if (_mesh->isDistributedMesh()) 131 : { 132 115 : 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 115 : if (!is_orthogonal_mesh) 137 0 : mooseError("Could not detect orthogonal dimension ranges for DistributedMesh."); 138 : } 139 : 140 321 : std::vector<std::string> auto_dirs = getParam<std::vector<std::string>>("auto_direction"); 141 : 142 321 : int dim_offset = _mesh->dimension() - 2; 143 937 : for (const auto & dir : auto_dirs) 144 : { 145 616 : int component = -1; 146 616 : if (dir == "X" || dir == "x") 147 321 : component = 0; 148 295 : else if (dir == "Y" || dir == "y") 149 : { 150 253 : if (dim_offset < 0) 151 0 : mooseError("Cannot wrap 'Y' direction when using a 1D mesh"); 152 253 : component = 1; 153 : } 154 42 : else if (dir == "Z" || dir == "z") 155 : { 156 42 : if (dim_offset <= 0) 157 0 : mooseError("Cannot wrap 'Z' direction when using a 1D or 2D mesh"); 158 42 : component = 2; 159 : } 160 : 161 616 : if (component >= 0) 162 : { 163 : const std::pair<BoundaryID, BoundaryID> * boundary_ids = 164 616 : _mesh->getPairedBoundaryMapping(component); 165 616 : RealVectorValue v; 166 616 : v(component) = _mesh->dimensionWidth(component); 167 616 : PeriodicBoundary p(v); 168 : 169 616 : 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 616 : p.myboundary = boundary_ids->first; 175 616 : p.pairedboundary = boundary_ids->second; 176 616 : setPeriodicVars(p, getParam<std::vector<VariableName>>("variable")); 177 616 : auto & eq = _problem->es(); 178 1848 : for (const auto i : make_range(eq.n_systems())) 179 1232 : eq.get_system(i).get_dof_map().add_periodic_boundary(p); 180 616 : 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 616 : } 187 : } 188 321 : return true; 189 321 : } 190 391 : return false; 191 712 : } 192 : 193 : void 194 2132 : AddPeriodicBCAction::act() 195 : { 196 2132 : 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 728 : Action::_mesh->allowRemoteElementRemoval(false); 200 : 201 2132 : if (_current_task == "add_algebraic_rm") 202 : { 203 692 : auto rm_params = _factory.getValidParams("ElementSideNeighborLayers"); 204 : 205 692 : rm_params.set<std::string>("for_whom") = "PeriodicBCs"; 206 692 : if (!_mesh) 207 0 : mooseError("We should have added periodic boundaries and consequently we should have set the " 208 : "_mesh by now"); 209 : 210 692 : 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 692 : rm_params.set<Moose::RelationshipManagerType>("rm_type") = 214 : Moose::RelationshipManagerType::ALGEBRAIC; 215 : 216 692 : if (rm_params.areAllRequiredParamsValid()) 217 : { 218 692 : auto rm_obj = _factory.create<RelationshipManager>( 219 692 : "ElementSideNeighborLayers", "periodic_bc_ghosting_" + name(), rm_params); 220 : 221 692 : if (!_app.addRelationshipManager(rm_obj)) 222 202 : _factory.releaseSharedObjects(*rm_obj); 223 692 : } 224 : else 225 0 : mooseError("Invalid initialization of ElementSideNeighborLayers"); 226 692 : } 227 : 228 2132 : if (_current_task == "add_periodic_bc") 229 : { 230 712 : auto & nl = _problem->getNonlinearSystemBase(/*nl_sys_num=*/0); 231 712 : _mesh = &_problem->mesh(); 232 712 : auto displaced_problem = _problem->getDisplacedProblem(); 233 : 234 712 : if (!autoTranslationBoundaries()) 235 : { 236 : // Check that the boundaries exist in the mesh 237 391 : const auto & primary_name = getParam<BoundaryName>("primary"); 238 391 : const auto & secondary_name = getParam<BoundaryName>("secondary"); 239 391 : if (!MooseMeshUtils::hasBoundaryName(*_mesh, primary_name)) 240 4 : paramError("primary", "Boundary '" + primary_name + "' does not exist in the mesh"); 241 387 : if (!MooseMeshUtils::hasBoundaryName(*_mesh, secondary_name)) 242 4 : paramError("secondary", "Boundary '" + secondary_name + "' does not exist in the mesh"); 243 : 244 383 : if (_pars.isParamValid("translation")) 245 : { 246 265 : RealVectorValue translation = getParam<RealVectorValue>("translation"); 247 : 248 265 : PeriodicBoundary p(translation); 249 265 : p.myboundary = _mesh->getBoundaryID(primary_name); 250 265 : p.pairedboundary = _mesh->getBoundaryID(secondary_name); 251 265 : setPeriodicVars(p, getParam<std::vector<VariableName>>("variable")); 252 : 253 261 : auto & eq = _problem->es(); 254 783 : for (const auto i : make_range(eq.n_systems())) 255 522 : eq.get_system(i).get_dof_map().add_periodic_boundary(p); 256 261 : if (displaced_problem) 257 : { 258 104 : auto & deq = displaced_problem->es(); 259 312 : for (const auto i : make_range(deq.n_systems())) 260 208 : deq.get_system(i).get_dof_map().add_periodic_boundary(p); 261 : } 262 261 : } 263 118 : else if (getParam<std::vector<std::string>>("transform_func") != std::vector<std::string>()) 264 : { 265 : std::vector<std::string> inv_fn_names = 266 118 : getParam<std::vector<std::string>>("inv_transform_func"); 267 118 : 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 118 : if (inv_fn_names == std::vector<std::string>()) 272 0 : mooseError("You must provide an inv_transform_func for FunctionPeriodicBoundary!"); 273 : 274 118 : FunctionPeriodicBoundary pb(*_problem, fn_names); 275 118 : pb.myboundary = _mesh->getBoundaryID(primary_name); 276 118 : pb.pairedboundary = _mesh->getBoundaryID(secondary_name); 277 118 : setPeriodicVars(pb, getParam<std::vector<VariableName>>("variable")); 278 : 279 114 : FunctionPeriodicBoundary ipb(*_problem, inv_fn_names); 280 114 : ipb.myboundary = _mesh->getBoundaryID(secondary_name); // these are swapped 281 114 : ipb.pairedboundary = _mesh->getBoundaryID(primary_name); // these are swapped 282 114 : setPeriodicVars(ipb, getParam<std::vector<VariableName>>("variable")); 283 : 284 : // Add the pair of periodic boundaries to the dof map 285 114 : auto & eq = _problem->es(); 286 342 : for (const auto i : make_range(eq.n_systems())) 287 228 : eq.get_system(i).get_dof_map().add_periodic_boundary(pb, ipb); 288 114 : 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 114 : } 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 1392 : _mesh->getMesh().default_ghosting().set_periodic_boundaries( 307 696 : nl.dofMap().get_periodic_boundaries()); 308 696 : if (displaced_problem) 309 208 : displaced_problem->mesh().getMesh().default_ghosting().set_periodic_boundaries( 310 104 : displaced_problem->solverSys(/*nl_sys_num=*/0).dofMap().get_periodic_boundaries()); 311 696 : } 312 2116 : }