libMesh
Loading...
Searching...
No Matches
variational_smoother_system.C
Go to the documentation of this file.
1// The libMesh Finite Element Library.
2// Copyright (C) 2002-2026 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner
3
4// This library is free software; you can redistribute it and/or
5// modify it under the terms of the GNU Lesser General Public
6// License as published by the Free Software Foundation; either
7// version 2.1 of the License, or (at your option) any later version.
8
9// This library is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Lesser General Public License for more details.
13
14// You should have received a copy of the GNU Lesser General Public
15// License along with this library; if not, write to the Free Software
16// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
18#include "libmesh/variational_smoother_system.h"
19
20#include "libmesh/elem.h"
21#include "libmesh/face_tri3.h"
22#include "libmesh/face_tri6.h"
23#include "libmesh/fe_base.h"
24#include "libmesh/fe_interface.h"
25#include "libmesh/fem_context.h"
26#include "libmesh/mesh.h"
27#include "libmesh/numeric_vector.h"
28#include "libmesh/parallel_ghost_sync.h"
29#include "libmesh/quadrature.h"
30#include "libmesh/string_to_enum.h"
31#include "libmesh/utility.h"
32#include "libmesh/enum_to_string.h"
33#include <libmesh/reference_elem.h>
34
35// C++ includes
36#include <functional> // std::reference_wrapper
37
38namespace libMesh
39{
40
41/*
42 * Gets the dof_id_type value corresponding to the minimum of the Real value.
43 */
44void communicate_pair_min(std::pair<Real, dof_id_type> & pair, const Parallel::Communicator & comm)
45{
46 // Get rank where minimum occurs
47 unsigned int rank;
48 comm.minloc(pair.first, rank);
49 comm.broadcast(pair.second, rank);
50}
51
52/*
53 * Gets the dof_id_type value corresponding to the maximum of the Real value.
54 */
55void communicate_pair_max(std::pair<Real, dof_id_type> & pair, const Parallel::Communicator & comm)
56{
57 // Get rank where minimum occurs
58 unsigned int rank;
59 comm.maxloc(pair.first, rank);
60 comm.broadcast(pair.second, rank);
61}
62
66Real chi_epsilon(const Real & x, const Real epsilon_squared)
67{
68 return 0.5 * (x + std::sqrt(epsilon_squared + Utility::pow<2>(x)));
69}
70
76 const unsigned int & dim,
77 const unsigned int & qp)
78{
79 libmesh_error_msg_if(dim > 3, "Unsupported dimension.");
80
81 // RealTensors are always 3x3, so we will fill any dimensions above dim
82 // with 1s on the diagonal. This indicates a 1 to 1 relationship between
83 // the physical and reference elements in these extra dimensions.
84
85 const auto & dxyzdxi = dim >= 1 ? fe_map.get_dxyzdxi()[qp] : RealGradient(1, 0, 0);
86 const auto & dxyzdeta = dim >= 2 ? fe_map.get_dxyzdeta()[qp] : RealGradient(0, 1, 0);
87 const auto & dxyzdzeta = dim >= 3 ? fe_map.get_dxyzdzeta()[qp] : RealGradient(0, 0, 1);
88
89 return RealTensor(dxyzdxi, dxyzdeta, dxyzdzeta).transpose(); // Note the transposition!
90}
91
95Real trace(const RealTensor & A, const unsigned int & dim)
96{
97 Real tr = 0.0;
98 for (const auto i : make_range(dim))
99 tr += A(i, i);
100
101 return tr;
102}
103
105
107 bool get_jacobian,
108 bool apply_heterogeneous_constraints,
109 bool apply_no_constraints)
110{
111 // Update the mesh based on the current variable values
112 auto & mesh = this->get_mesh();
113 this->solution->close();
114
115 for (auto * node : mesh.local_node_ptr_range())
116 {
117 for (const auto d : make_range(mesh.mesh_dimension()))
118 {
119 const auto dof_id = node->dof_number(this->number(), d, 0);
120 // Update mesh
121 (*node)(d) = libmesh_real((*current_local_solution)(dof_id));
122 }
123 }
124
125 SyncNodalPositions sync_object(mesh);
126 Parallel::sync_dofobject_data_by_id (mesh.comm(), mesh.nodes_begin(), mesh.nodes_end(), sync_object);
127
128 // Compute and update mesh quality information
130 const bool is_tangled = _mesh_info.mesh_is_tangled;
131
132 // Update _epsilon_squared_assembly based on whether we are untangling or
133 // smoothing
135 {
136 const Real & min_S = _mesh_info.min_qp_det_S;
137 // This component is based on what Larisa did in the original code.
138 const Real variable_component = 100. * Utility::pow<2>(_ref_vol * min_S);
139 _epsilon_squared_assembly = _epsilon_squared + variable_component;
140 }
141
142 else
144
145 FEMSystem::assembly(get_residual, get_jacobian, apply_heterogeneous_constraints, apply_no_constraints);
146
147 if (_untangling_solve && !is_tangled)
148 {
149 // Mesh is untangled, artificially reduce residual by factor 0.9 to tell
150 // the solver we are on the right track. The mesh may become re-tangled in
151 // subsequent nonlinear and line search iterations, so we will not use
152 // this reduction factor in the case of re-tangulation. This approach
153 // should drive the solver to eventually favor untangled solutions.
154 rhs->close();
155 (*rhs) *= 0.9;
156 }
157}
158
160{
161 const auto & mesh_info = get_mesh_info();
162 if (_verbosity > 10)
163 libMesh::out << "Initial " << mesh_info << std::endl;
164 if (mesh_info.mesh_is_tangled)
165 {
166 // Untangling solve
167 _untangling_solve = true;
168
169 // Untangling seems to work better using only the distortion metric.
170 // For a mixed metric, I've seen it *technically* untangle a mesh to a
171 // still-suboptimal mesh (i.e., a local minima), but not been able to
172 // smooth it well because the smoothest solution is on the other side of
173 // a tangulation barrier.
174 const auto dilation_weight = _dilation_weight;
175 _dilation_weight = 0.;
176
177 if (_verbosity > 10)
178 libMesh::out << "Untangling the mesh" << std::endl;
180
181 // Reset the dilation weight
182 _dilation_weight = dilation_weight;
183
184 if (_verbosity > 10)
185 libMesh::out << "Untangled " << mesh_info << std::endl;
186 }
187
188 // Smoothing solve
189 _untangling_solve = false;
190 if (_verbosity > 10)
191 libMesh::out << "Smoothing the mesh" << std::endl;
193 libmesh_error_msg_if(mesh_info.mesh_is_tangled, "The smoothing solve tangled the mesh!");
194 if (_verbosity > 10)
195 libMesh::out << "Smoothed " << mesh_info << std::endl;
196}
197
199{
200 auto & mesh = this->get_mesh();
201 const auto & elem_orders = mesh.elem_default_orders();
202 libmesh_error_msg_if(elem_orders.size() != 1,
203 "The variational smoother cannot be used for mixed-order meshes!");
204 const auto fe_order = *elem_orders.begin();
205 // Add a variable for each dimension of the mesh
206 // "r0" for x, "r1" for y, "r2" for z
207 for (const auto & d : make_range(mesh.mesh_dimension()))
208 this->add_variable ("r" + std::to_string(d), fe_order);
209
210 // Do the parent's initialization after variables are defined
212
213 // Set the current_local_solution to the current mesh for the initial guess
214 this->solution->close();
215
216 for (auto * node : mesh.local_node_ptr_range())
217 {
218 for (const auto d : make_range(mesh.mesh_dimension()))
219 {
220 const auto dof_id = node->dof_number(this->number(), d, 0);
221 // Update solution
222 (*solution).set(dof_id, (*node)(d));
223 }
224 }
225
226 this->prepare_for_smoothing();
227}
228
230{
231 // If this method has already been called to set _ref_vol, just return.
232 if (std::abs(_ref_vol) > TOLERANCE * TOLERANCE)
233 return;
234
235 std::unique_ptr<DiffContext> con = this->build_context();
236 FEMContext & femcontext = cast_ref<FEMContext &>(*con);
237 this->init_context(femcontext);
238
239 const auto & mesh = this->get_mesh();
240
241 Real elem_averaged_det_S_sum = 0.;
242
243 // Make pre-requests before reinit() for efficiency in
244 // --enable-deprecated builds, and to avoid errors in
245 // --disable-deprecated builds.
246 const auto & fe_map = femcontext.get_element_fe(0)->get_fe_map();
247 const auto & JxW = fe_map.get_JxW();
248
249 for (const auto * elem : mesh.active_local_element_ptr_range())
250 {
251 femcontext.pre_fe_reinit(*this, elem);
252 femcontext.elem_fe_reinit();
253
254 // Add target element info, if applicable
255 if (_target_jacobians.find(elem->type()) == _target_jacobians.end())
256 {
257 const auto [target_elem, target_nodes] = ReferenceElem::ideal_target(elem->type());
258 get_target_to_reference_jacobian(target_elem.get(),
259 femcontext,
260 _target_jacobians[elem->type()],
261 _target_jacobian_dets[elem->type()]);
262 }// if find == end()
263
264 // Reference volume computation
265 Real elem_integrated_det_S = 0.;
266 for (const auto qp : index_range(JxW))
267 elem_integrated_det_S += JxW[qp] / _target_jacobian_dets[elem->type()][qp];
268 const auto ref_elem_vol = elem->reference_elem()->volume();
269 elem_averaged_det_S_sum += elem_integrated_det_S / ref_elem_vol;
270
271 } // for elem
272
273 // Get contributions from elements on other processors
274 mesh.comm().sum(elem_averaged_det_S_sum);
275
276 _ref_vol = elem_averaged_det_S_sum / mesh.n_active_elem();
277 if (_verbosity > 10)
278 libMesh::out << "Reference volume: " << _ref_vol << std::endl;
279}
280
282{
283 FEMContext & c = cast_ref<FEMContext &>(context);
284
285 FEBase * my_fe = nullptr;
286
287 // Now make sure we have requested all the data
288 // we need to build the system.
289
290 // We might have a multi-dimensional mesh
291 const std::set<unsigned char> & elem_dims =
292 c.elem_dimensions();
293
294 for (const auto & dim : elem_dims)
295 {
296 c.get_element_fe( 0, my_fe, dim );
297 my_fe->get_nothing();
298
299 auto & fe_map = my_fe->get_fe_map();
300 fe_map.get_dxyzdxi();
301 fe_map.get_dxyzdeta();
302 fe_map.get_dxyzdzeta();
303 fe_map.get_JxW();
304
305 // Mesh may be tangled, allow negative Jacobians
306 fe_map.set_jacobian_tolerance(std::numeric_limits<Real>::lowest());
307
308 c.get_side_fe( 0, my_fe, dim );
309 my_fe->get_nothing();
310 }
311
313
314 // Override the default (interior-only) Gauss rule if a different quadrature
315 // type has been requested. A vertex-sampling rule (e.g. QTRAP, QSIMPSON,
316 // QNODAL, QGAUSS_LOBATTO) lets the metric "see" degeneracies localized at
317 // element corners that interior Gauss points miss. Do this after
318 // FEMSystem::init_context() so it is not overwritten. The System's
319 // extra_quadrature_order is respected for the point count.
322}
323
324
326 DiffContext & context)
327{
328 FEMContext & c = cast_ref<FEMContext &>(context);
329
330 const Elem & elem = c.get_elem();
331
332 unsigned int dim = c.get_dim();
333
334 unsigned int x_var = 0, y_var = 1, z_var = 2;
335 // In the case of lower dimensions, we will not access z (1D/2D) or y (1D)
336 if (dim < 3)
337 {
338 z_var = 0;
339 if (dim < 2)
340 y_var = 0;
341 }
342
343 // The subvectors and submatrices we need to fill:
344 // system residual
345 std::reference_wrapper<DenseSubVector<Number>> F[3] =
346 {
347 c.get_elem_residual(x_var),
348 c.get_elem_residual(y_var),
349 c.get_elem_residual(z_var)
350 };
351 // system jacobian
352 std::reference_wrapper<DenseSubMatrix<Number>> K[3][3] =
353 {
354 {c.get_elem_jacobian(x_var, x_var), c.get_elem_jacobian(x_var, y_var), c.get_elem_jacobian(x_var, z_var)},
355 {c.get_elem_jacobian(y_var, x_var), c.get_elem_jacobian(y_var, y_var), c.get_elem_jacobian(y_var, z_var)},
356 {c.get_elem_jacobian(z_var, x_var), c.get_elem_jacobian(z_var, y_var), c.get_elem_jacobian(z_var, z_var)}
357 };
358
359 // Quadrature info
360 const auto & quad_weights = c.get_element_qrule().get_weights();
361
362 const auto distortion_weight = 1. - _dilation_weight;
363
364 // Get some references to cell-specific data that
365 // will be used to assemble the linear system.
366
367 const auto & fe_map = c.get_element_fe(0)->get_fe_map();
368
369 const auto & dphidxi_map = fe_map.get_dphidxi_map();
370 const auto & dphideta_map = fe_map.get_dphideta_map();
371 const auto & dphidzeta_map = fe_map.get_dphidzeta_map();
372
373 const auto & target_jacobian_dets = _target_jacobian_dets[elem.type()];
374 const auto & target_jacobians = _target_jacobians[elem.type()];
375
376 // Integrate the distortion-dilation metric over the reference element
377 for (const auto qp : index_range(quad_weights))
378 {
379 // Compute quantities needed to evaluate the distortion-dilation metric
380 // and its gradient and Hessian.
381 // The metric will be minimized when it's gradient with respect to the node
382 // locations (R) is zero. For Newton's method, minimizing the gradient
383 // requires computation of the gradient's Jacobian with respect to R.
384 // The Jacobian of the distortion-dilation metric's gradientis the Hessian
385 // of the metric.
386
387 // Transform quad weight from reference element to quad weight for target element
388 const auto quad_weight = quad_weights[qp] / target_jacobian_dets[qp];
389
390 // Note that the term "Jacobian" has two meanings in this mesh smoothing
391 // application. The first meaning refers to the Jacobian w.r.t R of the
392 // gradient w.r.t. R, (i.e., the Hessian of the metric). This is the K
393 // variable defined above. This is also the Jacobian the 'request_jacobian'
394 // variable refers to. The second meaning refers to the Jacobian of the
395 // physical-to-target element mapping. This is the Jacobian used to
396 // compute the distorion-dilation metric.
397 //
398 // Grab the physical-to-reference mapping Jacobian matrix (i.e., "S") at this qp
399 RealTensor S = get_jacobian_at_qp(fe_map, dim, qp);
400
401 // Apply target element transformation to get the physical-to-target jacobian
402 S *= _target_jacobians[elem.type()][qp];
403
404 // Compute quantities needed for the smoothing algorithm
405
406 // determinant
407 const Real det = S.det();
408 const Real det_sq = det * det;
409 const Real det_cube = det_sq * det;
410
411 const Real ref_vol_sq = _ref_vol * _ref_vol;
412
413 // trace of S^T * S
414 // DO NOT USE RealTensor.tr for the trace, it will NOT be correct for
415 // 1D and 2D meshes because of our hack of putting 1s in the diagonal of
416 // S for the extra dimensions (see get_jacobian_at_qp)
417 const auto tr = trace(S.transpose() * S, dim);
418 const Real tr_div_dim = tr / dim;
419
420 // Precompute pow(tr_div_dim, 0.5 * dim - x) for x = 0, 1, 2
421 const Real half_dim = 0.5 * dim;
422 const std::vector<Real> trace_powers{
423 std::pow(tr_div_dim, half_dim),
424 std::pow(tr_div_dim, half_dim - 1.),
425 std::pow(tr_div_dim, half_dim - 2.),
426 };
427
428 // inverse of S
429 const RealTensor S_inv = S.inverse();
430 // inverse transpose of S
431 const RealTensor S_inv_T = S_inv.transpose();
432
433 // Identity matrix
434 const RealTensor I(1, 0, 0,
435 0, 1, 0,
436 0, 0, 1);
437
438 // The chi function allows us to handle degenerate elements
439 const auto chi = chi_epsilon(det, _epsilon_squared_assembly);
440 const Real chi_sq = chi * chi;
441 const Real sqrt_term = std::sqrt(_epsilon_squared_assembly + det_sq);
442 // dchi(x) / dx
443 const Real chi_prime = 0.5 * (1. + det / sqrt_term);
444 const Real chi_prime_sq = chi_prime * chi_prime;
445 // d2chi(x) / dx2
446 const Real chi_2prime = 0.5 * (1. / sqrt_term - det_sq / Utility::pow<3>(sqrt_term));
447
448 // Distortion metric (beta)
449 //const Real beta = trace_powers[0] / chi;
450 const RealTensor dbeta_dS = (trace_powers[1] / chi) * S - (trace_powers[0] / chi_sq * chi_prime * det) * S_inv_T;
451
452 // Dilation metric (mu)
453 //const Real mu = 0.5 * (_ref_vol + det_sq / _ref_vol) / chi;
454 // We represent d mu / dS as alpha(S) * S^-T, where alpha is a scalar function
455 const Real alpha = (-chi_prime * det_cube + 2. * det_sq * chi - ref_vol_sq * det * chi_prime) / (2. * _ref_vol * chi_sq);
456 const RealTensor dmu_dS = alpha * S_inv_T;
457
458 // Combined metric (E)
459 //const Real E = distortion_weight * beta + _dilation_weight * mu;
460 const RealTensor dE_dS = distortion_weight * dbeta_dS + _dilation_weight * dmu_dS;
461
462 // This vector is useful in computing dS/dR below
463 std::vector<std::vector<std::vector<Real>>> dphi_maps = {dphidxi_map, dphideta_map, dphidzeta_map};
464
465 // Compute residual (i.e., the gradient of the combined metric w.r.t node locations)
466 // Recall that when the gradient (residual) is zero, the combined metric is minimized
467 for (const auto l : elem.node_index_range())
468 {
469 for (const auto var_id : make_range(dim))
470 {
471 // Build dS/dR, the derivative of the physical-to-target mapping Jacobin w.r.t. the mesh
472 // node locations
473 RealTensor dS_dR = RealTensor(0);
474 for (const auto jj : make_range(dim))
475 dS_dR(var_id, jj) = dphi_maps[jj][l][qp];
476 dS_dR *= target_jacobians[qp];
477
478 // Residual contribution. The contraction of dE/dS and dS/dR gives us
479 // the gradient we are looking for, dE/dR
480 F[var_id](l) += quad_weight * dE_dS.contract(dS_dR);
481 }// for var_id
482 }// for l
483
484
485 if (request_jacobian)
486 {
487 // Compute jacobian of the smoothing system (i.e., the Hessian of the
488 // combined metric w.r.t. the mesh node locations)
489
490 // Precompute coefficients to be applied to each component of tensor
491 // products in the loops below. At first glance, these coefficients look
492 // like gibberish, but everything in the Hessian has been verified by
493 // taking finite differences of the gradient. We should probably write
494 // down the derivations of the gradient and Hessian somewhere...
495
496 // Recall that above, dbeta_dS takes the form:
497 // d(beta)/dS = c1(S) * S - c2(S) * S_inv_T,
498 // where c1 and c2 are scalar-valued functions.
499 const std::vector<Real> d2beta_dS2_coefs_times_distortion_weight = {
500 //Part 1: scaler coefficients of d(c1 * S) / dS
501 //
502 // multiplies I[i,a] x I[j,b]
503 (trace_powers[1] / chi) * distortion_weight,
504 // multiplies S[a,b] x S[i,j]
505 (((dim - 2.) / dim) * trace_powers[2] / chi) * distortion_weight,
506 // multiplies S_inv[b,a] * S[i,j]
507 (-(trace_powers[1] / chi_sq) * chi_prime * det) * distortion_weight,
508 //
509 //Part 2: scaler coefficients of d(-c2 * S_inv_T) / dS
510 //
511 // multiplies S[a,b] x S_inv[j,i]
512 (-(trace_powers[1] / chi_sq) * chi_prime * det) * distortion_weight,
513 // multiplies S_inv[b,a] x S_inv[j,i]
514 (trace_powers[0] * (det / chi_sq)
515 * ((2. * chi_prime_sq / chi - chi_2prime) * det - chi_prime)) * distortion_weight,
516 // multiplies S_inv[b,i] x S_inv[j,a]
517 ((trace_powers[0] / chi_sq) * chi_prime * det) * distortion_weight,
518 };
519
520 // d alpha / dS has the form c(S) * S^-T, where c is the scalar coefficient defined below
521 const Real dalpha_dS_coef_times_dilation_weight = ((det / (2. * _ref_vol * chi_sq))
522 * (-4. * _ref_vol * alpha * chi * chi_prime - chi_2prime * det_cube
523 - chi_prime * det_sq + 4 * chi * det - ref_vol_sq * (chi_prime + det * chi_2prime))) * _dilation_weight;
524
525 // This is also useful to precompute
526 const Real alpha_times_dilation_weight = alpha * _dilation_weight;
527
528 /*
529
530 To increase the efficiency of the Jacobian computation, we take
531 advantage of l-p symmetry, ij-ab symmetry, and the sparsity pattern of
532 dS_dR. We also factor all possible multipliers out of inner loops for
533 efficiency. The result is code that is more difficult to read. For
534 clarity, consult the pseudo-code below in this comment.
535
536 for (const auto l: elem.node_index_range()) // Contribution to Hessian
537 from node l
538 {
539 for (const auto var_id1 : make_range(dim)) // Contribution from each
540 x/y/z component of node l
541 {
542 // Build dS/dR_l, the derivative of the physical-to-target
543 mapping Jacobin w.r.t.
544 // the l-th node
545 RealTensor dS_dR_l = RealTensor(0);
546 for (const auto ii : make_range(dim))
547 dS_dR_l(var_id1, ii) = dphi_maps[ii][l][qp];
548 dS_dR_l *= target_jacobians[qp];
549
550 for (const auto p: elem.node_index_range()) // Contribution to
551 Hessian from node p
552 {
553 for (const auto var_id2 : make_range(dim)) // Contribution from
554 each x/y/z component of node p
555 {
556 // Build dS/dR_l, the derivative of the physical-to-target
557 mapping Jacobin w.r.t.
558 // the p-th node
559 RealTensor dS_dR_p = RealTensor(0);
560 for (const auto jj : make_range(dim))
561 dS_dR_p(var_id2, jj) = dphi_maps[jj][p][qp];
562 dS_dR_p *= target_jacobians[qp];
563
564 Real d2beta_dR2 = 0.;
565 Real d2mu_dR2 = 0.;
566 // Perform tensor contraction
567 for (const auto i : make_range(dim))
568 {
569 for (const auto j : make_range(dim))
570 {
571 for (const auto a : make_range(dim))
572 {
573 for (const auto b : make_range(dim))
574 {
575 // Nasty tensor products to be multiplied by
576 d2beta_dS2_coefs to get d2(beta) / dS2 const std::vector<Real>
577 d2beta_dS2_tensor_contributions =
578 {
579 I(i,a) * I(j,b),
580 S(a,b) * S(i,j),
581 S_inv(b,a) * S(i,j),
582 S(a,b) * S_inv(j,i),
583 S_inv(b,a) * S_inv(j,i),
584 S_inv(j,a) * S_inv(b,i),
585 };
586
587 // Combine precomputed coefficients with tensor products
588 to get d2(beta) / dS2 Real d2beta_dS2 = 0.; for (const auto comp_id :
589 index_range(d2beta_dS2_coefs))
590 {
591 const Real contribution = d2beta_dS2_coefs[comp_id] *
592 d2beta_dS2_tensor_contributions[comp_id]; d2beta_dS2 += contribution;
593 }
594
595 // Incorporate tensor product portion to get d2(mu) /
596 dS2 const Real d2mu_dS2 = dalpha_dS_coef * S_inv(b,a) * S_inv(j,i) -
597 alpha * S_inv(b,i) * S_inv(j,a);
598
599 // Chain rule to change d/dS to d/dR
600 d2beta_dR2 += d2beta_dS2 * dS_dR_l(a, b) * dS_dR_p(i,
601 j); d2mu_dR2 += d2mu_dS2 * dS_dR_l(a, b) * dS_dR_p(i, j);
602
603 }// for b
604 }// for a
605 }// for j
606 }// for i, end tensor contraction
607
608 // Jacobian contribution
609 K[var_id1][var_id2](l, p) += quad_weight * ((1. -
610 _dilation_weight) * d2beta_dR2 + _dilation_weight * d2mu_dR2);
611
612 }// for var_id2
613 }// for p
614 }// for var_id1
615 }// for l
616
617 End pseudo-code, begin efficient code
618
619 */
620
621 for (const auto l: elem.node_index_range()) // Contribution to Hessian from node l
622 {
623 for (const auto var_id1 : make_range(dim)) // Contribution from each x/y/z component of node l
624 {
625 // Build dS/dR_l, the derivative of the physical-to-target mapping Jacobin w.r.t.
626 // the l-th node
627 RealTensor dS_dR_l = RealTensor(0);
628 for (const auto ii : make_range(dim))
629 dS_dR_l(var_id1, ii) = dphi_maps[ii][l][qp];
630 dS_dR_l *= target_jacobians[qp];
631
632 // Jacobian is symmetric, only need to loop over lower triangular portion
633 for (const auto p: make_range(l + 1)) // Contribution to Hessian from node p
634 {
635 for (const auto var_id2 : make_range(dim)) // Contribution from each x/y/z component of node p
636 {
637 // Build dS/dR_l, the derivative of the physical-to-target mapping Jacobin w.r.t.
638 // the p-th node
639 RealTensor dS_dR_p = RealTensor(0);
640 for (const auto jj : make_range(dim))
641 dS_dR_p(var_id2, jj) = dphi_maps[jj][p][qp];
642 dS_dR_p *= target_jacobians[qp];
643
644 Real d2E_dR2 = 0.;
645 // Perform tensor contraction
646 for (const auto i : make_range(dim))
647 {
648
649 for (const auto j : make_range(dim))
650 {
651
652 const auto S_ij = S(i,j);
653 const auto S_inv_ji = S_inv(j,i);
654
655 // Apply the stuff only depending on i and j before entering a, b loops
656 // beta
657 const std::vector<Real> d2beta_dS2_coefs_ij_applied{
658 d2beta_dS2_coefs_times_distortion_weight[1] * S_ij,
659 d2beta_dS2_coefs_times_distortion_weight[2] * S_ij,
660 d2beta_dS2_coefs_times_distortion_weight[3] * S_inv_ji,
661 d2beta_dS2_coefs_times_distortion_weight[4] * S_inv_ji,
662 };
663 // mu
664 const auto dalpha_dS_coef_ij_applied = dalpha_dS_coef_times_dilation_weight * S_inv_ji;
665
666 Real d2E_dSdR_l = 0.;
667 Real d2E_dSdR_p = 0.;
668
669 for (const auto a : make_range(i + 1))
670 {
671
672 // If this condition is met, both the ijab and abij
673 // contributions to the Jacobian are zero due to the
674 // spasity patterns of dS_dR_l and dS_dR_p and this
675 // iteration may be skipped
676 if (!(a == var_id1 && i == var_id2) &&
677 !(a == var_id2 && i == var_id1))
678 continue;
679
680 const auto S_inv_ja = S_inv(j,a);
681
682 const Real d2beta_dS2_coef_ia_applied = d2beta_dS2_coefs_times_distortion_weight[0] * I(i,a);
683 const Real d2beta_dS2_coef_ja_applied = d2beta_dS2_coefs_times_distortion_weight[5] * S_inv_ja;
684 const Real alpha_ja_applied = alpha_times_dilation_weight * S_inv_ja;
685
686 const auto b_limit = (a == i) ? j + 1 : dim;
687 for (const auto b : make_range(b_limit))
688 {
689
690 // Combine precomputed coefficients with tensor products
691 // to get d2(beta) / dS2
692 Real d2beta_dS2_times_distortion_weight = (
693 d2beta_dS2_coef_ia_applied * I(j,b) +
694 d2beta_dS2_coefs_ij_applied[0] * S(a,b) +
695 d2beta_dS2_coefs_ij_applied[1] * S_inv(b,a) +
696 d2beta_dS2_coefs_ij_applied[2] * S(a,b) +
697 d2beta_dS2_coefs_ij_applied[3] * S_inv(b,a) +
698 d2beta_dS2_coef_ja_applied * S_inv(b,i)
699 );
700
701 // Incorporate tensor product portion to get d2(mu) /
702 // dS2
703 const Real d2mu_dS2_times_dilation_weight = dalpha_dS_coef_ij_applied * S_inv(b,a) - alpha_ja_applied * S_inv(b,i);
704
705
706 // Chain rule to change d/dS to d/dR
707 const auto d2E_dS2 =
708 d2beta_dS2_times_distortion_weight +
709 d2mu_dS2_times_dilation_weight;
710
711 // if !(a == var_id1 (next line) && i == var_id2
712 // (outside 'a' loop)), dS_dR_l(p) multiplier is zero
713 d2E_dSdR_l += d2E_dS2 * dS_dR_l(a, b);
714
715 if (!(i == a && j == b))
716 // if !(a == var_id2 (next line) && i == var_id1
717 // (outside 'a' loop)), dS_dR_p(l) multiplier is zero
718 d2E_dSdR_p += d2E_dS2 * dS_dR_p(a, b);
719
720 } // for b
721 } // for a
722 d2E_dR2 +=
723 d2E_dSdR_l * dS_dR_p(i, j) + d2E_dSdR_p * dS_dR_l(i, j);
724 }// for j
725 }// for i, end tensor contraction
726
727 // Jacobian contribution
728 const Real jacobian_contribution = quad_weight * d2E_dR2;
729 K[var_id1][var_id2](l, p) += jacobian_contribution;
730 // Jacobian is symmetric, add contribution to p,l entry
731 // Don't get the diagonal twice!
732 if (p < l)
733 // Note the transposition of var_id1 and var_id2 as these are also jacobian indices
734 K[var_id2][var_id1](p, l) += jacobian_contribution;
735
736 }// for var_id2
737 }// for p
738 }// for var_id1
739 }// for l
740 }
741 } // end of the quadrature point qp-loop
742
743 return request_jacobian;
744}
745
753
755{
756 // If the reference volume has not yet been computed, compute it.
757 if (std::abs(_ref_vol) < TOLERANCE * TOLERANCE)
759
760 std::unique_ptr<DiffContext> con = this->build_context();
761 FEMContext & femcontext = cast_ref<FEMContext &>(*con);
762 this->init_context(femcontext);
763
764 const auto & mesh = this->get_mesh();
765 const auto dim = mesh.mesh_dimension();
766 const Real half_dim = 0.5 * dim;
767 const auto distortion_weight = 1. - _dilation_weight;
768
769 // Make pre-requests before reinit() for efficiency in
770 // --enable-deprecated builds, and to avoid errors in
771 // --disable-deprecated builds.
772 const auto & fe_map = femcontext.get_element_fe(0)->get_fe_map();
773 const auto & quad_weights = femcontext.get_element_qrule().get_weights();
774 const auto & JxW = fe_map.get_JxW();
775 fe_map.get_dxyzdxi();
776 fe_map.get_dxyzdeta();
777 fe_map.get_dxyzdzeta();
778
779 MeshQualityInfo info;
780
781 for (const auto * elem : mesh.active_local_element_ptr_range())
782 {
783 femcontext.pre_fe_reinit(*this, elem);
784 femcontext.elem_fe_reinit();
785
786 // Element-integrated quantities
787 Real det_S_int = 0.;
788 Real beta_int = 0.;
789 Real mu_int = 0.;
790 Real combined_int = 0.;
791
792 const auto & target_jacobian_dets = _target_jacobian_dets[elem->type()];
793
794 for (const auto qp : index_range(JxW))
795 {
796 det_S_int += JxW[qp] / _target_jacobian_dets[elem->type()][qp];
797 const auto quad_weight = quad_weights[qp] / target_jacobian_dets[qp];
798
799 // Grab the physical-to-reference mapping Jacobian matrix (i.e., "S") at this qp
800 RealTensor S = get_jacobian_at_qp(fe_map, dim, qp);
801
802 // Apply target element transformation to get the physical-to-target jacobian
803 S *= _target_jacobians[elem->type()][qp];
804
805 // Determinant of S
806 const auto det = S.det();
807 const auto det_sq = det * det;
808
809 if (det > info.max_qp_det_S)
810 info.max_qp_det_S = det;
811 else if (det < info.min_qp_det_S)
812 info.min_qp_det_S = det;
813
814 if (det < TOLERANCE * TOLERANCE)
815 info.mesh_is_tangled = true;
816
817 // trace of S^T * S
818 const auto tr = trace(S.transpose() * S, dim);
819
820 // The chi function allows us to handle degenerate elements
821 const auto chi = chi_epsilon(det, _epsilon_squared_assembly);
822
823 // distortion
824 const Real beta = std::pow(tr / dim, half_dim) / chi;
825 beta_int += beta * quad_weight;
826
827 // dilation
828 const Real mu = 0.5 * (_ref_vol + det_sq / _ref_vol) / chi;
829 mu_int += mu * quad_weight;
830
831 // combined
832 const Real E = distortion_weight * beta + _dilation_weight * mu;
833 combined_int += E * quad_weight;
834 }
835
836 info.total_det_S += det_S_int;
837 if (det_S_int > info.max_elem_det_S.first)
838 info.max_elem_det_S = std::make_pair(det_S_int, elem->id());
839 if (det_S_int < info.min_elem_det_S.first)
840 info.min_elem_det_S = std::make_pair(det_S_int, elem->id());
841
842 info.total_distortion += beta_int;
843 if (beta_int > info.max_elem_distortion.first)
844 info.max_elem_distortion = std::make_pair(beta_int, elem->id());
845 if (beta_int < info.min_elem_distortion.first)
846 info.min_elem_distortion = std::make_pair(beta_int, elem->id());
847
848 info.total_dilation += mu_int;
849 if (mu_int > info.max_elem_dilation.first)
850 info.max_elem_dilation = std::make_pair(mu_int, elem->id());
851 if (mu_int < info.min_elem_dilation.first)
852 info.min_elem_dilation = std::make_pair(mu_int, elem->id());
853
854 info.total_combined += combined_int;
855 if (combined_int > info.max_elem_combined.first)
856 info.max_elem_combined = std::make_pair(combined_int, elem->id());
857 if (combined_int < info.min_elem_combined.first)
858 info.min_elem_combined = std::make_pair(combined_int, elem->id());
859
860 if (_verbosity > 90)
861 {
862 libMesh::out << "Elem " << elem->id() << " quality:" << std::endl
863 << " distortion-dilation metric: " << combined_int << std::endl
864 << " distortion metric: " << beta_int << std::endl
865 << " dilation metric: " << mu_int << std::endl
866 << " det(S): " << det_S_int << std::endl;
867 }
868
869 } // for elem
870
871 // Get contributions from elements on other processors
872 communicate_pair_max(info.max_elem_det_S, mesh.comm());
873 communicate_pair_min(info.min_elem_det_S, mesh.comm());
874 mesh.comm().max(info.max_qp_det_S);
875 mesh.comm().min(info.min_qp_det_S);
876 mesh.comm().sum(info.total_det_S);
877
878 communicate_pair_max(info.max_elem_distortion, mesh.comm());
879 communicate_pair_min(info.min_elem_distortion, mesh.comm());
880 mesh.comm().sum(info.total_distortion);
881
882 communicate_pair_max(info.max_elem_dilation, mesh.comm());
883 communicate_pair_min(info.min_elem_dilation, mesh.comm());
884 mesh.comm().sum(info.total_dilation);
885
886 communicate_pair_max(info.max_elem_combined, mesh.comm());
887 communicate_pair_min(info.min_elem_combined, mesh.comm());
888 mesh.comm().sum(info.total_combined);
889
890 mesh.comm().max(info.mesh_is_tangled);
891
892 info.initialized = true;
893
894 _mesh_info = info;
895
896 if (_verbosity > 50)
897 libMesh::out << info;
898}
899
901 const Elem * const target_elem,
902 const FEMContext & femcontext,
903 std::vector<RealTensor> & jacobians,
904 std::vector<Real> & jacobian_dets)
905{
906
907 const auto dim = target_elem->dim();
908
909 const auto & qrule_points = femcontext.get_element_qrule().get_points();
910 const auto & qrule_weights = femcontext.get_element_qrule().get_weights();
911 const auto nq_points = femcontext.get_element_qrule().n_points();
912
913 // If the target element is the reference element, Jacobian matrix is
914 // identity, det of inverse is 1. These will only be overwritten if a
915 // different target element is explicitly specified.
916 jacobians = std::vector<RealTensor>(nq_points, RealTensor(
917 1., 0., 0.,
918 0., 1., 0.,
919 0., 0., 1.));
920 jacobian_dets = std::vector<Real>(nq_points, 1.0);
921
922 // Don't use "if (*target_elem == *(target_elem->reference_elem()))" here, it
923 // only compares global node ids, not the node locations themselves.
924 bool target_equals_reference = true;
925 const auto * ref_elem = target_elem->reference_elem();
926 for (const auto local_id : make_range(target_elem->n_nodes()))
927 target_equals_reference &= target_elem->node_ref(local_id) == ref_elem->node_ref(local_id);
928 if (target_equals_reference)
929 return;
930
931 // Create FEMap to compute target_element mapping information
932 FEMap fe_map_target;
933
934 // pre-request mapping derivatives
935 fe_map_target.get_dxyzdxi();
936 fe_map_target.get_dxyzdeta();
937 fe_map_target.get_dxyzdzeta();
938
939 // build map
940 fe_map_target.init_reference_to_physical_map(dim, qrule_points, target_elem);
941 fe_map_target.compute_map(dim, qrule_weights, target_elem, /*d2phi=*/false);
942
943 for (const auto qp : make_range(nq_points))
944 {
945 // We use Larisa's H notation to denote the reference-to-target jacobian
946 RealTensor H = get_jacobian_at_qp(fe_map_target, dim, qp);
947
948 // The target-to-reference jacobian is the inverse of the
949 // reference-to-target jacobian
950 jacobians[qp] = H.inverse();
951 jacobian_dets[qp] = jacobians[qp].det();
952 }
953}
954
955std::ostream &operator<<(std::ostream &os, const MeshQualityInfo & info)
956{
957 os << "Mesh quality info:" << std::endl
958 << " Mesh distortion-dilation metric: "
959 << info.total_combined << std::endl
960 << " Mesh distortion metric: "
961 << info.total_distortion << std::endl
962 << " Mesh dilation metric: "
963 << info.total_dilation << std::endl
964 << " Max distortion-dilation is in elem "
965 << info.max_elem_combined.second << ": "
966 << info.max_elem_combined.first << std::endl
967 << " Max distortion is in elem "
968 << info.max_elem_distortion.second << ": "
969 << info.max_elem_distortion.first << std::endl
970 << " Max dilation is in elem "
971 << info.max_elem_dilation.second << ": "
972 << info.max_elem_dilation.first << std::endl
973 << " Max det(S) is in elem "
974 << info.max_elem_det_S.second << ": "
975 << info.max_elem_det_S.first << std::endl
976 << " Min distortion-dilation is in elem "
977 << info.min_elem_combined.second << ": "
978 << info.min_elem_combined.first << std::endl
979 << " Min distortion is in elem "
980 << info.min_elem_distortion.second << ": "
981 << info.min_elem_distortion.first << std::endl
982 << " Min dilation is in elem "
983 << info.min_elem_dilation.second << ": "
984 << info.min_elem_dilation.first << std::endl
985 << " Min det(S) is in elem "
986 << info.min_elem_det_S.second << ": "
987 << info.min_elem_det_S.first << std::endl
988 << " Max qp det(S): " << info.max_qp_det_S << std::endl
989 << " Min qp det(S): " << info.min_qp_det_S << std::endl
990 << " Mesh-integrated det(S): " << info.total_det_S << std::endl
991 << " Tangled: " << info.mesh_is_tangled << std::endl;
992
993 return os;
994}
995
996} // namespace libMesh
unsigned int dim
void maxloc(T &r, unsigned int &max_id) const
void minloc(T &r, unsigned int &min_id) const
void broadcast(T &data, const unsigned int root_id=0, const bool identical_sizes=false) const
This class provides all data required for a physics package (e.g.
const DenseVector< Number > & get_elem_residual() const
Const accessor for element residual.
const DenseMatrix< Number > & get_elem_jacobian() const
Const accessor for element Jacobian.
This is the base class from which all geometric element types are derived.
Definition elem.h:96
const Node & node_ref(const unsigned int i) const
Definition elem.h:2659
virtual unsigned int n_nodes() const =0
virtual unsigned short dim() const =0
const Elem * reference_elem() const
Definition elem.C:508
virtual ElemType type() const =0
IntRange< unsigned short > node_index_range() const
Definition elem.h:2821
NumericVector< Number > * rhs
The system matrix.
void get_nothing() const
const FEMap & get_fe_map() const
This class forms the foundation from which generic finite elements may be derived.
Definition fe_base.h:86
This class provides all data required for a physics package (e.g.
Definition fem_context.h:64
void get_side_fe(unsigned int var, FEGenericBase< OutputShape > *&fe) const
Accessor for edge/face (2D/3D) finite element object for variable var for the largest dimension in th...
const std::set< unsigned char > & elem_dimensions() const
virtual void pre_fe_reinit(const System &, const Elem *e)
Reinitializes local data vectors/matrices on the current geometric element.
const QBase & get_element_qrule() const
Accessor for element interior quadrature rule for the dimension of the current _elem.
unsigned char get_dim() const
Accessor for cached mesh dimension.
const Elem & get_elem() const
Accessor for current Elem object.
virtual void elem_fe_reinit(const std::vector< Point > *const pts=nullptr)
Reinitializes interior FE objects on the current geometric element.
void get_element_fe(unsigned int var, FEGenericBase< OutputShape > *&fe) const
Accessor for interior finite element object for variable var for the largest dimension in the mesh.
void use_quadrature_rules(QuadratureType qt, int extra_quadrature_order=0)
Use a specified quadrature rule type qt for every element, side, and edge dimension,...
virtual void solve() override
Invokes the solver associated with the system.
virtual void assembly(bool get_residual, bool get_jacobian, bool apply_heterogeneous_constraints=false, bool apply_no_constraints=false) override
Prepares matrix or rhs for matrix assembly.
Definition fem_system.C:875
virtual std::unique_ptr< DiffContext > build_context() override
Builds a FEMContext object with enough information to do evaluations on each element.
virtual void init_data() override
Initializes the member data fields associated with the system, so that, e.g., assemble() may be used.
Definition fem_system.C:868
virtual void init_context(DiffContext &) override
Class contained in FE that encapsulates mapping (i.e.
Definition fe_map.h:48
const std::vector< RealGradient > & get_dxyzdeta() const
Definition fe_map.h:247
const std::vector< RealGradient > & get_dxyzdzeta() const
Definition fe_map.h:255
void init_reference_to_physical_map(const std::vector< Point > &qp, const Elem *elem)
Definition fe_map.C:109
const std::vector< RealGradient > & get_dxyzdxi() const
Definition fe_map.h:239
virtual void compute_map(const unsigned int dim, const std::vector< Real > &qw, const Elem *elem, bool calculate_d2phi)
Compute the jacobian and some other additional data fields.
Definition fe_map.C:1323
virtual void close()=0
Calls the NumericVector's internal assembly routines, ensuring that the values are consistent across ...
const std::vector< Point > & get_points() const
Definition quadrature.h:156
unsigned int n_points() const
Definition quadrature.h:131
const std::vector< Real > & get_weights() const
Definition quadrature.h:168
int extra_quadrature_order
A member int that can be employed to indicate increased or reduced quadrature order.
Definition system.h:1640
std::unique_ptr< NumericVector< Number > > current_local_solution
All the values I need to compute my contribution to the simulation at hand.
Definition system.h:1667
unsigned int add_variable(std::string_view var, const FEType &type, const std::set< subdomain_id_type > *const active_subdomains=nullptr)
Adds the variable var to the list of variables for this system.
Definition system.C:1344
std::unique_ptr< NumericVector< Number > > solution
Data structure to hold solution values.
Definition system.h:1655
unsigned int number() const
Definition system.h:2393
const MeshBase & get_mesh() const
Definition system.h:2401
This class defines a tensor in LIBMESH_DIM dimensional Real or Complex space.
TypeTensor< T > inverse() const
TypeTensor< T > transpose() const
LIBMESH_DEVICE_INLINE CompareTypes< T, T2 >::supertype contract(const TypeTensor< T2 > &) const
Multiply 2 tensors together to return a scalar, i.e.
MeshQualityInfo _mesh_info
Information about the mesh quality.
bool _untangling_solve
Flag to indicate if the current solve is to untangle or smooth.
const MeshQualityInfo & get_mesh_info()
Getter for the _mesh_info attribute.
QuadratureType _quadrature_type
The quadrature rule type used to integrate the distortion-dilation metric.
Real _dilation_weight
The relative weight to give the dilation metric.
const Real _epsilon_squared
The small nonzero constant to prevent zero denominators (degenerate meshes only)
Real _epsilon_squared_assembly
Epsilon squared value determined at runtime during each assembly.
virtual void assembly(bool get_residual, bool get_jacobian, bool apply_heterogeneous_constraints=false, bool apply_no_constraints=false) override
Assembly method to update the mesh based on the smoother solve.
Real _ref_vol
The reference volume for each element.
std::map< ElemType, std::vector< RealTensor > > _target_jacobians
virtual void init_data() override
Initializes the member data fields associated with the system, so that, e.g., assemble() may be used.
virtual bool element_time_derivative(bool request_jacobian, libMesh::DiffContext &context) override
Adds the time derivative contribution on elem to elem_residual.
static void get_target_to_reference_jacobian(const Elem *const target_elem, const FEMContext &femcontext, std::vector< RealTensor > &jacobians, std::vector< Real > &jacobian_dets)
Get the jacobians (and determinants) of the target-to-reference element mapping.
std::map< ElemType, std::vector< Real > > _target_jacobian_dets
virtual void solve() override
Solves the system to smooth the mesh.
virtual void init_context(libMesh::DiffContext &context) override
static const Real b
MeshBase & mesh
void sync_dofobject_data_by_id(const Communicator &comm, const Iterator &range_begin, const Iterator &range_end, SyncFunctor &sync)
Request data about a range of ghost dofobjects uniquely identified by their id.
std::pair< std::unique_ptr< Elem >, std::vector< std::unique_ptr< Node > > > ideal_target(const ElemType type)
The libMesh namespace provides an interface to certain functionality in the library.
Real trace(const RealTensor &A, const unsigned int &dim)
Compute the trace of a dim-dimensional matrix.
Real chi_epsilon(const Real &x, const Real epsilon_squared)
Function to prevent dividing by zero for degenerate elements.
auto index_range(const T &sizable)
Helper function that returns an IntRange<std::size_t> representing all the indices of the passed-in v...
Definition int_range.h:153
std::ostream & operator<<(std::ostream &os, const OrderWrapper &order)
Overload stream operators.
Definition fe_type.h:182
void communicate_pair_min(std::pair< Real, dof_id_type > &pair, const Parallel::Communicator &comm)
OStreamProxy out
LIBMESH_DEVICE_INLINE T libmesh_real(T a)
RealTensorValue RealTensor
static constexpr Real TOLERANCE
RealTensor get_jacobian_at_qp(const FEMap &fe_map, const unsigned int &dim, const unsigned int &qp)
Given an fe_map, element dimension, and quadrature point index, returns the Jacobian of the physical-...
RealVectorValue RealGradient
void communicate_pair_max(std::pair< Real, dof_id_type > &pair, const Parallel::Communicator &comm)
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
IntRange< T > make_range(T beg, T end)
The 2-parameter make_range() helper function returns an IntRange<T> when both input parameters are of...
Definition int_range.h:176
Struct to hold smoother-relevant information about the mesh quality.