https://mooseframework.inl.gov
Loading...
Searching...
No Matches
BoundaryShortestDistanceToSurface.C
Go to the documentation of this file.
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
11#include "SBMUtils.h"
12#include "Function.h"
13#include "SBMInterfaceManager.h"
14
15#include <algorithm>
16#include <limits>
17#include <regex>
18
20
22
25{
27
28 params.addParam<std::vector<FunctionName>>(
29 "surfaces", {}, "Level-set or mesh-based functions that define the boundary distances.");
30 params.addParam<UserObjectName>(
31 "manager", "SBMInterfaceManager used instead of per-interface surface functions.");
32 params.addParam<std::vector<std::vector<SubdomainID>>>(
33 "interface_subdomain_pairs",
34 {},
35 "Ordered subdomain pair for each boundary. If omitted, pairs are inferred from boundary "
36 "names of the form grainX_grainY or blockX_blockY.");
37 params.addParam<bool>("suppress_distance_warning",
38 false,
39 "If true, warnings about large distances will be suppressed.");
40
41 params.addParam<bool>("debug_output", false, "If true, debug output will be enabled.");
42
44 "Provides, at each quadrature point on a (surrogate) boundary, the vector from that point "
45 "to its projection on the true surface, together with the true surface normal there. The "
46 "true surface may be described by level-set/distance functions or a mesh-based "
47 "representation.");
48 return params;
49}
50
52 const InputParameters & parameters)
53 : SideUserObject(parameters),
54 _suppress_distance_warning(getParam<bool>("suppress_distance_warning")),
55 _debug_output(getParam<bool>("debug_output"))
56{
57 const auto function_names = getParam<std::vector<FunctionName>>("surfaces");
58 const bool has_manager = isParamValid("manager");
59 if (has_manager == !function_names.empty())
60 paramError("surfaces", "Specify exactly one of 'surfaces' and 'manager'.");
61 if (!has_manager && isParamSetByUser("interface_subdomain_pairs"))
62 paramError("interface_subdomain_pairs", "This parameter is only valid with 'manager'.");
63
64 const auto boundary_names = getParam<std::vector<BoundaryName>>("boundary");
65 for (const auto i : index_range(boundary_names))
66 {
67 const BoundaryID id = _mesh.getBoundaryID(boundary_names[i]);
68 if (!_side_id_index.emplace(id, i).second)
69 paramError("boundary", "Boundary '", boundary_names[i], "' is configured more than once.");
70 }
71
72 if (has_manager)
73 {
74 _manager = &getUserObject<SBMInterfaceManager>("manager");
75 const auto & pairs =
76 getParam<std::vector<std::vector<SubdomainID>>>("interface_subdomain_pairs");
77 if (!pairs.empty() && pairs.size() != boundary_names.size())
78 paramError("interface_subdomain_pairs", "Provide exactly one subdomain pair per boundary.");
79
80 const std::regex pair_pattern("^(?:grain|block)([0-9]+)_(?:grain|block)([0-9]+)$");
81 for (const auto i : index_range(boundary_names))
82 {
83 SubdomainID first;
84 SubdomainID second;
85 if (!pairs.empty())
86 {
87 if (pairs[i].size() != 2 || pairs[i][0] == pairs[i][1])
88 paramError("interface_subdomain_pairs",
89 "Each interface entry must contain two distinct subdomain IDs.");
90 first = pairs[i][0];
91 second = pairs[i][1];
92 }
93 else
94 {
95 std::smatch match;
96 const std::string name = boundary_names[i];
97 if (!std::regex_match(name, match, pair_pattern))
98 paramError("boundary",
99 "Cannot infer a subdomain pair from boundary '",
100 name,
101 "'. Supply interface_subdomain_pairs.");
102 const auto first_value = std::stoull(match[1].str());
103 const auto second_value = std::stoull(match[2].str());
104 if (first_value > std::numeric_limits<SubdomainID>::max() ||
105 second_value > std::numeric_limits<SubdomainID>::max())
106 paramError("boundary", "Boundary '", name, "' contains an out-of-range subdomain ID.");
107 first = static_cast<SubdomainID>(first_value);
108 second = static_cast<SubdomainID>(second_value);
109 if (first == second)
110 paramError("boundary", "Boundary '", name, "' identifies the same subdomain twice.");
111 }
112 _boundary_subdomain_pairs.emplace(_mesh.getBoundaryID(boundary_names[i]),
113 std::make_pair(first, second));
114 }
115 }
116 else
117 {
119 if (_distance_functions.size() != 1 && boundary_names.size() != _distance_functions.size())
120 paramError("surfaces", "Number of surfaces must match number of boundaries.");
121 }
122
123 _true_interface_measures.resize(boundary_names.size(), 0.0);
124}
125
126void
128{
129 if (_manager)
130 for (const auto & [boundary, pair] : _boundary_subdomain_pairs)
131 if (!_manager->hasInterface(pair.first, pair.second))
132 paramError("interface_subdomain_pairs",
133 "Boundary '",
134 _mesh.getBoundaryName(boundary),
135 "' maps to an interface not detected by the manager.");
136}
137
138void
146
147void
149{
150 const unsigned int n_qp = _qrule->n_points();
151
153
154 // Resize the vectors
155 if (_distance_vectors[elem_side].size() != n_qp)
156 _distance_vectors[elem_side].resize(n_qp);
157
158 if (_normal_vectors[elem_side].size() != n_qp)
159 _normal_vectors[elem_side].resize(n_qp);
160
161 // The sequence of `surfaces` must match the sequence of boundary IDs{}
162 // in `BoundaryShortestDistanceToSurface` when multiple functions are provided.
163 const auto side_id_it = _side_id_index.find(_current_boundary_id);
164 mooseAssert(side_id_it != _side_id_index.end(), "Boundary ID not registered in side map");
165 const unsigned int bid_index = side_id_it->second;
166 const unsigned int func_idx =
167 (_distance_functions.size() == 1) ? 0 : bid_index; // single function or multiple functions
168
169 _elem_side_to_bid[elem_side] = bid_index;
170
171 for (const auto qp : make_range(n_qp))
172 {
173 const Point & pt = _q_point[qp];
174 const Function * func = nullptr;
175 if (_manager)
176 {
177 const auto & pair = libmesh_map_find(_boundary_subdomain_pairs, _current_boundary_id);
178 const auto result = _manager->queryInterface(pair.first, pair.second, pt);
179 _distance_vectors[elem_side][qp] = result.distance;
180 _normal_vectors[elem_side][qp] = result.normal;
181 }
182 else
183 {
184 func = _distance_functions.at(func_idx);
186 _normal_vectors[elem_side][qp] = SBMUtils::trueNormalFromFunction(func, pt, _t);
187 }
188
189 const auto & true_normal = _normal_vectors[elem_side][qp];
190
191 // This signed area correction factor integrates to the true-interface measure, so its
192 // boundary integral must be positive when the true normal has the correct orientation.
193 _true_interface_measures[bid_index] += _normals[qp] * true_normal * _JxW[qp];
194
195 if (_debug_output)
196 {
197 const auto true_normal_dot_surrogate_normal = true_normal * _normals[qp];
198 const auto nt_tangent = _normals[qp] - true_normal_dot_surrogate_normal * true_normal;
199
200 _console << "[" << name() << "] surface=" << (func ? func->name() : _manager->name())
201 << " point=" << pt << " distance=" << _distance_vectors[elem_side][qp]
202 << " true_normal=" << true_normal << " tangent=" << nt_tangent << std::endl;
203 }
204 }
205}
206
207void
209{
210 const auto & other = static_cast<const BoundaryShortestDistanceToSurface &>(uo);
211 _distance_vectors.insert(other._distance_vectors.begin(), other._distance_vectors.end());
212 _normal_vectors.insert(other._normal_vectors.begin(), other._normal_vectors.end());
213 _elem_side_to_bid.insert(other._elem_side_to_bid.begin(), other._elem_side_to_bid.end());
214 for (const auto i : index_range(_true_interface_measures))
216}
217
218void
220{
222
223 for (auto & [elem_side, normals] : _normal_vectors)
224 if (_true_interface_measures[libmesh_map_find(_elem_side_to_bid, elem_side)] < 0.0)
225 for (auto & normal : normals)
226 normal *= -1.0;
227}
228
229const RealVectorValue &
231 unsigned int qp) const
232{
233 const auto distance_it = _distance_vectors.find(elem_side);
234 if (distance_it == _distance_vectors.end())
235 mooseError("BoundaryShortestDistanceToSurface::surrogateDistance missing distance data for "
236 "elem_id = ",
237 elem_side.first,
238 ", side = ",
239 elem_side.second,
240 ".");
241
242 // Characteristic element length from volume, and maximum diagonal span across the element
243 const auto & distance = distance_it->second.at(qp);
244 const auto dim = _mesh.dimension();
245 const auto * elem = _mesh.elemPtr(elem_side.first);
246 const Real h = std::pow(elem->volume(), 1.0 / dim);
247 const Real element_diagonal = std::sqrt(dim) * h;
248
249 if (distance.norm() > element_diagonal && !_suppress_distance_warning)
250 mooseWarning("Distance exceeds the estimated element scale on element ",
251 elem_side.first,
252 ", side ",
253 elem_side.second,
254 ": the distance from the surrogate boundary quadrature point to the true "
255 "interface (",
256 distance.norm(),
257 ") is larger than the estimated element diagonal (",
258 element_diagonal,
259 "). This may indicate an incorrect interface selection or distance function. "
260 "Please verify the interface geometry.");
261
262 return distance;
263}
264
265const RealVectorValue &
266BoundaryShortestDistanceToSurface::trueNormal(const ElemSide & elem_side, unsigned int qp) const
267{
268 const auto normal_it = _normal_vectors.find(elem_side);
269 if (normal_it == _normal_vectors.end())
270 mooseError("BoundaryShortestDistanceToSurface::trueNormal missing normal data for elem_id = ",
271 elem_side.first,
272 ", side = ",
273 elem_side.second,
274 ".");
275 return normal_it->second.at(qp);
276}
boundary_id_type BoundaryID
BoundaryShortestDistanceToSurface::ElemSide ElemSide
registerMooseObject("ShiftedBoundaryMethodApp", BoundaryShortestDistanceToSurface)
unsigned int dim
Computes and stores the distance vectors and true surface normals from surrogate boundary quadrature ...
BoundaryShortestDistanceToSurface(const InputParameters &parameters)
std::pair< dof_id_type, unsigned short int > ElemSide
bool _suppress_distance_warning
Whether to suppress warnings about large distances.
const RealVectorValue & trueNormal(const ElemSide &elem_side, unsigned int qp) const
Provides query interfaces for normal vector.
std::map< BoundaryID, std::pair< SubdomainID, SubdomainID > > _boundary_subdomain_pairs
Ordered subdomain pair associated with each surrogate boundary.
std::map< BoundaryID, unsigned int > _side_id_index
Map from boundary ID to internal distance function index.
bool _debug_output
Whether to output debug information.
std::vector< const Function * > _distance_functions
Local distance functions when no external distance user object is supplied.
const SBMInterfaceManager * _manager
Optional manager supplying geometry for interfaces in a single surface mesh.
const RealVectorValue & surrogateDistance(const ElemSide &elem_side, unsigned int qp) const
Provides query interfaces for distance vector.
std::map< ElemSide, unsigned int > _elem_side_to_bid
Map from ElemSide to boundary ID index.
std::map< ElemSide, std::vector< RealVectorValue > > _distance_vectors
Distance vectors to the true interface at each quadrature point on an element side.
std::map< ElemSide, std::vector< RealVectorValue > > _normal_vectors
True interface normals at projected quadrature points on an element side.
std::vector< Real > _true_interface_measures
Signed true-interface measure integrated over each surrogate boundary.
virtual void threadJoin(const UserObject &uo) override
const ConsoleStream _console
void addParam(const std::string &name, const std::initializer_list< typename T::value_type > &value, const std::string &doc_string)
void addClassDescription(const std::string &doc_string)
const std::string & name() const
void paramError(const std::string &param, Args... args) const
bool isParamSetByUser(const std::string &name) const
void mooseError(Args &&... args) const
void mooseWarning(Args &&... args) const
bool isParamValid(const std::string &name) const
virtual unsigned int dimension() const
virtual Elem * elemPtr(const dof_id_type i)
const std::string & getBoundaryName(const BoundaryID boundary_id) const
BoundaryID getBoundaryID(const BoundaryName &boundary_name) const
InterfaceQueryResult queryInterface(SubdomainID first, SubdomainID second, const Point &point) const
Return distance and normal data for an interface. The normal points from first to second.
bool hasInterface(SubdomainID first, SubdomainID second) const
Whether an interface was detected between two subdomains.
const unsigned int & _current_side
static InputParameters validParams()
MooseMesh & _mesh
const QBase *const & _qrule
const MooseArray< Real > & _JxW
const MooseArray< Point > & _q_point
const MooseArray< Point > & _normals
const Elem *const & _current_elem
const BoundaryID & _current_boundary_id
const Parallel::Communicator & comm() const
std::vector< const Function * > buildDistanceFunctions(const std::vector< FunctionName > &function_names, const FunctionInterface &function_provider)
Build a list of distance functions based on names specified in input.
Definition SBMUtils.C:172
RealVectorValue distanceVectorFromFunction(const Function *func, const libMesh::Point &pt, Real t)
Compute the distance vector induced by a distance function.
Definition SBMUtils.C:197
RealVectorValue trueNormalFromFunction(const Function *func, const libMesh::Point &pt, Real t)
Compute the true boundary surface normal at the point on the boundary closest to pt.
Definition SBMUtils.C:217
RealVectorValue distance
Shortest distance vector to the interface.
Real distance(const Point &p)