720 * mass_minus_tau_Jacobian.copy_from(mass_matrix);
721 * mass_minus_tau_Jacobian.add(-tau, system_matrix);
723 * inverse_mass_minus_tau_Jacobian.
initialize(mass_minus_tau_Jacobian);
729 * inverse_mass_minus_tau_Jacobian.vmult(result, tmp);
739 * <a name=
"step_52-codeDiffusionoutput_resultscode"></a>
740 * <h4><code>Diffusion::output_results</code></h4>
744 * The following function then outputs the solution in
vtu files indexed by
745 * the number of the time step and the name of the time stepping method. Of
746 * course, the (exact) result should really be the same
for all time
747 * stepping method, but the output here at least allows us to compare them.
750 *
void Diffusion::output_results(
const double time,
751 *
const unsigned int time_step,
754 * std::string method_name;
760 * method_name =
"forward_euler";
765 * method_name =
"rk3";
770 * method_name =
"rk4";
775 * method_name =
"backward_euler";
780 * method_name =
"implicit_midpoint";
785 * method_name =
"sdirk";
790 * method_name =
"heun_euler";
795 * method_name =
"bogacki_shampine";
800 * method_name =
"dopri";
805 * method_name =
"fehlberg";
810 * method_name =
"cash_karp";
822 * data_out.add_data_vector(solution,
"solution");
824 * data_out.build_patches();
828 *
const std::string filename =
"solution_" + method_name +
"-" +
831 * std::ofstream output(filename);
832 * data_out.write_vtu(output);
834 *
static std::vector<std::pair<double, std::string>> times_and_names;
836 *
static std::string method_name_prev =
"";
837 *
static std::string pvd_filename;
838 *
if (method_name_prev != method_name)
840 * times_and_names.clear();
841 * method_name_prev = method_name;
842 * pvd_filename =
"solution_" + method_name +
".pvd";
844 * times_and_names.emplace_back(time, filename);
845 * std::ofstream pvd_output(pvd_filename);
853 * <a name=
"step_52-codeDiffusionexplicit_methodcode"></a>
854 * <h4><code>Diffusion::explicit_method</code></h4>
858 * This function is the driver
for all the
explicit methods. At the
859 * top it initializes the time stepping and the solution (by setting
860 * it to zero and then ensuring that boundary value and hanging node
861 * constraints are respected; of course, with the mesh we use here,
862 * hanging node constraints are not in fact an issue). It then calls
863 * <code>evolve_one_time_step</code> which performs one time step.
864 * Time is stored and incremented through a
DiscreteTime object.
868 * For
explicit methods, <code>evolve_one_time_step</code> needs to
869 * evaluate @f$M^{-1}(f(t,y))@f$, i.e, it needs
870 * <code>evaluate_diffusion</code>. Because
871 * <code>evaluate_diffusion</code> is a member function, it needs to
872 * be bound to <code>this</code>. After each evolution step, we
873 * again apply the correct boundary values and hanging node
878 * Finally, the solution is output
879 * every 10 time steps.
883 *
const unsigned int n_time_steps,
884 *
const double initial_time,
885 *
const double final_time)
887 *
const double time_step =
888 * (final_time - initial_time) /
static_cast<double>(n_time_steps);
891 * constraint_matrix.distribute(solution);
895 * output_results(initial_time, 0, method);
896 *
DiscreteTime time(initial_time, final_time, time_step);
897 *
while (time.is_at_end() ==
false)
899 * explicit_runge_kutta.evolve_one_time_step(
901 *
return this->evaluate_diffusion(time, y);
903 * time.get_current_time(),
904 * time.get_next_step_size(),
906 * time.advance_time();
908 * constraint_matrix.distribute(solution);
910 *
if (time.get_step_number() % 10 == 0)
911 * output_results(time.get_current_time(),
912 * time.get_step_number(),
922 * <a name=
"step_52-codeDiffusionimplicit_methodcode"></a>
923 * <h4><code>Diffusion::implicit_method</code></h4>
924 * This function is equivalent to <code>explicit_method</code> but
for
925 * implicit methods. When
using implicit methods, we need to evaluate
926 * @f$M^{-1}(f(t,y))@f$ and @f$\left(I-\tau M^{-1} \frac{\partial f(t,y)}{\partial
927 * y}\right)^{-1}@f$
for which we use the two member
functions previously
932 *
const unsigned int n_time_steps,
933 *
const double initial_time,
934 *
const double final_time)
936 *
const double time_step =
937 * (final_time - initial_time) /
static_cast<double>(n_time_steps);
940 * constraint_matrix.distribute(solution);
944 * output_results(initial_time, 0, method);
945 *
DiscreteTime time(initial_time, final_time, time_step);
946 *
while (time.is_at_end() ==
false)
948 * implicit_runge_kutta.evolve_one_time_step(
950 *
return this->evaluate_diffusion(time, y);
952 * [
this](
const double time,
const double tau,
const Vector<double> &y) {
953 *
return this->id_minus_tau_J_inverse(time, tau, y);
955 * time.get_current_time(),
956 * time.get_next_step_size(),
958 * time.advance_time();
960 * constraint_matrix.distribute(solution);
962 *
if (time.get_step_number() % 10 == 0)
963 * output_results(time.get_current_time(),
964 * time.get_step_number(),
974 * <a name=
"step_52-codeDiffusionembedded_explicit_methodcode"></a>
975 * <h4><code>Diffusion::embedded_explicit_method</code></h4>
976 * This function is the driver
for the embedded
explicit methods. It
requires
978 * - coarsen_param: factor multiplying the current time step when the error
979 * is below the threshold.
980 * - refine_param: factor multiplying the current time step when the error
981 * is above the threshold.
982 * - min_delta: smallest time step acceptable.
983 * - max_delta: largest time step acceptable.
984 * - refine_tol: threshold above which the time step is refined.
985 * - coarsen_tol: threshold below which the time step is
coarsen.
989 * Embedded methods use a guessed time step. If the error
using this time step
990 * is too large, the time step will be reduced. If the error is below the
991 * threshold, a larger time step will be tried
for the next time step.
992 * <code>delta_t_guess</code> is the guessed time step produced by the
993 * embedded method. In summary, time step size is potentially modified in
995 * - Reducing or increasing time step size within
997 * - Using the calculated <code>delta_t_guess</code>.
998 * - Automatically adjusting the step size of the last time step to ensure
999 * simulation ends precisely at <code>final_time</code>. This adjustment
1003 *
unsigned int Diffusion::embedded_explicit_method(
1005 *
const unsigned int n_time_steps,
1006 *
const double initial_time,
1007 *
const double final_time)
1009 *
const double time_step =
1010 * (final_time - initial_time) /
static_cast<double>(n_time_steps);
1011 *
const double coarsen_param = 1.2;
1012 *
const double refine_param = 0.8;
1013 *
const double min_delta = 1
e-8;
1014 *
const double max_delta = 10 * time_step;
1015 *
const double refine_tol = 1
e-1;
1016 *
const double coarsen_tol = 1
e-5;
1019 * constraint_matrix.distribute(solution);
1022 * embedded_explicit_runge_kutta(method,
1029 * output_results(initial_time, 0, method);
1030 *
DiscreteTime time(initial_time, final_time, time_step);
1031 *
while (time.is_at_end() ==
false)
1034 * embedded_explicit_runge_kutta.evolve_one_time_step(
1036 *
return this->evaluate_diffusion(time, y);
1038 * time.get_current_time(),
1039 * time.get_next_step_size(),
1041 * time.set_next_step_size(new_time - time.get_current_time());
1042 * time.advance_time();
1044 * constraint_matrix.distribute(solution);
1046 *
if (time.get_step_number() % 10 == 0)
1047 * output_results(time.get_current_time(),
1048 * time.get_step_number(),
1051 * time.set_desired_next_step_size(
1052 * embedded_explicit_runge_kutta.get_status().delta_t_guess);
1055 *
return time.get_step_number();
1063 * <a name=
"step_52-codeDiffusionruncode"></a>
1064 * <h4><code>Diffusion::run</code></h4>
1068 * The following is the main function of the program. At the top, we create
1069 * the grid (a @f$[0,5]\times [0,5]@f$ square) and
refine it four times to get a
1070 * mesh that has 16 by 16 cells,
for a total of 256. We then
set the boundary
1071 * indicator to 1
for those parts of the boundary where @f$x=0@f$ and @f$x=5@f$.
1074 *
void Diffusion::run()
1079 *
for (
const auto &cell :
triangulation.active_cell_iterators())
1080 * for (const auto &face : cell->face_iterators())
1081 * if (face->at_boundary())
1083 * if ((face->
center()[0] == 0.) || (face->
center()[0] == 5.))
1084 * face->set_boundary_id(1);
1086 * face->set_boundary_id(0);
1091 * Next, we
set up the linear systems and fill them with content so that
1092 * they can be used throughout the time stepping process:
1097 * assemble_system();
1101 * Finally, we solve the diffusion problem
using several of the
1102 * Runge-Kutta methods implemented in
namespace TimeStepping, each time
1103 * outputting the error at the
end time. (As explained in the
1104 * introduction, since the exact solution is zero at the
final time, the
1105 * error equals the numerical solution and can be computed by just taking
1106 * the @f$l_2@f$
norm of the solution vector.)
1109 *
unsigned int n_steps = 0;
1110 *
const unsigned int n_time_steps = 200;
1111 *
const double initial_time = 0.;
1112 *
const double final_time = 10.;
1114 * std::cout <<
"Explicit methods:" << std::endl;
1119 * std::cout <<
" Forward Euler: error=" << solution.l2_norm()
1126 * std::cout <<
" Third order Runge-Kutta: error=" << solution.l2_norm()
1133 * std::cout <<
" Fourth order Runge-Kutta: error=" << solution.l2_norm()
1135 * std::cout << std::endl;
1138 * std::cout <<
"Implicit methods:" << std::endl;
1143 * std::cout <<
" Backward Euler: error=" << solution.l2_norm()
1150 * std::cout <<
" Implicit Midpoint: error=" << solution.l2_norm()
1157 * std::cout <<
" Crank-Nicolson: error=" << solution.l2_norm()
1164 * std::cout <<
" SDIRK: error=" << solution.l2_norm()
1166 * std::cout << std::endl;
1169 * std::cout <<
"Embedded explicit methods:" << std::endl;
1174 * std::cout <<
" Heun-Euler: error=" << solution.l2_norm()
1176 * std::cout <<
" steps performed=" << n_steps << std::endl;
1182 * std::cout <<
" Bogacki-Shampine: error=" << solution.l2_norm()
1184 * std::cout <<
" steps performed=" << n_steps << std::endl;
1190 * std::cout <<
" Dopri: error=" << solution.l2_norm()
1192 * std::cout <<
" steps performed=" << n_steps << std::endl;
1198 * std::cout <<
" Fehlberg: error=" << solution.l2_norm()
1200 * std::cout <<
" steps performed=" << n_steps << std::endl;
1206 * std::cout <<
" Cash-Karp: error=" << solution.l2_norm()
1208 * std::cout <<
" steps performed=" << n_steps << std::endl;
1217 * <a name=
"step_52-Thecodemaincodefunction"></a>
1218 * <h3>The <code>main()</code> function</h3>
1222 * The following <code>main</code> function is similar to previous examples
1223 * and need not be commented on.
1230 * Step52::Diffusion diffusion;
1233 *
catch (std::exception &exc)
1235 * std::cerr << std::endl
1237 * <<
"----------------------------------------------------"
1239 * std::cerr <<
"Exception on processing: " << std::endl
1240 * << exc.what() << std::endl
1241 * <<
"Aborting!" << std::endl
1242 * <<
"----------------------------------------------------"
1248 * std::cerr << std::endl
1250 * <<
"----------------------------------------------------"
1252 * std::cerr <<
"Unknown exception!" << std::endl
1253 * <<
"Aborting!" << std::endl
1254 * <<
"----------------------------------------------------"
1262<a name=
"step_52-Results"></a><h1>Results</h1>
1265The
point of
this program is less to show particular results, but instead to
1266show how it is done. This we have already demonstrated simply by discussing
1267the code above. Consequently, the output the program yields is relatively
1268sparse and consists only of the console output and the solutions given in VTU
1269format
for visualization.
1271The console output contains both errors and,
for some of the methods, the
1272number of steps they performed:
1275 Forward Euler: error=1.00883
1276 Third order Runge-Kutta: error=0.000227982
1277 Fourth order Runge-Kutta: error=1.90541e-06
1280 Backward Euler: error=1.03428
1281 Implicit Midpoint: error=0.00862702
1282 Crank-Nicolson: error=0.00862675
1283 SDIRK: error=0.0042349
1285Embedded
explicit methods:
1286 Heun-Euler: error=0.0073012
1288 Bogacki-Shampine: error=0.000408407
1290 Dopri: error=0.000836695
1292 Fehlberg: error=0.00248922
1294 Cash-Karp: error=0.0787735
1298As expected the higher order methods give (much) more accurate solutions. We
1299also see that the (rather inaccurate) Heun-Euler method increased the number of
1300time steps in order to satisfy the tolerance. On the other hand, the other
1301embedded methods used a lot less time steps than what was prescribed.
1304<a name=
"step_52-PlainProg"></a>
1305<h1> The plain program</h1>
1306@include
"step-52.cc"
void attach_dof_handler(const DoFHandler< dim, spacedim > &)
void initialize(const SparsityPattern &sparsity_pattern)
double evolve_one_time_step(const std::function< VectorType(const double, const VectorType &)> &f, const std::function< VectorType(const double, const double, const VectorType &)> &id_minus_tau_J_inverse, double t, double delta_t, VectorType &y) override
void refine_global(const unsigned int times=1)
__global__ void set(Number *val, const Number s, const size_type N)
void write_pvd_record(std::ostream &out, const std::vector< std::pair< double, std::string > > ×_and_names)
void hyper_cube(Triangulation< dim, spacedim > &tria, const double left=0., const double right=1., const bool colorize=false)
void refine(Triangulation< dim, spacedim > &tria, const Vector< Number > &criteria, const double threshold, const unsigned int max_to_mark=numbers::invalid_unsigned_int)
void coarsen(Triangulation< dim, spacedim > &tria, const Vector< Number > &criteria, const double threshold)
double norm(const FEValuesBase< dim > &fe, const ArrayView< const std::vector< Tensor< 1, dim > > > &Du)
void mass_matrix(FullMatrix< double > &M, const FEValuesBase< dim > &fe, const 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)
@ RK_CLASSIC_FOURTH_ORDER
VectorType::value_type * end(VectorType &V)
std::string int_to_string(const unsigned int value, const unsigned int digits=numbers::invalid_unsigned_int)
int(& functions)(const void *v1, const void *v2)
const ::parallel::distributed::Triangulation< dim, spacedim > * triangulation