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