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
|