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 : }