PolyFEM
Loading...
Searching...
No Matches
SmoothingForms.cpp
Go to the documentation of this file.
2
9
10#include <Eigen/Core>
11
12#include <cassert>
13#include <memory>
14#include <numeric>
15#include <set>
16#include <utility>
17#include <vector>
18
19namespace polyfem::solver
20{
22 const VariableToSimulationGroup &variable_to_simulations,
23 std::shared_ptr<const varform::DifferentiableVarForm> varform,
24 const bool scale_invariant,
25 const int power,
26 const std::vector<int> &surface_selections,
27 const std::vector<int> &active_dims) : AdjointForm(variable_to_simulations), varform_(std::move(varform)), scale_invariant_(scale_invariant), power_(power), active_dims_(active_dims)
28 {
29 const auto &mesh = varform_->get_mesh();
30 const int dim = mesh.dimension();
31 const int n_verts = mesh.n_vertices();
32 assert(mesh.is_simplicial());
33 // empty implies all active.
34 if (active_dims_.empty())
35 {
36 active_dims_.resize(dim);
37 std::iota(active_dims_.begin(), active_dims_.end(), 0);
38 }
39
40 surface_ids_ = std::set(surface_selections.begin(), surface_selections.end());
41
42 // collect active nodes
43 std::vector<bool> active_mask;
44 active_mask.assign(n_verts, false);
45 std::vector<Eigen::Triplet<bool>> T_adj;
46
47 for (int b = 0; b < mesh.n_boundary_elements(); b++)
48 {
49 const int boundary_id = mesh.get_boundary_id(b);
50 if (!surface_ids_.empty() && surface_ids_.find(boundary_id) == surface_ids_.end())
51 continue;
52
53 for (int lv = 0; lv < dim; lv++)
54 {
55 active_mask[mesh.boundary_element_vertex(b, lv)] = true;
56 }
57
58 for (int lv1 = 0; lv1 < dim; lv1++)
59 for (int lv2 = 0; lv2 < lv1; lv2++)
60 {
61 const int v1 = mesh.boundary_element_vertex(b, lv1);
62 const int v2 = mesh.boundary_element_vertex(b, lv2);
63 T_adj.emplace_back(v2, v1, true);
64 T_adj.emplace_back(v1, v2, true);
65 }
66 }
67
68 adj.setZero();
69 adj.resize(n_verts, n_verts);
70 adj.setFromTriplets(T_adj.begin(), T_adj.end());
71
72 std::vector<int> degrees(n_verts, 0);
73 for (int k = 0; k < adj.outerSize(); ++k)
74 for (Eigen::SparseMatrix<bool, Eigen::RowMajor>::InnerIterator it(adj, k); it; ++it)
75 degrees[k]++;
76
77 L.setZero();
78 L.resize(n_verts, n_verts);
80 {
81 std::vector<Eigen::Triplet<double>> T_L;
82 for (int k = 0; k < adj.outerSize(); ++k)
83 {
84 if (!active_mask[k])
85 continue;
86 T_L.emplace_back(k, k, 1);
87 for (Eigen::SparseMatrix<bool, Eigen::RowMajor>::InnerIterator it(adj, k); it; ++it)
88 {
89 assert(it.row() == k);
90 T_L.emplace_back(it.row(), it.col(), -1. / degrees[k]);
91 }
92 }
93 L.setFromTriplets(T_L.begin(), T_L.end());
94 L.prune([](int i, int j, double val) { return abs(val) > 1e-12; });
95 }
96 }
97
98 double BoundarySmoothingForm::value_unweighted(const Eigen::VectorXd &x) const
99 {
100 const auto &mesh = varform_->get_mesh();
101 const int dim = mesh.dimension();
102 const int n_verts = mesh.n_vertices();
103
104 double val = 0;
106 {
107 for (int b = 0; b < adj.rows(); b++)
108 {
110 s.setZero(dim);
111 double sum_norm = 0;
112 int valence = 0;
113 for (Eigen::SparseMatrix<bool, Eigen::RowMajor>::InnerIterator it(adj, b); it; ++it)
114 {
115 assert(it.col() != b);
116 polyfem::RowVectorNd x = mesh.point(b) - mesh.point(it.col());
117 s += x;
118 sum_norm += x.norm();
119 valence += 1;
120 }
121 if (valence)
122 {
123 s = s / sum_norm;
124 val += pow(s.norm(), power_);
125 }
126 }
127 }
128 else
129 {
130 Eigen::MatrixXd V;
131 varform_->get_vertices(V);
132
133 val = (L * V(Eigen::all, active_dims_)).squaredNorm();
134 }
135
136 return val;
137 }
138
139 void BoundarySmoothingForm::compute_partial_gradient(const Eigen::VectorXd &x, Eigen::VectorXd &gradv) const
140 {
141 const auto &mesh = varform_->get_mesh();
142 const int dim = mesh.dimension();
143 const int n_verts = mesh.n_vertices();
144
145 Eigen::VectorXd grad;
147 {
148 grad.setZero(n_verts * dim);
149 for (int b = 0; b < adj.rows(); b++)
150 {
152 s.setZero(dim);
153 double sum_norm = 0;
154 polyfem::RowVectorNd sum_normalized = s;
155 int valence = 0;
156 for (Eigen::SparseMatrix<bool, Eigen::RowMajor>::InnerIterator it(adj, b); it; ++it)
157 {
158 assert(it.col() != b);
159 polyfem::RowVectorNd x = mesh.point(b) - mesh.point(it.col());
160 s += x;
161 sum_norm += x.norm();
162 sum_normalized += x.normalized();
163 valence += 1;
164 }
165 if (valence)
166 {
167 s = s / sum_norm;
168 const double coeff = power_ * pow(s.norm(), power_ - 2.) / sum_norm;
169
170 grad.segment(b * dim, dim) += (s * valence - s.squaredNorm() * sum_normalized) * coeff;
171 for (Eigen::SparseMatrix<bool, Eigen::RowMajor>::InnerIterator it(adj, b); it; ++it)
172 grad.segment(it.col() * dim, dim) -= (s + s.squaredNorm() * (mesh.point(it.col()) - mesh.point(b)).normalized()) * coeff;
173 }
174 }
175 }
176 else
177 {
178 Eigen::MatrixXd V;
179 varform_->get_vertices(V);
180
181 Eigen::MatrixXd grad_mat = 2 * (L.transpose() * (L * V));
182 for (int d = 0; d < dim; d++)
183 if (std::find(active_dims_.begin(), active_dims_.end(), d) == active_dims_.end())
184 grad_mat.col(d).setZero();
185 grad = utils::flatten(grad_mat);
186 }
187
189 return grad;
190 });
191 }
192
194 const VariableToSimulationGroup &variable_to_simulations,
195 std::shared_ptr<const varform::DifferentiableVarForm> varform,
196 const std::vector<int> &volume_selections)
197 : AdjointForm(variable_to_simulations), varform_(std::move(varform))
198 {
199 auto &mesh = varform_->get_mesh();
200 if (!mesh.is_conforming())
201 log_and_throw_adjoint_error("Elastic material smoothing form does not support non-conforming meshes!");
202
203 int elastic_mappings = 0;
204 for (auto &v2s : variable_to_simulations_.data)
205 {
206 if (v2s->parameter_type() == ParameterType::LameParameter
207 && v2s->affects_varform(*varform_))
208 {
209 ++elastic_mappings;
210 }
211 }
212 if (elastic_mappings != 1)
213 {
214 // Unclear how to define neighboring elements when multiple varforms (potentially multiple meshes) exists.
215 log_and_throw_adjoint_error("Elastic material smoothing form does not support more than one affects varform!");
216 }
217
218 std::set<int> volume_ids(volume_selections.begin(), volume_selections.end());
219 auto is_selected = [&mesh, &volume_ids](const int element) {
220 // empty volume selection implies all active.
221 return volume_ids.empty() || volume_ids.count(mesh.get_body_id(element)) > 0;
222 };
223
224 // Build adjacency information.
225 if (mesh.is_volume())
226 {
227 const auto *mesh3d = dynamic_cast<const mesh::Mesh3D *>(&mesh);
228 assert(mesh3d != nullptr);
229
230 for (int element = 0; element < mesh3d->n_cells(); ++element)
231 {
232 if (!is_selected(element))
233 continue;
234
235 // Iterate through all faces of an element then query the interfacing neighbor.
236 for (int face = 0; face < mesh3d->n_cell_faces(element); ++face)
237 {
238 // get_index_from_element takes global element id, local face id, local vertex id then
239 // return a navigation index struct. You can consider navigation index as a descriptor
240 // that maps between vertex <-> edge <-> face <-> element. With this info, we then call
241 // switch_element to query the neighboring element. Since the purpose is to find neighbors,
242 // vertex info is redundant hence the dummy 0.
243 auto index = mesh3d->get_index_from_element(element, face, 0);
244 int neighbor = mesh3d->switch_element(index).element;
245 // neighbor == -1 indicates boundary face.
246 if (neighbor >= 0 && is_selected(neighbor))
247 adjacent_elements_.emplace_back(element, neighbor);
248 }
249 }
250 }
251 else
252 {
253 const auto *mesh2d = dynamic_cast<const mesh::Mesh2D *>(&mesh);
254 assert(mesh2d != nullptr);
255
256 for (int element = 0; element < mesh2d->n_faces(); ++element)
257 {
258 if (!is_selected(element))
259 continue;
260
261 for (int edge = 0; edge < mesh2d->n_face_vertices(element); ++edge)
262 {
263 auto index = mesh2d->get_index_from_face(element, edge);
264 int neighbor = mesh2d->switch_face(index).face;
265 // neighbor == -1 indicates boundary edge.
266 if (neighbor >= 0 && is_selected(neighbor))
267 adjacent_elements_.emplace_back(element, neighbor);
268 }
269 }
270 }
271 }
272
273 Eigen::VectorXd ElasticMaterialSmoothingForm::lame_parameters(const Eigen::VectorXd &x) const
274 {
275 int n_elements = varform_->get_mesh().n_elements();
276 Eigen::VectorXd parameters = Eigen::VectorXd::Zero(2 * n_elements);
279 return parameters;
280 }
281
282 double ElasticMaterialSmoothingForm::value_unweighted(const Eigen::VectorXd &x) const
283 {
284 if (adjacent_elements_.empty())
285 return 0;
286
287 int n_elements = varform_->get_mesh().n_elements();
288 Eigen::VectorXd parameters = lame_parameters(x);
289 auto lmd = parameters.head(n_elements);
290 auto mu = parameters.tail(n_elements);
291
292 double value = 0;
293 // See class level doc.
294 for (auto &[a, b] : adjacent_elements_)
295 {
296 double lmd_penalty = 1 - lmd(a) / lmd(b);
297 double mu_penalty = 1 - mu(a) / mu(b);
298 value += lmd_penalty * lmd_penalty + mu_penalty * mu_penalty;
299 }
300
301 return value / adjacent_elements_.size();
302 }
303
304 void ElasticMaterialSmoothingForm::compute_partial_gradient(const Eigen::VectorXd &x, Eigen::VectorXd &gradv) const
305 {
306 if (adjacent_elements_.empty())
307 {
308 gradv = Eigen::VectorXd::Zero(x.size());
309 return;
310 }
311
312 int n_elements = varform_->get_mesh().n_elements();
313 Eigen::VectorXd parameters = lame_parameters(x);
314 auto lmd = parameters.head(n_elements);
315 auto mu = parameters.tail(n_elements);
316 Eigen::VectorXd grad = Eigen::VectorXd::Zero(parameters.size());
317
318 // See class level doc.
319 for (auto &[a, b] : adjacent_elements_)
320 {
321 double lmd_ratio = lmd(a) / lmd(b);
322 grad(a) += 2 * (lmd_ratio - 1) / lmd(b);
323 grad(b) += 2 * (1 - lmd_ratio) * lmd(a) / (lmd(b) * lmd(b));
324
325 double mu_ratio = mu(a) / mu(b);
326 grad(n_elements + a) += 2 * (mu_ratio - 1) / mu(b);
327 grad(n_elements + b) += 2 * (1 - mu_ratio) * mu(a) / (mu(b) * mu(b));
328 }
329
330 grad /= adjacent_elements_.size();
332 return grad;
333 });
334 }
335} // namespace polyfem::solver
int V
double val
Definition Assembler.cpp:90
int x
double value(const Eigen::VectorXd &x) const override
Compute the value of the form multiplied with the weigth.
const VariableToSimulationGroup variable_to_simulations_
BoundarySmoothingForm(const VariableToSimulationGroup &variable_to_simulations, std::shared_ptr< const varform::DifferentiableVarForm > varform, const bool scale_invariant, const int power, const std::vector< int > &surface_selections, const std::vector< int > &active_dims)
Eigen::SparseMatrix< bool, Eigen::RowMajor > adj
std::shared_ptr< const varform::DifferentiableVarForm > varform_
double value_unweighted(const Eigen::VectorXd &x) const override
Compute the value of the form.
void compute_partial_gradient(const Eigen::VectorXd &x, Eigen::VectorXd &gradv) const override
Eigen::SparseMatrix< double, Eigen::RowMajor > L
Eigen::VectorXd lame_parameters(const Eigen::VectorXd &x) const
void compute_partial_gradient(const Eigen::VectorXd &x, Eigen::VectorXd &gradv) const override
double value_unweighted(const Eigen::VectorXd &x) const override
Compute the value of the form.
std::vector< std::pair< int, int > > adjacent_elements_
Adjacent global element id. Contains both direction Ex. (elem a, elem b) and (elem b,...
std::shared_ptr< const varform::DifferentiableVarForm > varform_
ElasticMaterialSmoothingForm(const VariableToSimulationGroup &variable_to_simulations, std::shared_ptr< const varform::DifferentiableVarForm > varform, const std::vector< int > &volume_selections)
Create an elastic-material (Lamé-parameter) smoothing term.
virtual double weight() const
Get the form's multiplicative constant weight.
Definition Form.hpp:128
Eigen::VectorXd apply_parametrization_jacobian(ParameterType type, const varform::DifferentiableVarForm &target, const Eigen::VectorXd &x, const std::function< Eigen::VectorXd()> &grad) const
Compute parametrization jacobian for all var2sim matching parameter type and output to target varform...
void compute_state_variable(ParameterType type, const varform::DifferentiableVarForm &target, const Eigen::VectorXd &x, Eigen::VectorXd &state_variable) const
std::vector< std::shared_ptr< VariableToSimulation > > data
Eigen::VectorXd flatten(const Eigen::MatrixXd &X)
Flatten rowwises.
void log_and_throw_adjoint_error(const std::string &msg)
Definition Logger.cpp:79
Eigen::Matrix< double, 1, Eigen::Dynamic, Eigen::RowMajor, 1, 3 > RowVectorNd
Definition Types.hpp:13