LCOV - code coverage report
Current view: top level - src/meshgenerators - SCMTriDuctMeshGenerator.C (source / functions) Hit Total Coverage
Test: idaholab/moose subchannel: #32971 (54bef8) with base c6cf66 Lines: 99 103 96.1 %
Date: 2026-05-29 20:40:47 Functions: 9 9 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 "SCMTriDuctMeshGenerator.h"
      11             : #include "TriSubChannelMesh.h"
      12             : #include <cmath>
      13             : #include "libmesh/face_quad4.h"
      14             : #include "libmesh/unstructured_mesh.h"
      15             : 
      16             : registerMooseObject("SubChannelApp", SCMTriDuctMeshGenerator);
      17             : 
      18             : InputParameters
      19         154 : SCMTriDuctMeshGenerator::validParams()
      20             : {
      21         154 :   InputParameters params = MeshGenerator::validParams();
      22         154 :   params.addClassDescription(
      23             :       "Creates a mesh of 2D duct cells around a triangular lattice subassembly");
      24         308 :   params.addRequiredParam<MeshGeneratorName>("input", "The corresponding subchannel mesh");
      25         308 :   params.addParam<unsigned int>("block_id", 2, "Domain Index");
      26         308 :   params.addRequiredParam<unsigned int>("n_cells", "The number of cells in the axial direction");
      27         308 :   params.addParam<Real>("unheated_length_entry", 0.0, "Unheated length at entry [m]");
      28         308 :   params.addRequiredParam<Real>("heated_length", "Heated length [m]");
      29         308 :   params.addParam<Real>("unheated_length_exit", 0.0, "Unheated length at exit [m]");
      30         308 :   params.addRequiredParam<Real>("pitch", "Pitch is the distance between adjacent pins [m]");
      31         308 :   params.addRequiredParam<unsigned int>("nrings", "Number of fuel Pin rings in the assembly [-]");
      32         308 :   params.addRequiredParam<Real>("flat_to_flat",
      33             :                                 "Flat to flat distance for the hexagonal assembly [m]");
      34         154 :   return params;
      35           0 : }
      36             : 
      37          77 : SCMTriDuctMeshGenerator::SCMTriDuctMeshGenerator(const InputParameters & params)
      38             :   : MeshGenerator(params),
      39          77 :     _input(getMesh("input")),
      40         154 :     _n_cells(getParam<unsigned int>("n_cells")),
      41         154 :     _unheated_length_entry(getParam<Real>("unheated_length_entry")),
      42         154 :     _heated_length(getParam<Real>("heated_length")),
      43         154 :     _unheated_length_exit(getParam<Real>("unheated_length_exit")),
      44         154 :     _block_id(getParam<unsigned int>("block_id")),
      45         154 :     _pitch(getParam<Real>("pitch")),
      46         154 :     _n_rings(getParam<unsigned int>("nrings")),
      47         231 :     _flat_to_flat(getParam<Real>("flat_to_flat"))
      48             : {
      49          77 :   SubChannelMesh::generateZGrid(
      50          77 :       _unheated_length_entry, _heated_length, _unheated_length_exit, _n_cells, _z_grid);
      51          77 : }
      52             : 
      53             : std::unique_ptr<MeshBase>
      54          77 : SCMTriDuctMeshGenerator::generate()
      55             : {
      56          77 :   std::unique_ptr<MeshBase> mesh_base = std::move(_input);
      57          77 :   mesh_base->set_mesh_dimension(3);
      58             : 
      59             :   std::vector<Point> corners;
      60          77 :   ductCorners(corners, _flat_to_flat, Point(0, 0, 0));
      61             :   std::vector<Point> cross_sec;
      62          77 :   ductCrossSec(cross_sec, corners, _n_rings, _pitch);
      63             :   std::vector<Point> points;
      64          77 :   ductPoints(points, cross_sec, _z_grid);
      65             :   std::vector<std::vector<size_t>> elem_point_indices;
      66          77 :   ductElems(elem_point_indices, _z_grid.size(), cross_sec.size());
      67             :   std::vector<Node *> duct_nodes;
      68          77 :   buildDuct(mesh_base, duct_nodes, points, elem_point_indices, _block_id);
      69          77 :   mesh_base->subdomain_name(_block_id) = name();
      70             : 
      71          77 :   mesh_base->prepare_for_use();
      72             : 
      73          77 :   auto & sch_mesh = static_cast<TriSubChannelMesh &>(*_mesh);
      74          77 :   sch_mesh.setChannelToDuctMaps(duct_nodes);
      75          77 :   sch_mesh._duct_mesh_exist = true;
      76             : 
      77          77 :   return mesh_base;
      78          77 : }
      79             : 
      80             : void
      81          77 : SCMTriDuctMeshGenerator::ductCorners(std::vector<Point> & corners,
      82             :                                      Real flat_to_flat,
      83             :                                      const Point & center) const
      84             : {
      85          77 :   corners.resize(TriSubChannelMesh::N_CORNERS);
      86          77 :   Real r_corner = flat_to_flat / 2. / std::cos(libMesh::pi / 6.);
      87         539 :   for (size_t i = 0; i < TriSubChannelMesh::N_CORNERS; i++)
      88             :   {
      89         462 :     Real theta = i * libMesh::pi / 3.;
      90         462 :     Point corner = {r_corner * std::cos(theta), r_corner * std::sin(theta)};
      91         462 :     corners[i] = center + corner;
      92             :   }
      93          77 : }
      94             : 
      95             : void
      96          77 : SCMTriDuctMeshGenerator::ductCrossSec(std::vector<Point> & cross_sec,
      97             :                                       const std::vector<Point> & corners,
      98             :                                       unsigned int nrings,
      99             :                                       Real pitch) const
     100             : {
     101          77 :   cross_sec.clear();
     102             : 
     103          77 :   if (pitch <= 0.0)
     104           0 :     mooseError("The 'pitch' must be > 0.");
     105             : 
     106          77 :   if (nrings < 2)
     107           0 :     mooseError("The 'nrings' must be >= 2.");
     108             : 
     109             :   // Side length of the regular hexagon (distance between adjacent corners)
     110          77 :   const Real side_length = (corners[0] - corners[1]).norm();
     111             : 
     112             :   // start_offset = (hexagon_side - (nrings - 2)*pitch) / 2
     113          77 :   const Real start_offset = 0.5 * (side_length - (nrings - 2) * pitch);
     114             : 
     115             :   // If this is negative, the requested layout cannot fit.
     116          77 :   if (start_offset < 0.0)
     117           0 :     mooseError("SCMTriDuctMeshGenerator: computed start_offset is negative (",
     118             :                start_offset,
     119             :                "). Check 'nrings', 'pitch', and duct side length.");
     120             : 
     121         539 :   for (size_t i = 0; i < corners.size(); i++)
     122             :   {
     123         462 :     const Point left = corners[i];
     124         462 :     const Point right = corners[(i + 1) % corners.size()];
     125             : 
     126             :     // Always include the left corner
     127         462 :     cross_sec.push_back(left);
     128             : 
     129             :     // Unit direction along this side
     130         462 :     const Point direc = (right - left).unit();
     131             : 
     132             :     // Add exactly (nrings - 1) points after the left corner
     133             :     // at start_offset + k*pitch, k = 0..(nrings-2)
     134        2220 :     for (const auto k : make_range(nrings - 1))
     135             :     {
     136        1758 :       const Real offset_from_corner = start_offset + k * pitch;
     137        1758 :       cross_sec.push_back(left + direc * offset_from_corner);
     138             :     }
     139             :   }
     140          77 : }
     141             : 
     142             : size_t
     143      197970 : SCMTriDuctMeshGenerator::ductPointIndex(unsigned int points_per_layer,
     144             :                                         unsigned int layer,
     145             :                                         unsigned int point) const
     146             : {
     147      197970 :   return layer * points_per_layer + point;
     148             : }
     149             : 
     150             : void
     151          77 : SCMTriDuctMeshGenerator::ductPoints(std::vector<Point> & points,
     152             :                                     const std::vector<Point> & cross_sec,
     153             :                                     const std::vector<Real> & z_layers) const
     154             : {
     155          77 :   points.resize(cross_sec.size() * z_layers.size());
     156        1339 :   for (size_t i = 0; i < z_layers.size(); i++)
     157       42632 :     for (size_t j = 0; j < cross_sec.size(); j++)
     158       41370 :       points[ductPointIndex(cross_sec.size(), i, j)] =
     159       41370 :           Point(cross_sec[j](0), cross_sec[j](1), z_layers[i]);
     160          77 : }
     161             : 
     162             : void
     163          77 : SCMTriDuctMeshGenerator::ductElems(std::vector<std::vector<size_t>> & elem_point_indices,
     164             :                                    unsigned int n_layers,
     165             :                                    unsigned int points_per_layer) const
     166             : {
     167          77 :   elem_point_indices.clear();
     168        1262 :   for (unsigned int i = 0; i < n_layers - 1; i++)
     169             :   {
     170             :     unsigned int bottom = i;
     171        1185 :     unsigned int top = i + 1;
     172       40335 :     for (unsigned int j = 0; j < points_per_layer; j++)
     173             :     {
     174             :       unsigned int left = j;
     175       39150 :       unsigned int right = (j + 1) % points_per_layer;
     176      117450 :       elem_point_indices.push_back({ductPointIndex(points_per_layer, bottom, left),
     177       39150 :                                     ductPointIndex(points_per_layer, bottom, right),
     178       39150 :                                     ductPointIndex(points_per_layer, top, right),
     179       39150 :                                     ductPointIndex(points_per_layer, top, left)});
     180             :     }
     181             :   }
     182          77 : }
     183             : 
     184             : void
     185          77 : SCMTriDuctMeshGenerator::buildDuct(std::unique_ptr<MeshBase> & mesh,
     186             :                                    std::vector<Node *> & duct_nodes,
     187             :                                    const std::vector<Point> & points,
     188             :                                    const std::vector<std::vector<size_t>> & elem_point_indices,
     189             :                                    SubdomainID block) const
     190             : {
     191             :   // Create mesh nodes for all duct points and keep a local index -> Node* map
     192          77 :   duct_nodes.clear();
     193          77 :   duct_nodes.reserve(points.size());
     194       41447 :   for (const auto & p : points)
     195       41370 :     duct_nodes.push_back(mesh->add_point(p));
     196             : 
     197             :   // Create QUAD4 surface elements using libMesh factory style
     198       39227 :   for (const auto & elem_indices : elem_point_indices)
     199             :   {
     200             :     mooseAssert(elem_indices.size() == 4,
     201             :                 "Expected 4 node indices per element when building QUAD4 elements.");
     202             : 
     203       39150 :     auto elem = Elem::build(ElemType::QUAD4);
     204       39150 :     elem->subdomain_id() = block;
     205             : 
     206             :     // Set the 4 nodes of the QUAD4
     207      195750 :     for (unsigned int i = 0; i < 4; ++i)
     208             :     {
     209      156600 :       const auto idx = elem_indices[i];
     210             :       mooseAssert(idx < duct_nodes.size(), "Element node index out of range.");
     211      156600 :       elem->set_node(i, duct_nodes[idx]);
     212             :     }
     213             : 
     214             :     // Hand ownership to the mesh
     215       39150 :     mesh->add_elem(elem.release());
     216       39150 :   }
     217          77 : }

Generated by: LCOV version 1.14