LCOV - code coverage report
Current view: top level - src/csg - CSGRegion.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: #32971 (54bef8) with base c6cf66 Lines: 145 154 94.2 %
Date: 2026-05-29 20:35:17 Functions: 25 25 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : //* This file is part of the MOOSE framework
       2             : //* https://www.mooseframework.org
       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 "CSGRegion.h"
      11             : 
      12             : namespace CSG
      13             : {
      14             : 
      15             : char
      16         712 : CSGRegion::regionSymbol(const RegionType region_type)
      17             : {
      18             :   mooseAssert(region_type == RegionType::COMPLEMENT || region_type == RegionType::UNION ||
      19             :                   region_type == RegionType::INTERSECTION,
      20             :               "Unexpected region type");
      21             : 
      22         712 :   constexpr std::array<char, 5> symbols = {
      23             :       '\0', // CSGRegion::RegionType::EMPTY (unused)
      24             :       '\0', // CSGRegion::RegionType::HALFSPACE (unused)
      25             :       '~',  // CSGRegion::RegionType::COMPLEMENT
      26             :       '&',  // CSGRegion::RegionType::INTERSECTION
      27             :       '|'   // CSGRegion::RegionType::UNION
      28             :   };
      29             :   static_assert(symbols[static_cast<std::size_t>(RegionType::COMPLEMENT)] == '~');
      30             :   static_assert(symbols[static_cast<std::size_t>(RegionType::INTERSECTION)] == '&');
      31             :   static_assert(symbols[static_cast<std::size_t>(RegionType::UNION)] == '|');
      32             : 
      33         712 :   return symbols[static_cast<std::size_t>(region_type)];
      34             : }
      35             : 
      36             : char
      37        1568 : CSGRegion::halfspaceSymbol(const CSGSurface::Halfspace halfspace)
      38             : {
      39             :   mooseAssert(halfspace == CSGSurface::Halfspace::POSITIVE ||
      40             :                   halfspace == CSGSurface::Halfspace::NEGATIVE,
      41             :               "Unexpected halfspace");
      42             : 
      43        1568 :   constexpr std::array<char, 2> symbols = {
      44             :       '+', // CSGSurface::Halfspace::POSITIVE
      45             :       '-'  // CSGSurface::Halfspace::NEGATIVE
      46             :   };
      47             :   static_assert(symbols[static_cast<std::size_t>(CSGSurface::Halfspace::POSITIVE)] == '+');
      48             :   static_assert(symbols[static_cast<std::size_t>(CSGSurface::Halfspace::NEGATIVE)] == '-');
      49             : 
      50        1568 :   return symbols[static_cast<std::size_t>(halfspace)];
      51             : }
      52             : 
      53             : bool
      54         226 : CSGRegion::checkRegionEquality(const std::vector<PostfixTokenVariant> & other_tokens) const
      55             : {
      56         226 :   const auto & tokens = getPostfixTokens();
      57         226 :   if (tokens.size() != other_tokens.size())
      58           0 :     return false;
      59             : 
      60             :   // Loop through all tokens and check equality
      61         586 :   for (const auto i : index_range(tokens))
      62             :   {
      63         416 :     const auto & token = tokens[i];
      64         416 :     const auto & other_token = other_tokens[i];
      65         416 :     if (std::holds_alternative<std::reference_wrapper<const CSGSurface>>(token))
      66             :     {
      67             :       // For surface references, compare references themselves for equality
      68         208 :       if (!std::holds_alternative<std::reference_wrapper<const CSGSurface>>(other_token))
      69          56 :         return false;
      70         208 :       const auto & surf_ref = std::get<std::reference_wrapper<const CSGSurface>>(token);
      71         208 :       const auto & other_surf_ref = std::get<std::reference_wrapper<const CSGSurface>>(other_token);
      72         208 :       if (surf_ref.get() != other_surf_ref.get())
      73           0 :         return false;
      74             :     }
      75             :     else
      76             :     {
      77             :       // For region types and halfspaces, compare based on string representations
      78             :       mooseAssert(std::holds_alternative<RegionType>(token) ||
      79             :                       std::holds_alternative<CSGSurface::Halfspace>(token),
      80             :                   "Unexpected token type");
      81         208 :       if (std::holds_alternative<std::reference_wrapper<const CSGSurface>>(other_token))
      82           0 :         return false;
      83         208 :       if (postfixTokenToString(token) != postfixTokenToString(other_token))
      84          56 :         return false;
      85             :     }
      86             :   }
      87         170 :   return true;
      88             : }
      89             : 
      90         655 : CSGRegion::CSGRegion()
      91             : {
      92         131 :   _region_type = "EMPTY";
      93         131 :   _postfix_tokens.clear();
      94         131 : }
      95             : 
      96             : // halfspace constructor
      97        3420 : CSGRegion::CSGRegion(const CSGSurface & surf, const CSGSurface::Halfspace halfspace)
      98             : {
      99         684 :   _region_type = "HALFSPACE";
     100             : 
     101             :   // (halfspace surf) in postfix is represented as (surf halfspace)
     102         684 :   _postfix_tokens.push_back(surf);
     103         684 :   _postfix_tokens.push_back(halfspace);
     104         684 : }
     105             : 
     106             : // intersection and union constructor
     107         389 : CSGRegion::CSGRegion(const CSGRegion & region_a,
     108             :                      const CSGRegion & region_b,
     109        1945 :                      const std::string & region_type)
     110             : {
     111         389 :   _region_type = region_type;
     112         389 :   if (getRegionType() != RegionType::INTERSECTION && getRegionType() != RegionType::UNION)
     113          24 :     mooseError("Region type " + getRegionTypeString() + " is not supported for two regions.");
     114         764 :   if (region_a.getRegionType() == RegionType::EMPTY ||
     115         381 :       region_b.getRegionType() == RegionType::EMPTY)
     116          16 :     mooseError("Region operation " + getRegionTypeString() +
     117             :                " cannot be performed on an empty region.");
     118             : 
     119             :   // (region_a region_type region_b) in postfix is represented as (region_a region_b region_type)
     120         758 :   _postfix_tokens.insert(_postfix_tokens.end(),
     121         379 :                          region_a.getPostfixTokens().begin(),
     122         379 :                          region_a.getPostfixTokens().end());
     123         758 :   _postfix_tokens.insert(_postfix_tokens.end(),
     124         379 :                          region_b.getPostfixTokens().begin(),
     125         379 :                          region_b.getPostfixTokens().end());
     126         379 :   _postfix_tokens.push_back(getRegionType());
     127         399 : }
     128             : 
     129             : // complement or explicitly empty constructor
     130          40 : CSGRegion::CSGRegion(const CSGRegion & region, const std::string & region_type)
     131             : {
     132           8 :   _region_type = region_type;
     133           8 :   if (getRegionType() != RegionType::COMPLEMENT && getRegionType() != RegionType::EMPTY)
     134          24 :     mooseError("Region type " + getRegionTypeString() + " is not supported for a single region.");
     135             : 
     136           2 :   if (getRegionType() == RegionType::COMPLEMENT)
     137             :   {
     138             :     // (complement region) in postfix is represented as (region complement)
     139           2 :     _postfix_tokens = region.getPostfixTokens();
     140           2 :     _postfix_tokens.push_back(getRegionType());
     141             :   }
     142           0 :   else if (getRegionType() == RegionType::EMPTY)
     143           0 :     _postfix_tokens.clear();
     144          14 : }
     145             : 
     146             : nlohmann::json
     147         220 : CSGRegion::toInfixJSON() const
     148             : {
     149             :   // Return an empty JSON object if no postfix tokens are defined
     150         220 :   if (_postfix_tokens.empty())
     151           0 :     return nlohmann::json::parse("[]");
     152             : 
     153             :   // Build the region string using a stack, iterating through each token within _postfix_tokens
     154         220 :   std::stack<std::string> postfix_stack;
     155        1724 :   for (auto i : index_range(_postfix_tokens))
     156             :   {
     157        1504 :     const auto & token = _postfix_tokens[i];
     158             :     // Surface: Push name to stack
     159        1504 :     if (const auto surface_ref_ptr = std::get_if<std::reference_wrapper<const CSGSurface>>(&token))
     160         574 :       postfix_stack.push(surface_ref_ptr->get().getName());
     161             :     // Halfspaces and region operators
     162             :     else
     163             :     {
     164         930 :       std::string region_string;
     165             :       // Halfspace: Pop from the stack, update region string, push back
     166         930 :       if (const auto halfspace_ptr = std::get_if<CSGSurface::Halfspace>(&token))
     167             :       {
     168         574 :         std::string symbol = std::string(1, halfspaceSymbol(*halfspace_ptr));
     169         574 :         region_string = "\"" + symbol + postfix_stack.top() + "\"";
     170         574 :         postfix_stack.pop();
     171         574 :       }
     172             :       // Region operator: Pop 1 or 2 values, update region string, push back
     173             :       else
     174             :       {
     175         356 :         const auto region = std::get<RegionType>(token);
     176         712 :         const std::string symbol{regionSymbol(region)};
     177         356 :         if (region == RegionType::COMPLEMENT)
     178             :         {
     179           2 :           region_string = postfix_stack.top();
     180           2 :           postfix_stack.pop();
     181           2 :           if (region_string[0] == '[')
     182           2 :             region_string = "\"" + symbol + "\", " + region_string;
     183             :           else
     184           0 :             region_string = "\"" + symbol + "\", [" + region_string + "]";
     185             :         }
     186             :         else
     187             :         {
     188         354 :           auto region_string_b = postfix_stack.top();
     189         354 :           postfix_stack.pop();
     190         354 :           auto region_string_a = postfix_stack.top();
     191         354 :           postfix_stack.pop();
     192         354 :           region_string = region_string_a + ", \"" + symbol + "\", " + region_string_b;
     193             :           // Skip putting parentheses around the region string if the next region operator in the
     194             :           // postfix token list is identical
     195         354 :           if (!nextRegionOpIsIdentical(region, i + 1))
     196         114 :             region_string = "[" + region_string + "]";
     197         354 :         }
     198         356 :       }
     199         930 :       postfix_stack.push(region_string);
     200         930 :     }
     201             :   }
     202             : 
     203             :   // Top of stack should now have region string we desire. Now, we
     204             :   // parse the string into a JSON object
     205         220 :   std::string region_string = postfix_stack.top();
     206             :   // Wrap region string in square brackets so that it is always treated as a
     207             :   // list in the output JSON object
     208         220 :   if (region_string[0] != '[')
     209         108 :     region_string = "[" + region_string + "]";
     210         220 :   return nlohmann::json::parse(region_string);
     211         220 : }
     212             : 
     213             : std::vector<std::string>
     214         224 : CSGRegion::toPostfixStringList() const
     215             : {
     216         224 :   std::vector<std::string> postfix_string_list;
     217         224 :   postfix_string_list.reserve(_postfix_tokens.size());
     218        1736 :   for (const auto & token : _postfix_tokens)
     219        1512 :     postfix_string_list.push_back(postfixTokenToString(token));
     220         224 :   return postfix_string_list;
     221           0 : }
     222             : 
     223             : std::string
     224        1928 : CSGRegion::postfixTokenToString(const PostfixTokenVariant & token) const
     225             : {
     226             :   // Lambda function to return all variant types as strings
     227             :   return std::visit(
     228        3856 :       [](auto && arg) -> std::string
     229             :       {
     230             :         using T = std::decay_t<decltype(arg)>;
     231             :         if constexpr (std::is_same_v<T, std::reference_wrapper<const CSGSurface>>)
     232         578 :           return arg.get().getName();
     233             :         else if constexpr (std::is_same_v<T, RegionType>)
     234        1068 :           return std::string{regionSymbol(arg)};
     235             :         else // if constexpr (std::is_same_v<T, CSGSurface::Halfspace>)
     236        2982 :           return std::string{halfspaceSymbol(arg)};
     237             :       },
     238        3856 :       token);
     239             : }
     240             : 
     241             : bool
     242         354 : CSGRegion::nextRegionOpIsIdentical(const RegionType region,
     243             :                                    const std::size_t postfix_token_index) const
     244             : {
     245         834 :   for (const auto i : make_range(postfix_token_index, _postfix_tokens.size()))
     246         722 :     if (const auto region_ptr = std::get_if<RegionType>(&_postfix_tokens[i]))
     247         242 :       return region == *region_ptr;
     248         112 :   return false;
     249             : }
     250             : 
     251             : void
     252           2 : CSGRegion::updateSurfaceReferences(
     253             :     std::map<std::string, std::reference_wrapper<const CSGSurface>> & identical_surface_refs)
     254             : {
     255           6 :   for (auto & token : _postfix_tokens)
     256           4 :     if (std::holds_alternative<std::reference_wrapper<const CSGSurface>>(token))
     257             :     {
     258           2 :       const auto & surf_ref = std::get<std::reference_wrapper<const CSGSurface>>(token);
     259           2 :       const auto & surf_name = surf_ref.get().getName();
     260           2 :       if (identical_surface_refs.find(surf_name) != identical_surface_refs.end())
     261           2 :         token = identical_surface_refs.at(surf_name);
     262             :     }
     263           2 : }
     264             : 
     265             : std::vector<std::reference_wrapper<const CSGSurface>>
     266         319 : CSGRegion::getSurfaces() const
     267             : {
     268         319 :   std::vector<std::reference_wrapper<const CSGSurface>> surface_references;
     269        2272 :   for (auto & token : _postfix_tokens)
     270        1953 :     if (std::holds_alternative<std::reference_wrapper<const CSGSurface>>(token))
     271         754 :       surface_references.push_back(std::get<std::reference_wrapper<const CSGSurface>>(token));
     272             : 
     273         319 :   return surface_references;
     274           0 : }
     275             : 
     276             : CSGRegion &
     277         355 : CSGRegion::operator&=(const CSGRegion & other_region)
     278             : {
     279         355 :   if (this != &other_region)
     280         710 :     *this = CSGRegion(*this, other_region, "INTERSECTION");
     281         355 :   return *this;
     282             : }
     283             : 
     284             : CSGRegion &
     285           2 : CSGRegion::operator|=(const CSGRegion & other_region)
     286             : {
     287           2 :   if (this != &other_region)
     288           4 :     *this = CSGRegion(*this, other_region, "UNION");
     289           2 :   return *this;
     290             : }
     291             : 
     292             : // Operators for region construction
     293             : 
     294             : // positive halfspace
     295             : const CSGRegion
     296         360 : operator+(const CSGSurface & surf)
     297             : {
     298         360 :   return CSGRegion(surf, CSGSurface::Halfspace::POSITIVE);
     299             : }
     300             : 
     301             : // negative halfspace
     302             : const CSGRegion
     303         324 : operator-(const CSGSurface & surf)
     304             : {
     305         324 :   return CSGRegion(surf, CSGSurface::Halfspace::NEGATIVE);
     306             : }
     307             : 
     308             : // intersection
     309             : const CSGRegion
     310           4 : operator&(const CSGRegion & region_a, const CSGRegion & region_b)
     311             : {
     312           8 :   return CSGRegion(region_a, region_b, "INTERSECTION");
     313             : }
     314             : 
     315             : // union
     316             : const CSGRegion
     317          22 : operator|(const CSGRegion & region_a, const CSGRegion & region_b)
     318             : {
     319          44 :   return CSGRegion(region_a, region_b, "UNION");
     320             : }
     321             : 
     322             : // complement
     323             : const CSGRegion
     324           2 : operator~(const CSGRegion & region)
     325             : {
     326           4 :   return CSGRegion(region, "COMPLEMENT");
     327             : }
     328             : 
     329             : bool
     330         230 : CSGRegion::operator==(const CSGRegion & other) const
     331             : {
     332         230 :   const bool region_type_eq = this->getRegionType() == other.getRegionType();
     333         230 :   return (region_type_eq && checkRegionEquality(other.getPostfixTokens()));
     334             : }
     335             : 
     336             : bool
     337           4 : CSGRegion::operator!=(const CSGRegion & other) const
     338             : {
     339           4 :   return !(*this == other);
     340             : }
     341             : 
     342             : } // namespace CSG

Generated by: LCOV version 1.14