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] = get_target_elem(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
315
317 DiffContext & context)
318{
319 FEMContext & c = cast_ref<FEMContext &>(context);
320
321 const Elem & elem = c.get_elem();
322
323 unsigned int dim = c.get_dim();
324
325 unsigned int x_var = 0, y_var = 1, z_var = 2;
326 // In the case of lower dimensions, we will not access z (1D/2D) or y (1D)
327 if (dim < 3)
328 {
329 z_var = 0;
330 if (dim < 2)
331 y_var = 0;
332 }
333
334 // The subvectors and submatrices we need to fill:
335 // system residual
336 std::reference_wrapper<DenseSubVector<Number>> F[3] =
337 {
338 c.get_elem_residual(x_var),
339 c.get_elem_residual(y_var),
340 c.get_elem_residual(z_var)
341 };
342 // system jacobian
343 std::reference_wrapper<DenseSubMatrix<Number>> K[3][3] =
344 {
345 {c.get_elem_jacobian(x_var, x_var), c.get_elem_jacobian(x_var, y_var), c.get_elem_jacobian(x_var, z_var)},
346 {c.get_elem_jacobian(y_var, x_var), c.get_elem_jacobian(y_var, y_var), c.get_elem_jacobian(y_var, z_var)},
347 {c.get_elem_jacobian(z_var, x_var), c.get_elem_jacobian(z_var, y_var), c.get_elem_jacobian(z_var, z_var)}
348 };
349
350 // Quadrature info
351 const auto & quad_weights = c.get_element_qrule().get_weights();
352
353 const auto distortion_weight = 1. - _dilation_weight;
354
355 // Get some references to cell-specific data that
356 // will be used to assemble the linear system.
357
358 const auto & fe_map = c.get_element_fe(0)->get_fe_map();
359
360 const auto & dphidxi_map = fe_map.get_dphidxi_map();
361 const auto & dphideta_map = fe_map.get_dphideta_map();
362 const auto & dphidzeta_map = fe_map.get_dphidzeta_map();
363
364 const auto & target_jacobian_dets = _target_jacobian_dets[elem.type()];
365 const auto & target_jacobians = _target_jacobians[elem.type()];
366
367 // Integrate the distortion-dilation metric over the reference element
368 for (const auto qp : index_range(quad_weights))
369 {
370 // Compute quantities needed to evaluate the distortion-dilation metric
371 // and its gradient and Hessian.
372 // The metric will be minimized when it's gradient with respect to the node
373 // locations (R) is zero. For Newton's method, minimizing the gradient
374 // requires computation of the gradient's Jacobian with respect to R.
375 // The Jacobian of the distortion-dilation metric's gradientis the Hessian
376 // of the metric.
377
378 // Transform quad weight from reference element to quad weight for target element
379 const auto quad_weight = quad_weights[qp] / target_jacobian_dets[qp];
380
381 // Note that the term "Jacobian" has two meanings in this mesh smoothing
382 // application. The first meaning refers to the Jacobian w.r.t R of the
383 // gradient w.r.t. R, (i.e., the Hessian of the metric). This is the K
384 // variable defined above. This is also the Jacobian the 'request_jacobian'
385 // variable refers to. The second meaning refers to the Jacobian of the
386 // physical-to-target element mapping. This is the Jacobian used to
387 // compute the distorion-dilation metric.
388 //
389 // Grab the physical-to-reference mapping Jacobian matrix (i.e., "S") at this qp
390 RealTensor S = get_jacobian_at_qp(fe_map, dim, qp);
391
392 // Apply target element transformation to get the physical-to-target jacobian
393 S *= _target_jacobians[elem.type()][qp];
394
395 // Compute quantities needed for the smoothing algorithm
396
397 // determinant
398 const Real det = S.det();
399 const Real det_sq = det * det;
400 const Real det_cube = det_sq * det;
401
402 const Real ref_vol_sq = _ref_vol * _ref_vol;
403
404 // trace of S^T * S
405 // DO NOT USE RealTensor.tr for the trace, it will NOT be correct for
406 // 1D and 2D meshes because of our hack of putting 1s in the diagonal of
407 // S for the extra dimensions (see get_jacobian_at_qp)
408 const auto tr = trace(S.transpose() * S, dim);
409 const Real tr_div_dim = tr / dim;
410
411 // Precompute pow(tr_div_dim, 0.5 * dim - x) for x = 0, 1, 2
412 const Real half_dim = 0.5 * dim;
413 const std::vector<Real> trace_powers{
414 std::pow(tr_div_dim, half_dim),
415 std::pow(tr_div_dim, half_dim - 1.),
416 std::pow(tr_div_dim, half_dim - 2.),
417 };
418
419 // inverse of S
420 const RealTensor S_inv = S.inverse();
421 // inverse transpose of S
422 const RealTensor S_inv_T = S_inv.transpose();
423
424 // Identity matrix
425 const RealTensor I(1, 0, 0,
426 0, 1, 0,
427 0, 0, 1);
428
429 // The chi function allows us to handle degenerate elements
430 const auto chi = chi_epsilon(det, _epsilon_squared_assembly);
431 const Real chi_sq = chi * chi;
432 const Real sqrt_term = std::sqrt(_epsilon_squared_assembly + det_sq);
433 // dchi(x) / dx
434 const Real chi_prime = 0.5 * (1. + det / sqrt_term);
435 const Real chi_prime_sq = chi_prime * chi_prime;
436 // d2chi(x) / dx2
437 const Real chi_2prime = 0.5 * (1. / sqrt_term - det_sq / Utility::pow<3>(sqrt_term));
438
439 // Distortion metric (beta)
440 //const Real beta = trace_powers[0] / chi;
441 const RealTensor dbeta_dS = (trace_powers[1] / chi) * S - (trace_powers[0] / chi_sq * chi_prime * det) * S_inv_T;
442
443 // Dilation metric (mu)
444 //const Real mu = 0.5 * (_ref_vol + det_sq / _ref_vol) / chi;
445 // We represent d mu / dS as alpha(S) * S^-T, where alpha is a scalar function
446 const Real alpha = (-chi_prime * det_cube + 2. * det_sq * chi - ref_vol_sq * det * chi_prime) / (2. * _ref_vol * chi_sq);
447 const RealTensor dmu_dS = alpha * S_inv_T;
448
449 // Combined metric (E)
450 //const Real E = distortion_weight * beta + _dilation_weight * mu;
451 const RealTensor dE_dS = distortion_weight * dbeta_dS + _dilation_weight * dmu_dS;
452
453 // This vector is useful in computing dS/dR below
454 std::vector<std::vector<std::vector<Real>>> dphi_maps = {dphidxi_map, dphideta_map, dphidzeta_map};
455
456 // Compute residual (i.e., the gradient of the combined metric w.r.t node locations)
457 // Recall that when the gradient (residual) is zero, the combined metric is minimized
458 for (const auto l : elem.node_index_range())
459 {
460 for (const auto var_id : make_range(dim))
461 {
462 // Build dS/dR, the derivative of the physical-to-target mapping Jacobin w.r.t. the mesh
463 // node locations
464 RealTensor dS_dR = RealTensor(0);
465 for (const auto jj : make_range(dim))
466 dS_dR(var_id, jj) = dphi_maps[jj][l][qp];
467 dS_dR *= target_jacobians[qp];
468
469 // Residual contribution. The contraction of dE/dS and dS/dR gives us
470 // the gradient we are looking for, dE/dR
471 F[var_id](l) += quad_weight * dE_dS.contract(dS_dR);
472 }// for var_id
473 }// for l
474
475
476 if (request_jacobian)
477 {
478 // Compute jacobian of the smoothing system (i.e., the Hessian of the
479 // combined metric w.r.t. the mesh node locations)
480
481 // Precompute coefficients to be applied to each component of tensor
482 // products in the loops below. At first glance, these coefficients look
483 // like gibberish, but everything in the Hessian has been verified by
484 // taking finite differences of the gradient. We should probably write
485 // down the derivations of the gradient and Hessian somewhere...
486
487 // Recall that above, dbeta_dS takes the form:
488 // d(beta)/dS = c1(S) * S - c2(S) * S_inv_T,
489 // where c1 and c2 are scalar-valued functions.
490 const std::vector<Real> d2beta_dS2_coefs_times_distortion_weight = {
491 //Part 1: scaler coefficients of d(c1 * S) / dS
492 //
493 // multiplies I[i,a] x I[j,b]
494 (trace_powers[1] / chi) * distortion_weight,
495 // multiplies S[a,b] x S[i,j]
496 (((dim - 2.) / dim) * trace_powers[2] / chi) * distortion_weight,
497 // multiplies S_inv[b,a] * S[i,j]
498 (-(trace_powers[1] / chi_sq) * chi_prime * det) * distortion_weight,
499 //
500 //Part 2: scaler coefficients of d(-c2 * S_inv_T) / dS
501 //
502 // multiplies S[a,b] x S_inv[j,i]
503 (-(trace_powers[1] / chi_sq) * chi_prime * det) * distortion_weight,
504 // multiplies S_inv[b,a] x S_inv[j,i]
505 (trace_powers[0] * (det / chi_sq)
506 * ((2. * chi_prime_sq / chi - chi_2prime) * det - chi_prime)) * distortion_weight,
507 // multiplies S_inv[b,i] x S_inv[j,a]
508 ((trace_powers[0] / chi_sq) * chi_prime * det) * distortion_weight,
509 };
510
511 // d alpha / dS has the form c(S) * S^-T, where c is the scalar coefficient defined below
512 const Real dalpha_dS_coef_times_dilation_weight = ((det / (2. * _ref_vol * chi_sq))
513 * (-4. * _ref_vol * alpha * chi * chi_prime - chi_2prime * det_cube
514 - chi_prime * det_sq + 4 * chi * det - ref_vol_sq * (chi_prime + det * chi_2prime))) * _dilation_weight;
515
516 // This is also useful to precompute
517 const Real alpha_times_dilation_weight = alpha * _dilation_weight;
518
519 /*
520
521 To increase the efficiency of the Jacobian computation, we take
522 advantage of l-p symmetry, ij-ab symmetry, and the sparsity pattern of
523 dS_dR. We also factor all possible multipliers out of inner loops for
524 efficiency. The result is code that is more difficult to read. For
525 clarity, consult the pseudo-code below in this comment.
526
527 for (const auto l: elem.node_index_range()) // Contribution to Hessian
528 from node l
529 {
530 for (const auto var_id1 : make_range(dim)) // Contribution from each
531 x/y/z component of node l
532 {
533 // Build dS/dR_l, the derivative of the physical-to-target
534 mapping Jacobin w.r.t.
535 // the l-th node
536 RealTensor dS_dR_l = RealTensor(0);
537 for (const auto ii : make_range(dim))
538 dS_dR_l(var_id1, ii) = dphi_maps[ii][l][qp];
539 dS_dR_l *= target_jacobians[qp];
540
541 for (const auto p: elem.node_index_range()) // Contribution to
542 Hessian from node p
543 {
544 for (const auto var_id2 : make_range(dim)) // Contribution from
545 each x/y/z component of node p
546 {
547 // Build dS/dR_l, the derivative of the physical-to-target
548 mapping Jacobin w.r.t.
549 // the p-th node
550 RealTensor dS_dR_p = RealTensor(0);
551 for (const auto jj : make_range(dim))
552 dS_dR_p(var_id2, jj) = dphi_maps[jj][p][qp];
553 dS_dR_p *= target_jacobians[qp];
554
555 Real d2beta_dR2 = 0.;
556 Real d2mu_dR2 = 0.;
557 // Perform tensor contraction
558 for (const auto i : make_range(dim))
559 {
560 for (const auto j : make_range(dim))
561 {
562 for (const auto a : make_range(dim))
563 {
564 for (const auto b : make_range(dim))
565 {
566 // Nasty tensor products to be multiplied by
567 d2beta_dS2_coefs to get d2(beta) / dS2 const std::vector<Real>
568 d2beta_dS2_tensor_contributions =
569 {
570 I(i,a) * I(j,b),
571 S(a,b) * S(i,j),
572 S_inv(b,a) * S(i,j),
573 S(a,b) * S_inv(j,i),
574 S_inv(b,a) * S_inv(j,i),
575 S_inv(j,a) * S_inv(b,i),
576 };
577
578 // Combine precomputed coefficients with tensor products
579 to get d2(beta) / dS2 Real d2beta_dS2 = 0.; for (const auto comp_id :
580 index_range(d2beta_dS2_coefs))
581 {
582 const Real contribution = d2beta_dS2_coefs[comp_id] *
583 d2beta_dS2_tensor_contributions[comp_id]; d2beta_dS2 += contribution;
584 }
585
586 // Incorporate tensor product portion to get d2(mu) /
587 dS2 const Real d2mu_dS2 = dalpha_dS_coef * S_inv(b,a) * S_inv(j,i) -
588 alpha * S_inv(b,i) * S_inv(j,a);
589
590 // Chain rule to change d/dS to d/dR
591 d2beta_dR2 += d2beta_dS2 * dS_dR_l(a, b) * dS_dR_p(i,
592 j); d2mu_dR2 += d2mu_dS2 * dS_dR_l(a, b) * dS_dR_p(i, j);
593
594 }// for b
595 }// for a
596 }// for j
597 }// for i, end tensor contraction
598
599 // Jacobian contribution
600 K[var_id1][var_id2](l, p) += quad_weight * ((1. -
601 _dilation_weight) * d2beta_dR2 + _dilation_weight * d2mu_dR2);
602
603 }// for var_id2
604 }// for p
605 }// for var_id1
606 }// for l
607
608 End pseudo-code, begin efficient code
609
610 */
611
612 for (const auto l: elem.node_index_range()) // Contribution to Hessian from node l
613 {
614 for (const auto var_id1 : make_range(dim)) // Contribution from each x/y/z component of node l
615 {
616 // Build dS/dR_l, the derivative of the physical-to-target mapping Jacobin w.r.t.
617 // the l-th node
618 RealTensor dS_dR_l = RealTensor(0);
619 for (const auto ii : make_range(dim))
620 dS_dR_l(var_id1, ii) = dphi_maps[ii][l][qp];
621 dS_dR_l *= target_jacobians[qp];
622
623 // Jacobian is symmetric, only need to loop over lower triangular portion
624 for (const auto p: make_range(l + 1)) // Contribution to Hessian from node p
625 {
626 for (const auto var_id2 : make_range(dim)) // Contribution from each x/y/z component of node p
627 {
628 // Build dS/dR_l, the derivative of the physical-to-target mapping Jacobin w.r.t.
629 // the p-th node
630 RealTensor dS_dR_p = RealTensor(0);
631 for (const auto jj : make_range(dim))
632 dS_dR_p(var_id2, jj) = dphi_maps[jj][p][qp];
633 dS_dR_p *= target_jacobians[qp];
634
635 Real d2E_dR2 = 0.;
636 // Perform tensor contraction
637 for (const auto i : make_range(dim))
638 {
639
640 for (const auto j : make_range(dim))
641 {
642
643 const auto S_ij = S(i,j);
644 const auto S_inv_ji = S_inv(j,i);
645
646 // Apply the stuff only depending on i and j before entering a, b loops
647 // beta
648 const std::vector<Real> d2beta_dS2_coefs_ij_applied{
649 d2beta_dS2_coefs_times_distortion_weight[1] * S_ij,
650 d2beta_dS2_coefs_times_distortion_weight[2] * S_ij,
651 d2beta_dS2_coefs_times_distortion_weight[3] * S_inv_ji,
652 d2beta_dS2_coefs_times_distortion_weight[4] * S_inv_ji,
653 };
654 // mu
655 const auto dalpha_dS_coef_ij_applied = dalpha_dS_coef_times_dilation_weight * S_inv_ji;
656
657 Real d2E_dSdR_l = 0.;
658 Real d2E_dSdR_p = 0.;
659
660 for (const auto a : make_range(i + 1))
661 {
662
663 // If this condition is met, both the ijab and abij
664 // contributions to the Jacobian are zero due to the
665 // spasity patterns of dS_dR_l and dS_dR_p and this
666 // iteration may be skipped
667 if (!(a == var_id1 && i == var_id2) &&
668 !(a == var_id2 && i == var_id1))
669 continue;
670
671 const auto S_inv_ja = S_inv(j,a);
672
673 const Real d2beta_dS2_coef_ia_applied = d2beta_dS2_coefs_times_distortion_weight[0] * I(i,a);
674 const Real d2beta_dS2_coef_ja_applied = d2beta_dS2_coefs_times_distortion_weight[5] * S_inv_ja;
675 const Real alpha_ja_applied = alpha_times_dilation_weight * S_inv_ja;
676
677 const auto b_limit = (a == i) ? j + 1 : dim;
678 for (const auto b : make_range(b_limit))
679 {
680
681 // Combine precomputed coefficients with tensor products
682 // to get d2(beta) / dS2
683 Real d2beta_dS2_times_distortion_weight = (
684 d2beta_dS2_coef_ia_applied * I(j,b) +
685 d2beta_dS2_coefs_ij_applied[0] * S(a,b) +
686 d2beta_dS2_coefs_ij_applied[1] * S_inv(b,a) +
687 d2beta_dS2_coefs_ij_applied[2] * S(a,b) +
688 d2beta_dS2_coefs_ij_applied[3] * S_inv(b,a) +
689 d2beta_dS2_coef_ja_applied * S_inv(b,i)
690 );
691
692 // Incorporate tensor product portion to get d2(mu) /
693 // dS2
694 const Real d2mu_dS2_times_dilation_weight = dalpha_dS_coef_ij_applied * S_inv(b,a) - alpha_ja_applied * S_inv(b,i);
695
696
697 // Chain rule to change d/dS to d/dR
698 const auto d2E_dS2 =
699 d2beta_dS2_times_distortion_weight +
700 d2mu_dS2_times_dilation_weight;
701
702 // if !(a == var_id1 (next line) && i == var_id2
703 // (outside 'a' loop)), dS_dR_l(p) multiplier is zero
704 d2E_dSdR_l += d2E_dS2 * dS_dR_l(a, b);
705
706 if (!(i == a && j == b))
707 // if !(a == var_id2 (next line) && i == var_id1
708 // (outside 'a' loop)), dS_dR_p(l) multiplier is zero
709 d2E_dSdR_p += d2E_dS2 * dS_dR_p(a, b);
710
711 } // for b
712 } // for a
713 d2E_dR2 +=
714 d2E_dSdR_l * dS_dR_p(i, j) + d2E_dSdR_p * dS_dR_l(i, j);
715 }// for j
716 }// for i, end tensor contraction
717
718 // Jacobian contribution
719 const Real jacobian_contribution = quad_weight * d2E_dR2;
720 K[var_id1][var_id2](l, p) += jacobian_contribution;
721 // Jacobian is symmetric, add contribution to p,l entry
722 // Don't get the diagonal twice!
723 if (p < l)
724 // Note the transposition of var_id1 and var_id2 as these are also jacobian indices
725 K[var_id2][var_id1](p, l) += jacobian_contribution;
726
727 }// for var_id2
728 }// for p
729 }// for var_id1
730 }// for l
731 }
732 } // end of the quadrature point qp-loop
733
734 return request_jacobian;
735}
736
744
746{
747 // If the reference volume has not yet been computed, compute it.
748 if (std::abs(_ref_vol) < TOLERANCE * TOLERANCE)
750
751 std::unique_ptr<DiffContext> con = this->build_context();
752 FEMContext & femcontext = cast_ref<FEMContext &>(*con);
753 this->init_context(femcontext);
754
755 const auto & mesh = this->get_mesh();
756 const auto dim = mesh.mesh_dimension();
757 const Real half_dim = 0.5 * dim;
758 const auto distortion_weight = 1. - _dilation_weight;
759
760 // Make pre-requests before reinit() for efficiency in
761 // --enable-deprecated builds, and to avoid errors in
762 // --disable-deprecated builds.
763 const auto & fe_map = femcontext.get_element_fe(0)->get_fe_map();
764 const auto & quad_weights = femcontext.get_element_qrule().get_weights();
765 const auto & JxW = fe_map.get_JxW();
766 fe_map.get_dxyzdxi();
767 fe_map.get_dxyzdeta();
768 fe_map.get_dxyzdzeta();
769
770 MeshQualityInfo info;
771
772 for (const auto * elem : mesh.active_local_element_ptr_range())
773 {
774 femcontext.pre_fe_reinit(*this, elem);
775 femcontext.elem_fe_reinit();
776
777 // Element-integrated quantities
778 Real det_S_int = 0.;
779 Real beta_int = 0.;
780 Real mu_int = 0.;
781 Real combined_int = 0.;
782
783 const auto & target_jacobian_dets = _target_jacobian_dets[elem->type()];
784
785 for (const auto qp : index_range(JxW))
786 {
787 det_S_int += JxW[qp] / _target_jacobian_dets[elem->type()][qp];
788 const auto quad_weight = quad_weights[qp] / target_jacobian_dets[qp];
789
790 // Grab the physical-to-reference mapping Jacobian matrix (i.e., "S") at this qp
791 RealTensor S = get_jacobian_at_qp(fe_map, dim, qp);
792
793 // Apply target element transformation to get the physical-to-target jacobian
794 S *= _target_jacobians[elem->type()][qp];
795
796 // Determinant of S
797 const auto det = S.det();
798 const auto det_sq = det * det;
799
800 if (det > info.max_qp_det_S)
801 info.max_qp_det_S = det;
802 else if (det < info.min_qp_det_S)
803 info.min_qp_det_S = det;
804
805 if (det < TOLERANCE * TOLERANCE)
806 info.mesh_is_tangled = true;
807
808 // trace of S^T * S
809 const auto tr = trace(S.transpose() * S, dim);
810
811 // The chi function allows us to handle degenerate elements
812 const auto chi = chi_epsilon(det, _epsilon_squared_assembly);
813
814 // distortion
815 const Real beta = std::pow(tr / dim, half_dim) / chi;
816 beta_int += beta * quad_weight;
817
818 // dilation
819 const Real mu = 0.5 * (_ref_vol + det_sq / _ref_vol) / chi;
820 mu_int += mu * quad_weight;
821
822 // combined
823 const Real E = distortion_weight * beta + _dilation_weight * mu;
824 combined_int += E * quad_weight;
825 }
826
827 info.total_det_S += det_S_int;
828 if (det_S_int > info.max_elem_det_S.first)
829 info.max_elem_det_S = std::make_pair(det_S_int, elem->id());
830 if (det_S_int < info.min_elem_det_S.first)
831 info.min_elem_det_S = std::make_pair(det_S_int, elem->id());
832
833 info.total_distortion += beta_int;
834 if (beta_int > info.max_elem_distortion.first)
835 info.max_elem_distortion = std::make_pair(beta_int, elem->id());
836 if (beta_int < info.min_elem_distortion.first)
837 info.min_elem_distortion = std::make_pair(beta_int, elem->id());
838
839 info.total_dilation += mu_int;
840 if (mu_int > info.max_elem_dilation.first)
841 info.max_elem_dilation = std::make_pair(mu_int, elem->id());
842 if (mu_int < info.min_elem_dilation.first)
843 info.min_elem_dilation = std::make_pair(mu_int, elem->id());
844
845 info.total_combined += combined_int;
846 if (combined_int > info.max_elem_combined.first)
847 info.max_elem_combined = std::make_pair(combined_int, elem->id());
848 if (combined_int < info.min_elem_combined.first)
849 info.min_elem_combined = std::make_pair(combined_int, elem->id());
850
851 if (_verbosity > 90)
852 {
853 libMesh::out << "Elem " << elem->id() << " quality:" << std::endl
854 << " distortion-dilation metric: " << combined_int << std::endl
855 << " distortion metric: " << beta_int << std::endl
856 << " dilation metric: " << mu_int << std::endl
857 << " det(S): " << det_S_int << std::endl;
858 }
859
860 } // for elem
861
862 // Get contributions from elements on other processors
863 communicate_pair_max(info.max_elem_det_S, mesh.comm());
864 communicate_pair_min(info.min_elem_det_S, mesh.comm());
865 mesh.comm().max(info.max_qp_det_S);
866 mesh.comm().min(info.min_qp_det_S);
867 mesh.comm().sum(info.total_det_S);
868
869 communicate_pair_max(info.max_elem_distortion, mesh.comm());
870 communicate_pair_min(info.min_elem_distortion, mesh.comm());
871 mesh.comm().sum(info.total_distortion);
872
873 communicate_pair_max(info.max_elem_dilation, mesh.comm());
874 communicate_pair_min(info.min_elem_dilation, mesh.comm());
875 mesh.comm().sum(info.total_dilation);
876
877 communicate_pair_max(info.max_elem_combined, mesh.comm());
878 communicate_pair_min(info.min_elem_combined, mesh.comm());
879 mesh.comm().sum(info.total_combined);
880
881 mesh.comm().max(info.mesh_is_tangled);
882
883 info.initialized = true;
884
885 _mesh_info = info;
886
887 if (_verbosity > 50)
888 libMesh::out << info;
889}
890
891std::pair<std::unique_ptr<Elem>, std::vector<std::unique_ptr<Node>>>
893{
894 // Build target element
895 auto target_elem = Elem::build(type);
896
897 // Volume of reference element
898 const auto ref_vol = target_elem->reference_elem()->volume();
899
900 // Update the nodes of the target element, depending on type
901 const Real sqrt_2 = std::sqrt(Real(2));
902 const Real sqrt_3 = std::sqrt(Real(3));
903 std::vector<std::unique_ptr<Node>> owned_nodes;
904
905 const auto type_str = Utility::enum_to_string(type);
906
907 // Elems deriving from Tri
908 if (type_str.compare(0, 3, "TRI") == 0)
909 {
910
911 // The target element will be an equilateral triangle with area equal to
912 // the area of the reference element.
913
914 // Equilateral triangle side length preserving area of the reference element
915 const auto side_length = std::sqrt(4. / sqrt_3 * ref_vol);
916
917 // Define the nodal locations of the vertices
918 const auto & s = side_length;
919 // x y node_id
920 owned_nodes.emplace_back(Node::build(Point(0., 0.), 0));
921 owned_nodes.emplace_back(Node::build(Point(s, 0.), 1));
922 owned_nodes.emplace_back(Node::build(Point(0.5 * s, 0.5 * sqrt_3 * s), 2));
923
924 switch (type)
925 {
926 case TRI3: {
927 // Nothing to do here, vertices already added above
928 break;
929 }
930
931 case TRI6: {
932 // Define the midpoint nodes of the equilateral triangle
933 // x y node_id
934 owned_nodes.emplace_back(Node::build(Point(0.50 * s, 0.00), 3));
935 owned_nodes.emplace_back(Node::build(Point(0.75 * s, 0.25 * sqrt_3 * s), 4));
936 owned_nodes.emplace_back(Node::build(Point(0.25 * s, 0.25 * sqrt_3 * s), 5));
937
938 break;
939 }
940
941 default:
942 libmesh_error_msg("Unsupported triangular element: " << type_str);
943 break;
944 }
945 } // if Tri
946
947 // Elems deriving from Prism
948 else if (type_str.compare(0, 5, "PRISM") == 0)
949 {
950
951 // The target element will be a prism with an equilateral triangular
952 // base with volume equal to the volume of the reference element.
953
954 // For an equilateral triangular base with side length s, the
955 // base area is s^2 * sqrt(3) / 4.
956 // The prism height that will result in equal face areas is
957 // s * sqrt(3) / 4. We choose s such that the target element has
958 // the same volume as the reference element:
959 // v = (s^2 * sqrt(3) / 4) * (s * sqrt(3) / 4) = 3 * s^3 / 4
960 // --> s = (16 * v / 3)^(1/3)
961 // I have no particular motivation for imposing equal face areas,
962 // so this can be updated if a more `optimal` target prism is
963 // identified.
964
965 // Side length that preserves the volume of the reference element
966 const auto side_length = std::cbrt(16. * ref_vol / 3.);
967 // Prism height with the property that all faces have equal area
968 const auto target_height = 0.25 * side_length * sqrt_3;
969
970 const auto & s = side_length;
971 const auto & h = target_height;
972 // x y z node_id
973 owned_nodes.emplace_back(Node::build(Point(0., 0., 0.), 0));
974 owned_nodes.emplace_back(Node::build(Point(s, 0., 0.), 1));
975 owned_nodes.emplace_back(Node::build(Point(0.5 * s, 0.5 * sqrt_3 * s, 0.), 2));
976 owned_nodes.emplace_back(Node::build(Point(0., 0., h), 3));
977 owned_nodes.emplace_back(Node::build(Point(s, 0., h), 4));
978 owned_nodes.emplace_back(Node::build(Point(0.5 * s, 0.5 * sqrt_3 * s, h), 5));
979
980 if (type == PRISM15 || type == PRISM18 || type == PRISM20 || type == PRISM21)
981 {
982 // Define the edge midpoint nodes of the prism
983 const auto & on = owned_nodes;
984 owned_nodes.emplace_back(Node::build(Point((*on[0] + *on[1]) / 2.), 6));
985 owned_nodes.emplace_back(Node::build(Point((*on[1] + *on[2]) / 2.), 7));
986 owned_nodes.emplace_back(Node::build(Point((*on[2] + *on[0]) / 2.), 8));
987 owned_nodes.emplace_back(Node::build(Point((*on[0] + *on[3]) / 2.), 9));
988 owned_nodes.emplace_back(Node::build(Point((*on[1] + *on[4]) / 2.), 10));
989 owned_nodes.emplace_back(Node::build(Point((*on[2] + *on[5]) / 2.), 11));
990 owned_nodes.emplace_back(Node::build(Point((*on[3] + *on[4]) / 2.), 12));
991 owned_nodes.emplace_back(Node::build(Point((*on[4] + *on[5]) / 2.), 13));
992 owned_nodes.emplace_back(Node::build(Point((*on[5] + *on[3]) / 2.), 14));
993
994 if (type == PRISM18 || type == PRISM20 || type == PRISM21)
995 {
996 // Define the rectangular face midpoint nodes of the prism
997 owned_nodes.emplace_back(Node::build(Point((*on[0] + *on[1] + *on[3] + *on[4]) / 4.), 15));
998 owned_nodes.emplace_back(Node::build(Point((*on[1] + *on[2] + *on[4] + *on[5]) / 4.), 16));
999 owned_nodes.emplace_back(Node::build(Point((*on[0] + *on[2] + *on[3] + *on[5]) / 4.), 17));
1000
1001 if (type == PRISM20 || type == PRISM21)
1002 {
1003 // Define the triangular face midpoint nodes of the prism
1004 owned_nodes.emplace_back(Node::build(Point((*on[0] + *on[1] + *on[2]) / 3.), 18));
1005 owned_nodes.emplace_back(Node::build(Point((*on[3] + *on[4] + *on[5]) / 3.), 19));
1006
1007 if (type == PRISM21)
1008 // Define the interior point of the prism
1009 owned_nodes.emplace_back(Node::build(Point((*on[9] + *on[10] + *on[11]) / 3.), 20));
1010
1011 }
1012 }
1013 }
1014
1015 else if (type != PRISM6)
1016 libmesh_error_msg("Unsupported prism element: " << type_str);
1017
1018 } // if Prism
1019
1020 // Elems deriving from Pyramid
1021 else if (type_str.compare(0, 7, "PYRAMID") == 0)
1022 {
1023
1024 // The target element is a pyramid with an square base and
1025 // equilateral triangular sides with volume equal to the volume of the
1026 // reference element.
1027
1028 // A pyramid with square base sidelength s and equilateral triangular
1029 // sides has height h = s / sqrt(2).
1030 // The volume is v = s^2 h / 3 = s^3 / ( 3 sqrt(2)).
1031 // Solving for s: s = (3 sqrt(2) v)^(1/3), where v is the volume of the
1032 // non-optimal reference element.
1033
1034 // Side length that preserves the volume of the reference element
1035 const auto side_length = std::cbrt(3. * sqrt_2 * ref_vol);
1036 // Pyramid height with the property that all faces are equilateral triangles
1037 const auto target_height = side_length / sqrt_2;
1038
1039 const auto & s = side_length;
1040 const auto & h = target_height;
1041
1042 // x y z node_id
1043 owned_nodes.emplace_back(Node::build(Point(0., 0., 0.), 0));
1044 owned_nodes.emplace_back(Node::build(Point(s, 0., 0.), 1));
1045 owned_nodes.emplace_back(Node::build(Point(s, s, 0.), 2));
1046 owned_nodes.emplace_back(Node::build(Point(0., s, 0.), 3));
1047 owned_nodes.emplace_back(Node::build(Point(0.5 * s, 0.5 * s, h), 4));
1048
1049 if (type == PYRAMID13 || type == PYRAMID14 || type == PYRAMID18)
1050 {
1051 const auto & on = owned_nodes;
1052 // Define the edge midpoint nodes of the pyramid
1053
1054 // Base node to base node midpoint nodes
1055 owned_nodes.emplace_back(Node::build((*on[0] + *on[1]) / 2., 5));
1056 owned_nodes.emplace_back(Node::build((*on[1] + *on[2]) / 2., 6));
1057 owned_nodes.emplace_back(Node::build((*on[2] + *on[3]) / 2., 7));
1058 owned_nodes.emplace_back(Node::build((*on[3] + *on[0]) / 2., 8));
1059
1060 // Base node to apex node midpoint nodes
1061 owned_nodes.emplace_back(Node::build(Point((*on[0] + *on[4]) / 2.), 9));
1062 owned_nodes.emplace_back(Node::build(Point((*on[1] + *on[4]) / 2.), 10));
1063 owned_nodes.emplace_back(Node::build(Point((*on[2] + *on[4]) / 2.), 11));
1064 owned_nodes.emplace_back(Node::build(Point((*on[3] + *on[4]) / 2.), 12));
1065
1066 if (type == PYRAMID14 || type == PYRAMID18)
1067 {
1068 // Define the square face midpoint node of the pyramid
1069 owned_nodes.emplace_back(
1070 Node::build(Point((*on[0] + *on[1] + *on[2] + *on[3]) / 4.), 13));
1071
1072 if (type == PYRAMID18)
1073 {
1074 // Define the triangular face nodes
1075 owned_nodes.emplace_back(Node::build(Point((*on[0] + *on[1] + *on[4]) / 3.), 14));
1076 owned_nodes.emplace_back(Node::build(Point((*on[1] + *on[2] + *on[4]) / 3.), 15));
1077 owned_nodes.emplace_back(Node::build(Point((*on[2] + *on[3] + *on[4]) / 3.), 16));
1078 owned_nodes.emplace_back(Node::build(Point((*on[3] + *on[0] + *on[4]) / 3.), 17));
1079 }
1080 }
1081 }
1082
1083 else if (type != PYRAMID5)
1084 libmesh_error_msg("Unsupported pyramid element: " << type_str);
1085
1086 } // if Pyramid
1087
1088 // Elems deriving from Tet
1089 else if (type_str.compare(0, 3, "TET") == 0)
1090 {
1091
1092 // The ideal target element is a a regular tet with equilateral
1093 // triangles for all faces, with volume equal to the volume of the
1094 // reference element.
1095
1096 // The volume of a tet is given by v = b * h / 3, where b is the area of
1097 // the base face and h is the height of the apex node. The area of an
1098 // equilateral triangle with side length s is b = sqrt(3) s^2 / 4.
1099 // For all faces to have side length s, the height of the apex node is
1100 // h = sqrt(2/3) * s. Then the volume is v = sqrt(2) * s^3 / 12.
1101 // Solving for s, the side length that will preserve the volume of the
1102 // reference element is s = (6 * sqrt(2) * v)^(1/3), where v is the volume
1103 // of the non-optimal reference element (i.e., a right tet).
1104
1105 // Side length that preserves the volume of the reference element
1106 const auto side_length = std::cbrt(6. * sqrt_2 * ref_vol);
1107 // tet height with the property that all faces are equilateral triangles
1108 const auto target_height = sqrt_2 / sqrt_3 * side_length;
1109
1110 const auto & s = side_length;
1111 const auto & h = target_height;
1112
1113 // For regular tet
1114 // x y z node_id
1115 owned_nodes.emplace_back(Node::build(Point(0., 0., 0.), 0));
1116 owned_nodes.emplace_back(Node::build(Point(s, 0., 0.), 1));
1117 owned_nodes.emplace_back(Node::build(Point(0.5 * s, 0.5 * sqrt_3 * s, 0.), 2));
1118 owned_nodes.emplace_back(Node::build(Point(0.5 * s, sqrt_3 / 6. * s, h), 3));
1119
1120 if (type == TET10 || type == TET14)
1121 {
1122 const auto & on = owned_nodes;
1123 // Define the edge midpoint nodes of the tet
1124
1125 // Base node to base node midpoint nodes
1126 owned_nodes.emplace_back(Node::build(Point((*on[0] + *on[1]) / 2.), 4));
1127 owned_nodes.emplace_back(Node::build(Point((*on[1] + *on[2]) / 2.), 5));
1128 owned_nodes.emplace_back(Node::build(Point((*on[2] + *on[0]) / 2.), 6));
1129 // Base node to apex node midpoint nodes
1130 owned_nodes.emplace_back(Node::build(Point((*on[0] + *on[3]) / 2.), 7));
1131 owned_nodes.emplace_back(Node::build(Point((*on[1] + *on[3]) / 2.), 8));
1132 owned_nodes.emplace_back(Node::build(Point((*on[2] + *on[3]) / 2.), 9));
1133
1134 if (type == TET14)
1135 {
1136 // Define the face midpoint nodes of the tet
1137 owned_nodes.emplace_back(Node::build(Point((*on[0] + *on[1] + *on[2]) / 3.), 10));
1138 owned_nodes.emplace_back(Node::build(Point((*on[0] + *on[1] + *on[3]) / 3.), 11));
1139 owned_nodes.emplace_back(Node::build(Point((*on[1] + *on[2] + *on[3]) / 3.), 12));
1140 owned_nodes.emplace_back(Node::build(Point((*on[0] + *on[2] + *on[3]) / 3.), 13));
1141 }
1142 }
1143
1144 else if (type != TET4)
1145 libmesh_error_msg("Unsupported tet element: " << type_str);
1146
1147 } // if Tet
1148
1149 // Set the target_elem equal to the reference elem
1150 else
1151 for (const auto & node : target_elem->reference_elem()->node_ref_range())
1152 owned_nodes.emplace_back(Node::build(node, node.id()));
1153
1154 // Set nodes of target element
1155 for (const auto & node_ptr : owned_nodes)
1156 target_elem->set_node(node_ptr->id(), node_ptr.get());
1157
1158 libmesh_assert(relative_fuzzy_equals(target_elem->volume(), ref_vol, TOLERANCE));
1159
1160 return std::make_pair(std::move(target_elem), std::move(owned_nodes));
1161}
1162
1164 const Elem * const target_elem,
1165 const FEMContext & femcontext,
1166 std::vector<RealTensor> & jacobians,
1167 std::vector<Real> & jacobian_dets)
1168{
1169
1170 const auto dim = target_elem->dim();
1171
1172 const auto & qrule_points = femcontext.get_element_qrule().get_points();
1173 const auto & qrule_weights = femcontext.get_element_qrule().get_weights();
1174 const auto nq_points = femcontext.get_element_qrule().n_points();
1175
1176 // If the target element is the reference element, Jacobian matrix is
1177 // identity, det of inverse is 1. These will only be overwritten if a
1178 // different target element is explicitly specified.
1179 jacobians = std::vector<RealTensor>(nq_points, RealTensor(
1180 1., 0., 0.,
1181 0., 1., 0.,
1182 0., 0., 1.));
1183 jacobian_dets = std::vector<Real>(nq_points, 1.0);
1184
1185 // Don't use "if (*target_elem == *(target_elem->reference_elem()))" here, it
1186 // only compares global node ids, not the node locations themselves.
1187 bool target_equals_reference = true;
1188 const auto * ref_elem = target_elem->reference_elem();
1189 for (const auto local_id : make_range(target_elem->n_nodes()))
1190 target_equals_reference &= target_elem->node_ref(local_id) == ref_elem->node_ref(local_id);
1191 if (target_equals_reference)
1192 return;
1193
1194 // Create FEMap to compute target_element mapping information
1195 FEMap fe_map_target;
1196
1197 // pre-request mapping derivatives
1198 fe_map_target.get_dxyzdxi();
1199 fe_map_target.get_dxyzdeta();
1200 fe_map_target.get_dxyzdzeta();
1201
1202 // build map
1203 fe_map_target.init_reference_to_physical_map(dim, qrule_points, target_elem);
1204 fe_map_target.compute_map(dim, qrule_weights, target_elem, /*d2phi=*/false);
1205
1206 for (const auto qp : make_range(nq_points))
1207 {
1208 // We use Larisa's H notation to denote the reference-to-target jacobian
1209 RealTensor H = get_jacobian_at_qp(fe_map_target, dim, qp);
1210
1211 // The target-to-reference jacobian is the inverse of the
1212 // reference-to-target jacobian
1213 jacobians[qp] = H.inverse();
1214 jacobian_dets[qp] = jacobians[qp].det();
1215 }
1216}
1217
1218std::ostream &operator<<(std::ostream &os, const MeshQualityInfo & info)
1219{
1220 os << "Mesh quality info:" << std::endl
1221 << " Mesh distortion-dilation metric: "
1222 << info.total_combined << std::endl
1223 << " Mesh distortion metric: "
1224 << info.total_distortion << std::endl
1225 << " Mesh dilation metric: "
1226 << info.total_dilation << std::endl
1227 << " Max distortion-dilation is in elem "
1228 << info.max_elem_combined.second << ": "
1229 << info.max_elem_combined.first << std::endl
1230 << " Max distortion is in elem "
1231 << info.max_elem_distortion.second << ": "
1232 << info.max_elem_distortion.first << std::endl
1233 << " Max dilation is in elem "
1234 << info.max_elem_dilation.second << ": "
1235 << info.max_elem_dilation.first << std::endl
1236 << " Max det(S) is in elem "
1237 << info.max_elem_det_S.second << ": "
1238 << info.max_elem_det_S.first << std::endl
1239 << " Min distortion-dilation is in elem "
1240 << info.min_elem_combined.second << ": "
1241 << info.min_elem_combined.first << std::endl
1242 << " Min distortion is in elem "
1243 << info.min_elem_distortion.second << ": "
1244 << info.min_elem_distortion.first << std::endl
1245 << " Min dilation is in elem "
1246 << info.min_elem_dilation.second << ": "
1247 << info.min_elem_dilation.first << std::endl
1248 << " Min det(S) is in elem "
1249 << info.min_elem_det_S.second << ": "
1250 << info.min_elem_det_S.first << std::endl
1251 << " Max qp det(S): " << info.max_qp_det_S << std::endl
1252 << " Min qp det(S): " << info.min_qp_det_S << std::endl
1253 << " Mesh-integrated det(S): " << info.total_det_S << std::endl
1254 << " Tangled: " << info.mesh_is_tangled << std::endl;
1255
1256 return os;
1257}
1258
1259} // 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:2538
virtual unsigned int n_nodes() const =0
static std::unique_ptr< Elem > build(const ElemType type, Elem *p=nullptr)
Definition elem.C:442
virtual unsigned short dim() const =0
const Elem * reference_elem() const
Definition elem.C:568
virtual ElemType type() const =0
IntRange< unsigned short > node_index_range() const
Definition elem.h:2700
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:63
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.
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
static std::unique_ptr< Node > build(const Node &n)
Definition node.h:315
virtual void close()=0
Calls the NumericVector's internal assembly routines, ensuring that the values are consistent across ...
A Point defines a location in LIBMESH_DIM dimensional Real space.
Definition point.h:40
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
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
CompareTypes< T, T2 >::supertype contract(const TypeTensor< T2 > &) const
Multiply 2 tensors together to return a scalar, i.e.
TypeTensor< T > transpose() const
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.
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
static std::pair< std::unique_ptr< Elem >, std::vector< std::unique_ptr< Node > > > get_target_elem(const ElemType &type)
Get the target element for a given element type.
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::string enum_to_string(const T e)
The libMesh namespace provides an interface to certain functionality in the library.
bool relative_fuzzy_equals(const T &var1, const T2 &var2, const Real tol=TOLERANCE *TOLERANCE)
Function to check whether two variables are equal within a relative tolerance.
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
ElemType
Defines an enum for geometric element types.
T libmesh_real(T a)
libmesh_assert(ctx)
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
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.