499 * <a name=
"ThecodeMinimalSurfaceProblemcodeclassimplementation"></a>
505 * <a name=
"MinimalSurfaceProblemMinimalSurfaceProblem"></a>
524 * <a name=
"MinimalSurfaceProblemsetup_system"></a>
539 *
dof_handler.distribute_dofs(fe);
556 *
sparsity_pattern.copy_from(
dsp);
557 *
system_matrix.
reinit(sparsity_pattern);
563 * <a name=
"Assemblingthelinearsystem"></a>
569 * <a name=
"Manualassembly"></a>
602 *
const unsigned int dofs_per_cell = fe.n_dofs_per_cell();
631 *
using CellIteratorType =
decltype(dof_handler.begin_active());
641 *
const ScratchData sample_scratch_data(fe,
646 *
const CopyData sample_copy_data(dofs_per_cell);
665 * `scratch_data`
object.
677 *
ScratchData & scratch_data,
678 *
CopyData & copy_data) {
679 *
const auto &fe_values = scratch_data.
reinit(cell);
683 *
std::vector<types::global_dof_index> &local_dof_indices =
684 *
copy_data.local_dof_indices[0];
685 *
cell->get_dof_indices(local_dof_indices);
689 *
For Newton
's method, we require the gradient of the solution at the
690 * point about which the problem is being linearized.
694 * Once we have that, we can perform assembly for this cell in
695 * the usual way. One minor difference to @ref step_15 "step-15" is that we've
701 *
fe_values.n_quadrature_points);
705 *
for (
const unsigned int q : fe_values.quadrature_point_indices())
711 *
for (
const unsigned int i : fe_values.dof_indices())
715 *
(((fe_values.shape_grad(i,
q)
717 *
* fe_values.shape_grad(
j,
q))
719 *
(fe_values.shape_grad(i,
q)
721 *
* (fe_values.shape_grad(
j,
q)
724 *
* fe_values.JxW(
q));
726 *
cell_rhs(i) -= (fe_values.shape_grad(i,
q)
729 *
* fe_values.JxW(
q));
749 *
const std::vector<types::global_dof_index> &local_dof_indices =
750 *
copy_data.local_dof_indices[0];
752 *
for (
unsigned int i = 0; i < dofs_per_cell; ++i)
754 *
for (
unsigned int j = 0;
j < dofs_per_cell; ++
j)
755 *
system_matrix.add(local_dof_indices[i],
756 *
local_dof_indices[
j],
775 *
MeshWorker::mesh_loop(dof_handler.active_cell_iterators(),
778 *
sample_scratch_data,
793 *
VectorTools::interpolate_boundary_values(dof_handler,
839 *
const unsigned int dofs_per_cell = fe.n_dofs_per_cell();
843 *
using CellIteratorType =
decltype(dof_handler.begin_active());
845 *
const ScratchData sample_scratch_data(fe,
850 *
const CopyData sample_copy_data(dofs_per_cell);
854 *
We'll define up front the AD data structures that we'll be using,
860 *
know that we'll only be linearizing the residual, which means that we
861 * only need to compute first-order derivatives. The return values from the
862 * calculations are to be of type `double`.
866 * We also need an extractor to retrieve some data related to the field
867 * solution to the problem.
870 * using ADHelper = Differentiation::AD::ResidualLinearization<
871 * Differentiation::AD::NumberTypes::sacado_dfad,
873 * using ADNumberType = typename ADHelper::ad_type;
875 * const FEValuesExtractors::Scalar u_fe(0);
879 * With this, let us define the lambda function that will be used
880 * to compute the cell contributions to the Jacobian matrix and
881 * the right hand side:
884 * const auto cell_worker = [&u_fe, this](const CellIteratorType &cell,
885 * ScratchData & scratch_data,
886 * CopyData & copy_data) {
887 * const auto & fe_values = scratch_data.reinit(cell);
888 * const unsigned int dofs_per_cell = fe_values.get_fe().n_dofs_per_cell();
890 * FullMatrix<double> & cell_matrix = copy_data.matrices[0];
891 * Vector<double> & cell_rhs = copy_data.vectors[0];
892 * std::vector<types::global_dof_index> &local_dof_indices =
893 * copy_data.local_dof_indices[0];
894 * cell->get_dof_indices(local_dof_indices);
912 * local solution coefficients
matches the number
of local residual
916 *
const unsigned
int n_independent_variables = local_dof_indices.
size();
917 *
const unsigned int n_dependent_variables = dofs_per_cell;
959 *
fe_values.n_quadrature_points);
960 *
fe_values[
u_fe].get_function_gradients_from_local_dof_values(
975 * value explicitly. After that, apart from a sign change the residual
976 * assembly looks much the same as we saw for the cell RHS vector before:
977 * We loop over all quadrature points, ensure that the coefficient now
978 * encodes its dependence on the (sensitive) finite element DoF values by
979 * using the correct `ADNumberType`, and finally we assemble the
980 * components of the residual vector. For complete clarity, the finite
981 * element shape functions (and their gradients, etc.) as well as the
982 * "JxW" values remain scalar
983 * valued, but the @p coeff and the @p old_solution_gradients at each
984 * quadrature point are computed in terms of the independent
988 * std::vector<ADNumberType> residual_ad(n_dependent_variables,
989 * ADNumberType(0.0));
990 * for (const unsigned int q : fe_values.quadrature_point_indices())
992 * const ADNumberType coeff =
993 * 1.0 / std::sqrt(1.0 + old_solution_gradients[q] *
994 * old_solution_gradients[q]);
996 * for (const unsigned int i : fe_values.dof_indices())
998 * residual_ad[i] += (fe_values.shape_grad(i, q) // \nabla \phi_i
1000 * * old_solution_gradients[q]) // * \nabla u_n
1001 * * fe_values.JxW(q); // * dx
1007 * Once we have the full cell residual vector computed, we can register
1008 * it with the helper class.
1012 * Thereafter, we compute the residual values (basically,
1013 * extracting the real values from what we already computed) and
1014 * their Jacobian (the linearization of each residual component
1015 * with respect to all cell DoFs) at the evaluation point. For
1016 * the purposes of assembly into the global linear system, we
1017 * have to respect the sign difference between the residual and
1018 * the RHS contribution: For Newton's method,
the right
hand
1028 *
ad_helper.compute_linearization(cell_matrix);
1036 *
const auto copier = [dofs_per_cell,
this](
const CopyData ©_data) {
1039 *
const std::vector<types::global_dof_index> &local_dof_indices =
1040 *
copy_data.local_dof_indices[0];
1042 *
for (
unsigned int i = 0; i < dofs_per_cell; ++i)
1044 *
for (
unsigned int j = 0;
j < dofs_per_cell; ++
j)
1045 *
system_matrix.add(local_dof_indices[i],
1046 *
local_dof_indices[
j],
1056 *
sample_scratch_data,
1077 * <a name=
"Assemblyviadifferentiationoftheenergyfunctional"></a>
1101 *
template <
int dim>
1104 *
system_matrix = 0;
1107 *
const unsigned int dofs_per_cell = fe.n_dofs_per_cell();
1111 *
using CellIteratorType =
decltype(dof_handler.begin_active());
1113 *
const ScratchData sample_scratch_data(fe,
1118 *
const CopyData sample_copy_data(dofs_per_cell);
1129 *
its gradient).
We'll then need to linearize the residual, implying that
1130 * second derivatives of the potential energy must be computed. You might
1131 * want to compare this with the definition of `ADHelper` used int
1132 * previous function, where we used
1133 * `Differentiation::AD::ResidualLinearization<Differentiation::AD::NumberTypes::sacado_dfad,double>`.
1136 * using ADHelper = Differentiation::AD::EnergyFunctional<
1137 * Differentiation::AD::NumberTypes::sacado_dfad_dfad,
1139 * using ADNumberType = typename ADHelper::ad_type;
1141 * const FEValuesExtractors::Scalar u_fe(0);
1145 * Let us then again define the lambda function that does the integration on
1150 * To initialize an instance of the helper class, we now only require
1151 * that the number of independent variables (that is, the number
1152 * of degrees of freedom associated with the element solution vector)
1153 * are known up front. This is because the second-derivative matrix that
1154 * results from an energy functional is necessarily square (and also,
1155 * incidentally, symmetric).
1158 * const auto cell_worker = [&u_fe, this](const CellIteratorType &cell,
1159 * ScratchData & scratch_data,
1160 * CopyData & copy_data) {
1161 * const auto &fe_values = scratch_data.reinit(cell);
1163 * FullMatrix<double> & cell_matrix = copy_data.matrices[0];
1164 * Vector<double> & cell_rhs = copy_data.vectors[0];
1165 * std::vector<types::global_dof_index> &local_dof_indices =
1166 * copy_data.local_dof_indices[0];
1167 * cell->get_dof_indices(local_dof_indices);
1169 * const unsigned int n_independent_variables = local_dof_indices.size();
1170 * ADHelper ad_helper(n_independent_variables);
1174 * Once more, we register all cell DoFs values with the helper, followed
1175 * by extracting the "sensitive" variant of these values that are to be
1176 * used in subsequent operations that must be differentiated -- one of
1177 * those being the calculation of the solution gradients.
1180 * ad_helper.register_dof_values(current_solution, local_dof_indices);
1182 * const std::vector<ADNumberType> &dof_values_ad =
1183 * ad_helper.get_sensitive_dof_values();
1185 * std::vector<Tensor<1, dim, ADNumberType>> old_solution_gradients(
1186 * fe_values.n_quadrature_points);
1187 * fe_values[u_fe].get_function_gradients_from_local_dof_values(
1188 * dof_values_ad, old_solution_gradients);
1192 * We next create a variable that stores the cell total energy.
1193 * Once more we emphasize that we explicitly zero-initialize this value,
1194 * thereby ensuring the integrity of the data for this starting value.
1198 * The aim for our approach is then to compute the cell total
1199 * energy, which is the sum of the internal (due to right hand
1200 * side functions, typically linear in @f$U@f$) and external
1201 * energies. In this particular case, we have no external
1202 * energies (e.g., from source terms or Neumann boundary
1212 *
for (
const unsigned int q : fe_values.quadrature_point_indices())
1222 *
After we've computed the total energy on this cell, we'll
1235 *
ad_helper.compute_linearization(cell_matrix);
1244 *
const auto copier = [dofs_per_cell,
this](
const CopyData ©_data) {
1247 *
const std::vector<types::global_dof_index> &local_dof_indices =
1248 *
copy_data.local_dof_indices[0];
1250 *
for (
unsigned int i = 0; i < dofs_per_cell; ++i)
1252 *
for (
unsigned int j = 0;
j < dofs_per_cell; ++
j)
1253 *
system_matrix.add(local_dof_indices[i],
1254 *
local_dof_indices[
j],
1264 *
sample_scratch_data,
1286 * <a name=
"MinimalSurfaceProblemsolve"></a>
1287 * <
h4>MinimalSurfaceProblem::solve</
h4>
1294 *
template <
int dim>
1302 *
preconditioner.initialize(system_matrix, 1.2);
1316 * <a name=
"MinimalSurfaceProblemrefine_mesh"></a>
1325 *
template <
int dim>
1347 *
dof_handler.distribute_dofs(fe);
1368 * <a name=
"MinimalSurfaceProblemset_boundary_values"></a>
1376 *
template <
int dim>
1394 * <a name=
"MinimalSurfaceProblemcompute_residual"></a>
1395 * <
h4>MinimalSurfaceProblem::compute_residual</
h4>
1403 * @
ref step_15
"step-15".
1406 *
template <
int dim>
1420 *
const unsigned int dofs_per_cell = fe.n_dofs_per_cell();
1424 *
std::vector<Tensor<1, dim>>
gradients(n_q_points);
1426 *
std::vector<types::global_dof_index> local_dof_indices(dofs_per_cell);
1428 *
for (
const auto &cell : dof_handler.active_cell_iterators())
1431 *
fe_values.
reinit(cell);
1435 *
for (
unsigned int q = 0;
q < n_q_points; ++
q)
1437 *
const double coeff =
1438 *
1.0 /
std::sqrt(1.0 + gradients[
q] * gradients[
q]);
1440 *
for (
unsigned int i = 0; i < dofs_per_cell; ++i)
1444 *
* fe_values.JxW(
q));
1447 *
cell->get_dof_indices(local_dof_indices);
1448 *
for (
unsigned int i = 0; i < dofs_per_cell; ++i)
1458 *
return residual.l2_norm();
1466 * <a name=
"MinimalSurfaceProblemdetermine_step_length"></a>
1473 * @
ref step_15
"step-15".
1476 *
template <
int dim>
1487 * <a name=
"MinimalSurfaceProblemoutput_results"></a>
1503 *
data_out.attach_dof_handler(dof_handler);
1506 *
data_out.build_patches();
1511 *
data_out.write_vtu(output);
1518 * <a name=
"MinimalSurfaceProblemrun"></a>
1519 * <
h4>MinimalSurfaceProblem::run</
h4>
1529 * the time taken to assemble for each of the three methods, we've also
1536 *
template <
int dim>
1538 *
const double tolerance)
1540 *
std::cout <<
"******** Assembly approach ********" << std::endl;
1542 *
{
"Unassisted implementation (full hand linearization).",
1543 *
"Automated linearization of the finite element residual.",
1544 *
"Automated computation of finite element residual and linearization using a variational formulation."}};
1566 *
std::cout <<
" Initial residual: " << compute_residual(0) << std::endl;
1592 *
std::cout <<
" Residual: " << compute_residual(0) << std::endl;
1598 *
std::cout << std::endl;
1607 * <a name=
"Themainfunction"></a>
1626 *
using namespace Step72;
1641 *
parameters.tolerance);
1643 *
catch (std::exception &exc)
1645 *
std::cerr << std::endl
1647 *
<<
"----------------------------------------------------"
1649 *
std::cerr <<
"Exception on processing: " << std::endl
1650 *
<< exc.what() << std::endl
1651 *
<<
"Aborting!" << std::endl
1652 *
<<
"----------------------------------------------------"
1659 *
std::cerr << std::endl
1661 *
<<
"----------------------------------------------------"
1663 *
std::cerr <<
"Unknown exception!" << std::endl
1664 *
<<
"Aborting!" << std::endl
1665 *
<<
"----------------------------------------------------"
1707relative time between the different implementations):
1709******** Assembly approach ********
1710Unassisted implementation (full hand linearization).
1714+---------------------------------------------+------------+------------+
1715| Total wallclock time elapsed since start | 35.1s | |
1717| Section | no. calls | wall time | % of total |
1718+---------------------------------+-----------+------------+------------+
1719| Assemble | 50 | 1.56s | 4.5% |
1720| Solve | 50 | 30.8s | 88% |
1721+---------------------------------+-----------+------------+------------+
1723And for the implementation that linearizes the residual in an automated
1724manner using the Sacado dynamic forward AD number type:
1726******** Assembly approach ********
1727Automated linearization of the finite element residual.
1731+---------------------------------------------+------------+------------+
1732| Total wallclock time elapsed since start | 40.1s | |
1734| Section | no. calls | wall time | % of total |
1735+---------------------------------+-----------+------------+------------+
1736| Assemble | 50 | 8.8s | 22% |
1737| Solve | 50 | 28.6s | 71% |
1738+---------------------------------+-----------+------------+------------+
1740And, lastly, for the implementation that computes both the residual and
1741its linearization directly from an energy functional (using nested Sacado
1742dynamic forward AD numbers):
1744******** Assembly approach ********
1745Automated computation of finite element residual and linearization using a variational formulation.
1749+---------------------------------------------+------------+------------+
1750| Total wallclock time elapsed since start | 48.8s | |
1752| Section | no. calls | wall time | % of total |
1753+---------------------------------+-----------+------------+------------+
1754| Assemble | 50 | 16.7s | 34% |
1755| Solve | 50 | 29.3s | 60% |
1756+---------------------------------+-----------+------------+------------+
1814 each cell quadrature point).
1826<a name=
"PlainProg"></a>
value_type * data() const noexcept
void reinit(value_type *starting_element, const std::size_t n_elements)
const unsigned int dofs_per_cell
static void estimate(const Mapping< dim, spacedim > &mapping, const DoFHandler< dim, spacedim > &dof, const Quadrature< dim - 1 > &quadrature, const std::map< types::boundary_id, const Function< spacedim, typename InputVector::value_type > * > &neumann_bc, const InputVector &solution, Vector< float > &error, const ComponentMask &component_mask=ComponentMask(), const Function< spacedim > *coefficients=nullptr, const unsigned int n_threads=numbers::invalid_unsigned_int, const types::subdomain_id subdomain_id=numbers::invalid_subdomain_id, const types::material_id material_id=numbers::invalid_material_id, const Strategy strategy=cell_diameter_over_24)
static void initialize(const std::string &filename="", const std::string &output_filename="", const ParameterHandler::OutputStyle output_style_for_output_filename=ParameterHandler::Short, ParameterHandler &prm=ParameterAcceptor::prm, const ParameterHandler::OutputStyle output_style_for_filename=ParameterHandler::DefaultStyle)
__global__ void reduction(Number *result, const Number *v, const size_type N)
__global__ void set(Number *val, const Number s, const size_type N)
static ::ExceptionBase & ExcNotImplemented()
#define AssertIndexRange(index, range)
#define AssertThrow(cond, exc)
void mesh_loop(const CellIteratorType &begin, const CellIteratorType &end, const CellWorkerFunctionType &cell_worker, const CopierType &copier, const ScratchData &sample_scratch_data, const CopyData &sample_copy_data, const AssembleFlags flags=assemble_own_cells, const BoundaryWorkerFunctionType &boundary_worker=BoundaryWorkerFunctionType(), const FaceWorkerFunctionType &face_worker=FaceWorkerFunctionType(), const unsigned int queue_length=2 *MultithreadInfo::n_threads(), const unsigned int chunk_size=8)
void make_hanging_node_constraints(const DoFHandler< dim, spacedim > &dof_handler, AffineConstraints< number > &constraints)
void make_sparsity_pattern(const DoFHandler< dim, spacedim > &dof_handler, SparsityPatternBase &sparsity_pattern, const AffineConstraints< number > &constraints=AffineConstraints< number >(), const bool keep_constrained_dofs=true, const types::subdomain_id subdomain_id=numbers::invalid_subdomain_id)
@ update_JxW_values
Transformed quadrature weights.
@ update_gradients
Shape function gradients.
@ update_quadrature_points
Transformed quadrature points.
CGAL::Exact_predicates_exact_constructions_kernel_with_sqrt K
void hyper_ball(Triangulation< dim > &tria, const Point< dim > ¢er=Point< dim >(), const double radius=1., const bool attach_spherical_manifold_on_boundary_cells=false)
void refine_and_coarsen_fixed_number(Triangulation< dim, spacedim > &triangulation, const Vector< Number > &criteria, const double top_fraction_of_cells, const double bottom_fraction_of_cells, const unsigned int max_n_cells=std::numeric_limits< unsigned int >::max())
@ matrix
Contents is actually a matrix.
void cell_matrix(FullMatrix< double > &M, const FEValuesBase< dim > &fe, const FEValuesBase< dim > &fetest, const ArrayView< const std::vector< double > > &velocity, const double factor=1.)
void cell_residual(Vector< double > &result, const FEValuesBase< dim > &fe, const std::vector< Tensor< 1, dim > > &input, const ArrayView< const std::vector< double > > &velocity, double factor=1.)
Point< spacedim > point(const gp_Pnt &p, const double tolerance=1e-10)
SymmetricTensor< 2, dim, Number > e(const Tensor< 2, dim, Number > &F)
SymmetricTensor< 2, dim, Number > b(const Tensor< 2, dim, Number > &F)
Tensor< 2, dim, Number > F(const Tensor< 2, dim, Number > &Grad_u)
constexpr const ReferenceCell Line
constexpr ReturnType< rank, T >::value_type & extract(T &t, const ArrayType &indices)
void call(const std::function< RT()> &function, internal::return_value< RT > &ret_val)
std::string int_to_string(const unsigned int value, const unsigned int digits=numbers::invalid_unsigned_int)
void run(const Iterator &begin, const std_cxx20::type_identity_t< Iterator > &end, Worker worker, Copier copier, const ScratchData &sample_scratch_data, const CopyData &sample_copy_data, const unsigned int queue_length, const unsigned int chunk_size)
void copy(const T *begin, const T *end, U *dest)
int(&) functions(const void *v1, const void *v2)
static constexpr double PI
::VectorizedArray< Number, width > sin(const ::VectorizedArray< Number, width > &)
::VectorizedArray< Number, width > sqrt(const ::VectorizedArray< Number, width > &)
const ::parallel::distributed::Triangulation< dim, spacedim > * triangulation