LCOV - code coverage report
Current view: top level - src/actioncomponents - ComponentJunction.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: #32971 (54bef8) with base c6cf66 Lines: 194 211 91.9 %
Date: 2026-05-29 20:35:17 Functions: 5 7 71.4 %
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             : // MOOSE includes
      11             : #include "ComponentJunction.h"
      12             : #include "MooseUtils.h"
      13             : 
      14             : registerMooseAction("MooseApp", ComponentJunction, "add_mesh_generator");
      15             : // ComponentJunction is an example of ComponentPhysicsInterface
      16             : registerMooseAction("MooseApp", ComponentJunction, "init_component_physics");
      17             : // ComponentJunction is an example of ComponentMaterialPropertyInterface
      18             : registerMooseAction("MooseApp", ComponentJunction, "add_material");
      19             : // ComponentJunction is an example of ComponentInitialConditionInterface
      20             : registerMooseAction("MooseApp", ComponentJunction, "check_integrity");
      21             : registerActionComponent("MooseApp", ComponentJunction);
      22             : 
      23             : InputParameters
      24         150 : ComponentJunction::validParams()
      25             : {
      26         150 :   InputParameters params = ActionComponent::validParams();
      27         150 :   params += ComponentPhysicsInterface::validParams();
      28         150 :   params += ComponentMaterialPropertyInterface::validParams();
      29         150 :   params += ComponentInitialConditionInterface::validParams();
      30         150 :   params += ComponentBoundaryConditionInterface::validParams();
      31             : 
      32         300 :   params.addClassDescription("Component to join two other components.");
      33             : 
      34         600 :   params.addRequiredParam<ComponentName>("first_component", "First component to join");
      35         600 :   params.addRequiredParam<BoundaryName>("first_boundary", "First boundary to connect to.");
      36         600 :   params.addRequiredParam<ComponentName>("second_component", "Second component to join");
      37         600 :   params.addRequiredParam<BoundaryName>("second_boundary", "Second boundary to connect to.");
      38             : 
      39         600 :   MooseEnum junction_type("stitch_meshes extrude_boundary", "extrude_boundary");
      40         600 :   params.addParam<MooseEnum>("junction_method", junction_type, "How to join the two components");
      41             : 
      42             :   /* Stitching parameters */
      43         450 :   params.addParam<bool>("enforce_all_nodes_match_on_boundaries",
      44         300 :                         true,
      45             :                         "Only stitch if all nodes match on the boundary. Defaults to true because "
      46             :                         "there is a search algorithm that forces nodes to match assuming there are "
      47             :                         "equal number of nodes on each target boundary.");
      48             : 
      49             :   /* Meshing the gap parameters */
      50             :   // Parameters for the region between meshes
      51         600 :   params.addParam<unsigned int>("n_elem_normal",
      52             :                                 "Number of elements in the normal direction of the junction");
      53         600 :   params.addParam<SubdomainName>("block", "Block name for the junction, if a block is created.");
      54             : 
      55             :   // Parameters for changing radius -- final radius will be calculated under the hood
      56         600 :   MooseEnum radial_growth_methods("LINEAR CUBIC", "CUBIC");
      57         600 :   params.addParam<MooseEnum>("radial_growth_method",
      58             :                              radial_growth_methods,
      59             :                              "Functional form to change radius while extruding along curve.");
      60         600 :   params.addParam<Real>("start_radial_growth_rate", 0, "Starting rate of radial expansion.");
      61         600 :   params.addParam<Real>("end_radial_growth_rate", 0, "Ending rate of radial expansion.");
      62             : 
      63             :   // Parameters for the 1D spline joining the two components (serving as an extrusion guide for
      64             :   // 2,3D)
      65         900 :   params.addRangeCheckedParam<libMesh::Real>("sharpness",
      66             :                                              "sharpness>0 & sharpness<=1",
      67             :                                              "Sharpness of curve bend. See BSplineCurveGenerator "
      68             :                                              "for explanation of the meaning given to sharpness");
      69         450 :   params.addParam<unsigned int>(
      70             :       "num_cps",
      71         300 :       6,
      72             :       "Number of control points used to draw the curve. Miniumum of degree+1 points are required.");
      73         600 :   MooseEnum edge_elem_type("EDGE2 EDGE3 EDGE4", "EDGE2");
      74         600 :   params.addParam<MooseEnum>(
      75             :       "edge_element_type", edge_elem_type, "Type of the EDGE elements to be generated.");
      76             : 
      77         600 :   params.addParamNamesToGroup("sharpness num_cps edge_element_type", "1D mesh junction");
      78         450 :   params.addParamNamesToGroup(
      79             :       "radial_growth_method start_radial_growth_rate end_radial_growth_rate",
      80             :       "Radial expansion in 2D and 3D junction");
      81             : 
      82         300 :   return params;
      83         150 : }
      84             : 
      85         126 : ComponentJunction::ComponentJunction(const InputParameters & params)
      86             :   : ActionComponent(params),
      87             :     ComponentPhysicsInterface(params),
      88             :     ComponentMaterialPropertyInterface(params),
      89             :     ComponentInitialConditionInterface(params),
      90             :     ComponentBoundaryConditionInterface(params),
      91         126 :     _junction_method(getParam<MooseEnum>("junction_method")),
      92         378 :     _enforce_all_nodes_match_on_boundaries(getParam<bool>("enforce_all_nodes_match_on_boundaries"))
      93             : {
      94         252 :   addRequiredTask("add_mesh_generator");
      95             : 
      96             :   // Check parameters
      97         126 :   if (_junction_method != "extrude_boundary")
      98         490 :     errorDependentParameter("junction_method",
      99             :                             "extrude_boundary",
     100             :                             {"n_elem_normal",
     101             :                              "block",
     102             :                              "radial_growth_method",
     103             :                              "start_radial_growth_rate",
     104             :                              "end_radial_growth_rate",
     105             :                              "sharpness",
     106             :                              "num_cps",
     107             :                              "edge_element_type"});
     108             :   else
     109             :   {
     110         168 :     if (!isParamValid("n_elem_normal"))
     111           0 :       paramError("n_elem_normal", "Should be specified if junction_method = 'extrude_boundary'");
     112             :   }
     113             :   // The 1D and 2D meshing parameters will be re-checked later, once we know the dimension
     114         126 : }
     115             : 
     116             : void
     117         126 : ComponentJunction::addMeshGenerators()
     118             : {
     119             :   auto & first_component =
     120         252 :       _awh.getAction<ActionComponent>(getParam<ComponentName>("first_component"));
     121             :   auto & second_component =
     122         252 :       _awh.getAction<ActionComponent>(getParam<ComponentName>("second_component"));
     123         252 :   const auto first_boundary = getParam<BoundaryName>("first_boundary");
     124         252 :   const auto second_boundary = getParam<BoundaryName>("second_boundary");
     125             : 
     126             :   // Get the dimension of the components
     127         126 :   const auto dimension_first = first_component.dimension();
     128         126 :   const auto dimension_second = second_component.dimension();
     129             : 
     130         126 :   if (dimension_first == 0 || dimension_second == 0)
     131           0 :     mooseError("Connecting 0 dimension meshes not implemented!");
     132             : 
     133             :   // Perform junction
     134         126 :   if (_junction_method == "stitch_meshes")
     135             :   {
     136             :     // Fairly easy to stitch this
     137          70 :     if (dimension_first == dimension_second)
     138             :     {
     139          70 :       if (first_component.getCurrentTopLevelMeshGeneratorName() !=
     140         140 :           second_component.getCurrentTopLevelMeshGeneratorName())
     141             :       { // Stitch the two meshes
     142         120 :         InputParameters params = _factory.getValidParams("StitchMeshGenerator");
     143         120 :         params.set<std::vector<MeshGeneratorName>>("inputs") = {
     144             :             first_component.getCurrentTopLevelMeshGeneratorName(),
     145         240 :             second_component.getCurrentTopLevelMeshGeneratorName()};
     146         120 :         params.set<std::vector<std::vector<std::string>>>("stitch_boundaries_pairs") = {
     147         300 :             {first_boundary, second_boundary}};
     148          60 :         params.set<bool>("verbose_stitching") = _verbose;
     149          60 :         params.set<bool>("enforce_all_nodes_match_on_boundaries") =
     150          60 :             _enforce_all_nodes_match_on_boundaries;
     151         120 :         _app.getMeshGeneratorSystem().addMeshGenerator(
     152         120 :             "StitchMeshGenerator", name() + "_base", params);
     153          60 :         _mg_names.push_back(name() + "_base");
     154          60 :       }
     155             :       else
     156             :       {
     157             :         // Handles case of needing to close a mesh. Using StitchBoundaryMeshGenerator prevents
     158             :         // issues with element overlap.
     159          20 :         InputParameters params = _factory.getValidParams("StitchBoundaryMeshGenerator");
     160          20 :         params.set<MeshGeneratorName>("input") =
     161          30 :             first_component.getCurrentTopLevelMeshGeneratorName();
     162          20 :         params.set<std::vector<std::vector<std::string>>>("stitch_boundaries_pairs") = {
     163          50 :             {first_boundary, second_boundary}};
     164          10 :         params.set<bool>("show_info") = _verbose;
     165          20 :         _app.getMeshGeneratorSystem().addMeshGenerator(
     166          20 :             "StitchBoundaryMeshGenerator", name() + "_close", params);
     167          10 :         _mg_names.push_back(name() + "_close");
     168          10 :       }
     169             :     }
     170             :     else
     171           0 :       mooseError("Stiching meshes of different dimensions is not implemented");
     172             :   }
     173             : 
     174          56 :   else if (_junction_method == "extrude_boundary")
     175             :   {
     176             :     //
     177             :     // This junction method is set to use a B-Spline to draw a 1D curve between, then extrude along
     178             :     // that spline
     179             :     //
     180             : 
     181             :     // find start and end directions (may need to take the negative of the end direction).
     182             :     // obey user parameters if set
     183             :     // TODO: modify the spline curve generator to figure out the direction so we can support other
     184             :     // component types
     185             :     // TODO: this really should be the surface direction, not the component direction. Maybe all
     186             :     // this should be deleted and replaced by logic in the Spline curve generator instead.
     187         112 :     auto get_direction = [this](const auto & component, const auto & param_name) -> RealVectorValue
     188             :     {
     189         112 :       const auto & cname = component.name();
     190         112 :       if (dynamic_cast<const ComponentMeshTransformHelper *>(
     191         112 :               &_awh.getAction<ActionComponent>(cname)))
     192         112 :         return _awh.getAction<ComponentMeshTransformHelper>(cname).direction();
     193           0 :       else if (component.isParamValid("direction"))
     194           0 :         return component.template getParam<RealVectorValue>("direction");
     195             :       else
     196           0 :         paramError(param_name,
     197             :                    "Only components inheriting from 'ComponentMeshTransformerHelper' or with a "
     198             :                    "'direction' parameter are supported at this time");
     199          56 :     };
     200             : 
     201          56 :     RealVectorValue start_direction = get_direction(first_component, "first_component");
     202          56 :     RealVectorValue end_direction = get_direction(second_component, "second_component");
     203             : 
     204         168 :     InputParameters bspline_params = _factory.getValidParams("BSplineCurveGenerator");
     205          56 :     bspline_params.set<RealVectorValue>("start_direction") = start_direction;
     206         112 :     bspline_params.set<RealVectorValue>("end_direction") = -end_direction;
     207         224 :     bspline_params.set<unsigned int>("num_elements") = getParam<unsigned int>("n_elem_normal");
     208         168 :     if (isParamValid("sharpness"))
     209           0 :       bspline_params.set<Real>("sharpness") = getParam<Real>("sharpness");
     210         224 :     bspline_params.set<unsigned int>("num_cps") = getParam<unsigned int>("num_cps");
     211         112 :     bspline_params.set<MeshGeneratorName>("start_mesh") =
     212         112 :         first_component.meshGeneratorNames().back();
     213         112 :     bspline_params.set<MeshGeneratorName>("end_mesh") =
     214         112 :         second_component.meshGeneratorNames().back();
     215         112 :     bspline_params.set<BoundaryName>("boundary_providing_start_point") =
     216         224 :         getParam<BoundaryName>("first_boundary");
     217         112 :     bspline_params.set<BoundaryName>("boundary_providing_end_point") =
     218         224 :         getParam<BoundaryName>("second_boundary");
     219             : 
     220          56 :     if (dimension_first == 1)
     221             :     {
     222         120 :       if (isParamValid("block"))
     223          40 :         bspline_params.set<SubdomainName>("new_subdomain_name") = getParam<SubdomainName>("block");
     224          80 :       bspline_params.set<std::vector<BoundaryName>>("edge_nodesets") = {
     225         240 :           name() + "_bspline_start_node", name() + "_bspline_end_node"};
     226         120 :       bspline_params.set<bool>("output") = _verbose;
     227             :     }
     228             : 
     229         112 :     _app.getMeshGeneratorSystem().addMeshGenerator(
     230         112 :         "BSplineCurveGenerator", name() + "_curve", bspline_params);
     231          56 :     _mg_names.push_back(name() + "_curve");
     232             : 
     233             :     // Extrude boundary from first component
     234          56 :     if (dimension_first > 1)
     235             :     {
     236             :       // create lower dimension mesh on the boundary
     237          32 :       InputParameters ld_source_params = _factory.getValidParams("LowerDBlockFromSidesetGenerator");
     238          32 :       ld_source_params.set<MeshGeneratorName>("input") =
     239          32 :           first_component.meshGeneratorNames().back();
     240          32 :       ld_source_params.set<std::vector<BoundaryName>>("sidesets") =
     241          80 :           std::vector{getParam<BoundaryName>("first_boundary")};
     242          32 :       ld_source_params.set<SubdomainName>("new_block_name") =
     243          48 :           (SubdomainName)(name() + "_LowerDBlockSource");
     244          32 :       _app.getMeshGeneratorSystem().addMeshGenerator(
     245          32 :           "LowerDBlockFromSidesetGenerator", name() + "_lowerDGenerationSource", ld_source_params);
     246          16 :       _mg_names.push_back(name() + "_lowerDGenerationSource");
     247             : 
     248          32 :       InputParameters _bmc_source_params = _factory.getValidParams("BlockToMeshConverterGenerator");
     249          48 :       _bmc_source_params.set<MeshGeneratorName>("input") = name() + "_lowerDGenerationSource";
     250          32 :       _bmc_source_params.set<std::vector<SubdomainName>>("target_blocks") = {
     251          64 :           (SubdomainName)(name() + "_LowerDBlockSource")};
     252          32 :       _app.getMeshGeneratorSystem().addMeshGenerator(
     253          32 :           "BlockToMeshConverterGenerator", name() + "_blockToMeshSource", _bmc_source_params);
     254          16 :       _mg_names.push_back(name() + "_blockToMeshSource");
     255             : 
     256             :       // set up AdvancedExtruderGenerator
     257          32 :       InputParameters aeg_params = _factory.getValidParams("AdvancedExtruderGenerator");
     258          48 :       aeg_params.set<MeshGeneratorName>("extrusion_curve") = (MeshGeneratorName)(name() + "_curve");
     259          32 :       aeg_params.set<MeshGeneratorName>("input") =
     260          48 :           (MeshGeneratorName)(name() + "_blockToMeshSource");
     261             : 
     262          32 :       aeg_params.set<RealVectorValue>("start_extrusion_direction") = start_direction;
     263          16 :       aeg_params.set<RealVectorValue>("end_extrusion_direction") = end_direction;
     264             : 
     265          64 :       aeg_params.set<Real>("start_radial_growth_rate") = getParam<Real>("start_radial_growth_rate");
     266          64 :       aeg_params.set<Real>("end_radial_growth_rate") = getParam<Real>("end_radial_growth_rate");
     267          32 :       aeg_params.set<MooseEnum>("radial_growth_method") =
     268          64 :           getParam<MooseEnum>("radial_growth_method");
     269             : 
     270          48 :       aeg_params.set<BoundaryName>("bottom_boundary") = name() + "_aeg_bottom_boundary";
     271          48 :       aeg_params.set<BoundaryName>("top_boundary") = name() + "_aeg_top_boundary";
     272          48 :       if (isParamValid("block"))
     273           0 :         paramError("block", "Not yet implemented for 2D or 3D junction");
     274          32 :       aeg_params.set<bool>("output") = _verbose;
     275             : 
     276          32 :       _app.getMeshGeneratorSystem().addMeshGenerator(
     277          32 :           "AdvancedExtruderGenerator", name() + "_aeg", aeg_params);
     278          16 :       _mg_names.push_back(name() + "_aeg");
     279          16 :     }
     280             : 
     281             :     // Stitch the extrusion / curve (in 1D) to the components
     282          56 :     if (first_component.getCurrentTopLevelMeshGeneratorName() !=
     283         112 :         second_component.getCurrentTopLevelMeshGeneratorName())
     284             :     {
     285          96 :       InputParameters stitcher_params = _factory.getValidParams("StitchMeshGenerator");
     286          96 :       stitcher_params.set<std::vector<MeshGeneratorName>>("inputs") =
     287         288 :           std::vector<MeshGeneratorName>{first_component.getCurrentTopLevelMeshGeneratorName(),
     288          48 :                                          _mg_names.back(),
     289         240 :                                          second_component.getCurrentTopLevelMeshGeneratorName()};
     290          48 :       if (dimension_first > 1)
     291          32 :         stitcher_params.set<std::vector<std::vector<std::string>>>("stitch_boundaries_pairs") = {
     292          16 :             {first_boundary, name() + "_aeg_bottom_boundary"},
     293         128 :             {name() + "_aeg_top_boundary", second_boundary}};
     294             :       else
     295             :       {
     296          32 :         stitcher_params.set<bool>("clear_stitched_boundary_ids") = false;
     297          64 :         stitcher_params.set<std::vector<std::vector<std::string>>>("stitch_boundaries_pairs") = {
     298          32 :             {first_boundary, name() + "_bspline_start_node"},
     299         256 :             {name() + "_bspline_end_node", second_boundary}};
     300             :       }
     301             : 
     302          96 :       stitcher_params.set<bool>("verbose_stitching") = _verbose;
     303          96 :       stitcher_params.set<bool>("output") = _verbose;
     304          48 :       stitcher_params.set<bool>("enforce_all_nodes_match_on_boundaries") =
     305          48 :           _enforce_all_nodes_match_on_boundaries;
     306          96 :       _app.getMeshGeneratorSystem().addMeshGenerator(
     307          96 :           "StitchMeshGenerator", name() + "_stitcher", stitcher_params);
     308          48 :       _mg_names.push_back(name() + "_stitcher");
     309          48 :     }
     310             :     else
     311             :     {
     312             :       // Handles case of needing to close a mesh. Using StitchBoundaryMeshGenerator prevents
     313             :       // issues with element overlap.
     314          16 :       InputParameters mesh_stitcher_params = _factory.getValidParams("StitchMeshGenerator");
     315          16 :       mesh_stitcher_params.set<std::vector<MeshGeneratorName>>("inputs") =
     316          40 :           std::vector<MeshGeneratorName>{first_component.getCurrentTopLevelMeshGeneratorName(),
     317          32 :                                          _mg_names.back()};
     318           8 :       if (dimension_first > 1)
     319           0 :         mesh_stitcher_params.set<std::vector<std::vector<std::string>>>(
     320           0 :             "stitch_boundaries_pairs") = {{first_boundary, name() + "_aeg_bottom_boundary"}};
     321             :       else
     322          16 :         mesh_stitcher_params.set<std::vector<std::vector<std::string>>>(
     323          40 :             "stitch_boundaries_pairs") = {{first_boundary, name() + "_bspline_start_node"}};
     324          24 :       mesh_stitcher_params.set<bool>("verbose_stitching") = _verbose;
     325             :       // TODO: remove this once we understand why it errors without
     326           8 :       mesh_stitcher_params.set<bool>("clear_stitched_boundary_ids") = false;
     327           8 :       mesh_stitcher_params.set<bool>("enforce_all_nodes_match_on_boundaries") =
     328           8 :           _enforce_all_nodes_match_on_boundaries;
     329          16 :       _app.getMeshGeneratorSystem().addMeshGenerator(
     330          16 :           "StitchMeshGenerator", name() + "_mesh_stitcher", mesh_stitcher_params);
     331           8 :       _mg_names.push_back(name() + "_stitcher");
     332             : 
     333             :       InputParameters boundary_stitcher_params =
     334          16 :           _factory.getValidParams("StitchBoundaryMeshGenerator");
     335          24 :       boundary_stitcher_params.set<MeshGeneratorName>("input") = name() + "_mesh_stitcher";
     336           8 :       if (dimension_first > 1)
     337           0 :         boundary_stitcher_params.set<std::vector<std::vector<std::string>>>(
     338           0 :             "stitch_boundaries_pairs") = {{name() + "_aeg_top_boundary", second_boundary}};
     339             :       else
     340          16 :         boundary_stitcher_params.set<std::vector<std::vector<std::string>>>(
     341          40 :             "stitch_boundaries_pairs") = {{name() + "_bspline_end_node", second_boundary}};
     342          16 :       boundary_stitcher_params.set<bool>("show_info") = _verbose;
     343          16 :       _app.getMeshGeneratorSystem().addMeshGenerator(
     344          16 :           "StitchBoundaryMeshGenerator", name() + "_closed", boundary_stitcher_params);
     345           8 :       _mg_names.push_back(name() + "_closed");
     346           8 :     }
     347          56 :   }
     348             :   else
     349           0 :     mooseError("junction_method specified is invalid!");
     350             : 
     351         126 :   _top_mg_name = _mg_names.back();
     352         126 :   first_component.addConnectedComponent(second_component);
     353             :   // Sets it for all connected components
     354             :   // Connected might not be the right abstraction here. It's more like "included in a common mesh"
     355         468 :   for (auto * component : first_component.getConnectedComponents())
     356         342 :     component->setCurrentTopLevelMeshGeneratorName(_top_mg_name);
     357             : 
     358             :   // For now this is a safe choice. We might want to decide otherwise once we
     359             :   // do mixed-dimensions. Build the junction with the dimension of the first component?
     360             :   mooseAssert(dimension_first == dimension_second, "Should be the same");
     361         126 :   _dimension = std::max(dimension_first, dimension_second);
     362        1224 : }
     363             : 
     364             : void
     365           0 : ComponentJunction::checkIntegrity()
     366             : {
     367           0 :   ComponentInitialConditionInterface::checkIntegrity();
     368           0 :   ComponentBoundaryConditionInterface::checkIntegrity();
     369           0 : }

Generated by: LCOV version 1.14