PolyFEM
Loading...
Searching...
No Matches
PeriodicBoundaryLagrangianForm.cpp
Go to the documentation of this file.
2
4
5#include <algorithm>
6#include <cmath>
7#include <limits>
8#include <map>
9#include <set>
10#include <vector>
11
12namespace polyfem::solver
13{
14 namespace
15 {
16 struct BoundaryNode
17 {
19 std::vector<std::pair<int, double>> weights;
20 };
21
22 using BoundaryNodes = std::vector<BoundaryNode>;
23
24 bool same_weights(
25 const std::vector<std::pair<int, double>> &lhs,
26 const std::vector<std::pair<int, double>> &rhs)
27 {
28 if (lhs.size() != rhs.size())
29 return false;
30 for (int i = 0; i < int(lhs.size()); ++i)
31 {
32 if (lhs[i].first != rhs[i].first || std::abs(lhs[i].second - rhs[i].second) > 1e-12)
33 return false;
34 }
35 return true;
36 }
37
38 BoundaryNodes collect_boundary_nodes(
39 const int boundary_id,
40 const mesh::Mesh &mesh,
41 const std::vector<basis::ElementBases> &bases,
42 const std::vector<mesh::LocalBoundary> &local_boundary)
43 {
44 BoundaryNodes result;
45 for (const mesh::LocalBoundary &lb : local_boundary)
46 {
47 const basis::ElementBases &element_bases = bases.at(lb.element_id());
48 for (int i = 0; i < lb.size(); ++i)
49 {
50 const int primitive_id = lb.global_primitive_id(i);
51 if (mesh.get_boundary_id(primitive_id) != boundary_id)
52 continue;
53
54 const Eigen::VectorXi local_nodes = element_bases.local_nodes_for_primitive(primitive_id, mesh);
55 for (const int local_node : local_nodes)
56 {
57 std::map<int, double> accumulated_weights;
58 RowVectorNd point = RowVectorNd::Zero(mesh.dimension());
59 for (const basis::Local2Global &global : element_bases.bases.at(local_node).global())
60 {
61 accumulated_weights[global.index] += global.val;
62 point += global.val * global.node;
63 }
64
65 BoundaryNode node;
66 node.point = point;
67 for (const auto &[index, weight] : accumulated_weights)
68 {
69 if (std::abs(weight) > 1e-14)
70 node.weights.emplace_back(index, weight);
71 }
72 if (node.weights.empty())
73 log_and_throw_error("Unable to assemble a DoF on periodic boundary {}", boundary_id);
74
75 const auto duplicate = std::find_if(
76 result.begin(), result.end(),
77 [&](const BoundaryNode &other) { return same_weights(node.weights, other.weights); });
78 if (duplicate == result.end())
79 result.emplace_back(std::move(node));
80 else if ((duplicate->point - point).norm() > 1e-12)
81 log_and_throw_error("Inconsistent position for a DoF on periodic boundary {}", boundary_id);
82 }
83 }
84 }
85 return result;
86 }
87 } // namespace
88
90 const int ndof,
91 const int value_dim,
92 const mesh::Mesh &mesh,
93 const std::vector<basis::ElementBases> &bases,
94 const std::vector<mesh::LocalBoundary> &local_boundary,
95 const std::array<int, 2> &boundary_ids,
96 const double relative_tolerance)
97 : PeriodicBoundaryLagrangianForm(build_mapping(
98 ndof, value_dim, mesh, bases, local_boundary, boundary_ids, relative_tolerance))
99 {
100 }
101
106
108 const int ndof,
109 const int value_dim,
110 const mesh::Mesh &mesh,
111 const std::vector<basis::ElementBases> &bases,
112 const std::vector<mesh::LocalBoundary> &local_boundary,
113 const std::array<int, 2> &boundary_ids,
114 const double relative_tolerance)
115 {
116 if (boundary_ids[0] < 0 || boundary_ids[1] < 0 || boundary_ids[0] == boundary_ids[1])
117 log_and_throw_error("Periodic boundary IDs must be distinct non-negative integers");
118 if (relative_tolerance <= 0)
119 log_and_throw_error("Periodic boundary tolerance must be positive");
120
121 const BoundaryNodes first = collect_boundary_nodes(boundary_ids[0], mesh, bases, local_boundary);
122 const BoundaryNodes second = collect_boundary_nodes(boundary_ids[1], mesh, bases, local_boundary);
123 if (first.empty() || second.empty())
125 "Unable to find DoFs for periodic boundary pair ({}, {}): found {} and {} DoFs",
126 boundary_ids[0], boundary_ids[1], first.size(), second.size());
127 if (first.size() != second.size())
129 "Periodic boundary pair ({}, {}) has different DoF counts: {} and {}",
130 boundary_ids[0], boundary_ids[1], first.size(), second.size());
131
132 RowVectorNd first_centroid = RowVectorNd::Zero(mesh.dimension());
133 RowVectorNd second_centroid = RowVectorNd::Zero(mesh.dimension());
134 for (const BoundaryNode &node : first)
135 first_centroid += node.point;
136 for (const BoundaryNode &node : second)
137 second_centroid += node.point;
138 first_centroid /= double(first.size());
139 second_centroid /= double(second.size());
140
141 // The paired boundaries are assumed to be translated copies. Use their
142 // trace-DoF centroids to estimate the translation, then match the
143 // translated DoF positions below. The tolerance is used only to find the
144 // correspondence; the assembled periodic constraints are exact equalities.
145 const RowVectorNd translation = second_centroid - first_centroid;
146
147 RowVectorNd bbox_min, bbox_max;
148 mesh.bounding_box(bbox_min, bbox_max);
149 const double tolerance = relative_tolerance * (bbox_max - bbox_min).maxCoeff();
150
151 std::vector<std::pair<int, int>> pairs;
152 pairs.reserve(first.size());
153 std::set<int> used_second;
154 for (int first_index = 0; first_index < int(first.size()); ++first_index)
155 {
156 int matched_index = -1;
157 double matched_distance = std::numeric_limits<double>::infinity();
158 for (int second_index = 0; second_index < int(second.size()); ++second_index)
159 {
160 if (used_second.count(second_index) > 0)
161 continue;
162
163 const double distance = (first[first_index].point + translation - second[second_index].point).norm();
164 if (distance < matched_distance)
165 {
166 matched_distance = distance;
167 matched_index = second_index;
168 }
169 }
170
171 if (matched_index < 0 || matched_distance > tolerance)
173 "No matching DoF found on periodic boundary {} for trace DoF {} on boundary {} (distance {}, tolerance {})",
174 boundary_ids[1], first_index, boundary_ids[0], matched_distance, tolerance);
175 if (!used_second.insert(matched_index).second)
177 "Periodic boundary pair ({}, {}) does not have a bijective DoF correspondence; DoF {} was matched more than once",
178 boundary_ids[0], boundary_ids[1], matched_index);
179 pairs.emplace_back(first_index, matched_index);
180 }
181
182 std::vector<Eigen::Triplet<double>> entries;
183 for (int i = 0; i < int(pairs.size()); ++i)
184 {
185 for (int d = 0; d < value_dim; ++d)
186 {
187 const int row = i * value_dim + d;
188 for (const auto &[index, weight] : first[pairs[i].first].weights)
189 {
190 const int dof = index * value_dim + d;
191 if (dof < 0 || dof >= ndof)
192 log_and_throw_error("Periodic boundary DoF index exceeds the problem size");
193 entries.emplace_back(row, dof, weight);
194 }
195 for (const auto &[index, weight] : second[pairs[i].second].weights)
196 {
197 const int dof = index * value_dim + d;
198 if (dof < 0 || dof >= ndof)
199 log_and_throw_error("Periodic boundary DoF index exceeds the problem size");
200 entries.emplace_back(row, dof, -weight);
201 }
202 }
203 }
204
205 Mapping data;
206 data.translation = translation;
207 std::set<int> boundary_dofs;
208 for (const BoundaryNode &node : first)
209 for (const auto &[index, weight] : node.weights)
210 boundary_dofs.insert(index);
211 for (const BoundaryNode &node : second)
212 for (const auto &[index, weight] : node.weights)
213 boundary_dofs.insert(index);
214 data.boundary_dofs.assign(boundary_dofs.begin(), boundary_dofs.end());
215 data.A.resize(int(pairs.size()) * value_dim, ndof);
216 data.A.setFromTriplets(entries.begin(), entries.end());
217 data.A.makeCompressed();
218 data.b.setZero(data.A.rows(), 1);
219 return data;
220 }
221} // namespace polyfem::solver
std::vector< Eigen::Triplet< double > > entries
Eigen::RowVectorXd point
std::vector< std::pair< int, double > > weights
Abstract mesh class to capture 2d/3d conforming and non-conforming meshes.
Definition Mesh.hpp:49
virtual void bounding_box(RowVectorNd &min, RowVectorNd &max) const =0
computes the bbox of the mesh
int dimension() const
utily for dimension
Definition Mesh.hpp:164
virtual double weight() const
Get the form's multiplicative constant weight.
Definition Form.hpp:128
Form of the lagrangian in augmented lagrangian.
Linear equality constraints coupling corresponding DoFs on two tagged boundaries.
PeriodicBoundaryLagrangianForm(int ndof, int value_dim, const mesh::Mesh &mesh, const std::vector< basis::ElementBases > &bases, const std::vector< mesh::LocalBoundary > &local_boundary, const std::array< int, 2 > &boundary_ids, double relative_tolerance)
static Mapping build_mapping(int ndof, int value_dim, const mesh::Mesh &mesh, const std::vector< basis::ElementBases > &bases, const std::vector< mesh::LocalBoundary > &local_boundary, const std::array< int, 2 > &boundary_ids, double relative_tolerance)
Eigen::Matrix< double, 1, Eigen::Dynamic, Eigen::RowMajor, 1, 3 > RowVectorNd
Definition Types.hpp:13
void log_and_throw_error(const std::string &msg)
Definition Logger.cpp:73