LCOV - code coverage report
Current view: top level - src/meshgenerators - ProjectSideSetOntoLevelSetGenerator.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: #32971 (54bef8) with base c6cf66 Lines: 89 97 91.8 %
Date: 2026-05-29 20:35:17 Functions: 5 5 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 "ProjectSideSetOntoLevelSetGenerator.h"
      11             : #include "MooseMeshUtils.h"
      12             : #include "CastUniquePointer.h"
      13             : 
      14             : #include "libmesh/elem.h"
      15             : #include "libmesh/boundary_info.h"
      16             : #include "libmesh/mesh_base.h"
      17             : 
      18             : // C++ includes
      19             : #include <cmath>
      20             : 
      21             : registerMooseObject("MooseApp", ProjectSideSetOntoLevelSetGenerator);
      22             : 
      23             : InputParameters
      24        3077 : ProjectSideSetOntoLevelSetGenerator::validParams()
      25             : {
      26        3077 :   InputParameters params = MeshGenerator::validParams();
      27        3077 :   params += FunctionParserUtils<false>::validParams();
      28             : 
      29       12308 :   params.addRequiredParam<Point>("direction", "Projection direction");
      30       12308 :   params.addParam<SubdomainName>(
      31             :       "subdomain_name", "projected", "Name of the subdomain of the projected surface");
      32        9231 :   params.addParam<Real>("max_search_distance",
      33        6154 :                         1e6,
      34             :                         "Maximum distance from the sideset to search for the projected point in "
      35             :                         "the projection direction.");
      36             : 
      37             :   // Source domain parameters
      38       12308 :   params.addRequiredParam<MeshGeneratorName>(
      39             :       "input", "The input mesh generator in which the sideset exists.");
      40       12308 :   params.addRequiredParam<BoundaryName>("sideset", "The name of the sideset to project");
      41             : 
      42             :   // Surface definition
      43       12308 :   params.addRequiredParam<std::string>(
      44             :       "level_set",
      45             :       "Level set to define the surface to project onto as a function of x, y, and z. The surface "
      46             :       "should be 'in front of' the sideset when following the projection direction.");
      47       12308 :   params.addParam<std::vector<std::string>>(
      48             :       "constant_names", {}, "Vector of constants used in the parsed function");
      49       12308 :   params.addParam<std::vector<std::string>>(
      50             :       "constant_expressions",
      51             :       {},
      52             :       "Vector of values for the constants in constant_names (can be an FParser expression)");
      53       12308 :   params.addParamNamesToGroup("level_set constant_names constant_expressions", "Level set surface");
      54             : 
      55        3077 :   params.addClassDescription(
      56             :       "Projects a sideset onto a surface defined by a level set and creates a surface mesh.");
      57             : 
      58        3077 :   return params;
      59           0 : }
      60             : 
      61           8 : ProjectSideSetOntoLevelSetGenerator::ProjectSideSetOntoLevelSetGenerator(
      62           8 :     const InputParameters & parameters)
      63             :   : MeshGenerator(parameters),
      64             :     FunctionParserUtils<false>(parameters),
      65           8 :     _input_name(getParam<MeshGeneratorName>("input")),
      66           8 :     _input(getMeshByName(_input_name)),
      67          16 :     _level_set(getParam<std::string>("level_set")),
      68          40 :     _max_search_distance(getParam<Real>("max_search_distance"))
      69             : {
      70             :   // Create parsed function
      71           8 :   _func_level_set = std::make_shared<SymFunction>();
      72          56 :   parsedFunctionSetup(_func_level_set,
      73           8 :                       _level_set,
      74             :                       "x,y,z",
      75             :                       getParam<std::vector<std::string>>("constant_names"),
      76             :                       getParam<std::vector<std::string>>("constant_expressions"),
      77             :                       comm());
      78           8 :   _func_params.resize(3);
      79             : 
      80             :   // Get the projection direction and normalize it
      81          24 :   auto proj_d = getParam<Point>("direction");
      82           8 :   if (MooseUtils::absoluteFuzzyEqual(proj_d.norm_sq(), 0))
      83           0 :     paramError("direction", "Should not be 0");
      84           8 :   _proj_dir = proj_d.unit();
      85           8 : }
      86             : 
      87             : std::unique_ptr<MeshBase>
      88           8 : ProjectSideSetOntoLevelSetGenerator::generate()
      89             : {
      90           8 :   auto mesh_uptr = std::move(_input);
      91           8 :   auto mesh = mesh_uptr.get();
      92           8 :   auto new_mesh = buildMeshBaseObject(3);
      93             : 
      94           8 :   if (!mesh_uptr->is_serial())
      95           0 :     paramError("input", "Input mesh must not be distributed");
      96             : 
      97             :   // Use a new subdomain ID to avoid an overlap if merging the two later
      98           8 :   const auto projection_block_id = MooseMeshUtils::getNextFreeSubdomainID(*mesh);
      99             : 
     100           8 :   BoundaryInfo & boundary_info = mesh->get_boundary_info();
     101           8 :   const auto bdry_side_list = boundary_info.build_side_list();
     102          16 :   const auto side_id = boundary_info.get_id_by_name(getParam<BoundaryName>("sideset"));
     103             : 
     104             :   // Loop over the sides on a given set
     105         424 :   for (const auto & [elem_id, side_i, bc_id] : bdry_side_list)
     106             :   {
     107         416 :     if (bc_id != side_id)
     108         208 :       continue;
     109             : 
     110             :     // Find the intersection with the surface (projection) for each node of the side
     111         208 :     std::unique_ptr<Elem> side_elem = mesh->elem_ptr(elem_id)->build_side_ptr(side_i);
     112         208 :     const auto n_nodes = side_elem->n_nodes();
     113         208 :     const auto side_type = side_elem->type();
     114         208 :     std::vector<Node *> new_nodes(n_nodes);
     115             : 
     116        1040 :     for (const auto i : make_range(n_nodes))
     117             :     {
     118         832 :       const auto start = side_elem->point(i);
     119         832 :       const auto end = start + _max_search_distance * _proj_dir;
     120             : 
     121         832 :       new_nodes[i] = new_mesh->add_point(pointPairLevelSetInterception(start, end));
     122             :     }
     123             : 
     124             :     // Build the same element as the side, on the surface, using the projected nodes
     125         208 :     if (side_type == libMesh::C0POLYGON)
     126           0 :       mooseError("Projection of polygonal sides is not supported at this time");
     127         208 :     std::unique_ptr<Elem> new_elem = Elem::build(side_type);
     128        1040 :     for (const auto i : make_range(n_nodes))
     129         832 :       new_elem->set_node(i, new_nodes[i]);
     130             : 
     131             :     // User-selected block name: same for all element types for now
     132         208 :     new_elem->subdomain_id() = projection_block_id;
     133             : 
     134         208 :     new_mesh->add_elem(std::move(new_elem));
     135         208 :   }
     136             : 
     137          16 :   new_mesh->subdomain_name(projection_block_id) = getParam<SubdomainName>("subdomain_name");
     138           8 :   new_mesh->unset_is_prepared();
     139          16 :   return dynamic_pointer_cast<MeshBase>(new_mesh);
     140           8 : }
     141             : 
     142             : Point
     143         832 : ProjectSideSetOntoLevelSetGenerator::pointPairLevelSetInterception(const Point & point1,
     144             :                                                                    const Point & point2)
     145             : {
     146         832 :   Real dist1 = levelSetEvaluator(point1);
     147         832 :   Real dist2 = levelSetEvaluator(point2);
     148             : 
     149         832 :   if (MooseUtils::absoluteFuzzyEqual(dist1, 0.0) || MooseUtils::absoluteFuzzyEqual(dist2, 0.0))
     150           0 :     mooseError("At least one of the two points are on the plane.");
     151         832 :   if (MooseUtils::absoluteFuzzyGreaterThan(dist1 * dist2, 0.0))
     152           0 :     mooseError("The two points are on the same side of the plane.");
     153             : 
     154         832 :   Point p1(point1);
     155         832 :   Point p2(point2);
     156         832 :   Real dist = abs(dist1) + abs(dist2);
     157         832 :   Point mid_point;
     158             : 
     159             :   // Bisection method to find midpoint
     160         832 :   unsigned int num_its = 0;
     161       51584 :   while (MooseUtils::absoluteFuzzyGreaterThan(dist, 0.0) && num_its < 1e3)
     162             :   {
     163       50752 :     num_its++;
     164       50752 :     mid_point = 0.5 * (p1 + p2);
     165       50752 :     const Real dist_mid = levelSetEvaluator(mid_point);
     166             :     // We do not need Fuzzy here as it will be covered by the while loop
     167       50752 :     if (dist_mid == 0.0)
     168           0 :       dist = 0.0;
     169       50752 :     else if (dist_mid * dist1 < 0.0)
     170             :     {
     171       32480 :       p2 = mid_point;
     172       32480 :       dist2 = levelSetEvaluator(p2);
     173       32480 :       dist = abs(dist1) + abs(dist2);
     174             :     }
     175             :     else
     176             :     {
     177       18272 :       p1 = mid_point;
     178       18272 :       dist1 = levelSetEvaluator(p1);
     179       18272 :       dist = abs(dist1) + abs(dist2);
     180             :     }
     181             :   }
     182             : 
     183         832 :   if (num_its == 1e3)
     184           0 :     mooseError("Projection failed for point " + Moose::stringify(point1) +
     185             :                ". Is the level set 'in front of' the sideset when following the projection "
     186             :                "direction?");
     187        1664 :   return mid_point;
     188             : }
     189             : 
     190             : Real
     191      103168 : ProjectSideSetOntoLevelSetGenerator::levelSetEvaluator(const Point & point)
     192             : {
     193      103168 :   _func_params[0] = point(0);
     194      103168 :   _func_params[1] = point(1);
     195      103168 :   _func_params[2] = point(2);
     196      309504 :   return evaluate(_func_level_set);
     197             : }

Generated by: LCOV version 1.14