libMesh
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 
38 namespace libMesh
39 {
40 
41 /*
42  * Gets the dof_id_type value corresponding to the minimum of the Real value.
43  */
44 void 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  */
55 void 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 
66 Real 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 
95 Real 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 
106 void VariationalSmootherSystem::assembly (bool get_residual,
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
134  if (_untangling_solve)
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 
312  FEMSystem::init_context(context);
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 
738 {
739  if (!_mesh_info.initialized)
741 
742  return _mesh_info;
743 }
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 
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 
891 std::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 
1218 std::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
T libmesh_real(T a)
virtual std::unique_ptr< DiffContext > build_context() override
Builds a FEMContext object with enough information to do evaluations on each element.
Definition: fem_system.C:1346
virtual void init_data() override
Initializes the member data fields associated with the system, so that, e.g., assemble() may be used...
virtual void solve() override
Solves the system to smooth the mesh.
ElemType
Defines an enum for geometric element types.
RealVectorValue RealGradient
const DenseMatrix< Number > & get_elem_jacobian() const
Const accessor for element Jacobian.
Definition: diff_context.h:274
This class provides all data required for a physics package (e.g.
Definition: diff_context.h:55
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...
Definition: fem_context.h:317
virtual void pre_fe_reinit(const System &, const Elem *e)
Reinitializes local data vectors/matrices on the current geometric element.
Definition: fem_context.C:1683
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
MPI_Info info
void minloc(T &r, unsigned int &min_id) const
static constexpr Real TOLERANCE
const Elem & get_elem() const
Accessor for current Elem object.
Definition: fem_context.h:908
unsigned int dim
virtual void init_context(DiffContext &) override
Definition: fem_system.C:1370
TypeTensor< T > transpose() const
Definition: type_tensor.h:1057
Real trace(const RealTensor &A, const unsigned int &dim)
Compute the trace of a dim-dimensional matrix.
Real _epsilon_squared_assembly
Epsilon squared value determined at runtime during each assembly.
This is the base class from which all geometric element types are derived.
Definition: elem.h:94
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.
MeshBase & mesh
Real chi_epsilon(const Real &x, const Real epsilon_squared)
Function to prevent dividing by zero for degenerate elements.
const std::vector< Real > & get_weights() const
Definition: quadrature.h:168
std::ostream & operator<<(std::ostream &os, const OrderWrapper &order)
Overload stream operators.
Definition: fe_type.h:182
NumericVector< Number > * rhs
The system matrix.
RealTensorValue RealTensor
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.
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.
const std::vector< RealGradient > & get_dxyzdzeta() const
Definition: fe_map.h:255
The libMesh namespace provides an interface to certain functionality in the library.
const MeshBase & get_mesh() const
Definition: system.h:2401
Real _dilation_weight
The relative weight to give the dilation metric.
unsigned char get_dim() const
Accessor for cached mesh dimension.
Definition: fem_context.h:936
virtual void elem_fe_reinit(const std::vector< Point > *const pts=nullptr)
Reinitializes interior FE objects on the current geometric element.
Definition: fem_context.C:1476
Struct to hold smoother-relevant information about the mesh quality.
unsigned int number() const
Definition: system.h:2393
T pow(const T &x)
Definition: utility.h:296
const Node & node_ref(const unsigned int i) const
Definition: elem.h:2535
virtual unsigned int n_nodes() const =0
const std::vector< RealGradient > & get_dxyzdxi() const
Definition: fe_map.h:239
static std::unique_ptr< Elem > build(const ElemType type, Elem *p=nullptr)
Definition: elem.C:442
const MeshQualityInfo & get_mesh_info()
Getter for the _mesh_info attribute.
std::unique_ptr< NumericVector< Number > > solution
Data structure to hold solution values.
Definition: system.h:1655
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:873
libmesh_assert(ctx)
This class provides all data required for a physics package (e.g.
Definition: fem_context.h:62
bool _untangling_solve
Flag to indicate if the current solve is to untangle or smooth.
const Elem * reference_elem() const
Definition: elem.C:568
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
unsigned int n_points() const
Definition: quadrature.h:131
virtual bool element_time_derivative(bool request_jacobian, libMesh::DiffContext &context) override
Adds the time derivative contribution on elem to elem_residual.
void maxloc(T &r, unsigned int &max_id) const
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.
unsigned int _verbosity
Verbosity setting.
void broadcast(T &data, const unsigned int root_id=0, const bool identical_sizes=false) const
void communicate_pair_min(std::pair< Real, dof_id_type > &pair, const Parallel::Communicator &comm)
virtual void close()=0
Calls the NumericVector&#39;s internal assembly routines, ensuring that the values are consistent across ...
CompareTypes< T, T2 >::supertype contract(const TypeTensor< T2 > &) const
Multiply 2 tensors together to return a scalar, i.e.
Definition: type_tensor.h:1275
std::string enum_to_string(const T e)
void init_reference_to_physical_map(const std::vector< Point > &qp, const Elem *elem)
Definition: fe_map.C:109
Real _ref_vol
The reference volume for each element.
static std::unique_ptr< Node > build(const Node &n)
Definition: node.h:315
const DenseVector< Number > & get_elem_residual() const
Const accessor for element residual.
Definition: diff_context.h:242
const Real _epsilon_squared
The small nonzero constant to prevent zero denominators (degenerate meshes only)
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
std::map< ElemType, std::vector< Real > > _target_jacobian_dets
const std::vector< Point > & get_points() const
Definition: quadrature.h:156
virtual unsigned short dim() const =0
TypeTensor< T > inverse() const
Definition: type_tensor.h:1087
static const Real b
OStreamProxy out
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:173
void communicate_pair_max(std::pair< Real, dof_id_type > &pair, const Parallel::Communicator &comm)
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
virtual void init_context(libMesh::DiffContext &context) override
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...
Definition: fem_context.h:277
virtual void solve() override
Invokes the solver associated with the system.
Definition: fem_system.C:1076
const std::vector< RealGradient > & get_dxyzdeta() const
Definition: fe_map.h:247
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.
Definition: fuzzy_equals.h:78
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:880
std::map< ElemType, std::vector< RealTensor > > _target_jacobians
Class contained in FE that encapsulates mapping (i.e.
Definition: fe_map.h:47
A Point defines a location in LIBMESH_DIM dimensional Real space.
Definition: point.h:39
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:150
This class forms the foundation from which generic finite elements may be derived.
const QBase & get_element_qrule() const
Accessor for element interior quadrature rule for the dimension of the current _elem.
Definition: fem_context.h:802
const std::set< unsigned char > & elem_dimensions() const
Definition: fem_context.h:951
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-...
This class defines a tensor in LIBMESH_DIM dimensional Real or Complex space.
MeshQualityInfo _mesh_info
Information about the mesh quality.