deal.II version GIT relicensing-2173-gae8fc9d14b 2024-11-24 06:40:00+00:00
\(\newcommand{\dealvcentcolon}{\mathrel{\mathop{:}}}\) \(\newcommand{\dealcoloneq}{\dealvcentcolon\mathrel{\mkern-1.2mu}=}\) \(\newcommand{\jump}[1]{\left[\!\left[ #1 \right]\!\right]}\) \(\newcommand{\average}[1]{\left\{\!\left\{ #1 \right\}\!\right\}}\)
Loading...
Searching...
No Matches
step-25.h
Go to the documentation of this file.
1 = 0) const override
514 *   {
515 *   const double t = this->get_time();
516 *  
517 *   switch (dim)
518 *   {
519 *   case 1:
520 *   {
521 *   const double m = 0.5;
522 *   const double c1 = 0.;
523 *   const double c2 = 0.;
524 *   return -4. * std::atan(m / std::sqrt(1. - m * m) *
525 *   std::sin(std::sqrt(1. - m * m) * t + c2) /
526 *   std::cosh(m * p[0] + c1));
527 *   }
528 *  
529 *   case 2:
530 *   {
531 *   const double theta = numbers::PI / 4.;
532 *   const double lambda = 1.;
533 *   const double a0 = 1.;
534 *   const double s = 1.;
535 *   const double arg = p[0] * std::cos(theta) +
536 *   std::sin(theta) * (p[1] * std::cosh(lambda) +
537 *   t * std::sinh(lambda));
538 *   return 4. * std::atan(a0 * std::exp(s * arg));
539 *   }
540 *  
541 *   case 3:
542 *   {
543 *   const double theta = numbers::PI / 4;
544 *   const double phi = numbers::PI / 4;
545 *   const double tau = 1.;
546 *   const double c0 = 1.;
547 *   const double s = 1.;
548 *   const double arg = p[0] * std::cos(theta) +
549 *   p[1] * std::sin(theta) * std::cos(phi) +
550 *   std::sin(theta) * std::sin(phi) *
551 *   (p[2] * std::cosh(tau) + t * std::sinh(tau));
552 *   return 4. * std::atan(c0 * std::exp(s * arg));
553 *   }
554 *  
555 *   default:
557 *   return -1e8;
558 *   }
559 *   }
560 *   };
561 *  
562 * @endcode
563 *
564 * In the second part of this section, we provide the initial conditions. We
565 * are lazy (and cautious) and don't want to implement the same functions as
566 * above a second time. Rather, if we are queried for initial conditions, we
567 * create an object <code>ExactSolution</code>, set it to the correct time,
568 * and let it compute whatever values the exact solution has at that time:
569 *
570 * @code
571 *   template <int dim>
572 *   class InitialValues : public Function<dim>
573 *   {
574 *   public:
575 *   InitialValues(const unsigned int n_components = 1, const double time = 0.)
576 *   : Function<dim>(n_components, time)
577 *   {}
578 *  
579 *   virtual double value(const Point<dim> &p,
580 *   const unsigned int component = 0) const override
581 *   {
582 *   return ExactSolution<dim>(1, this->get_time()).value(p, component);
583 *   }
584 *   };
585 *  
586 *  
587 * @endcode
588 *
589 *
590 * <a name="step_25-ImplementationofthecodeSineGordonProblemcodeclass"></a>
591 * <h3>Implementation of the <code>SineGordonProblem</code> class</h3>
592 *
593
594 *
595 * Let's move on to the implementation of the main class, as it implements
596 * the algorithm outlined in the introduction.
597 *
598
599 *
600 *
601 * <a name="step_25-SineGordonProblemSineGordonProblem"></a>
602 * <h4>SineGordonProblem::SineGordonProblem</h4>
603 *
604
605 *
606 * This is the constructor of the <code>SineGordonProblem</code> class. It
607 * specifies the desired polynomial degree of the finite elements,
608 * associates a <code>DoFHandler</code> to the <code>triangulation</code>
609 * object (just as in the example programs @ref step_3 "step-3" and @ref step_4 "step-4"), initializes
610 * the current or initial time, the final time, the time step size, and the
611 * value of @f$\theta@f$ for the time stepping scheme. Since the solutions we
612 * compute here are time-periodic, the actual value of the start-time
613 * doesn't matter, and we choose it so that we start at an interesting time.
614 *
615
616 *
617 * Note that if we were to chose the explicit Euler time stepping scheme
618 * (@f$\theta = 0@f$), then we must pick a time step @f$k \le h@f$, otherwise the
619 * scheme is not stable and oscillations might arise in the solution. The
620 * Crank-Nicolson scheme (@f$\theta = \frac{1}{2}@f$) and the implicit Euler
621 * scheme (@f$\theta=1@f$) do not suffer from this deficiency, since they are
622 * unconditionally stable. However, even then the time step should be chosen
623 * to be on the order of @f$h@f$ in order to obtain a good solution. Since we
624 * know that our mesh results from the uniform subdivision of a rectangle,
625 * we can compute that time step easily; if we had a different domain, the
626 * technique in @ref step_24 "step-24" using GridTools::minimal_cell_diameter would work as
627 * well.
628 *
629 * @code
630 *   template <int dim>
631 *   SineGordonProblem<dim>::SineGordonProblem()
632 *   : fe(1)
633 *   , dof_handler(triangulation)
634 *   , n_global_refinements(6)
635 *   , time(-5.4414)
636 *   , final_time(2.7207)
637 *   , time_step(10 * 1. / std::pow(2., 1. * n_global_refinements))
638 *   , theta(0.5)
639 *   , output_timestep_skip(1)
640 *   {}
641 *  
642 * @endcode
643 *
644 *
645 * <a name="step_25-SineGordonProblemmake_grid_and_dofs"></a>
646 * <h4>SineGordonProblem::make_grid_and_dofs</h4>
647 *
648
649 *
650 * This function creates a rectangular grid in <code>dim</code> dimensions
651 * and refines it several times. Also, all matrix and vector members of the
652 * <code>SineGordonProblem</code> class are initialized to their appropriate
653 * sizes once the degrees of freedom have been assembled. Like @ref step_24 "step-24", we
654 * use <code>MatrixCreator</code> functions to generate a mass matrix @f$M@f$
655 * and a Laplace matrix @f$A@f$ and store them in the appropriate variables for
656 * the remainder of the program's life.
657 *
658 * @code
659 *   template <int dim>
660 *   void SineGordonProblem<dim>::make_grid_and_dofs()
661 *   {
663 *   triangulation.refine_global(n_global_refinements);
664 *  
665 *   std::cout << " Number of active cells: " << triangulation.n_active_cells()
666 *   << std::endl
667 *   << " Total number of cells: " << triangulation.n_cells()
668 *   << std::endl;
669 *  
670 *   dof_handler.distribute_dofs(fe);
671 *  
672 *   std::cout << " Number of degrees of freedom: " << dof_handler.n_dofs()
673 *   << std::endl;
674 *  
675 *   DynamicSparsityPattern dsp(dof_handler.n_dofs(), dof_handler.n_dofs());
676 *   DoFTools::make_sparsity_pattern(dof_handler, dsp);
677 *   sparsity_pattern.copy_from(dsp);
678 *  
679 *   system_matrix.reinit(sparsity_pattern);
680 *   mass_matrix.reinit(sparsity_pattern);
681 *   laplace_matrix.reinit(sparsity_pattern);
682 *  
683 *   MatrixCreator::create_mass_matrix(dof_handler,
684 *   QGauss<dim>(fe.degree + 1),
685 *   mass_matrix);
687 *   QGauss<dim>(fe.degree + 1),
688 *   laplace_matrix);
689 *  
690 *   solution.reinit(dof_handler.n_dofs());
691 *   solution_update.reinit(dof_handler.n_dofs());
692 *   old_solution.reinit(dof_handler.n_dofs());
693 *   M_x_velocity.reinit(dof_handler.n_dofs());
694 *   system_rhs.reinit(dof_handler.n_dofs());
695 *   }
696 *  
697 * @endcode
698 *
699 *
700 * <a name="step_25-SineGordonProblemassemble_system"></a>
701 * <h4>SineGordonProblem::assemble_system</h4>
702 *
703
704 *
705 * This function assembles the system matrix and right-hand side vector for
706 * each iteration of Newton's method. The reader should refer to the
707 * Introduction for the explicit formulas for the system matrix and
708 * right-hand side.
709 *
710
711 *
712 * Note that during each time step, we have to add up the various
713 * contributions to the matrix and right hand sides. In contrast to @ref step_23 "step-23"
714 * and @ref step_24 "step-24", this requires assembling a few more terms, since they depend
715 * on the solution of the previous time step or previous nonlinear step. We
716 * use the functions <code>compute_nl_matrix</code> and
717 * <code>compute_nl_term</code> to do this, while the present function
718 * provides the top-level logic.
719 *
720 * @code
721 *   template <int dim>
722 *   void SineGordonProblem<dim>::assemble_system()
723 *   {
724 * @endcode
725 *
726 * First we assemble the Jacobian matrix @f$F'_h(U^{n,l})@f$, where @f$U^{n,l}@f$
727 * is stored in the vector <code>solution</code> for convenience.
728 *
729 * @code
730 *   system_matrix.copy_from(mass_matrix);
731 *   system_matrix.add(Utilities::fixed_power<2>(time_step * theta),
732 *   laplace_matrix);
733 *  
734 *   SparseMatrix<double> tmp_matrix(sparsity_pattern);
735 *   compute_nl_matrix(old_solution, solution, tmp_matrix);
736 *   system_matrix.add(Utilities::fixed_power<2>(time_step * theta), tmp_matrix);
737 *  
738 * @endcode
739 *
740 * Next we compute the right-hand side vector. This is just the
741 * combination of matrix-vector products implied by the description of
742 * @f$-F_h(U^{n,l})@f$ in the introduction.
743 *
744 * @code
745 *   system_rhs = 0.;
746 *  
747 *   Vector<double> tmp_vector(solution.size());
748 *  
749 *   mass_matrix.vmult(system_rhs, solution);
750 *   laplace_matrix.vmult(tmp_vector, solution);
751 *   system_rhs.add(Utilities::fixed_power<2>(time_step * theta), tmp_vector);
752 *  
753 *   mass_matrix.vmult(tmp_vector, old_solution);
754 *   system_rhs.add(-1.0, tmp_vector);
755 *   laplace_matrix.vmult(tmp_vector, old_solution);
756 *   system_rhs.add(Utilities::fixed_power<2>(time_step) * theta * (1 - theta),
757 *   tmp_vector);
758 *  
759 *   system_rhs.add(-time_step, M_x_velocity);
760 *  
761 *   compute_nl_term(old_solution, solution, tmp_vector);
762 *   system_rhs.add(Utilities::fixed_power<2>(time_step) * theta, tmp_vector);
763 *  
764 *   system_rhs *= -1.;
765 *   }
766 *  
767 * @endcode
768 *
769 *
770 * <a name="step_25-SineGordonProblemcompute_nl_term"></a>
771 * <h4>SineGordonProblem::compute_nl_term</h4>
772 *
773
774 *
775 * This function computes the vector @f$S(\cdot,\cdot)@f$, which appears in the
776 * nonlinear term in both equations of the split formulation. This
777 * function not only simplifies the repeated computation of this term, but
778 * it is also a fundamental part of the nonlinear iterative solver that we
779 * use when the time stepping is implicit (i.e. @f$\theta\ne 0@f$). Moreover, we
780 * must allow the function to receive as input an "old" and a "new"
781 * solution. These may not be the actual solutions of the problem stored in
782 * <code>old_solution</code> and <code>solution</code>, but are simply the
783 * two functions we linearize about. For the purposes of this function, let
784 * us call the first two arguments @f$w_{\mathrm{old}}@f$ and @f$w_{\mathrm{new}}@f$
785 * in the documentation of this class below, respectively.
786 *
787
788 *
789 * As a side-note, it is perhaps worth investigating what order quadrature
790 * formula is best suited for this type of integration. Since @f$\sin(\cdot)@f$
791 * is not a polynomial, there are probably no quadrature formulas that can
792 * integrate these terms exactly. It is usually sufficient to just make sure
793 * that the right hand side is integrated up to the same order of accuracy
794 * as the discretization scheme is, but it may be possible to improve on the
795 * constant in the asymptotic statement of convergence by choosing a more
796 * accurate quadrature formula.
797 *
798 * @code
799 *   template <int dim>
800 *   void SineGordonProblem<dim>::compute_nl_term(const Vector<double> &old_data,
801 *   const Vector<double> &new_data,
802 *   Vector<double> &nl_term) const
803 *   {
804 *   nl_term = 0;
805 *   const QGauss<dim> quadrature_formula(fe.degree + 1);
806 *   FEValues<dim> fe_values(fe,
807 *   quadrature_formula,
810 *  
811 *   const unsigned int dofs_per_cell = fe.n_dofs_per_cell();
812 *   const unsigned int n_q_points = quadrature_formula.size();
813 *  
814 *   Vector<double> local_nl_term(dofs_per_cell);
815 *   std::vector<types::global_dof_index> local_dof_indices(dofs_per_cell);
816 *   std::vector<double> old_data_values(n_q_points);
817 *   std::vector<double> new_data_values(n_q_points);
818 *  
819 *   for (const auto &cell : dof_handler.active_cell_iterators())
820 *   {
821 *   local_nl_term = 0;
822 * @endcode
823 *
824 * Once we re-initialize our <code>FEValues</code> instantiation to
825 * the current cell, we make use of the
826 * <code>get_function_values</code> routine to get the values of the
827 * "old" data (presumably at @f$t=t_{n-1}@f$) and the "new" data
828 * (presumably at @f$t=t_n@f$) at the nodes of the chosen quadrature
829 * formula.
830 *
831 * @code
832 *   fe_values.reinit(cell);
833 *   fe_values.get_function_values(old_data, old_data_values);
834 *   fe_values.get_function_values(new_data, new_data_values);
835 *  
836 * @endcode
837 *
838 * Now, we can evaluate @f$\int_K \sin\left[\theta w_{\mathrm{new}} +
839 * (1-\theta) w_{\mathrm{old}}\right] \,\varphi_j\,\mathrm{d}x@f$ using
840 * the desired quadrature formula.
841 *
842 * @code
843 *   for (unsigned int q_point = 0; q_point < n_q_points; ++q_point)
844 *   for (unsigned int i = 0; i < dofs_per_cell; ++i)
845 *   local_nl_term(i) +=
846 *   (std::sin(theta * new_data_values[q_point] +
847 *   (1 - theta) * old_data_values[q_point]) *
848 *   fe_values.shape_value(i, q_point) * fe_values.JxW(q_point));
849 *  
850 * @endcode
851 *
852 * We conclude by adding up the contributions of the integrals over
853 * the cells to the global integral.
854 *
855 * @code
856 *   cell->get_dof_indices(local_dof_indices);
857 *  
858 *   for (unsigned int i = 0; i < dofs_per_cell; ++i)
859 *   nl_term(local_dof_indices[i]) += local_nl_term(i);
860 *   }
861 *   }
862 *  
863 * @endcode
864 *
865 *
866 * <a name="step_25-SineGordonProblemcompute_nl_matrix"></a>
867 * <h4>SineGordonProblem::compute_nl_matrix</h4>
868 *
869
870 *
871 * This is the second function dealing with the nonlinear scheme. It
872 * computes the matrix @f$N(\cdot,\cdot)@f$, which appears in the nonlinear
873 * term in the Jacobian of @f$F(\cdot)@f$. Just as <code>compute_nl_term</code>,
874 * we must allow this function to receive as input an "old" and a "new"
875 * solution, which we again call @f$w_{\mathrm{old}}@f$ and @f$w_{\mathrm{new}}@f$
876 * below, respectively.
877 *
878 * @code
879 *   template <int dim>
880 *   void SineGordonProblem<dim>::compute_nl_matrix(
881 *   const Vector<double> &old_data,
882 *   const Vector<double> &new_data,
883 *   SparseMatrix<double> &nl_matrix) const
884 *   {
885 *   const QGauss<dim> quadrature_formula(fe.degree + 1);
886 *   FEValues<dim> fe_values(fe,
887 *   quadrature_formula,
890 *  
891 *   const unsigned int dofs_per_cell = fe.n_dofs_per_cell();
892 *   const unsigned int n_q_points = quadrature_formula.size();
893 *  
894 *   FullMatrix<double> local_nl_matrix(dofs_per_cell, dofs_per_cell);
895 *   std::vector<types::global_dof_index> local_dof_indices(dofs_per_cell);
896 *   std::vector<double> old_data_values(n_q_points);
897 *   std::vector<double> new_data_values(n_q_points);
898 *  
899 *   for (const auto &cell : dof_handler.active_cell_iterators())
900 *   {
901 *   local_nl_matrix = 0;
902 * @endcode
903 *
904 * Again, first we re-initialize our <code>FEValues</code>
905 * instantiation to the current cell.
906 *
907 * @code
908 *   fe_values.reinit(cell);
909 *   fe_values.get_function_values(old_data, old_data_values);
910 *   fe_values.get_function_values(new_data, new_data_values);
911 *  
912 * @endcode
913 *
914 * Then, we evaluate @f$\int_K \cos\left[\theta w_{\mathrm{new}} +
915 * (1-\theta) w_{\mathrm{old}}\right]\, \varphi_i\,
916 * \varphi_j\,\mathrm{d}x@f$ using the desired quadrature formula.
917 *
918 * @code
919 *   for (unsigned int q_point = 0; q_point < n_q_points; ++q_point)
920 *   for (unsigned int i = 0; i < dofs_per_cell; ++i)
921 *   for (unsigned int j = 0; j < dofs_per_cell; ++j)
922 *   local_nl_matrix(i, j) +=
923 *   (std::cos(theta * new_data_values[q_point] +
924 *   (1 - theta) * old_data_values[q_point]) *
925 *   fe_values.shape_value(i, q_point) *
926 *   fe_values.shape_value(j, q_point) * fe_values.JxW(q_point));
927 *  
928 * @endcode
929 *
930 * Finally, we add up the contributions of the integrals over the
931 * cells to the global integral.
932 *
933 * @code
934 *   cell->get_dof_indices(local_dof_indices);
935 *  
936 *   for (unsigned int i = 0; i < dofs_per_cell; ++i)
937 *   for (unsigned int j = 0; j < dofs_per_cell; ++j)
938 *   nl_matrix.add(local_dof_indices[i],
939 *   local_dof_indices[j],
940 *   local_nl_matrix(i, j));
941 *   }
942 *   }
943 *  
944 *  
945 *  
946 * @endcode
947 *
948 *
949 * <a name="step_25-SineGordonProblemsolve"></a>
950 * <h4>SineGordonProblem::solve</h4>
951 *
952
953 *
954 * As discussed in the Introduction, this function uses the CG iterative
955 * solver on the linear system of equations resulting from the finite
956 * element spatial discretization of each iteration of Newton's method for
957 * the (nonlinear) first equation of the split formulation. The solution to
958 * the system is, in fact, @f$\delta U^{n,l}@f$ so it is stored in
959 * <code>solution_update</code> and used to update <code>solution</code> in
960 * the <code>run</code> function.
961 *
962
963 *
964 * Note that we re-set the solution update to zero before solving for
965 * it. This is not necessary: iterative solvers can start from any point and
966 * converge to the correct solution. If one has a good estimate about the
967 * solution of a linear system, it may be worthwhile to start from that
968 * vector, but as a general observation it is a fact that the starting point
969 * doesn't matter very much: it has to be a very, very good guess to reduce
970 * the number of iterations by more than a few. It turns out that for this
971 * problem, using the previous nonlinear update as a starting point actually
972 * hurts convergence and increases the number of iterations needed, so we
973 * simply set it to zero.
974 *
975
976 *
977 * The function returns the number of iterations it took to converge to a
978 * solution. This number will later be used to generate output on the screen
979 * showing how many iterations were needed in each nonlinear iteration.
980 *
981 * @code
982 *   template <int dim>
983 *   unsigned int SineGordonProblem<dim>::solve()
984 *   {
985 *   SolverControl solver_control(1000, 1e-12 * system_rhs.l2_norm());
986 *   SolverCG<Vector<double>> cg(solver_control);
987 *  
988 *   PreconditionSSOR<SparseMatrix<double>> preconditioner;
989 *   preconditioner.initialize(system_matrix, 1.2);
990 *  
991 *   cg.solve(system_matrix, solution_update, system_rhs, preconditioner);
992 *  
993 *   return solver_control.last_step();
994 *   }
995 *  
996 * @endcode
997 *
998 *
999 * <a name="step_25-SineGordonProblemoutput_results"></a>
1000 * <h4>SineGordonProblem::output_results</h4>
1001 *
1002
1003 *
1004 * This function outputs the results to a file. It is pretty much identical
1005 * to the respective functions in @ref step_23 "step-23" and @ref step_24 "step-24":
1006 *
1007 * @code
1008 *   template <int dim>
1009 *   void SineGordonProblem<dim>::output_results(
1010 *   const unsigned int timestep_number) const
1011 *   {
1012 *   DataOut<dim> data_out;
1013 *  
1014 *   data_out.attach_dof_handler(dof_handler);
1015 *   data_out.add_data_vector(solution, "u");
1016 *   data_out.build_patches();
1017 *  
1018 *   const std::string filename =
1019 *   "solution-" + Utilities::int_to_string(timestep_number, 3) + ".vtu";
1020 *   DataOutBase::VtkFlags vtk_flags;
1022 *   data_out.set_flags(vtk_flags);
1023 *   std::ofstream output(filename);
1024 *   data_out.write_vtu(output);
1025 *   }
1026 *  
1027 * @endcode
1028 *
1029 *
1030 * <a name="step_25-SineGordonProblemrun"></a>
1031 * <h4>SineGordonProblem::run</h4>
1032 *
1033
1034 *
1035 * This function has the top-level control over everything: it runs the
1036 * (outer) time-stepping loop, the (inner) nonlinear-solver loop, and
1037 * outputs the solution after each time step.
1038 *
1039 * @code
1040 *   template <int dim>
1041 *   void SineGordonProblem<dim>::run()
1042 *   {
1043 *   make_grid_and_dofs();
1044 *  
1045 * @endcode
1046 *
1047 * To acknowledge the initial condition, we must use the function @f$u_0(x)@f$
1048 * to compute @f$U^0@f$. To this end, below we will create an object of type
1049 * <code>InitialValues</code>; note that when we create this object (which
1050 * is derived from the <code>Function</code> class), we set its internal
1051 * time variable to @f$t_0@f$, to indicate that the initial condition is a
1052 * function of space and time evaluated at @f$t=t_0@f$.
1053 *
1054
1055 *
1056 * Then we produce @f$U^0@f$ by projecting @f$u_0(x)@f$ onto the grid using
1057 * <code>VectorTools::project</code>. We have to use the same construct
1058 * using hanging node constraints as in @ref step_21 "step-21": the VectorTools::project
1059 * function requires a hanging node constraints object, but to be used we
1060 * first need to close it:
1061 *
1062 * @code
1063 *   {
1064 *   AffineConstraints<double> constraints;
1065 *   constraints.close();
1066 *   VectorTools::project(dof_handler,
1067 *   constraints,
1068 *   QGauss<dim>(fe.degree + 1),
1069 *   InitialValues<dim>(1, time),
1070 *   solution);
1071 *   }
1072 *  
1073 * @endcode
1074 *
1075 * For completeness, we output the zeroth time step to a file just like
1076 * any other time step.
1077 *
1078 * @code
1079 *   output_results(0);
1080 *  
1081 * @endcode
1082 *
1083 * Now we perform the time stepping: at every time step we solve the
1084 * matrix equation(s) corresponding to the finite element discretization
1085 * of the problem, and then advance our solution according to the time
1086 * stepping formulas we discussed in the Introduction.
1087 *
1088 * @code
1089 *   unsigned int timestep_number = 1;
1090 *   for (time += time_step; time <= final_time;
1091 *   time += time_step, ++timestep_number)
1092 *   {
1093 *   old_solution = solution;
1094 *  
1095 *   std::cout << std::endl
1096 *   << "Time step #" << timestep_number << "; "
1097 *   << "advancing to t = " << time << '.' << std::endl;
1098 *  
1099 * @endcode
1100 *
1101 * At the beginning of each time step we must solve the nonlinear
1102 * equation in the split formulation via Newton's method ---
1103 * i.e. solve for @f$\delta U^{n,l}@f$ then compute @f$U^{n,l+1}@f$ and so
1104 * on. The stopping criterion for this nonlinear iteration is that
1105 * @f$\|F_h(U^{n,l})\|_2 \le 10^{-6} \|F_h(U^{n,0})\|_2@f$. Consequently,
1106 * we need to record the norm of the residual in the first iteration.
1107 *
1108
1109 *
1110 * At the end of each iteration, we output to the console how many
1111 * linear solver iterations it took us. When the loop below is done,
1112 * we have (an approximation of) @f$U^n@f$.
1113 *
1114 * @code
1115 *   double initial_rhs_norm = 0.;
1116 *   bool first_iteration = true;
1117 *   do
1118 *   {
1119 *   assemble_system();
1120 *  
1121 *   if (first_iteration == true)
1122 *   initial_rhs_norm = system_rhs.l2_norm();
1123 *  
1124 *   const unsigned int n_iterations = solve();
1125 *  
1126 *   solution += solution_update;
1127 *  
1128 *   if (first_iteration == true)
1129 *   std::cout << " " << n_iterations;
1130 *   else
1131 *   std::cout << '+' << n_iterations;
1132 *   first_iteration = false;
1133 *   }
1134 *   while (system_rhs.l2_norm() > 1e-6 * initial_rhs_norm);
1135 *  
1136 *   std::cout << " CG iterations per nonlinear step." << std::endl;
1137 *  
1138 * @endcode
1139 *
1140 * Upon obtaining the solution to the first equation of the problem at
1141 * @f$t=t_n@f$, we must update the auxiliary velocity variable
1142 * @f$V^n@f$. However, we do not compute and store @f$V^n@f$ since it is not a
1143 * quantity we use directly in the problem. Hence, for simplicity, we
1144 * update @f$MV^n@f$ directly:
1145 *
1146 * @code
1147 *   Vector<double> tmp_vector(solution.size());
1148 *   laplace_matrix.vmult(tmp_vector, solution);
1149 *   M_x_velocity.add(-time_step * theta, tmp_vector);
1150 *  
1151 *   laplace_matrix.vmult(tmp_vector, old_solution);
1152 *   M_x_velocity.add(-time_step * (1 - theta), tmp_vector);
1153 *  
1154 *   compute_nl_term(old_solution, solution, tmp_vector);
1155 *   M_x_velocity.add(-time_step, tmp_vector);
1156 *  
1157 * @endcode
1158 *
1159 * Oftentimes, in particular for fine meshes, we must pick the time
1160 * step to be quite small in order for the scheme to be
1161 * stable. Therefore, there are a lot of time steps during which
1162 * "nothing interesting happens" in the solution. To improve overall
1163 * efficiency -- in particular, speed up the program and save disk
1164 * space -- we only output the solution every
1165 * <code>output_timestep_skip</code> time steps:
1166 *
1167 * @code
1168 *   if (timestep_number % output_timestep_skip == 0)
1169 *   output_results(timestep_number);
1170 *   }
1171 *   }
1172 *   } // namespace Step25
1173 *  
1174 * @endcode
1175 *
1176 *
1177 * <a name="step_25-Thecodemaincodefunction"></a>
1178 * <h3>The <code>main</code> function</h3>
1179 *
1180
1181 *
1182 * This is the main function of the program. It creates an object of top-level
1183 * class and calls its principal function. If exceptions are thrown during the
1184 * execution of the run method of the <code>SineGordonProblem</code> class, we
1185 * catch and report them here. For more information about exceptions the
1186 * reader should consult @ref step_6 "step-6".
1187 *
1188 * @code
1189 *   int main()
1190 *   {
1191 *   try
1192 *   {
1193 *   using namespace Step25;
1194 *  
1195 *   SineGordonProblem<1> sg_problem;
1196 *   sg_problem.run();
1197 *   }
1198 *   catch (std::exception &exc)
1199 *   {
1200 *   std::cerr << std::endl
1201 *   << std::endl
1202 *   << "----------------------------------------------------"
1203 *   << std::endl;
1204 *   std::cerr << "Exception on processing: " << std::endl
1205 *   << exc.what() << std::endl
1206 *   << "Aborting!" << std::endl
1207 *   << "----------------------------------------------------"
1208 *   << std::endl;
1209 *  
1210 *   return 1;
1211 *   }
1212 *   catch (...)
1213 *   {
1214 *   std::cerr << std::endl
1215 *   << std::endl
1216 *   << "----------------------------------------------------"
1217 *   << std::endl;
1218 *   std::cerr << "Unknown exception!" << std::endl
1219 *   << "Aborting!" << std::endl
1220 *   << "----------------------------------------------------"
1221 *   << std::endl;
1222 *   return 1;
1223 *   }
1224 *  
1225 *   return 0;
1226 *   }
1227 * @endcode
1228<a name="step_25-Results"></a><h1>Results</h1>
1229
1230The 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.
1231
1232In the simulations below, we solve the sine-Gordon equation on the interval @f$\Omega =
1233[-10,10]@f$ in 1D and on the square @f$\Omega = [-10,10]\times [-10,10]@f$ in 2D. In
1234each case, the respective grid is refined uniformly 6 times, i.e. @f$h\sim
12352^{-6}@f$.
1236
1237<a name="step_25-An11dSolution"></a><h3>An (1+1)-d Solution</h3>
1238
1239The first example we discuss is the so-called 1D (stationary) breather
1240solution of the sine-Gordon equation. The breather has the following
1241closed-form expression, as mentioned in the Introduction:
1242\f[
1243u_{\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),
1244\f]
1245where @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.
1246
1247<img src="https://www.dealii.org/images/steps/developer/step-25.1d-breather.gif" alt="Animation of the 1D stationary breather.">
1248
1249Though not shown how to do this in the program, another way to visualize the
1250(1+1)-d solution is to use output generated by the DataOutStack class; it
1251allows to "stack" the solutions of individual time steps, so that we get
12522D space-time graphs from 1D time-dependent
1253solutions. This produces the space-time plot below instead of the animation
1254above.
1255
1256<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.">
1257
1258Furthermore, since the breather is an analytical solution of the sine-Gordon
1259equation, we can use it to validate our code, although we have to assume that
1260the error introduced by our choice of Neumann boundary conditions is small
1261compared to the numerical error. Under this assumption, one could use the
1262VectorTools::integrate_difference function to compute the difference between
1263the numerical solution and the function described by the
1264<code>ExactSolution</code> class of this program. For the
1265simulation shown in the two images above, the @f$L^2@f$ norm of the error in the
1266finite element solution at each time step remained on the order of
1267@f$10^{-2}@f$. Hence, we can conclude that the numerical method has been
1268implemented correctly in the program.
1269
1270
1271<a name="step_25-Afew21DSolutions"></a><h3>A few (2+1)D Solutions</h3>
1272
1273
1274The 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:
1275 @f[
1276 u(x,y,t) = 4 \arctan \left[a_0 e^{s\xi}\right]
1277 @f]
1278with
1279 @f[
1280 \xi = x \cos\vartheta + \sin(\vartheta) (y\cosh\lambda + t\sinh \lambda)
1281 @f]
1282where @f$a_0@f$, @f$\vartheta@f$ and @f$\lambda@f$ are constants. In the simulation below
1283we have chosen @f$a_0=\lambda=1@f$. Notice that if @f$\vartheta=\pi@f$ the kink is
1284stationary, hence it would make a good solution against which we can
1285validate the program in 2D because no reflections off the boundary of the
1286domain occur.
1287
1288The 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.
1289
1290<img src="https://www.dealii.org/images/steps/developer/step-25.2d-kink.png" alt="Stationary 2D kink.">
1291
1292Now that we have validated the code in 1D and 2D, we move to a problem where the analytical solution is unknown.
1293
1294To this end, we rotate the kink solution discussed above about the @f$z@f$
1295axis: we let @f$\vartheta=\frac{\pi}{4}@f$. The latter results in a
1296solitary wave that is not aligned with the grid, so reflections occur
1297at the boundaries of the domain immediately. For the simulation shown
1298below, we have taken @f$u_0(x)=u_{\mathrm{kink}}(x,t_0)@f$,
1299@f$\theta=\frac{2}{3}@f$, @f$k=20h@f$, @f$t_0=0@f$ and @f$t_f=20@f$. Moreover, we had
1300to pick @f$\theta=\frac{2}{3}@f$ because for any @f$\theta\le\frac{1}{2}@f$
1301oscillations arose at the boundary, which are likely due to the scheme
1302and not the equation, thus picking a value of @f$\theta@f$ a good bit into
1303the "exponentially damped" spectrum of the time stepping schemes
1304assures these oscillations are not created.
1305
1306<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.">
1307
1308Another interesting solution to the sine-Gordon equation (which cannot be
1309obtained analytically) can be produced by using two 1D breathers to construct
1310the following separable 2D initial condition:
1311\f[
1312 u_0(x) =
1313 u_{\mathrm{pseudobreather}}(x,t_0) =
1314 16\arctan \left(
1315 \frac{m}{\sqrt{1-m^2}}
1316 \frac{\sin\left(\sqrt{1-m^2}t_0\right)}{\cosh(mx_1)} \right)
1317 \arctan \left(
1318 \frac{m}{\sqrt{1-m^2}}
1319 \frac{\sin\left(\sqrt{1-m^2}t_0\right)}{\cosh(mx_2)} \right),
1320\f]
1321where @f$x=(x_1,x_2)\in{R}^2@f$, @f$m=0.5<1@f$ as in the 1D case we discussed
1322above. For the simulation shown below, we have chosen @f$\theta=\frac{1}{2}@f$,
1323@f$k=10h@f$, @f$t_0=-5.4414@f$ and @f$t_f=2.7207@f$. The solution is pretty interesting
1324--- it acts like a breather (as far as the pictures are concerned); however,
1325it appears to break up and reassemble, rather than just oscillate.
1326
1327<img src="https://www.dealii.org/images/steps/developer/step-25.2d-pseudobreather.gif" alt="Animation of a 2D pseudobreather.">
1328
1329
1330<a name="step-25-extensions"></a>
1331<a name="step_25-Possibilitiesforextensions"></a><h3>Possibilities for extensions</h3>
1332
1333
1334It is instructive to change the initial conditions. Most choices will not lead
1335to solutions that stay localized (in the soliton community, such
1336solutions are called "stationary", though the solution does change
1337with time), but lead to solutions where the wave-like
1338character of the equation dominates and a wave travels away from the location
1339of a localized initial condition. For example, it is worth playing around with
1340the <code>InitialValues</code> class, by replacing the call to the
1341<code>ExactSolution</code> class by something like this function:
1342@f[
1343 u_0(x,y) = \cos\left(\frac x2\right)\cos\left(\frac y2\right)
1344@f]
1345if @f$|x|,|y|\le \frac\pi 2@f$, and @f$u_0(x,y)=0@f$ outside this region.
1346
1347A second area would be to investigate whether the scheme is
1348energy-preserving. For the pure wave equation, discussed in @ref
1349step_23 "step-23", this is the case if we choose the time stepping
1350parameter such that we get the Crank-Nicolson scheme. One could do a
1351similar thing here, noting that the energy in the sine-Gordon solution
1352is defined as
1353@f[
1354 E(t) = \frac 12 \int_\Omega \left(\frac{\partial u}{\partial
1355 t}\right)^2
1356 + \left(\nabla u\right)^2 + 2 (1-\cos u) \; dx.
1357@f]
1358(We use @f$1-\cos u@f$ instead of @f$-\cos u@f$ in the formula to ensure that all
1359contributions to the energy are positive, and so that decaying solutions have
1360finite energy on unbounded domains.)
1361
1362Beyond this, there are two obvious areas:
1363
1364- Clearly, adaptivity (i.e. time-adaptive grids) would be of interest
1365 to problems like these. Their complexity leads us to leave this out
1366 of this program again, though the general comments in the
1367 introduction of @ref step_23 "step-23" remain true.
1368
1369- Faster schemes to solve this problem. While computers today are
1370 plenty fast enough to solve 2d and, frequently, even 3d stationary
1371 problems within not too much time, time dependent problems present
1372 an entirely different class of problems. We address this topic in
1373 @ref step_48 "step-48" where we show how to solve this problem in parallel and
1374 without assembling or inverting any matrix at all.
1375 *
1376 *
1377<a name="step_25-PlainProg"></a>
1378<h1> The plain program</h1>
1379@include "step-25.cc"
1380*/
void attach_dof_handler(const DoFHandler< dim, spacedim > &)
void reinit(const TriaIterator< DoFCellAccessor< dim, spacedim, level_dof_access > > &cell)
void initialize(const MatrixType &A, const AdditionalData &parameters=AdditionalData())
Point< 2 > second
Definition grid_out.cc:4624
Point< 2 > first
Definition grid_out.cc:4623
unsigned int level
Definition grid_out.cc:4626
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())
Definition loop.h:564
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)
@ update_values
Shape function values.
@ update_JxW_values
Transformed quadrature weights.
@ update_quadrature_points
Transformed quadrature points.
#define DEAL_II_NOT_IMPLEMENTED()
std::vector< index_type > data
Definition mpi.cc:735
std::size_t size
Definition mpi.cc:734
std::vector< value_type > split(const typename ::Triangulation< dim, spacedim >::cell_iterator &parent, const value_type parent_value)
const Event initial
Definition event.cc:64
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.)
Definition l2.h:57
void create_mass_matrix(const Mapping< dim, spacedim > &mapping, const DoFHandler< dim, spacedim > &dof, const Quadrature< dim > &q, MatrixType &matrix, const Function< spacedim, typename MatrixType::value_type > *const a=nullptr, const AffineConstraints< typename MatrixType::value_type > &constraints=AffineConstraints< typename MatrixType::value_type >())
void create_laplace_matrix(const Mapping< dim, spacedim > &mapping, const DoFHandler< dim, spacedim > &dof, const Quadrature< dim > &q, MatrixType &matrix, const Function< spacedim, typename MatrixType::value_type > *const a=nullptr, const AffineConstraints< typename MatrixType::value_type > &constraints=AffineConstraints< typename MatrixType::value_type >())
Point< spacedim > point(const gp_Pnt &p, const double tolerance=1e-10)
Definition utilities.cc:191
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)
VectorType::value_type * end(VectorType &V)
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 get_time()
std::string int_to_string(const unsigned int value, const unsigned int digits=numbers::invalid_unsigned_int)
Definition utilities.cc:470
void project(const Mapping< dim, spacedim > &mapping, const DoFHandler< dim, spacedim > &dof, const AffineConstraints< typename VectorType::value_type > &constraints, const Quadrature< dim > &quadrature, const Function< spacedim, typename VectorType::value_type > &function, VectorType &vec, const bool enforce_zero_boundary=false, const Quadrature< dim - 1 > &q_boundary=(dim > 1 ? QGauss< dim - 1 >(2) :Quadrature< dim - 1 >()), const bool project_to_boundary_first=false)
int(&) functions(const void *v1, const void *v2)
static constexpr double PI
Definition numbers.h:254
::VectorizedArray< Number, width > exp(const ::VectorizedArray< Number, width > &)
inline ::VectorizedArray< Number, width > sinh(const ::VectorizedArray< Number, width > &x)
::VectorizedArray< Number, width > cos(const ::VectorizedArray< Number, width > &)
::VectorizedArray< Number, width > sin(const ::VectorizedArray< Number, width > &)
inline ::VectorizedArray< Number, width > cosh(const ::VectorizedArray< Number, width > &x)
::VectorizedArray< Number, width > sqrt(const ::VectorizedArray< Number, width > &)
inline ::VectorizedArray< Number, width > atan(const ::VectorizedArray< Number, width > &x)
const ::parallel::distributed::Triangulation< dim, spacedim > * triangulation
DataOutBase::CompressionLevel compression_level
void advance(std::tuple< I1, I2 > &t, const unsigned int n)