PolyFEM
Loading...
Searching...
No Matches
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"],
135 m.state.args["solver"]["contact"]["CCD"]["broad_phase"],
136 m.state.args["solver"]["contact"]["CCD"]["tolerance"],
137 m.state.args["solver"]["contact"]["CCD"]["max_iterations"],
138 // Augmented lagrangian form
139 boundary_nodes, /*obstacle_ndof=*/0, to_projection_quantities.col(i),
140 // Initial guess
141 to_projection_quantities.col(i));
142 logger().set_level(level_before);
143 }
144
145 // Minimize the L2 norm with the boundary fixed.
147 M, A, from_projection_quantities.rightCols(n_unconstrained_quantaties),
148 boundary_nodes, projected_quantities.rightCols(n_unconstrained_quantaties));
149
150 // --------------------------------------------------------------------
151
152 assert(projected_quantities.rows() == m.dim() * new_local_mesh.num_vertices());
153 for (int i = 0; i < new_local_mesh.num_vertices(); i++)
154 {
155 m.vertex_attrs[new_local_mesh.local_to_global()[i]].projection_quantities =
156 projected_quantities.middleRows(m.dim() * i, m.dim());
157 }
158 }
159
160 // =========================================================================
161
162 template <class WMTKMesh>
164 {
165 if (!WMTKMesh::smooth_before(v))
166 return false;
167 return !vertex_attrs[v.vid(*this)].fixed;
168 }
169
170 template <class WMTKMesh>
172 {
173 if (!Super::smooth_before(v))
174 return false;
175
176 if (this->op_cache == nullptr)
177 {
178 if constexpr (std::is_same_v<WMTKMesh, wmtk::TriMesh>)
179 this->op_cache = std::make_shared<TriOperationCache>();
180 else
181 this->op_cache = std::make_shared<TetOperationCache>();
182 }
183
184 this->op_cache->local_energy = local_mesh_energy(
185 vertex_attrs[v.vid(*this)].rest_position);
186
187 return true;
188 }
189
190 // -------------------------------------------------------------------------
191
192 template <class WMTKMesh>
194 {
195 if (!WMTKMesh::smooth_before(v))
196 return false;
197
198 const size_t vid = v.vid(*this);
199
200 const std::vector<Tuple> one_ring = this->get_one_ring_elements_for_vertex(v);
201 assert(one_ring.size() > 0);
202
203 // ---------------------------------------------------------------------
204 // 1. update rest position of new vertex
205
206 // Computes the maximal error around the one ring
207 // that is needed to ensure the operation will decrease the error measure
208 double max_quality = 0;
209 for (const Tuple &t : get_one_ring_elements_for_vertex(v))
210 max_quality = std::max(max_quality, get_quality(*this, t));
211 assert(max_quality > 0); // If max quality is zero it is likely that the triangles are flipped
212
213 // Collects the coordinate of all vertices in the 1-ring
214 constexpr int NDOF = DIM * VERTICES_PER_ELEMENT;
215 std::vector<std::array<double, NDOF>> assembles(one_ring.size());
216
217 // For each triangle, make a reordered copy of the vertices so that
218 // the vertex to optimize is always the first
219 for (int i = 0; i < one_ring.size(); i++)
220 {
221 const Tuple &t = one_ring[i];
222 auto t_id = t.fid(*this);
223
224 assert(!is_inverted(t));
225 const auto local_verts = orient_preserve_element_reorder(element_vids(t), vid);
226
227 for (auto j = 0; j < VERTICES_PER_ELEMENT; j++)
228 for (auto d = 0; d < DIM; d++)
229 assembles[i][j * DIM + d] = vertex_attrs[local_verts[j]].rest_position[d];
230 }
231
232 // Make a backup of the current configuration
233 LocalMesh<This> old_local_mesh(*this, one_ring, false);
234 const VectorNd old_rest_position = vertex_attrs[vid].rest_position;
235
236 // Minimize distortion using newton's method
237 const auto log_lvl = wmtk::logger().level();
238 wmtk::logger().set_level(spdlog::level::warn);
239 if constexpr (DIM == 2)
240 vertex_attrs[vid].rest_position = wmtk::newton_method_from_stack_2d(
241 assembles, wmtk::AMIPS2D_energy, wmtk::AMIPS2D_jacobian, wmtk::AMIPS2D_hessian);
242 else
243 vertex_attrs[vid].rest_position = wmtk::newton_method_from_stack(
244 assembles, wmtk::AMIPS_energy, wmtk::AMIPS_jacobian, wmtk::AMIPS_hessian);
245 wmtk::logger().set_level(log_lvl);
246
247 // The AMIPS energy should have prevented inversions
248 if (std::any_of(one_ring.begin(), one_ring.end(), [this](const Tuple &t) {
249 return this->is_rest_inverted(t);
250 }))
251 {
252 assert(false);
253 return false;
254 }
255
256 // ---------------------------------------------------------------------
257 // 2. project quantities so to minimize the L2 error
258
259 // Adjust the previous displacements so they results in the same previous positions
260 vertex_attrs[vid].projection_quantities.leftCols(n_quantities() / 3) +=
261 old_rest_position - vertex_attrs[vid].rest_position;
262
263 LocalMesh<This> new_local_mesh(*this, one_ring, false);
264
265 project_local_quantities(*this, old_local_mesh, new_local_mesh);
266
267 // The Constrained L2 Projection should have prevented inversions
268 if (std::any_of(one_ring.begin(), one_ring.end(), [this](const Tuple &t) {
269 return this->is_inverted(t);
270 }))
271 {
272 assert(false);
273 return false;
274 }
275
276 return true;
277 }
278
279 template <class WMTKMesh>
281 {
282 utils::Timer timer(this->timings["Smooth vertex after"]);
283 timer.start();
284 if (!Super::smooth_after(v))
285 return false;
286 // local relaxation has its own timers
287 timer.stop();
288
289 // ---------------------------------------------------------------------
290 // 3. perform a local relaxation of the n-ring to get an estimate of the
291 // energy decrease.
292 const std::vector<Tuple> one_ring = this->get_one_ring_elements_for_vertex(v);
293 return local_relaxation(one_ring, args["smooth"]["acceptance_tolerance"]);
294 }
295
296 template <class WMTKMesh>
298 {
299 executor.priority = [](const WildRemesher<WMTKMesh> &m, std::string op, const Tuple &v) -> double {
300 double max_quality = 0;
301 for (const Tuple &t : m.get_one_ring_elements_for_vertex(v))
302 max_quality = std::max(max_quality, get_quality(m, t));
303 assert(max_quality > 0); // If max quality is zero it is likely that the triangles are flipped
304 return max_quality;
305 };
306 executor.should_renew = [](auto) { return true; };
307 executor.renew_neighbor_tuples = [](auto &, auto, auto &) -> Operations { return {}; };
308
309 const int max_iters = args["smooth"]["max_iters"];
310 for (int i = 0; i < max_iters; i++)
311 {
312 Operations smooths;
313 for (auto &v : WMTKMesh::get_vertices())
314 smooths.emplace_back("vertex_smooth", v);
315 executor(*this, smooths);
316 if (executor.cnt_success() == 0)
317 break;
318 }
319 }
320
321 // ------------------------------------------------------------------------
322 // Template specializations
323
324 template class WildRemesher<wmtk::TriMesh>;
325 template class WildRemesher<wmtk::TetMesh>;
326 template class PhysicsRemesher<wmtk::TriMesh>;
327 template class PhysicsRemesher<wmtk::TetMesh>;
328
329} // 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:324
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:171
bool smooth_after(const Tuple &t) override
Definition Smooth.cpp:280
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:163
typename WMTKMesh::Tuple Tuple
wmtk::AttributeCollection< VertexAttributes > vertex_attrs
virtual void smooth_vertices()
Definition Smooth.cpp:297
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:193
std::shared_ptr< solver::ContactForm > contact_form
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_convergent_formulation, 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)
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
spdlog::logger & logger()
Retrieves the current logger.
Definition Logger.cpp:42