619 *
if ((center - p).
norm() <= radius)
629 * <a name=
"step_93-Theprincipalclass"></a>
630 * <h3>The principal
class</h3>
634 * The main
class is very similar to @ref step_4
"step-4" in structure, given that this is
635 * a relatively simple program that does not use adaptive mesh refinement.
636 * However, there are three
new member variables:
640 * - `nonlocal_dofs`:
A `std::vector` of dof indices that stores the
641 * dof
index for the nonlocal dofs.
645 * - `heat_functions`:
A `std::vector` of CircularIndicatorFunction objects;
646 * these are the heat sources.
650 * - `target_function`: This is the function we want to match. We store it
651 * as a
class variable because it is used both in assemble_system() and
665 *
void setup_system();
666 *
void assemble_system();
668 *
void output_results()
const;
684 * std::vector<types::global_dof_index> nonlocal_dofs;
686 * std::vector<CircularIndicatorFunction<dim>> heat_functions;
688 *
const TargetFunction<dim> target_function;
695 * <a name=
"step_93-TheStep93constructor"></a>
696 * <h4>The Step93 constructor</h4>
697 * In
this constructor, we set up several of the fundamental data
698 * structures
this program needs. Specifically, we generate the
699 * finite element collection, which is basically a list of all the
700 * possible finite elements we could use on each cell. This
701 * collection has two elements:
one FESystem that has two degree 2
703 * that are not
"special"), and
one FESystem that has two degree 2
704 *
FE_Q elements and
one degree 0
FE_DGQ element (to be used on
705 * those
"special" cells we use to anchor the non-local degrees of
706 * freedom, as discussed in the introduction).
710 * Where we have a collection of elements, we then also need a
711 * collection of quadratures -- which here has only a single element
712 * because we can use the same quadrature on all cells.
716 * The
default constructor also initializes
717 * dof_handler and target_function,
718 * and it generates a vector of CircularIndicatorFunctions objects which
723 * Step93<dim>::Step93()
724 * : dof_handler(triangulation)
725 * , target_function(3,
732 * Here, we generate the finite element collection, which is basically a
733 * list of all the possible finite elements we could use on each cell. This
739 * fe_collection.push_back(
745 * The quadrature collection is just
one degree 3
QGauss element.
752 * Here, we choose the center points
for the heat
functions to be the
753 * vertices of a lattice, in
this case the corners of a hypercube
754 * centered at 0, with side length 1. The block of code below
755 * then creates the CircularIndicatorFunction
756 * objects
for the main
class. To make these, we
check the
dimension of the
757 * problem and create 2, 4, or 8 function objects.
763 * heat_functions.emplace_back(
Point<dim>({0.5}), 0.2);
764 * heat_functions.emplace_back(
Point<dim>({-0.5}), 0.2);
767 * heat_functions.emplace_back(
Point<dim>({0.5, 0.5}), 0.2);
768 * heat_functions.emplace_back(
Point<dim>({0.5, -0.5}), 0.2);
769 * heat_functions.emplace_back(
Point<dim>({-0.5, 0.5}), 0.2);
770 * heat_functions.emplace_back(
Point<dim>({-0.5, -0.5}), 0.2);
773 * heat_functions.emplace_back(
Point<dim>({0.5, 0.5, 0.5}), 0.2);
774 * heat_functions.emplace_back(
Point<dim>({0.5, 0.5, -0.5}), 0.2);
775 * heat_functions.emplace_back(
Point<dim>({0.5, -0.5, 0.5}), 0.2);
776 * heat_functions.emplace_back(
Point<dim>({0.5, -0.5, -0.5}), 0.2);
777 * heat_functions.emplace_back(
Point<dim>({-0.5, 0.5, 0.5}), 0.2);
778 * heat_functions.emplace_back(
Point<dim>({-0.5, 0.5, -0.5}), 0.2);
779 * heat_functions.emplace_back(
Point<dim>({-0.5, -0.5, 0.5}), 0.2);
780 * heat_functions.emplace_back(
Point<dim>({-0.5, -0.5, -0.5}), 0.2);
791 * <a name=
"step_93-Step93make_grid"></a>
792 * <h4>Step93::make_grid()</h4>
796 * The make_grid() function makes a hypercube grid, see @ref step_4
"step-4":
800 *
void Step93<dim>::make_grid()
803 * triangulation.refine_global(7);
805 * std::cout <<
"Number of active cells: " << triangulation.n_active_cells()
813 * <a name=
"step_93-Step93setup_system"></a>
814 * <h4>Step93::setup_system()</h4>
818 * The `setup_system()` function is similar to @ref step_4
"step-4", except we have to add a
819 * few steps to prepare
for the nonlocal dofs.
823 *
void Step93<dim>::setup_system()
828 * corresponds to the system with 2
FE_Q elements and
one FE_DGQ element. We
829 *
do this until we have enough dofs
for each heat function. Note that we
830 * use the global active cell
index to measure when to stop the
833 * degrees of freedom.
836 * for (const auto &cell : dof_handler.active_cell_iterators())
838 *
if (cell->global_active_cell_index() < heat_functions.size())
840 * cell->set_active_fe_index(1);
847 * dof_handler.distribute_dofs(fe_collection);
851 * Once we
've assigned dofs, the code block below counts the
852 * number of dofs in the system, and outputs to the console. In
853 * other contexts, we might want to use *block* matrices (see, for
854 * example, @ref step_20 "step-20" or @ref step_22 "step-22") to build more efficient linear
855 * solvers; here, we will just put everything into one big matrix
856 * and so knowing the number of unknowns for each of the variables
857 * is purely for informational purposes.
860 * const std::vector<types::global_dof_index> dofs_per_component =
861 * DoFTools::count_dofs_per_fe_component(dof_handler);
862 * const unsigned int n_dofs_u = dofs_per_component[0],
863 * n_dofs_l = dofs_per_component[1],
864 * n_dofs_c = dofs_per_component[2];
865 * std::cout << "Number of degrees of freedom: " << n_dofs_u << "+" << n_dofs_l
866 * << "+" << n_dofs_c << " = " << n_dofs_u + n_dofs_l + n_dofs_c
871 * Finally, we need to extract the indices of the finite elements which
872 * correspond to the non-local dofs.
876 * First, we make a component mask, which is `false` except for
877 * the third component. This will extract only the dofs from the
878 * third component of the FE system. Next, we actually extract the
879 * dofs, and store them in an IndexSet variable. Finally, we add
880 * each extracted index to the member array `nonlocal_dofs`.
883 * const ComponentMask component_mask_c({false, false, true});
884 * const IndexSet indices_c =
885 * DoFTools::extract_dofs(dof_handler, component_mask_c);
887 * for (const types::global_dof_index non_local_index : indices_c)
888 * nonlocal_dofs.push_back(non_local_index);
890 * std::cout << "Number of nonlocal dofs: " << nonlocal_dofs.size()
896 * The mesh we created above is not locally refined, and so there
897 * are no hanging node constraints to keep track of. But it does
898 * not hurt to just use the same setup we have used starting in
899 * @ref step_6 "step-6" of building a constraints object that contains hanging
900 * node constraints (anticipating that perhaps we'd want to
do
901 * adaptive mesh refinement in a later step) into which we then
902 * also put the constraints
for boundary values on both the @f$u@f$
903 * and @f$\lambda@f$ variables.
907 * Because the nonlocal degrees of freedom use discontinuous
908 * elements, they
do not contribute to boundary values
909 * (discontinuous elements
do not have degrees of freedom
910 * logically located on the boundary that could be interpolated)
911 * and we
do not need to exclude these solution components
912 * explicitly when calling
916 * constraints.clear();
922 * constraints.close();
926 * The remainder of the function deals with building the sparsity
927 * pattern. It consists of two parts: The entries that result from
928 * the usual integration of products of shape
functions, and then
929 * the entries that result from integrals that contain nonlocal
930 * degrees of freedom (which one can think of as associated with
931 * shape functions that are constant across the entire
932 * domain). The first part is easily built
using standard tools:
940 * The other part is more awkward. We have
matrix entries that
941 * result from terms such as @f$\int_{\Omega}\varphi_j f_k@f$ where
942 * each @f$\varphi_j@f$ is a shape function associated to @f$\lambda@f$
943 * and each @f$f_k@f$ is a
944 * characteristic function of a part of the domain. As a
946 * (along with its
transpose @f$A_{kj}@f$)
if there is overlap between
947 * @f$\varphi_j@f$ and @f$f_k@f$. In practice, because we will use
948 * quadrature,
this means that we end up with a quadrature point
949 * on a cell on which @f$\varphi_j@f$ lives and at which @f$f_k@f$ is not
950 *
zero. (We will implicitly assume that a shape function that
951 * lives on the current cell is nonzero at all quadrature points
952 * -- an assumption that is generally
true unless one chooses
953 * specific quadrature formulas.) Determining which sparsity
954 * pattern entries we need to add then essentially comes down to
955 *
"simulating" what would happen
if we actually computed
956 * integrals and which
matrix entries would
end up being non-
zero
957 * in the process. As a consequence, the following code
's general
958 * structure looks very similar to what we will do for the
959 * nonlocal contributions in the `assemble_system()` function
964 * To get this started, we create an `hp_fe_values` that we
965 * will only use to query the quadrature point locations. The
966 * non-local dofs will need to interact with the second component
967 * of the fe system (namely, @f$\lambda@f$), so we also declare a
968 * variable that will help us extract this scalar field for use
972 * hp::FEValues<dim> hp_fe_values(fe_collection,
973 * quadrature_collection,
974 * update_quadrature_points);
978 * Then, we loop over the cells, then over the quadrature points, and
979 * finally over the indices, as if we were constructing a mass matrix.
980 * However, what we instead do here is check two things. First, we check if
981 * the quadrature point is within the radius of a circular indicator
982 * function that represents our non-local dof. If so
983 * then we add an entry to the sparse matrix at the
984 * (nonlocal dof index, lambda dof index) entry and the (lambda dof index,
985 * nonlocal dof index) entry for all lambda degrees of freedom. (Because the
986 * matrix we solve with has both the lambda-nonlocal interacting block and
987 * its transpose, we need to add two entries each time.)
990 * std::vector<types::global_dof_index> local_dof_indices;
991 * for (const auto &cell : dof_handler.active_cell_iterators())
993 * hp_fe_values.reinit(cell);
995 * const FEValues<dim> &fe_values = hp_fe_values.get_present_fe_values();
997 * local_dof_indices.resize(fe_values.dofs_per_cell);
998 * cell->get_dof_indices(local_dof_indices);
1000 * for (const unsigned int q_index : fe_values.quadrature_point_indices())
1002 * const Point<dim> q_point = fe_values.quadrature_point(q_index);
1003 * for (const unsigned int i : fe_values.dof_indices())
1005 * if (fe_values.get_fe().system_to_component_index(i).first ==
1006 * 1) // 'i
' is a lambda shape function
1008 * for (unsigned int j = 0; j < heat_functions.size(); ++j)
1009 * if (heat_functions[j].value(q_point) != 0)
1011 * dsp.add(local_dof_indices[i], nonlocal_dofs[j]);
1012 * dsp.add(nonlocal_dofs[j], local_dof_indices[i]);
1021 * The rest (below) is standard setup code, see @ref step_4 "step-4":
1024 * sparsity_pattern.copy_from(dsp);
1026 * system_matrix.reinit(sparsity_pattern);
1028 * solution.reinit(dof_handler.n_dofs());
1029 * system_rhs.reinit(dof_handler.n_dofs());
1036 * <a name="step_93-Step93assemble_system"></a>
1037 * <h4>Step93::assemble_system()</h4>
1041 * The `assemble_system()` function works very similar to how is
1042 * does in other tutorial programs (cf. @ref step_4 "step-4", @ref step_6 "step-6", @ref step_8 "step-8", and
1043 * for the vector-valued case see @ref step_22 "step-22"). However, there is an
1044 * additional component to constructing the system matrix, because
1045 * we need to handle the nonlocal dofs manually.
1048 * template <int dim>
1049 * void Step93<dim>::assemble_system()
1053 * First, we do a standard loop setup for constructing the system matrix.
1056 * hp::FEValues<dim> hp_fe_values(fe_collection,
1057 * quadrature_collection,
1058 * update_values | update_gradients |
1059 * update_quadrature_points |
1060 * update_JxW_values);
1062 * FullMatrix<double> cell_matrix;
1063 * Vector<double> cell_rhs;
1065 * std::vector<types::global_dof_index> local_dof_indices;
1067 * const FEValuesExtractors::Scalar u(0);
1068 * const FEValuesExtractors::Scalar lambda(1);
1070 * for (const auto &cell : dof_handler.active_cell_iterators())
1072 * const unsigned int dofs_per_cell = cell->get_fe().n_dofs_per_cell();
1074 * cell_matrix.reinit(dofs_per_cell, dofs_per_cell);
1075 * cell_rhs.reinit(dofs_per_cell);
1076 * hp_fe_values.reinit(cell);
1081 * const FEValues<dim> &fe_values = hp_fe_values.get_present_fe_values();
1083 * local_dof_indices.resize(fe_values.dofs_per_cell);
1085 * cell->get_dof_indices(local_dof_indices);
1090 * In the loop over quadrature points, we start by building
1091 * all of the usual terms that are bilinear in shape functions
1092 * corresponding to the @f$u@f$ and @f$\lambda@f$ variables:
1095 * for (const unsigned int q_index : fe_values.quadrature_point_indices())
1097 * const double JxW = fe_values.JxW(q_index);
1098 * for (const unsigned int i : fe_values.dof_indices())
1100 * const double phi_i_u = fe_values[u].value(i, q_index),
1101 * phi_i_l = fe_values[lambda].value(i, q_index);
1103 * const Tensor<1, dim> grad_i_u =
1104 * fe_values[u].gradient(i, q_index),
1106 * fe_values[lambda].gradient(i, q_index);
1108 * for (const unsigned int j : fe_values.dof_indices())
1110 * const double phi_j_u = fe_values[u].value(j, q_index);
1112 * const Tensor<1, dim> grad_j_u =
1113 * fe_values[u].gradient(j, q_index),
1115 * fe_values[lambda].gradient(j,
1118 * cell_matrix(i, j) += phi_i_u * phi_j_u * JxW;
1119 * cell_matrix(i, j) += -grad_i_u * grad_j_l * JxW;
1120 * cell_matrix(i, j) += -grad_i_l * grad_j_u * JxW;
1123 * const Point<dim> q_point = fe_values.quadrature_point(q_index);
1124 * cell_rhs(i) += (phi_i_u * target_function.value(q_point) * JxW);
1129 * For the integrals that involve the nonlocal dofs,
1130 * we make use of the quadrature point again. To
1131 * compute the integrals, we loop over the
1132 * heat functions, adding the numeric integral of each
1133 * heat equation with each @f$\lambda@f$ shape function,
1134 * at the appropriate indices (which we found in
1135 * `setup_system()`). Note that if we try to add 0 to
1136 * a matrix entry we have not previously indicated should
1137 * be nonzero, there will not be a problem; but if we
1138 * try to add a nonzero value to an entry not
1139 * previously added to the sparsity pattern, we will
1140 * get an error. In other words, the following lines
1141 * of the code check that we adjusted the sparsity
1142 * pattern correctly in the previous function.
1145 * for (unsigned int j = 0; j < heat_functions.size(); ++j)
1147 * system_matrix.add(local_dof_indices[i],
1149 * heat_functions[j].value(q_point) *
1151 * system_matrix.add(nonlocal_dofs[j],
1152 * local_dof_indices[i],
1153 * heat_functions[j].value(q_point) *
1161 * Finally, we copy the local contributions to the linear
1162 * system into the global matrix and right hand side vector,
1163 * taking into account hanging node and boundary values
1167 * constraints.distribute_local_to_global(
1168 * cell_matrix, cell_rhs, local_dof_indices, system_matrix, system_rhs);
1176 * <a name="step_93-Step93solve"></a>
1177 * <h4>Step93::solve()</h4>
1181 * The solve() function works similar to how it is done in @ref step_6 "step-6"
1182 * and @ref step_8 "step-8", except we need to use a different solver because the
1183 * linear problem we are trying to solve is a saddle point problem
1184 * for which the Conjugate Gradient algorithm is not
1185 * applicable. But, because the matrix is symmetric, we can use
1186 * SolverMinRes, an iterative solver specialized for symmetric
1187 * indefinite problems. This solver could be improved with the use
1188 * of preconditioners, but we don't
do that here
for simplicity (see
1189 * the Possibilities
for Extensions section below).
1193 * As you will see in the output, given that we are not
using a
1194 * preconditioner, we need a *lot* of iterations to solve
this
1195 * linear system. We set the maximum to
one million, more than we
1196 * need of course, but an indication that
this is not an efficient
1197 * solver. For smaller problems,
one can also use a direct solver
1198 * (see @ref step_29
"step-29")
for which you would just replace the main part of
1199 *
this function by the following three lines of code:
1200 * <div class=CodeFragmentInTutorialComment>
1204 * direct_solver.vmult(solution, system_rhs);
1209 *
template <
int dim>
1210 *
void Step93<dim>::solve()
1215 * std::cout <<
"Beginning solve..." << std::endl;
1217 *
SolverControl solver_control(1'000'000, 1e-6 * system_rhs.l2_norm());
1224 * std::cout <<
"Wall time: " << timer.wall_time() <<
"s" << std::endl;
1225 * std::cout <<
"Solved in " << solver_control.last_step()
1226 * <<
" MINRES iterations." << std::endl;
1233 * <a name=
"step_93-Step93output_results"></a>
1234 * <h4>Step93::output_results()</h4>
1238 * The `output_results()` function is a bit more robust
for this program than
1239 * is typical. This is because, in order to visualize the heat sources we have
1240 * optimized, we need to
do extra work and
interpolate them onto a mesh. We
do
1241 *
this by instantiating a
new DoFHandler object and then
using the helper
1246 * The top of the function is as
always when
using vector-valued
1247 * elements (see,
for example, @ref step_22
"step-22") and simply outputs all of
1248 * the solution variables on the mesh cells they are defined on:
1251 *
template <
int dim>
1252 *
void Step93<dim>::output_results() const
1254 *
const std::vector<std::string> solution_names = {
"u",
"lambda",
"c"};
1256 *
const std::vector<DataComponentInterpretation::DataComponentInterpretation>
1269 * The non-local degrees of freedom are of course defined on the
1270 * first several cells, but at least logically are considered to
1271 * live everywhere (or,
if you prefer, nowhere at all since they
1272 *
do not represent spatial functions). But, conceptually, we use
1273 * them as multipliers
for the heat sources, and so
while the
1274 * coefficients @f$C^k@f$ are non-local, the heat source @f$\sum_k
C^k
1275 * f_k(\mathbf x)@f$ *is* a spatially variable function. It would be
1276 * nice
if we could visualize that as well. The same is
true for
1277 * the target heat distribution @f$\bar u@f$ we are trying to match.
1282 * create a
new dof handler to output the target function
1283 * and heat plate values, and associate it with
1284 * a finite element with a degree that matches what we used to solve
for @f$u@f$
1285 * and @f$\lambda@f$, although in reality
this is an arbitrary choice:
1291 * new_dof_handler.distribute_dofs(new_fe);
1295 * To get started with the visualization, we need a vector which
1296 * stores the interpolated target function. We create the vector,
1297 *
interpolate the target function @f$\bar u@f$ onto the mesh, then
1298 * add the data to our data_out
object.
1305 *
return target_function.value(x, 0);
1308 * data_out.add_data_vector(new_dof_handler, target,
"u_bar");
1312 * In order to visualize the
sum of the heat sources @f$\sum_k
C^k
1313 * f_k(\mathbf x)@f$, we create a vector which will store the
1314 * interpolated values of
this function. Then, we
loop through
1315 * the heat
functions, create a vector to store the interpolated
1317 * vector, multiply the interpolated data by the nonlocal dof
1318 *
value @f$C^k@f$ (so that the heat plate is set to the correct
1319 * temperature), and then add this data to the sum of heat
1320 * sources. Because we can, we also add the vector for each source
1321 * individually to the
DataOut object, so that they can be
1322 * visualized individually.
1325 *
Vector<
double> full_heat_profile(new_dof_handler.n_dofs());
1327 * for (
unsigned int i = 0; i < heat_functions.size(); ++i)
1332 * heat_functions[i],
1335 * hot_plate_i *= solution[nonlocal_dofs[i]];
1336 * full_heat_profile += hot_plate_i;
1338 *
const std::string data_name =
1340 * data_out.add_data_vector(new_dof_handler, hot_plate_i, data_name);
1345 * Once all the heat
functions have been combined, we add them to the
1346 * data_out object, and output everything into a file :
1349 * data_out.add_data_vector(new_dof_handler,
1350 * full_heat_profile,
1351 *
"Full_Heat_Profile");
1353 * data_out.build_patches();
1355 * std::ofstream output(
"solution.vtu");
1356 * data_out.write_vtu(output);
1360 * Finally, we output the nonlocal coefficient values to the console:
1363 * std::cout <<
"The c coefficients are " << std::endl;
1364 *
for (
long unsigned int i = 0; i < nonlocal_dofs.size(); ++i)
1366 * std::cout <<
"\tc" << i + 1 <<
": " << solution[nonlocal_dofs[i]]
1375 * <a name=
"step_93-Step93run"></a>
1376 * <h4>Step93::run()</h4>
1380 * The
run() function runs through each step of the program, nothing new here:
1383 * template <
int dim>
1384 *
void Step93<dim>::run()
1388 * assemble_system();
1398 * <a name=
"step_93-Themainfunction"></a>
1399 * <h3>The main() function</h3>
1403 * The `main()` function looks essentially like that of most other tutorial
1411 * Step93::Step93<2> heat_optimization_problem;
1412 * heat_optimization_problem.run();
1414 *
catch (std::exception &exc)
1416 * std::cerr << std::endl
1418 * <<
"----------------------------------------------------"
1420 * std::cerr <<
"Exception on processing: " << std::endl
1421 * << exc.what() << std::endl
1422 * <<
"Aborting!" << std::endl
1423 * <<
"----------------------------------------------------"
1430 * std::cerr << std::endl
1432 * <<
"----------------------------------------------------"
1434 * std::cerr <<
"Unknown exception!" << std::endl
1435 * <<
"Aborting!" << std::endl
1436 * <<
"----------------------------------------------------"
1444<a name=
"step_93-Results"></a><h1>Results</h1>
1447When you
run the program with the step target function (in 2D), the output looks something like
this:
1450Number of active cells: 16384
1451Number of degrees of freedom: 66049+66049+4 = 132102
1452Number of nonlocal dofs: 4
1455Solved in 39973 MINRES iterations.
1456The c coefficients are
1463When you
run the program with the Gaussian target function (in 2D), the output should look like
this:
1466Number of active cells: 16384
1467Number of degrees of freedom: 66049+66049+4 = 132102
1468Number of nonlocal dofs: 4
1471Solved in 62131 MINRES iterations.
1472The c coefficients are
1479The goal of
this program is to determine which temperature settings best match the target function, so first
1480let
's see what these targets look like:
1482<table align="center" class="doxtable">
1485 <center><b>Step Target %Function</b></center>
1488 <center><b>Gaussian Target %Function</b></center>
1493 <img src="https://www.dealii.org/images/steps/developer/step-93.target_step.png"
1494 alt="Step target function"
1498 <img src="https://www.dealii.org/images/steps/developer/step-93.target_gauss.png"
1499 alt="Gaussian target function"
1505After solving the Lagrangian system, we arrive at solutions @f$U_\text{step}@f$ and @f$U_\text{gauss}@f$ that
1508<table align="center" class="doxtable">
1511 <center><b>@f$U_\text{step}@f$</b></center>
1514 <center><b>@f$U_\text{gauss}@f$</b></center>
1519 <img src="https://www.dealii.org/images/steps/developer/step-93.U_step.png"
1520 alt="Solution for step shaped target function"
1524 <img src="https://www.dealii.org/images/steps/developer/step-93.U_gauss.png"
1525 alt="Solution for Gaussian target function"
1531Notice that @f$U_\text{gauss}@f$ matches the target much better than
1532@f$U_\text{step}@f$. Intuitively, this makes sense: in general, solutions
1533to the heat equation look something like Gaussians, so the
1534Gaussian target function is a much more "natural" thing to match than
1535a sharp step function. We can also see this in the optimal heat
1538<table align="center" class="doxtable">
1541 <center><b>Heat plate settings for matching step function</b></center>
1544 <center><b>Heat plate settings for matching Gaussian</b></center>
1549 <img src="https://www.dealii.org/images/steps/developer/step-93.heat_profile_step.png"
1550 alt="Heat plate settings for matching step function"
1554 <img src="https://www.dealii.org/images/steps/developer/step-93.heat_profile_gauss.png"
1555 alt="Heat plate settings for matching Gaussian"
1561Notice that for the Gaussian target, the 4 plates are set to less extreme values. In contrast, to try to match the step function, higher and lower temperatures must be applied.
1563While it does not contain much useful information, we can also plot the Lagrange multiplier @f$\Lambda@f$, which has an interesting shape:
1565<table align="center" class="doxtable">
1568 <center><b>@f$\Lambda_\text{step}@f$</b></center>
1571 <center><b>@f$\Lambda_\text{gauss}@f$</b></center>
1576 <img src="https://www.dealii.org/images/steps/developer/step-93.L_step.png"
1577 alt="Lagrange multiplier for step target function"
1581 <img src="https://www.dealii.org/images/steps/developer/step-93.L_gauss.png"
1582 alt="Lagrange multiplier for Gaussian target function"
1590<a name="step_93-Possibilitiesforextensions"></a><h3>Possibilities for extensions</h3>
1593There are a few ways that this program could be extended, which we list below.
15951. As mentioned in the code documentation, this program does not make
1596use of any preconditioners before solving. This is because, for a 2D
1597problem, the code runs fast enough that optimization is not
1598necessary. However, as shown in the screen output above, the number of
1599iterations required to solve the linear system is quite large. Thus,
1600for larger problems, it would be good if the solver ran more
1601quickly. See the "Possibilities for extensions" section of @ref step_6 "step-6" for
1602a more detailed discussion on how to change preconditioners. We should
1603note that since the block matrix we use has many zeros on the
1604diagonal, preconditioners like PreconditionJacobi will not work
1605because they divide by diagonal entries. Instead, block
1606preconditioners such as those discussed in @ref step_20 "step-20" or @ref step_22 "step-22" (among
1607many others) will likely be useful. For block preconditioners, the
1608key realizations is that the blocks of the system matrix
1610 \left(\begin{array}{c c c}
1611 \mathcal{M} & -\mathcal{N}^T & 0\\
1612 -\mathcal{N} & 0 & \mathcal{F}^T\\
1616can often individually be solved with quite efficiently; for example,
1617@f$\mathcal{M}@f$ is a mass matrix that is easily solved with using a CG
1618iteration, and @f$\mathcal N@f$ is a Laplace matrix for which CG with
1619a geometric or algebraic multigrid preconditioner is very effective.
1620Using the ideas of @ref step_20 "step-20" and @ref step_22 "step-22", we should then create a
1621@f$3\times 3@f$ block preconditioner in which some blocks correspond to the
1622inverses of @f$\mathcal{M}@f$ or @f$\mathcal{N}@f$, or some kind of Schur
1623complement. A starting point for this kind of consideration is
1624@cite Battermann_1998 .
16262. To validate the optimization problem is working correctly, we could
1627try to match a target function which is itself a solution to the
1628Poisson equation with prescribed heat profile. If the optimization
1629problem is being solved correctly, it should be able to perfectly
1630match this solution. To create such a function, we would need to first
1631solve the Poisson problem on a scalar field, with a RHS described by
1632the chosen heat profile. See @ref step_7 "step-7" for more information on the method
1633of manufactured solutions.
16353. The program at the moment has the number of nonlocal degrees of freedom
1636hardcoded as @f$2^d@f$ (see the constructor). We then assign each of these
1637degrees of freedom to one of the first @f$2^d@f$ cells. This is not going to
1638be much of a problem because there are always enough cells for this as
1639long as you start with a mesh that is at least once refined. What would
1640we do if we had a number of nonlocal DoFs that is not easily predictable,
1641and that may be larger than the number of cells? Perhaps a better approach
1642would be to come up with a way to assign *all* of these to the first cell,
1643because there is *always* at least one cell. The way to achieve this would
1644be to replace the use of FE_DGQ(0) (an element with exactly one degree of
1645freedom) by an element that has more than one -- and in particular exactly
1646the right number of degrees of freedom -- and that can be used on the first
1647cell; all other cells would then use FE_Nothing. At the time of writing
1648this program, there is no element class that can easily be given a *specific*
1649number of degrees of freedom, but it would not be very difficult to write
1653<a name="step_93-PlainProg"></a>
1654<h1> The plain program</h1>
1655@include "step-93.cc"
void add_data_vector(const VectorType &data, const std::vector< std::string > &names, const DataVectorType type=type_automatic, const std::vector< DataComponentInterpretation::DataComponentInterpretation > &data_component_interpretation={})
void distribute_dofs(const FiniteElement< dim, spacedim > &fe)
void initialize(const SparsityPattern &sparsity_pattern)
const unsigned int DoFAccessor< structdim, dim, spacedim, level_dof_access >::dimension
#define DEAL_II_ASSERT_UNREACHABLE()
void loop(IteratorType begin, std_cxx20::type_identity_t< IteratorType > end, DOFINFO &dinfo, INFOBOX &info, const std::function< void(std_cxx20::type_identity_t< DOFINFO > &, typename INFOBOX::CellInfo &)> &cell_worker, const std::function< void(std_cxx20::type_identity_t< DOFINFO > &, typename INFOBOX::CellInfo &)> &boundary_worker, const std::function< void(std_cxx20::type_identity_t< DOFINFO > &, std_cxx20::type_identity_t< DOFINFO > &, typename INFOBOX::CellInfo &, typename INFOBOX::CellInfo &)> &face_worker, AssemblerType &assembler, const LoopControl &lctrl=LoopControl())
const bool IsBlockVector< VectorType >::value
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={}, const bool keep_constrained_dofs=true, const types::subdomain_id subdomain_id=numbers::invalid_subdomain_id)
void hyper_cube(Triangulation< dim, spacedim > &tria, const double left=0., const double right=1., const bool colorize=false)
@ matrix
Contents is actually a matrix.
constexpr types::blas_int zero
constexpr types::blas_int one
double norm(const FEValuesBase< dim > &fe, const ArrayView< const std::vector< Tensor< 1, dim > > > &Du)
SymmetricTensor< 2, dim, Number > C(const Tensor< 2, dim, Number > &F)
SymmetricTensor< 2, dim, Number > d(const Tensor< 2, dim, Number > &F, const Tensor< 2, dim, Number > &dF_dt)
VectorType::value_type * end(VectorType &V)
T sum(const T &t, const MPI_Comm mpi_communicator)
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)
bool check(const ConstraintKinds kind_in, const unsigned int dim)
int(& functions)(const void *v1, const void *v2)