Loading [MathJax]/extensions/tex2jax.js
PolyFEM
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
Smooth.cpp
Go to the documentation of this file.
6
7#include <wmtk/ExecutionScheduler.hpp>
8#include <wmtk/utils/TriQualityUtils.hpp>
9#include <wmtk/utils/TetraQualityUtils.hpp>
10#include <wmtk/utils/TupleUtils.hpp>
11#include <wmtk/utils/AMIPS.h>
12#include <wmtk/utils/AMIPS2D.h>
13
14namespace polyfem::mesh
15{
16 template <class WMTKMesh>
17 double get_quality(const WildRemesher<WMTKMesh> &m, const typename WMTKMesh::Tuple &t)
18 {
19 // Global ids of the vertices of the triangle
20 auto vids = m.element_vids(t);
21
22 // Temporary variable to store the stacked coordinates of the triangle
24 std::array<double, NDOF> T;
25 for (auto i = 0; i < WildRemesher<WMTKMesh>::VERTICES_PER_ELEMENT; i++)
26 for (auto j = 0; j < WildRemesher<WMTKMesh>::DIM; j++)
28 m.vertex_attrs[vids[i]].rest_position[j];
29
30 // Energy evaluation
31 double energy;
32 if constexpr (std::is_same_v<wmtk::TriMesh, WMTKMesh>)
33 energy = wmtk::AMIPS2D_energy(T);
34 else
35 energy = wmtk::AMIPS_energy(T);
36
37 // Filter for numerical issues
38 if (!std::isfinite(energy))
39 return std::numeric_limits<double>::max();
40
41 return energy;
42 }
43
44 template <class WMTKMesh>
47 LocalMesh<WildRemesher<WMTKMesh>> &old_local_mesh,
48 LocalMesh<WildRemesher<WMTKMesh>> &new_local_mesh)
49 {
50 POLYFEM_REMESHER_SCOPED_TIMER("Project local quantities");
52 const std::vector<basis::ElementBases> from_bases =
53 old_local_mesh.build_bases(m.state.formulation());
54 const std::vector<basis::ElementBases> to_bases =
55 new_local_mesh.build_bases(m.state.formulation());
56
57 const Eigen::MatrixXd &from_projection_quantities = old_local_mesh.projection_quantities();
58 const Eigen::MatrixXd &to_projection_quantities = new_local_mesh.projection_quantities();
59
60 // solve M x = A y for x where M is the mass matrix and A is the cross mass matrix.
61 Eigen::SparseMatrix<double> M, A;
62 {
64 assembler::Mass mass_matrix_assembler;
66
67 mass_matrix_assembler.assemble(
68 m.is_volume(), new_local_mesh.num_vertices(),
69 to_bases, to_bases, cache, 0, M, true);
70 assert(M.rows() == new_local_mesh.num_vertices() * m.dim());
71
72 assembler.assemble_cross(
73 m.is_volume(), m.dim(),
74 old_local_mesh.num_vertices(), from_bases, from_bases,
75 new_local_mesh.num_vertices(), to_bases, to_bases,
76 cache, A);
77 assert(A.rows() == new_local_mesh.num_vertices() * m.dim());
78 assert(A.cols() == old_local_mesh.num_vertices() * m.dim());
79 }
80
81 // --------------------------------------------------------------------
82
83 // NOTE: This assumes the boundary is fixed.
84 ipc::CollisionMesh collision_mesh;
85 // ipc::CollisionMesh collision_mesh = ipc::CollisionMesh::build_from_full_mesh(
86 // local_mesh.rest_positions(), local_mesh.boundary_edges(), local_mesh.boundary_faces());
87 // // Ignore all collisions between fixed elements.
88 // std::vector<bool> is_vertex_fixed(local_mesh.num_vertices(), false);
89 // for (const int vi : local_mesh.fixed_vertices())
90 // is_vertex_fixed[vi] = true;
91 // collision_mesh.can_collide = [is_vertex_fixed, &collision_mesh](size_t vi, size_t vj) {
92 // return !is_vertex_fixed[collision_mesh.to_full_vertex_id(vi)]
93 // || !is_vertex_fixed[collision_mesh.to_full_vertex_id(vj)];
94 // };
95
96 // --------------------------------------------------------------------
97
98 std::vector<int> boundary_nodes;
99 for (int d = 0; d < m.dim(); ++d)
100 {
101 // Internal vertices that are on the boundary of the local mesh
102 for (const int fv : new_local_mesh.fixed_vertices())
103 boundary_nodes.push_back(fv * m.dim() + d);
104 // Boundary vertices that are on the boundary of the local mesh
105 for (const int fv : new_local_mesh.boundary_facets().reshaped())
106 boundary_nodes.push_back(fv * m.dim() + d);
107 }
108 std::sort(boundary_nodes.begin(), boundary_nodes.end());
109 auto new_end = std::unique(boundary_nodes.begin(), boundary_nodes.end());
110 boundary_nodes.erase(new_end, boundary_nodes.end());
111
112 // --------------------------------------------------------------------
113
114 Eigen::MatrixXd projected_quantities = to_projection_quantities;
115 const int n_constrained_quantaties = projected_quantities.cols() / 3;
116 const int n_unconstrained_quantaties = projected_quantities.cols() - n_constrained_quantaties;
117
118 auto nl_solver = m.state.make_nl_solver(/*for_al=*/false);
119 for (int i = 0; i < n_constrained_quantaties; ++i)
121 const auto level_before = logger().level();
122 logger().set_level(spdlog::level::warn);
123 projected_quantities.col(i) = constrained_L2_projection(
124 nl_solver,
125 // L2 projection form
126 M, A, /*y=*/from_projection_quantities.col(i),
127 // Inversion-free form
128 new_local_mesh.rest_positions(), new_local_mesh.elements(), m.dim(),
129 // Contact form
130 collision_mesh, m.state.args["contact"]["dhat"],
132 ? m.state.solve_data.contact_form->barrier_stiffness()
133 : 1.0,
134 m.state.args["contact"]["use_convergent_formulation"] ? bool(m.state.args["contact"]["use_area_weighting"]) : false,
135 m.state.args["contact"]["use_convergent_formulation"] ? bool(m.state.args["contact"]["use_improved_max_operator"]) : false,
136 m.state.args["contact"]["use_convergent_formulation"] ? bool(m.state.args["contact"]["use_physical_barrier"]) : false,
137 m.state.args["solver"]["contact"]["CCD"]["broad_phase"],
138 m.state.args["solver"]["contact"]["CCD"]["tolerance"],
139 m.state.args["solver"]["contact"]["CCD"]["max_iterations"],
140 // Augmented lagrangian form
141 boundary_nodes, /*obstacle_ndof=*/0, to_projection_quantities.col(i),
142 // Initial guess
143 to_projection_quantities.col(i));
144 logger().set_level(level_before);
145 }
146
147 // Minimize the L2 norm with the boundary fixed.
149 M, A, from_projection_quantities.rightCols(n_unconstrained_quantaties),
150 boundary_nodes, projected_quantities.rightCols(n_unconstrained_quantaties));
151
152 // --------------------------------------------------------------------
153
154 assert(projected_quantities.rows() == m.dim() * new_local_mesh.num_vertices());
155 for (int i = 0; i < new_local_mesh.num_vertices(); i++)
156 {
157 m.vertex_attrs[new_local_mesh.local_to_global()[i]].projection_quantities =
158 projected_quantities.middleRows(m.dim() * i, m.dim());
159 }
160 }
161
162 // =========================================================================
163
164 template <class WMTKMesh>
166 {
167 if (!WMTKMesh::smooth_before(v))
168 return false;
169 return !vertex_attrs[v.vid(*this)].fixed;
170 }
171
172 template <class WMTKMesh>
174 {
175 if (!Super::smooth_before(v))
176 return false;
177
178 if (this->op_cache == nullptr)
179 {
180 if constexpr (std::is_same_v<WMTKMesh, wmtk::TriMesh>)
181 this->op_cache = std::make_shared<TriOperationCache>();
182 else
183 this->op_cache = std::make_shared<TetOperationCache>();
184 }
185
186 this->op_cache->local_energy = local_mesh_energy(
187 vertex_attrs[v.vid(*this)].rest_position);
188
189 return true;
190 }
191
192 // -------------------------------------------------------------------------
193
194 template <class WMTKMesh>
196 {
197 if (!WMTKMesh::smooth_before(v))
198 return false;
199
200 const size_t vid = v.vid(*this);
201
202 const std::vector<Tuple> one_ring = this->get_one_ring_elements_for_vertex(v);
203 assert(one_ring.size() > 0);
204
205 // ---------------------------------------------------------------------
206 // 1. update rest position of new vertex
207
208 // Computes the maximal error around the one ring
209 // that is needed to ensure the operation will decrease the error measure
210 double max_quality = 0;
211 for (const Tuple &t : get_one_ring_elements_for_vertex(v))
212 max_quality = std::max(max_quality, get_quality(*this, t));
213 assert(max_quality > 0); // If max quality is zero it is likely that the triangles are flipped
214
215 // Collects the coordinate of all vertices in the 1-ring
216 constexpr int NDOF = DIM * VERTICES_PER_ELEMENT;
217 std::vector<std::array<double, NDOF>> assembles(one_ring.size());
218
219 // For each triangle, make a reordered copy of the vertices so that
220 // the vertex to optimize is always the first
221 for (int i = 0; i < one_ring.size(); i++)
222 {
223 const Tuple &t = one_ring[i];
224 auto t_id = t.fid(*this);
225
226 assert(!is_inverted(t));
227 const auto local_verts = orient_preserve_element_reorder(element_vids(t), vid);
228
229 for (auto j = 0; j < VERTICES_PER_ELEMENT; j++)
230 for (auto d = 0; d < DIM; d++)
231 assembles[i][j * DIM + d] = vertex_attrs[local_verts[j]].rest_position[d];
232 }
233
234 // Make a backup of the current configuration
235 LocalMesh<This> old_local_mesh(*this, one_ring, false);
236 const VectorNd old_rest_position = vertex_attrs[vid].rest_position;
237
238 // Minimize distortion using newton's method
239 const auto log_lvl = wmtk::logger().level();
240 wmtk::logger().set_level(spdlog::level::warn);
241 if constexpr (DIM == 2)
242 vertex_attrs[vid].rest_position = wmtk::newton_method_from_stack_2d(
243 assembles, wmtk::AMIPS2D_energy, wmtk::AMIPS2D_jacobian, wmtk::AMIPS2D_hessian);
244 else
245 vertex_attrs[vid].rest_position = wmtk::newton_method_from_stack(
246 assembles, wmtk::AMIPS_energy, wmtk::AMIPS_jacobian, wmtk::AMIPS_hessian);
247 wmtk::logger().set_level(log_lvl);
248
249 // The AMIPS energy should have prevented inversions
250 if (std::any_of(one_ring.begin(), one_ring.end(), [this](const Tuple &t) {
251 return this->is_rest_inverted(t);
252 }))
253 {
254 assert(false);
255 return false;
256 }
257
258 // ---------------------------------------------------------------------
259 // 2. project quantities so to minimize the L2 error
260
261 // Adjust the previous displacements so they results in the same previous positions
262 vertex_attrs[vid].projection_quantities.leftCols(n_quantities() / 3) +=
263 old_rest_position - vertex_attrs[vid].rest_position;
264
265 LocalMesh<This> new_local_mesh(*this, one_ring, false);
266
267 project_local_quantities(*this, old_local_mesh, new_local_mesh);
268
269 // The Constrained L2 Projection should have prevented inversions
270 if (std::any_of(one_ring.begin(), one_ring.end(), [this](const Tuple &t) {
271 return this->is_inverted(t);
272 }))
273 {
274 assert(false);
275 return false;
276 }
277
278 return true;
279 }
280
281 template <class WMTKMesh>
283 {
284 utils::Timer timer(this->timings["Smooth vertex after"]);
285 timer.start();
286 if (!Super::smooth_after(v))
287 return false;
288 // local relaxation has its own timers
289 timer.stop();
290
291 // ---------------------------------------------------------------------
292 // 3. perform a local relaxation of the n-ring to get an estimate of the
293 // energy decrease.
294 const std::vector<Tuple> one_ring = this->get_one_ring_elements_for_vertex(v);
295 return local_relaxation(one_ring, args["smooth"]["acceptance_tolerance"]);
296 }
297
298 template <class WMTKMesh>
300 {
301 executor.priority = [](const WildRemesher<WMTKMesh> &m, std::string op, const Tuple &v) -> double {
302 double max_quality = 0;
303 for (const Tuple &t : m.get_one_ring_elements_for_vertex(v))
304 max_quality = std::max(max_quality, get_quality(m, t));
305 assert(max_quality > 0); // If max quality is zero it is likely that the triangles are flipped
306 return max_quality;
307 };
308 executor.should_renew = [](auto) { return true; };
309 executor.renew_neighbor_tuples = [](auto &, auto, auto &) -> Operations { return {}; };
310
311 const int max_iters = args["smooth"]["max_iters"];
312 for (int i = 0; i < max_iters; i++)
313 {
314 Operations smooths;
315 for (auto &v : WMTKMesh::get_vertices())
316 smooths.emplace_back("vertex_smooth", v);
317 executor(*this, smooths);
318 if (executor.cnt_success() == 0)
319 break;
320 }
321 }
322
323 // ------------------------------------------------------------------------
324 // Template specializations
325
326 template class WildRemesher<wmtk::TriMesh>;
327 template class WildRemesher<wmtk::TetMesh>;
328 template class PhysicsRemesher<wmtk::TriMesh>;
329 template class PhysicsRemesher<wmtk::TetMesh>;
330
331} // namespace polyfem::mesh
std::unique_ptr< MatrixCache > cache
Definition Assembler.cpp:21
#define POLYFEM_REMESHER_SCOPED_TIMER(name)
Definition Remesher.hpp:15
std::string formulation() const
return the formulation (checks if the problem is scalar or not and deals with multiphysics)
Definition State.cpp:328
std::shared_ptr< polysolve::nonlinear::Solver > make_nl_solver(bool for_al) const
factory to create the nl solver depending on input
json args
main input arguments containing all defaults
Definition State.hpp:101
solver::SolveData solve_data
timedependent stuff cached
Definition State.hpp:321
Caches basis evaluation and geometric mapping at every element.
Eigen::Matrix< double, Eigen::Dynamic, 1, 0, 9, 1 > assemble(const LinearAssemblerData &data) const override
computes and returns local stiffness matrix (1x1) for bases i,j (where i,j is passed in through data)...
Definition Mass.cpp:5
void assemble_cross(const bool is_volume, const int size, const int n_from_basis, const std::vector< basis::ElementBases > &from_bases, const std::vector< basis::ElementBases > &from_gbases, const int n_to_basis, const std::vector< basis::ElementBases > &to_bases, const std::vector< basis::ElementBases > &to_gbases, const AssemblyValsCache &cache, StiffnessMatrix &mass) const
Assembles the cross mass matrix between to function spaces.
bool smooth_before(const Tuple &t) override
Definition Smooth.cpp:173
bool smooth_after(const Tuple &t) override
Definition Smooth.cpp:282
virtual bool is_volume() const
Is the mesh a volumetric mesh.
Definition Remesher.hpp:102
const State & state
Reference to the simulation state.
Definition Remesher.hpp:191
std::array< size_t, VERTICES_PER_ELEMENT > element_vids(const Tuple &t) const
Get the vertex ids of an element.
std::vector< std::pair< std::string, Tuple > > Operations
virtual bool smooth_before(const Tuple &t) override
Definition Smooth.cpp:165
typename WMTKMesh::Tuple Tuple
wmtk::AttributeCollection< VertexAttributes > vertex_attrs
virtual void smooth_vertices()
Definition Smooth.cpp:299
std::vector< Tuple > get_one_ring_elements_for_vertex(const Tuple &t) const
Get the one ring of elements around a vertex.
int dim() const override
Dimension of the mesh.
Eigen::Matrix< double, DIM, 1 > VectorNd
virtual bool smooth_after(const Tuple &t) override
Definition Smooth.cpp:195
std::shared_ptr< solver::ContactForm > contact_form
void project_local_quantities(WildRemesher< WMTKMesh > &m, LocalMesh< WildRemesher< WMTKMesh > > &old_local_mesh, LocalMesh< WildRemesher< WMTKMesh > > &new_local_mesh)
Definition Smooth.cpp:45
void reduced_L2_projection(const Eigen::MatrixXd &M, const Eigen::MatrixXd &A, const Eigen::Ref< const Eigen::MatrixXd > &y, const std::vector< int > &boundary_nodes, Eigen::Ref< Eigen::MatrixXd > x)
double get_quality(const WildRemesher< WMTKMesh > &m, const typename WMTKMesh::Tuple &t)
Definition Smooth.cpp:17
Eigen::VectorXd constrained_L2_projection(std::shared_ptr< polysolve::nonlinear::Solver > nl_solver, const Eigen::SparseMatrix< double > &M, const Eigen::SparseMatrix< double > &A, const Eigen::VectorXd &y, const Eigen::MatrixXd &rest_positions, const Eigen::MatrixXi &elements, const int dim, const ipc::CollisionMesh &collision_mesh, const double dhat, const double barrier_stiffness, const bool use_area_weighting, const bool use_improved_max_operator, const bool use_physical_barrier, const ipc::BroadPhaseMethod broad_phase_method, const double ccd_tolerance, const int ccd_max_iterations, const std::vector< int > &boundary_nodes, const size_t obstacle_ndof, const Eigen::VectorXd &target_x, const Eigen::VectorXd &x0)
spdlog::logger & logger()
Retrieves the current logger.
Definition Logger.cpp:42