14#include "libmesh/boundary_info.h"
15#include "libmesh/elem.h"
16#include "libmesh/elem_side_builder.h"
17#include "libmesh/parallel_algebra.h"
22#if NANOFLANN_VERSION < 0x150
27template <
typename T,
typename U>
49 "automatic_pairing_boundaries",
50 "List of boundary names to consider when automatically detecting contact pairs.");
52 "automatic_pairing_distance",
53 "Maximum distance (node-to-node or centroid-to-centroid) at which two boundaries are "
54 "considered a contact pair.");
56 "automatic_pairing_method",
58 "Strategy used to detect pairs: NODE uses a KD-tree over boundary nodes; "
59 "CENTROID uses sideset center-of-gravity distances.");
61 "prefix",
"Prefix prepended to the names of generated subdomain blocks.");
63 "create_lower_d_blocks",
64 "Whether to create primary and secondary lower-dimensional subdomain blocks for each "
67 "Detects contact surface pairs by proximity, stores them in the mesh meta-data, and "
68 "optionally creates lower-dimensional subdomain blocks for use in mortar contact.");
75 _input(getMesh(
"input")),
76 _pairing_boundaries(getParam<
std::vector<BoundaryName>>(
"automatic_pairing_boundaries")),
77 _pairing_distance(getParam<Real>(
"automatic_pairing_distance")),
78 _pairing_method(getParam<
MooseEnum>(
"automatic_pairing_method")),
79 _prefix(getParam<
std::string>(
"prefix")),
80 _create_lower_d_blocks(getParam<bool>(
"create_lower_d_blocks"))
87std::unique_ptr<MeshBase>
92 std::vector<std::pair<BoundaryName, BoundaryName>> pairs;
101 mooseError(
"No contact pairs found within distance ",
103 " among boundaries ",
111 const bool multiple_pairs = pairs.size() > 1;
112 for (
const auto & pair : pairs)
114 const auto & [primary_boundary, secondary_boundary] = pair;
115 const std::string suffix = multiple_pairs ?
pairSuffix(pair) :
"";
116 const std::string primary_name =
_prefix +
"_primary_subdomain" + suffix;
117 const std::string secondary_name =
_prefix +
"_secondary_subdomain" + suffix;
135 return "_p_" + pair.first +
"_s_" + pair.second;
138std::vector<AutomaticContactPairingGenerator::CandidateBoundary>
143 std::vector<CandidateBoundary> candidates;
148 std::find_if(candidates.begin(),
151 if (it != candidates.end())
153 if (it->name == bname)
156 "' is listed more than once in 'automatic_pairing_boundaries'. Each boundary "
157 "may be listed only once.");
163 "' in 'automatic_pairing_boundaries' refer to the same boundary (ID ",
165 "). Each boundary may be listed only once.");
167 candidates.push_back({bname, bid, 0, Point(0, 0, 0)});
173 const bool serial =
mesh.is_serial();
175 for (
const auto & [eid, side, bid] :
mesh.get_boundary_info().build_side_list())
177 const auto it = std::find_if(candidates.begin(),
180 { return candidate.id == bid; });
181 if (it == candidates.end())
184 const Elem & elem = *
mesh.elem_ptr(eid);
185 if (!serial && elem.processor_id() !=
mesh.processor_id())
188 const Elem & side_elem = side_builder(elem, side);
189 const Real area = side_elem.volume();
191 it->centroid += side_elem.true_centroid() * area;
197 std::vector<Real> sums;
198 sums.reserve(candidates.size() * (1 +
Moose::dim));
199 for (
const auto & candidate : candidates)
201 sums.push_back(candidate.area);
203 sums.push_back(candidate.centroid(
d));
205 mesh.comm().sum(sums);
207 auto sum_it = sums.begin();
208 for (
auto & candidate : candidates)
210 candidate.area = *sum_it++;
212 candidate.centroid(
d) = *sum_it++;
216 for (
auto & candidate : candidates)
217 if (candidate.area > 0)
218 candidate.centroid /= candidate.area;
223std::pair<BoundaryName, BoundaryName>
232 const Real area_tol = TOLERANCE * std::max(
a.area,
b.area);
233 const bool a_is_primary = std::abs(
a.area -
b.area) > area_tol ?
a.area >
b.area :
a.id >
b.id;
235 return {
a.name,
b.name};
237 return {
b.name,
a.name};
240std::vector<std::pair<BoundaryName, BoundaryName>>
247 std::find_if(candidates.begin(),
250 mooseAssert(it != candidates.end(),
"Boundary not in candidate list");
258 const bool serial =
mesh.is_serial();
259 std::vector<NodeBoundaryIDInfo> node_bid_list;
260 for (
const auto & candidate : candidates)
263 const Node & node =
mesh.node_ref(node_id);
264 if (serial || node.processor_id() ==
mesh.processor_id())
265 node_bid_list.emplace_back(node, candidate.id);
269 mesh.comm().allgather(node_bid_list);
272 std::sort(node_bid_list.begin(),
275 { return a.second < b.second; });
277 using KDTreeType = nanoflann::KDTreeSingleIndexAdaptor<
278 nanoflann::L2_Simple_Adaptor<Real, PointListAdaptor<NodeBoundaryIDInfo>, Real, std::size_t>,
283 const unsigned int max_leaf_size = 20;
286 auto kd_tree = std::make_unique<KDTreeType>(
287 Moose::dim, point_list, nanoflann::KDTreeSingleIndexAdaptorParams(max_leaf_size));
288 kd_tree->buildIndex();
291 std::vector<nanoflann::ResultItem<std::size_t, Real>> ret_matches;
292 std::vector<std::pair<BoundaryName, BoundaryName>> pairs;
294 for (
const auto & [entry_node, entry_bid] : node_bid_list)
297 kd_tree->radiusSearch(
300 for (
const auto & [pair_idx, _] : ret_matches)
302 const auto & [pair_node, pair_bid] = node_bid_list[pair_idx];
303 libmesh_ignore(pair_node);
306 if (pair_bid == entry_bid)
309 pairs.push_back(
orderPair(find_candidate(entry_bid), find_candidate(pair_bid)));
317std::vector<std::pair<BoundaryName, BoundaryName>>
321 for (
const auto & candidate : candidates)
322 if (candidate.area == 0)
323 mooseError(
"Boundary '", candidate.name,
"' not found in mesh.");
326 std::vector<std::pair<BoundaryName, BoundaryName>> pairs;
327 for (
const auto i : index_range(candidates))
328 for (
const auto j : make_range(i + 1, candidates.size()))
329 if ((candidates[i].centroid - candidates[j].centroid).norm() <=
_pairing_distance)
330 pairs.push_back(
orderPair(candidates[i], candidates[j]));
338 std::vector<std::pair<BoundaryName, BoundaryName>> & pairs)
340 std::sort(pairs.begin(), pairs.end());
341 pairs.erase(std::unique(pairs.begin(), pairs.end()), pairs.end());
boundary_id_type BoundaryID
static InputParameters validParams()
void mooseError(Args &&... args) const
const Point & getPoint(const PointObject &item) const
void createSubdomainFromSidesets(MeshBase &mesh, std::vector< BoundaryName > boundary_names, const SubdomainID new_subdomain_id, const SubdomainName &new_subdomain_name)
std::set< dof_id_type > getBoundaryNodes(const MeshBase &mesh, const BoundaryID boundary_id)
SubdomainID getNextFreeSubdomainID(MeshBase &input_mesh)
BoundaryID getBoundaryID(const BoundaryName &boundary_name, const MeshBase &mesh)
static constexpr std::size_t dim
std::string stringify(const T &t)
std::pair< T, U > ResultItem
SearchParams SearchParameters