513 *
const double t = this->
get_time();
519 *
const double m = 0.5;
520 *
const double c1 = 0.;
521 *
const double c2 = 0.;
522 *
return -4. * std::atan(m /
std::sqrt(1. - m * m) *
524 *
std::cosh(m * p[0] +
c1));
530 *
const double lambda = 1.;
531 *
const double a0 = 1.;
532 *
const double s = 1.;
534 *
std::sin(theta) * (p[1] * std::cosh(lambda) +
535 *
t * std::sinh(lambda));
543 *
const double tau = 1.;
544 *
const double c0 = 1.;
545 *
const double s = 1.;
549 *
(p[2] * std::cosh(
tau) + t * std::sinh(
tau));
554 *
Assert(
false, ExcNotImplemented());
564 * above a second time. Rather, if we are queried for initial conditions, we
565 * create an object <code>ExactSolution</code>, set it to the correct time,
566 * and let it compute whatever values the exact solution has at that time:
570 * class InitialValues : public Function<dim>
573 * InitialValues(const unsigned int n_components = 1, const double time = 0.)
574 * : Function<dim>(n_components, time)
577 * virtual double value(const Point<dim> & p,
578 * const unsigned int component = 0) const override
580 * return ExactSolution<dim>(1, this->get_time()).value(p, component);
588 * <a name="ImplementationofthecodeSineGordonProblemcodeclass"></a>
589 * <h3>Implementation of the <code>SineGordonProblem</code> class</h3>
599 * <a name=
"SineGordonProblemSineGordonProblem"></a>
611 *
doesn't matter, and we choose it so that we start at an interesting time.
615 * Note that if we were to chose the explicit Euler time stepping scheme
616 * (@f$\theta = 0@f$), then we must pick a time step @f$k \le h@f$, otherwise the
617 * scheme is not stable and oscillations might arise in the solution. The
618 * Crank-Nicolson scheme (@f$\theta = \frac{1}{2}@f$) and the implicit Euler
619 * scheme (@f$\theta=1@f$) do not suffer from this deficiency, since they are
620 * unconditionally stable. However, even then the time step should be chosen
621 * to be on the order of @f$h@f$ in order to obtain a good solution. Since we
622 * know that our mesh results from the uniform subdivision of a rectangle,
623 * we can compute that time step easily; if we had a different domain, the
624 * technique in @ref step_24 "step-24" using GridTools::minimal_cell_diameter would work as
629 * SineGordonProblem<dim>::SineGordonProblem()
631 * , dof_handler(triangulation)
632 * , n_global_refinements(6)
634 * , final_time(2.7207)
635 * , time_step(10 * 1. / std::pow(2., 1. * n_global_refinements))
637 * , output_timestep_skip(1)
643 * <a name="SineGordonProblemmake_grid_and_dofs"></a>
644 * <h4>SineGordonProblem::make_grid_and_dofs</h4>
648 * This function creates a rectangular grid in <code>dim</code> dimensions
649 * and refines it several times. Also, all matrix and vector members of the
650 * <code>SineGordonProblem</code> class are initialized to their appropriate
651 * sizes once the degrees of freedom have been assembled. Like @ref step_24 "step-24", we
652 * use <code>MatrixCreator</code> functions to generate a mass matrix @f$M@f$
653 * and a Laplace matrix @f$A@f$ and store them in the appropriate variables for
654 * the remainder of the program's
life.
663 *
std::cout <<
" Number of active cells: " <<
triangulation.n_active_cells()
668 *
dof_handler.distribute_dofs(fe);
670 *
std::cout <<
" Number of degrees of freedom: " << dof_handler.n_dofs()
675 *
sparsity_pattern.copy_from(
dsp);
677 *
system_matrix.
reinit(sparsity_pattern);
688 *
solution.
reinit(dof_handler.n_dofs());
698 * <a name=
"SineGordonProblemassemble_system"></a>
705 * Introduction for the explicit formulas for the system matrix and
710 * Note that during each time step, we have to add up the various
711 * contributions to the matrix and right hand sides. In contrast to @ref step_23 "step-23"
712 * and @ref step_24 "step-24", this requires assembling a few more terms, since they depend
713 * on the solution of the previous time step or previous nonlinear step. We
714 * use the functions <code>compute_nl_matrix</code> and
715 * <code>compute_nl_term</code> to do this, while the present function
716 * provides the top-level logic.
720 * void SineGordonProblem<dim>::assemble_system()
724 * First we assemble the Jacobian matrix @f$F'_h(U^{n,
l})@
f$,
where @
f$U^{n,
l}@
f$
728 *
system_matrix.copy_from(mass_matrix);
766 * <a name=
"SineGordonProblemcompute_nl_term"></a>
807 *
const unsigned int dofs_per_cell = fe.n_dofs_per_cell();
811 *
std::vector<types::global_dof_index> local_dof_indices(dofs_per_cell);
815 *
for (
const auto &cell : dof_handler.active_cell_iterators())
840 *
for (
unsigned int i = 0; i < dofs_per_cell; ++i)
852 *
cell->get_dof_indices(local_dof_indices);
854 *
for (
unsigned int i = 0; i < dofs_per_cell; ++i)
862 * <a name=
"SineGordonProblemcompute_nl_matrix"></a>
887 *
const unsigned int dofs_per_cell = fe.n_dofs_per_cell();
891 *
std::vector<types::global_dof_index> local_dof_indices(dofs_per_cell);
895 *
for (
const auto &cell : dof_handler.active_cell_iterators())
916 *
for (
unsigned int i = 0; i < dofs_per_cell; ++i)
917 *
for (
unsigned int j = 0;
j < dofs_per_cell; ++
j)
921 *
fe_values.shape_value(i,
q_point) *
930 *
cell->get_dof_indices(local_dof_indices);
932 *
for (
unsigned int i = 0; i < dofs_per_cell; ++i)
933 *
for (
unsigned int j = 0;
j < dofs_per_cell; ++
j)
935 *
local_dof_indices[
j],
945 * <a name=
"SineGordonProblemsolve"></a>
946 * <
h4>SineGordonProblem::solve</
h4>
953 * the (nonlinear) first equation of the split formulation. The solution to
954 * the system is, in fact, @f$\delta U^{n,l}@f$ so it is stored in
955 * <code>solution_update</code> and used to update <code>solution</code> in
956 * the <code>run</code> function.
960 * Note that we re-set the solution update to zero before solving for
961 * it. This is not necessary: iterative solvers can start from any point and
962 * converge to the correct solution. If one has a good estimate about the
963 * solution of a linear system, it may be worthwhile to start from that
964 * vector, but as a general observation it is a fact that the starting point
985 *
preconditioner.initialize(system_matrix, 1.2);
989 *
return solver_control.last_step();
995 * <a name=
"SineGordonProblemoutput_results"></a>
1004 *
template <
int dim>
1010 *
data_out.attach_dof_handler(dof_handler);
1011 *
data_out.add_data_vector(solution,
"u");
1012 *
data_out.build_patches();
1018 *
data_out.set_flags(vtk_flags);
1020 *
data_out.write_vtu(output);
1026 * <a name=
"SineGordonProblemrun"></a>
1027 * <
h4>SineGordonProblem::run</
h4>
1036 *
template <
int dim>
1061 *
constraints.close();
1072 * any
other time step.
1086 *
for (time +=
time_step; time <= final_time;
1091 *
std::cout << std::endl
1093 *
<<
"advancing to t = " << time <<
'.' << std::endl;
1099 * i.e. solve for @f$\delta U^{n,l}@f$ then compute @f$U^{n,l+1}@f$ and so
1100 * on. The stopping criterion for this nonlinear iteration is that
1101 * @f$\|F_h(U^{n,l})\|_2 \le 10^{-6} \|F_h(U^{n,0})\|_2@f$. Consequently,
1102 * we need to record the norm of the residual in the first iteration.
1106 * At the end of each iteration, we output to the console how many
1107 * linear solver iterations it took us. When the loop below is done,
1108 * we have (an approximation of) @f$U^n@f$.
1111 * double initial_rhs_norm = 0.;
1112 * bool first_iteration = true;
1115 * assemble_system();
1117 * if (first_iteration == true)
1118 * initial_rhs_norm = system_rhs.l2_norm();
1120 * const unsigned int n_iterations = solve();
1122 * solution += solution_update;
1124 * if (first_iteration == true)
1125 * std::cout << " " << n_iterations;
1127 * std::cout << '+
' << n_iterations;
1128 * first_iteration = false;
1130 * while (system_rhs.l2_norm() > 1e-6 * initial_rhs_norm);
1132 * std::cout << " CG iterations per nonlinear step." << std::endl;
1136 * Upon obtaining the solution to the first equation of the problem at
1137 * @f$t=t_n@f$, we must update the auxiliary velocity variable
1138 * @f$V^n@f$. However, we do not compute and store @f$V^n@f$ since it is not a
1139 * quantity we use directly in the problem. Hence, for simplicity, we
1140 * update @f$MV^n@f$ directly:
1143 * Vector<double> tmp_vector(solution.size());
1144 * laplace_matrix.vmult(tmp_vector, solution);
1145 * M_x_velocity.add(-time_step * theta, tmp_vector);
1147 * laplace_matrix.vmult(tmp_vector, old_solution);
1148 * M_x_velocity.add(-time_step * (1 - theta), tmp_vector);
1150 * compute_nl_term(old_solution, solution, tmp_vector);
1151 * M_x_velocity.add(-time_step, tmp_vector);
1155 * Oftentimes, in particular for fine meshes, we must pick the time
1156 * step to be quite small in order for the scheme to be
1157 * stable. Therefore, there are a lot of time steps during which
1158 * "nothing interesting happens" in the solution. To improve overall
1159 * efficiency -- in particular, speed up the program and save disk
1160 * space -- we only output the solution every
1161 * <code>output_timestep_skip</code> time steps:
1164 * if (timestep_number % output_timestep_skip == 0)
1165 * output_results(timestep_number);
1168 * } // namespace Step25
1173 * <a name="Thecodemaincodefunction"></a>
1174 * <h3>The <code>main</code> function</h3>
1178 * This is the main function of the program. It creates an object of top-level
1179 * class and calls its principal function. If exceptions are thrown during the
1180 * execution of the run method of the <code>SineGordonProblem</code> class, we
1181 * catch and report them here. For more information about exceptions the
1182 * reader should consult @ref step_6 "step-6".
1189 * using namespace Step25;
1191 * SineGordonProblem<1> sg_problem;
1194 * catch (std::exception &exc)
1196 * std::cerr << std::endl
1198 * << "----------------------------------------------------"
1200 * std::cerr << "Exception on processing: " << std::endl
1201 * << exc.what() << std::endl
1202 * << "Aborting!" << std::endl
1203 * << "----------------------------------------------------"
1210 * std::cerr << std::endl
1212 * << "----------------------------------------------------"
1214 * std::cerr << "Unknown exception!" << std::endl
1215 * << "Aborting!" << std::endl
1216 * << "----------------------------------------------------"
1224<a name="Results"></a><h1>Results</h1>
1226The explicit Euler time stepping scheme (@f$\theta=0@f$) performs adequately for the problems we wish to solve. Unfortunately, a rather small time step has to be chosen due to stability issues --- @f$k\sim h/10@f$ appears to work for most the simulations we performed. On the other hand, the Crank-Nicolson scheme (@f$\theta=\frac{1}{2}@f$) is unconditionally stable, and (at least for the case of the 1D breather) we can pick the time step to be as large as @f$25h@f$ without any ill effects on the solution. The implicit Euler scheme (@f$\theta=1@f$) is "exponentially damped," so it is not a good choice for solving the sine-Gordon equation, which is conservative. However, some of the damped schemes in the continuum that is offered by the @f$\theta@f$-method were useful for eliminating spurious oscillations due to boundary effects.
1228In the simulations below, we solve the sine-Gordon equation on the interval @f$\Omega =
1229[-10,10]@f$ in 1D and on the square @f$\Omega = [-10,10]\times [-10,10]@f$ in 2D. In
1230each case, the respective grid is refined uniformly 6 times, i.e. @f$h\sim
1233<a name="An11dSolution"></a><h3>An (1+1)-d Solution</h3>
1235The first example we discuss is the so-called 1D (stationary) breather
1236solution of the sine-Gordon equation. The breather has the following
1237closed-form expression, as mentioned in the Introduction:
1239u_{\mathrm{breather}}(x,t) = -4\arctan \left(\frac{m}{\sqrt{1-m^2}} \frac{\sin\left(\sqrt{1-m^2}t +c_2\right)}{\cosh(mx+c_1)} \right),
1241where @f$c_1@f$, @f$c_2@f$ and @f$m<1@f$ are constants. In the simulation below, we have chosen @f$c_1=0@f$, @f$c_2=0@f$, @f$m=0.5@f$. Moreover, it is know that the period of oscillation of the breather is @f$2\pi\sqrt{1-m^2}@f$, hence we have chosen @f$t_0=-5.4414@f$ and @f$t_f=2.7207@f$ so that we can observe three oscillations of the solution. Then, taking @f$u_0(x) = u_{\mathrm{breather}}(x,t_0)@f$, @f$\theta=0@f$ and @f$k=h/10@f$, the program computed the following solution.
1243<img src="https://www.dealii.org/images/steps/developer/step-25.1d-breather.gif" alt="Animation of the 1D stationary breather.">
1245Though not shown how to do this in the program, another way to visualize the
1246(1+1)-d solution is to use output generated by the DataOutStack class; it
1247allows to "stack" the solutions of individual time steps, so that we get
12482D space-time graphs from 1D time-dependent
1249solutions. This produces the space-time plot below instead of the animation
1252<img src="https://www.dealii.org/images/steps/developer/step-25.1d-breather_stp.png" alt="A space-time plot of the 1D stationary breather.">
1254Furthermore, since the breather is an analytical solution of the sine-Gordon
1255equation, we can use it to validate our code, although we have to assume that
1256the error introduced by our choice of Neumann boundary conditions is small
1257compared to the numerical error. Under this assumption, one could use the
1258VectorTools::integrate_difference function to compute the difference between
1259the numerical solution and the function described by the
1260<code>ExactSolution</code> class of this program. For the
1261simulation shown in the two images above, the @f$L^2@f$ norm of the error in the
1262finite element solution at each time step remained on the order of
1263@f$10^{-2}@f$. Hence, we can conclude that the numerical method has been
1264implemented correctly in the program.
1267<a name="Afew21DSolutions"></a><h3>A few (2+1)D Solutions</h3>
1270The only analytical solution to the sine-Gordon equation in (2+1)D that can be found in the literature is the so-called kink solitary wave. It has the following closed-form expression:
1272 u(x,y,t) = 4 \arctan \left[a_0 e^{s\xi}\right]
1276 \xi = x \cos\vartheta + \sin(\vartheta) (y\cosh\lambda + t\sinh \lambda)
1278where @f$a_0@f$, @f$\vartheta@f$ and @f$\lambda@f$ are constants. In the simulation below
1279we have chosen @f$a_0=\lambda=1@f$. Notice that if @f$\vartheta=\pi@f$ the kink is
1280stationary, hence it would make a good solution against which we can
1281validate the program in 2D because no reflections off the boundary of the
1284The simulation shown below was performed with @f$u_0(x) = u_{\mathrm{kink}}(x,t_0)@f$, @f$\theta=\frac{1}{2}@f$, @f$k=20h@f$, @f$t_0=1@f$ and @f$t_f=500@f$. The @f$L^2@f$ norm of the error of the finite element solution at each time step remained on the order of @f$10^{-2}@f$, showing that the program is working correctly in 2D, as well as 1D. Unfortunately, the solution is not very interesting, nonetheless we have included a snapshot of it below for completeness.
1286<img src="https://www.dealii.org/images/steps/developer/step-25.2d-kink.png" alt="Stationary 2D kink.">
1288Now that we have validated the code in 1D and 2D, we move to a problem where the analytical solution is unknown.
1290To this end, we rotate the kink solution discussed above about the @f$z@f$
1291axis: we let @f$\vartheta=\frac{\pi}{4}@f$. The latter results in a
1292solitary wave that is not aligned with the grid, so reflections occur
1293at the boundaries of the domain immediately. For the simulation shown
1294below, we have taken @f$u_0(x)=u_{\mathrm{kink}}(x,t_0)@f$,
1295@f$\theta=\frac{2}{3}@f$, @f$k=20h@f$, @f$t_0=0@f$ and @f$t_f=20@f$. Moreover, we had
1296to pick @f$\theta=\frac{2}{3}@f$ because for any @f$\theta\le\frac{1}{2}@f$
1297oscillations arose at the boundary, which are likely due to the scheme
1298and not the equation, thus picking a value of @f$\theta@f$ a good bit into
1299the "exponentially damped" spectrum of the time stepping schemes
1300assures these oscillations are not created.
1302<img src="https://www.dealii.org/images/steps/developer/step-25.2d-angled_kink.gif" alt="Animation of a moving 2D kink, at 45 degrees to the axes of the grid, showing boundary effects.">
1304Another interesting solution to the sine-Gordon equation (which cannot be
1305obtained analytically) can be produced by using two 1D breathers to construct
1306the following separable 2D initial condition:
1309 u_{\mathrm{pseudobreather}}(x,t_0) =
1311 \frac{m}{\sqrt{1-m^2}}
1312 \frac{\sin\left(\sqrt{1-m^2}t_0\right)}{\cosh(mx_1)} \right)
1314 \frac{m}{\sqrt{1-m^2}}
1315 \frac{\sin\left(\sqrt{1-m^2}t_0\right)}{\cosh(mx_2)} \right),
1317where @f$x=(x_1,x_2)\in{R}^2@f$, @f$m=0.5<1@f$ as in the 1D case we discussed
1318above. For the simulation shown below, we have chosen @f$\theta=\frac{1}{2}@f$,
1319@f$k=10h@f$, @f$t_0=-5.4414@f$ and @f$t_f=2.7207@f$. The solution is pretty interesting
1320--- it acts like a breather (as far as the pictures are concerned); however,
1321it appears to break up and reassemble, rather than just oscillate.
1323<img src="https://www.dealii.org/images/steps/developer/step-25.2d-pseudobreather.gif" alt="Animation of a 2D pseudobreather.">
1326<a name="extensions"></a>
1327<a name="Possibilitiesforextensions"></a><h3>Possibilities for extensions</h3>
1330It is instructive to change the initial conditions. Most choices will not lead
1331to solutions that stay localized (in the soliton community, such
1332solutions are called "stationary", though the solution does change
1333with time), but lead to solutions where the wave-like
1334character of the equation dominates and a wave travels away from the location
1335of a localized initial condition. For example, it is worth playing around with
1336the <code>InitialValues</code> class, by replacing the call to the
1337<code>ExactSolution</code> class by something like this function:
1339 u_0(x,y) = \cos\left(\frac x2\right)\cos\left(\frac y2\right)
1341if @f$|x|,|y|\le \frac\pi 2@f$, and @f$u_0(x,y)=0@f$ outside this region.
1343A second area would be to investigate whether the scheme is
1344energy-preserving. For the pure wave equation, discussed in @ref
1345step_23 "step-23", this is the case if we choose the time stepping
1346parameter such that we get the Crank-Nicolson scheme. One could do a
1347similar thing here, noting that the energy in the sine-Gordon solution
1350 E(t) = \frac 12 \int_\Omega \left(\frac{\partial u}{\partial
1352 + \left(\nabla u\right)^2 + 2 (1-\cos u) \; dx.
1354(We use @f$1-\cos u@f$ instead of @f$-\cos u@f$ in the formula to ensure that all
1355contributions to the energy are positive, and so that decaying solutions have
1356finite energy on unbounded domains.)
1358Beyond this, there are two obvious areas:
1360- Clearly, adaptivity (i.e. time-adaptive grids) would be of interest
1361 to problems like these. Their complexity leads us to leave this out
1362 of this program again, though the general comments in the
1363 introduction of @ref step_23 "step-23" remain true.
1365- Faster schemes to solve this problem. While computers today are
1366 plenty fast enough to solve 2d and, frequently, even 3d stationary
1367 problems within not too much time, time dependent problems present
1368 an entirely different class of problems. We address this topic in
1369 @ref step_48 "step-48" where we show how to solve this problem in parallel and
1370 without assembling or inverting any matrix at all.
1373<a name="PlainProg"></a>
1374<h1> The plain program</h1>
1375@include "step-25.cc"
value_type * data() const noexcept
void reinit(value_type *starting_element, const std::size_t n_elements)
__global__ void set(Number *val, const Number s, const size_type N)
#define Assert(cond, exc)
void loop(ITERATOR begin, std_cxx20::type_identity_t< ITERATOR > end, DOFINFO &dinfo, INFOBOX &info, const std::function< void(DOFINFO &, typename INFOBOX::CellInfo &)> &cell_worker, const std::function< void(DOFINFO &, typename INFOBOX::CellInfo &)> &boundary_worker, const std::function< void(DOFINFO &, DOFINFO &, typename INFOBOX::CellInfo &, typename INFOBOX::CellInfo &)> &face_worker, ASSEMBLER &assembler, const LoopControl &lctrl=LoopControl())
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_values
Shape function values.
@ update_JxW_values
Transformed quadrature weights.
@ update_quadrature_points
Transformed quadrature points.
std::vector< value_type > split(const typename ::Triangulation< dim, spacedim >::cell_iterator &parent, const value_type parent_value)
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.
void mass_matrix(FullMatrix< double > &M, const FEValuesBase< dim > &fe, const double factor=1.)
void create_mass_matrix(const Mapping< dim, spacedim > &mapping, const DoFHandler< dim, spacedim > &dof, const Quadrature< dim > &q, SparseMatrixType &matrix, const Function< spacedim, typename SparseMatrixType::value_type > *const a=nullptr, const AffineConstraints< typename SparseMatrixType::value_type > &constraints=AffineConstraints< typename SparseMatrixType::value_type >())
void create_laplace_matrix(const Mapping< dim, spacedim > &mapping, const DoFHandler< dim, spacedim > &dof, const Quadrature< dim > &q, SparseMatrixType &matrix, const Function< spacedim, typename SparseMatrixType::value_type > *const a=nullptr, const AffineConstraints< typename SparseMatrixType::value_type > &constraints=AffineConstraints< typename SparseMatrixType::value_type >())
Point< spacedim > point(const gp_Pnt &p, const double tolerance=1e-10)
Tensor< 2, dim, Number > l(const Tensor< 2, dim, Number > &F, const Tensor< 2, dim, Number > &dF_dt)
SymmetricTensor< 2, dim, Number > d(const Tensor< 2, dim, Number > &F, const Tensor< 2, dim, Number > &dF_dt)
void call(const std::function< RT()> &function, internal::return_value< RT > &ret_val)
T reduce(const T &local_value, const MPI_Comm comm, const std::function< T(const T &, const T &)> &combiner, const unsigned int root_process=0)
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)
static constexpr double PI
::VectorizedArray< Number, width > exp(const ::VectorizedArray< Number, width > &)
::VectorizedArray< Number, width > cos(const ::VectorizedArray< Number, width > &)
::VectorizedArray< Number, width > sin(const ::VectorizedArray< Number, width > &)
::VectorizedArray< Number, width > sqrt(const ::VectorizedArray< Number, width > &)
::VectorizedArray< Number, width > pow(const ::VectorizedArray< Number, width > &, const Number p)
const ::parallel::distributed::Triangulation< dim, spacedim > * triangulation
void advance(std::tuple< I1, I2 > &t, const unsigned int n)