From 089ce11e382446338d372262d9706c5621c18446 Mon Sep 17 00:00:00 2001 From: bangerth Date: Fri, 9 Sep 2011 03:55:52 +0000 Subject: [PATCH] Move things local to each program into a local namespace. git-svn-id: https://svn.dealii.org/trunk@24293 0785d39b-7218-0410-832d-ea1e28bc413d --- deal.II/examples/step-30/step-30.cc | 2090 +++++----- deal.II/examples/step-31/step-31.cc | 5188 ++++++++++++------------- deal.II/examples/step-33/step-33.cc | 5440 ++++++++++++++------------- deal.II/examples/step-34/step-34.cc | 2553 ++++++------- deal.II/examples/step-35/step-35.cc | 2633 ++++++------- deal.II/examples/step-36/step-36.cc | 963 ++--- deal.II/examples/step-38/step-38.cc | 1023 ++--- deal.II/examples/step-39/step-39.cc | 1717 ++++----- 8 files changed, 10832 insertions(+), 10775 deletions(-) diff --git a/deal.II/examples/step-30/step-30.cc b/deal.II/examples/step-30/step-30.cc index 8d096406a6..bf85b9f58b 100644 --- a/deal.II/examples/step-30/step-30.cc +++ b/deal.II/examples/step-30/step-30.cc @@ -4,7 +4,7 @@ /* $Id$ */ /* Version: $Name$ */ /* */ -/* Copyright (C) 2007, 2008 by the deal.II authors */ +/* Copyright (C) 2007, 2008, 2011 by the deal.II authors */ /* */ /* This file is subject to QPL and may not be distributed */ /* without copyright and license information. Please refer */ @@ -43,1110 +43,1118 @@ // The last step is as in all // previous programs: -using namespace dealii; - - // @sect3{Equation data} - // - // The classes describing equation data and the - // actual assembly of individual terms are - // almost entirely copied from step-12. We will - // comment on differences. -template -class RHS: public Function +namespace Step30 { - public: - virtual void value_list (const std::vector > &points, - std::vector &values, - const unsigned int component=0) const; -}; - - -template -class BoundaryValues: public Function -{ - public: - virtual void value_list (const std::vector > &points, - std::vector &values, - const unsigned int component=0) const; -}; - - -template -class Beta -{ - public: - Beta () {} - void value_list (const std::vector > &points, - std::vector > &values) const; -}; - - -template -void RHS::value_list(const std::vector > &points, - std::vector &values, - const unsigned int) const -{ - Assert(values.size()==points.size(), - ExcDimensionMismatch(values.size(),points.size())); - - for (unsigned int i=0; i -void Beta::value_list(const std::vector > &points, - std::vector > &values) const -{ - Assert(values.size()==points.size(), - ExcDimensionMismatch(values.size(),points.size())); - - for (unsigned int i=0; i 0) - { - values[i](0) = -points[i](1); - values[i](1) = points[i](0); - } - else - { - values[i] = Point(); - values[i](0) = -points[i](1); - } - } -} + using namespace dealii; + + // @sect3{Equation data} + // + // The classes describing equation data and the + // actual assembly of individual terms are + // almost entirely copied from step-12. We will + // comment on differences. + template + class RHS: public Function + { + public: + virtual void value_list (const std::vector > &points, + std::vector &values, + const unsigned int component=0) const; + }; + + + template + class BoundaryValues: public Function + { + public: + virtual void value_list (const std::vector > &points, + std::vector &values, + const unsigned int component=0) const; + }; + + + template + class Beta + { + public: + Beta () {} + void value_list (const std::vector > &points, + std::vector > &values) const; + }; + + + template + void RHS::value_list(const std::vector > &points, + std::vector &values, + const unsigned int) const + { + Assert(values.size()==points.size(), + ExcDimensionMismatch(values.size(),points.size())); + + for (unsigned int i=0; i + void Beta::value_list(const std::vector > &points, + std::vector > &values) const + { + Assert(values.size()==points.size(), + ExcDimensionMismatch(values.size(),points.size())); + + for (unsigned int i=0; i 0) + { + values[i](0) = -points[i](1); + values[i](1) = points[i](0); + } + else + { + values[i] = Point(); + values[i](0) = -points[i](1); + } + } + } -template -void BoundaryValues::value_list(const std::vector > &points, + template + void BoundaryValues::value_list(const std::vector > &points, std::vector &values, const unsigned int) const -{ - Assert(values.size()==points.size(), - ExcDimensionMismatch(values.size(),points.size())); + { + Assert(values.size()==points.size(), + ExcDimensionMismatch(values.size(),points.size())); - for (unsigned int i=0; i -class DGTransportEquation -{ - public: - DGTransportEquation(); - - void assemble_cell_term(const FEValues& fe_v, - FullMatrix &ui_vi_matrix, - Vector &cell_vector) const; - - void assemble_boundary_term(const FEFaceValues& fe_v, - FullMatrix &ui_vi_matrix, - Vector &cell_vector) const; - - void assemble_face_term2(const FEFaceValuesBase& fe_v, - const FEFaceValuesBase& fe_v_neighbor, - FullMatrix &ui_vi_matrix, - FullMatrix &ue_vi_matrix, - FullMatrix &ui_ve_matrix, - FullMatrix &ue_ve_matrix) const; - private: - const Beta beta_function; - const RHS rhs_function; - const BoundaryValues boundary_function; -}; - - - // Likewise, the constructor of the - // class as well as the functions - // assembling the terms corresponding - // to cell interiors and boundary - // faces are unchanged from - // before. The function that - // assembles face terms between cells - // also did not change because all it - // does is operate on two objects of - // type FEFaceValuesBase (which is - // the base class of both - // FEFaceValues and - // FESubfaceValues). Where these - // objects come from, i.e. how they - // are initialized, is of no concern - // to this function: it simply - // assumes that the quadrature points - // on faces or subfaces represented - // by the two objects correspond to - // the same points in physical space. -template -DGTransportEquation::DGTransportEquation () - : - beta_function (), - rhs_function (), - boundary_function () -{} - - -template -void DGTransportEquation::assemble_cell_term( - const FEValues &fe_v, - FullMatrix &ui_vi_matrix, - Vector &cell_vector) const -{ - const std::vector &JxW = fe_v.get_JxW_values (); - - std::vector > beta (fe_v.n_quadrature_points); - std::vector rhs (fe_v.n_quadrature_points); - - beta_function.value_list (fe_v.get_quadrature_points(), beta); - rhs_function.value_list (fe_v.get_quadrature_points(), rhs); - - for (unsigned int point=0; point -void DGTransportEquation::assemble_boundary_term( - const FEFaceValues& fe_v, - FullMatrix &ui_vi_matrix, - Vector &cell_vector) const -{ - const std::vector &JxW = fe_v.get_JxW_values (); - const std::vector > &normals = fe_v.get_normal_vectors (); - - std::vector > beta (fe_v.n_quadrature_points); - std::vector g(fe_v.n_quadrature_points); - - beta_function.value_list (fe_v.get_quadrature_points(), beta); - boundary_function.value_list (fe_v.get_quadrature_points(), g); - - for (unsigned int point=0; point0) - for (unsigned int i=0; i -void DGTransportEquation::assemble_face_term2( - const FEFaceValuesBase& fe_v, - const FEFaceValuesBase& fe_v_neighbor, - FullMatrix &ui_vi_matrix, - FullMatrix &ue_vi_matrix, - FullMatrix &ui_ve_matrix, - FullMatrix &ue_ve_matrix) const -{ - const std::vector &JxW = fe_v.get_JxW_values (); - const std::vector > &normals = fe_v.get_normal_vectors (); - - std::vector > beta (fe_v.n_quadrature_points); - - beta_function.value_list (fe_v.get_quadrature_points(), beta); - - for (unsigned int point=0; point0) + } + + + // @sect3{Class: DGTransportEquation} + // + // This declaration of this + // class is utterly unaffected by our + // current changes. The only + // substantial change is that we use + // only the second assembly scheme + // described in step-12. + template + class DGTransportEquation + { + public: + DGTransportEquation(); + + void assemble_cell_term(const FEValues& fe_v, + FullMatrix &ui_vi_matrix, + Vector &cell_vector) const; + + void assemble_boundary_term(const FEFaceValues& fe_v, + FullMatrix &ui_vi_matrix, + Vector &cell_vector) const; + + void assemble_face_term2(const FEFaceValuesBase& fe_v, + const FEFaceValuesBase& fe_v_neighbor, + FullMatrix &ui_vi_matrix, + FullMatrix &ue_vi_matrix, + FullMatrix &ui_ve_matrix, + FullMatrix &ue_ve_matrix) const; + private: + const Beta beta_function; + const RHS rhs_function; + const BoundaryValues boundary_function; + }; + + + // Likewise, the constructor of the + // class as well as the functions + // assembling the terms corresponding + // to cell interiors and boundary + // faces are unchanged from + // before. The function that + // assembles face terms between cells + // also did not change because all it + // does is operate on two objects of + // type FEFaceValuesBase (which is + // the base class of both + // FEFaceValues and + // FESubfaceValues). Where these + // objects come from, i.e. how they + // are initialized, is of no concern + // to this function: it simply + // assumes that the quadrature points + // on faces or subfaces represented + // by the two objects correspond to + // the same points in physical space. + template + DGTransportEquation::DGTransportEquation () + : + beta_function (), + rhs_function (), + boundary_function () + {} + + + template + void DGTransportEquation::assemble_cell_term( + const FEValues &fe_v, + FullMatrix &ui_vi_matrix, + Vector &cell_vector) const + { + const std::vector &JxW = fe_v.get_JxW_values (); + + std::vector > beta (fe_v.n_quadrature_points); + std::vector rhs (fe_v.n_quadrature_points); + + beta_function.value_list (fe_v.get_quadrature_points(), beta); + rhs_function.value_list (fe_v.get_quadrature_points(), rhs); + + for (unsigned int point=0; point -class DGMethod -{ - public: - DGMethod (const bool anisotropic); - ~DGMethod (); - - void run (); - - private: - void setup_system (); - void assemble_system1 (); - void assemble_system2 (); - void solve (Vector &solution); - void refine_grid (); - void set_anisotropic_flags (); - void output_results (const unsigned int cycle) const; - - Triangulation triangulation; - const MappingQ1 mapping; - // Again we want to use DG elements of - // degree 1 (but this is only specified in - // the constructor). If you want to use a - // DG method of a different degree replace - // 1 in the constructor by the new degree. - const unsigned int degree; - FE_DGQ fe; - DoFHandler dof_handler; - - SparsityPattern sparsity_pattern; - SparseMatrix system_matrix; - // This is new, the threshold value used in - // the evaluation of the anisotropic jump - // indicator explained in the - // introduction. Its value is set to 3.0 in - // the constructor, but it can easily be - // changed to a different value greater - // than 1. - const double anisotropic_threshold_ratio; - // This is a bool flag indicating whether - // anisotropic refinement shall be used or - // not. It is set by the constructor, which - // takes an argument of the same name. - const bool anisotropic; - - const QGauss quadrature; - const QGauss face_quadrature; - - Vector solution2; - Vector right_hand_side; - - const DGTransportEquation dg; -}; - - -template -DGMethod::DGMethod (const bool anisotropic) - : - mapping (), - // Change here for DG - // methods of - // different degrees. - degree(1), - fe (degree), - dof_handler (triangulation), - anisotropic_threshold_ratio(3.), - anisotropic(anisotropic), - // As beta is a - // linear function, - // we can choose the - // degree of the - // quadrature for - // which the - // resulting - // integration is - // correct. Thus, we - // choose to use - // degree+1 - // gauss points, - // which enables us - // to integrate - // exactly - // polynomials of - // degree - // 2*degree+1, - // enough for all the - // integrals we will - // perform in this - // program. - quadrature (degree+1), - face_quadrature (degree+1), - dg () -{} - - -template -DGMethod::~DGMethod () -{ - dof_handler.clear (); -} - - -template -void DGMethod::setup_system () -{ - dof_handler.distribute_dofs (fe); - sparsity_pattern.reinit (dof_handler.n_dofs(), - dof_handler.n_dofs(), - (GeometryInfo::faces_per_cell - *GeometryInfo::max_children_per_face+1)*fe.dofs_per_cell); - - DoFTools::make_flux_sparsity_pattern (dof_handler, sparsity_pattern); - - sparsity_pattern.compress(); - - system_matrix.reinit (sparsity_pattern); - - solution2.reinit (dof_handler.n_dofs()); - right_hand_side.reinit (dof_handler.n_dofs()); -} + } - // @sect4{Function: assemble_system2} - // - // We proceed with the - // assemble_system2 function that - // implements the DG discretization in its - // second version. This function is very - // similar to the assemble_system2 - // function from step-12, even the four cases - // considered for the neighbor-relations of a - // cell are the same, namely a) cell is at the - // boundary, b) there are finer neighboring - // cells, c) the neighbor is neither coarser - // nor finer and d) the neighbor is coarser. - // However, the way in which we decide upon - // which case we have are modified in the way - // described in the introduction. -template -void DGMethod::assemble_system2 () -{ - const unsigned int dofs_per_cell = dof_handler.get_fe().dofs_per_cell; - std::vector dofs (dofs_per_cell); - std::vector dofs_neighbor (dofs_per_cell); - - const UpdateFlags update_flags = update_values - | update_gradients - | update_quadrature_points - | update_JxW_values; - - const UpdateFlags face_update_flags = update_values - | update_quadrature_points - | update_JxW_values - | update_normal_vectors; - - const UpdateFlags neighbor_face_update_flags = update_values; - - FEValues fe_v ( - mapping, fe, quadrature, update_flags); - FEFaceValues fe_v_face ( - mapping, fe, face_quadrature, face_update_flags); - FESubfaceValues fe_v_subface ( - mapping, fe, face_quadrature, face_update_flags); - FEFaceValues fe_v_face_neighbor ( - mapping, fe, face_quadrature, neighbor_face_update_flags); - - - FullMatrix ui_vi_matrix (dofs_per_cell, dofs_per_cell); - FullMatrix ue_vi_matrix (dofs_per_cell, dofs_per_cell); - - FullMatrix ui_ve_matrix (dofs_per_cell, dofs_per_cell); - FullMatrix ue_ve_matrix (dofs_per_cell, dofs_per_cell); - - Vector cell_vector (dofs_per_cell); - - typename DoFHandler::active_cell_iterator - cell = dof_handler.begin_active(), - endc = dof_handler.end(); - for (;cell!=endc; ++cell) - { - ui_vi_matrix = 0; - cell_vector = 0; - - fe_v.reinit (cell); + template + void DGTransportEquation::assemble_boundary_term( + const FEFaceValues& fe_v, + FullMatrix &ui_vi_matrix, + Vector &cell_vector) const + { + const std::vector &JxW = fe_v.get_JxW_values (); + const std::vector > &normals = fe_v.get_normal_vectors (); - dg.assemble_cell_term(fe_v, - ui_vi_matrix, - cell_vector); - - cell->get_dof_indices (dofs); - - for (unsigned int face_no=0; face_no::faces_per_cell; ++face_no) - { - typename DoFHandler::face_iterator face= - cell->face(face_no); + std::vector > beta (fe_v.n_quadrature_points); + std::vector g(fe_v.n_quadrature_points); - // Case a) - if (face->at_boundary()) - { - fe_v_face.reinit (cell, face_no); + beta_function.value_list (fe_v.get_quadrature_points(), beta); + boundary_function.value_list (fe_v.get_quadrature_points(), g); - dg.assemble_boundary_term(fe_v_face, - ui_vi_matrix, - cell_vector); - } - else - { - Assert (cell->neighbor(face_no).state() == IteratorState::valid, - ExcInternalError()); - typename DoFHandler::cell_iterator neighbor= - cell->neighbor(face_no); - // Case b), we decide that there - // are finer cells as neighbors - // by asking the face, whether it - // has children. if so, then - // there must also be finer cells - // which are children or farther - // offsprings of our neighbor. - if (face->has_children()) - { - // We need to know, which of - // the neighbors faces points - // in the direction of our - // cell. Using the @p - // neighbor_face_no function - // we get this information - // for both coarser and - // non-coarser neighbors. - const unsigned int neighbor2= - cell->neighbor_face_no(face_no); - - // Now we loop over all - // subfaces, i.e. the - // children and possibly - // grandchildren of the - // current face. - for (unsigned int subface_no=0; - subface_nonumber_of_children(); ++subface_no) - { - // To get the cell behind - // the current subface we - // can use the @p - // neighbor_child_on_subface - // function. it takes - // care of all the - // complicated situations - // of anisotropic - // refinement and - // non-standard faces. - typename DoFHandler::cell_iterator neighbor_child - = cell->neighbor_child_on_subface (face_no, subface_no); - Assert (!neighbor_child->has_children(), ExcInternalError()); - - // The remaining part of - // this case is - // unchanged. - ue_vi_matrix = 0; - ui_ve_matrix = 0; - ue_ve_matrix = 0; - - fe_v_subface.reinit (cell, face_no, subface_no); - fe_v_face_neighbor.reinit (neighbor_child, neighbor2); - - dg.assemble_face_term2(fe_v_subface, - fe_v_face_neighbor, - ui_vi_matrix, - ue_vi_matrix, - ui_ve_matrix, - ue_ve_matrix); - - neighbor_child->get_dof_indices (dofs_neighbor); - - for (unsigned int i=0; ineighbor_is_coarser(face_no) && - (neighbor->index() > cell->index() || - (neighbor->level() < cell->level() && - neighbor->index() == cell->index()))) - { - // Here we know, that the - // neigbor is not coarser - // so we can use the - // usual @p - // neighbor_of_neighbor - // function. However, we - // could also use the - // more general @p - // neighbor_face_no - // function. - const unsigned int neighbor2=cell->neighbor_of_neighbor(face_no); - - ue_vi_matrix = 0; - ui_ve_matrix = 0; - ue_ve_matrix = 0; - - fe_v_face.reinit (cell, face_no); - fe_v_face_neighbor.reinit (neighbor, neighbor2); - - dg.assemble_face_term2(fe_v_face, - fe_v_face_neighbor, - ui_vi_matrix, - ue_vi_matrix, - ui_ve_matrix, - ue_ve_matrix); - - neighbor->get_dof_indices (dofs_neighbor); - - for (unsigned int i=0; i0) + for (unsigned int i=0; i + void DGTransportEquation::assemble_face_term2( + const FEFaceValuesBase& fe_v, + const FEFaceValuesBase& fe_v_neighbor, + FullMatrix &ui_vi_matrix, + FullMatrix &ue_vi_matrix, + FullMatrix &ui_ve_matrix, + FullMatrix &ue_ve_matrix) const + { + const std::vector &JxW = fe_v.get_JxW_values (); + const std::vector > &normals = fe_v.get_normal_vectors (); - // @sect3{Solver} - // - // For this simple problem we use the simple - // Richardson iteration again. The solver is - // completely unaffected by our anisotropic - // changes. -template -void DGMethod::solve (Vector &solution) -{ - SolverControl solver_control (1000, 1e-12, false, false); - SolverRichardson<> solver (solver_control); + std::vector > beta (fe_v.n_quadrature_points); - PreconditionBlockSSOR > preconditioner; + beta_function.value_list (fe_v.get_quadrature_points(), beta); - preconditioner.initialize(system_matrix, fe.dofs_per_cell); + for (unsigned int point=0; point0) + { + for (unsigned int i=0; i + class DGMethod + { + public: + DGMethod (const bool anisotropic); + ~DGMethod (); + + void run (); + + private: + void setup_system (); + void assemble_system1 (); + void assemble_system2 (); + void solve (Vector &solution); + void refine_grid (); + void set_anisotropic_flags (); + void output_results (const unsigned int cycle) const; + + Triangulation triangulation; + const MappingQ1 mapping; + // Again we want to use DG elements of + // degree 1 (but this is only specified in + // the constructor). If you want to use a + // DG method of a different degree replace + // 1 in the constructor by the new degree. + const unsigned int degree; + FE_DGQ fe; + DoFHandler dof_handler; + + SparsityPattern sparsity_pattern; + SparseMatrix system_matrix; + // This is new, the threshold value used in + // the evaluation of the anisotropic jump + // indicator explained in the + // introduction. Its value is set to 3.0 in + // the constructor, but it can easily be + // changed to a different value greater + // than 1. + const double anisotropic_threshold_ratio; + // This is a bool flag indicating whether + // anisotropic refinement shall be used or + // not. It is set by the constructor, which + // takes an argument of the same name. + const bool anisotropic; + + const QGauss quadrature; + const QGauss face_quadrature; + + Vector solution2; + Vector right_hand_side; + + const DGTransportEquation dg; + }; + + + template + DGMethod::DGMethod (const bool anisotropic) + : + mapping (), + // Change here for DG + // methods of + // different degrees. + degree(1), + fe (degree), + dof_handler (triangulation), + anisotropic_threshold_ratio(3.), + anisotropic(anisotropic), + // As beta is a + // linear function, + // we can choose the + // degree of the + // quadrature for + // which the + // resulting + // integration is + // correct. Thus, we + // choose to use + // degree+1 + // gauss points, + // which enables us + // to integrate + // exactly + // polynomials of + // degree + // 2*degree+1, + // enough for all the + // integrals we will + // perform in this + // program. + quadrature (degree+1), + face_quadrature (degree+1), + dg () + {} + + + template + DGMethod::~DGMethod () + { + dof_handler.clear (); + } + + + template + void DGMethod::setup_system () + { + dof_handler.distribute_dofs (fe); + sparsity_pattern.reinit (dof_handler.n_dofs(), + dof_handler.n_dofs(), + (GeometryInfo::faces_per_cell + *GeometryInfo::max_children_per_face+1)*fe.dofs_per_cell); + + DoFTools::make_flux_sparsity_pattern (dof_handler, sparsity_pattern); + + sparsity_pattern.compress(); + + system_matrix.reinit (sparsity_pattern); + + solution2.reinit (dof_handler.n_dofs()); + right_hand_side.reinit (dof_handler.n_dofs()); + } + + + // @sect4{Function: assemble_system2} + // + // We proceed with the + // assemble_system2 function that + // implements the DG discretization in its + // second version. This function is very + // similar to the assemble_system2 + // function from step-12, even the four cases + // considered for the neighbor-relations of a + // cell are the same, namely a) cell is at the + // boundary, b) there are finer neighboring + // cells, c) the neighbor is neither coarser + // nor finer and d) the neighbor is coarser. + // However, the way in which we decide upon + // which case we have are modified in the way + // described in the introduction. + template + void DGMethod::assemble_system2 () + { + const unsigned int dofs_per_cell = dof_handler.get_fe().dofs_per_cell; + std::vector dofs (dofs_per_cell); + std::vector dofs_neighbor (dofs_per_cell); + + const UpdateFlags update_flags = update_values + | update_gradients + | update_quadrature_points + | update_JxW_values; + + const UpdateFlags face_update_flags = update_values + | update_quadrature_points + | update_JxW_values + | update_normal_vectors; + + const UpdateFlags neighbor_face_update_flags = update_values; + + FEValues fe_v ( + mapping, fe, quadrature, update_flags); + FEFaceValues fe_v_face ( + mapping, fe, face_quadrature, face_update_flags); + FESubfaceValues fe_v_subface ( + mapping, fe, face_quadrature, face_update_flags); + FEFaceValues fe_v_face_neighbor ( + mapping, fe, face_quadrature, neighbor_face_update_flags); + + + FullMatrix ui_vi_matrix (dofs_per_cell, dofs_per_cell); + FullMatrix ue_vi_matrix (dofs_per_cell, dofs_per_cell); + + FullMatrix ui_ve_matrix (dofs_per_cell, dofs_per_cell); + FullMatrix ue_ve_matrix (dofs_per_cell, dofs_per_cell); + + Vector cell_vector (dofs_per_cell); + + typename DoFHandler::active_cell_iterator + cell = dof_handler.begin_active(), + endc = dof_handler.end(); + for (;cell!=endc; ++cell) + { + ui_vi_matrix = 0; + cell_vector = 0; - solver.solve (system_matrix, solution, right_hand_side, - preconditioner); -} + fe_v.reinit (cell); + dg.assemble_cell_term(fe_v, + ui_vi_matrix, + cell_vector); - // @sect3{Refinement} - // - // We refine the grid according to the same - // simple refinement criterion used in step-12, - // namely an approximation to the - // gradient of the solution. -template -void DGMethod::refine_grid () -{ - Vector gradient_indicator (triangulation.n_active_cells()); - - // We approximate the gradient, - DerivativeApproximation::approximate_gradient (mapping, - dof_handler, - solution2, - gradient_indicator); - - // and scale it to obtain an error indicator. - typename DoFHandler::active_cell_iterator - cell = dof_handler.begin_active(), - endc = dof_handler.end(); - for (unsigned int cell_no=0; cell!=endc; ++cell, ++cell_no) - gradient_indicator(cell_no)*=std::pow(cell->diameter(), 1+1.0*dim/2); - // Then we use this indicator to flag the 30 - // percent of the cells with highest error - // indicator to be refined. - GridRefinement::refine_and_coarsen_fixed_number (triangulation, - gradient_indicator, - 0.3, 0.1); - // Now the refinement flags are set for those - // cells with a large error indicator. If - // nothing is done to change this, those - // cells will be refined isotropically. If - // the @p anisotropic flag given to this - // function is set, we now call the - // set_anisotropic_flags() function, which - // uses the jump indicator to reset some of - // the refinement flags to anisotropic - // refinement. - if (anisotropic) - set_anisotropic_flags(); - // Now execute the refinement considering - // anisotropic as well as isotropic - // refinement flags. - triangulation.execute_coarsening_and_refinement (); -} + cell->get_dof_indices (dofs); - // Once an error indicator has been evaluated - // and the cells with largerst error are - // flagged for refinement we want to loop over - // the flagged cells again to decide whether - // they need isotropic refinemnt or whether - // anisotropic refinement is more - // appropriate. This is the anisotropic jump - // indicator explained in the introduction. -template -void DGMethod::set_anisotropic_flags () -{ - // We want to evaluate the jump over faces of - // the flagged cells, so we need some objects - // to evaluate values of the solution on - // faces. - UpdateFlags face_update_flags - = UpdateFlags(update_values | update_JxW_values); - - FEFaceValues fe_v_face (mapping, fe, face_quadrature, face_update_flags); - FESubfaceValues fe_v_subface (mapping, fe, face_quadrature, face_update_flags); - FEFaceValues fe_v_face_neighbor (mapping, fe, face_quadrature, update_values); - - // Now we need to loop over all active cells. - typename DoFHandler::active_cell_iterator cell=dof_handler.begin_active(), - endc=dof_handler.end(); - - for (; cell!=endc; ++cell) - // We only need to consider cells which are - // flaged for refinement. - if (cell->refine_flag_set()) - { - Point jump; - Point area; - for (unsigned int face_no=0; face_no::faces_per_cell; ++face_no) { - typename DoFHandler::face_iterator face = cell->face(face_no); - - if (!face->at_boundary()) + typename DoFHandler::face_iterator face= + cell->face(face_no); + + // Case a) + if (face->at_boundary()) { - Assert (cell->neighbor(face_no).state() == IteratorState::valid, ExcInternalError()); - typename DoFHandler::cell_iterator neighbor = cell->neighbor(face_no); - - std::vector u (fe_v_face.n_quadrature_points); - std::vector u_neighbor (fe_v_face.n_quadrature_points); - - // The four cases of different - // neighbor relations senn in - // the assembly routines are - // repeated much in the same - // way here. + fe_v_face.reinit (cell, face_no); + + dg.assemble_boundary_term(fe_v_face, + ui_vi_matrix, + cell_vector); + } + else + { + Assert (cell->neighbor(face_no).state() == IteratorState::valid, + ExcInternalError()); + typename DoFHandler::cell_iterator neighbor= + cell->neighbor(face_no); + // Case b), we decide that there + // are finer cells as neighbors + // by asking the face, whether it + // has children. if so, then + // there must also be finer cells + // which are children or farther + // offsprings of our neighbor. if (face->has_children()) { - // The neighbor is refined. - // First we store the - // information, which of - // the neighbor's faces - // points in the direction - // of our current - // cell. This property is - // inherited to the - // children. - unsigned int neighbor2=cell->neighbor_face_no(face_no); - // Now we loop over all subfaces, - for (unsigned int subface_no=0; subface_nonumber_of_children(); ++subface_no) + // We need to know, which of + // the neighbors faces points + // in the direction of our + // cell. Using the @p + // neighbor_face_no function + // we get this information + // for both coarser and + // non-coarser neighbors. + const unsigned int neighbor2= + cell->neighbor_face_no(face_no); + + // Now we loop over all + // subfaces, i.e. the + // children and possibly + // grandchildren of the + // current face. + for (unsigned int subface_no=0; + subface_nonumber_of_children(); ++subface_no) { - // get an iterator - // pointing to the cell - // behind the present - // subface... - typename DoFHandler::cell_iterator neighbor_child = cell->neighbor_child_on_subface(face_no,subface_no); + // To get the cell behind + // the current subface we + // can use the @p + // neighbor_child_on_subface + // function. it takes + // care of all the + // complicated situations + // of anisotropic + // refinement and + // non-standard faces. + typename DoFHandler::cell_iterator neighbor_child + = cell->neighbor_child_on_subface (face_no, subface_no); Assert (!neighbor_child->has_children(), ExcInternalError()); - // ... and reinit the - // respective - // FEFaceValues und - // FESubFaceValues - // objects. + + // The remaining part of + // this case is + // unchanged. + ue_vi_matrix = 0; + ui_ve_matrix = 0; + ue_ve_matrix = 0; + fe_v_subface.reinit (cell, face_no, subface_no); fe_v_face_neighbor.reinit (neighbor_child, neighbor2); - // We obtain the function values - fe_v_subface.get_function_values(solution2, u); - fe_v_face_neighbor.get_function_values(solution2, u_neighbor); - // as well as the - // quadrature weights, - // multiplied by the - // jacobian determinant. - const std::vector &JxW = fe_v_subface.get_JxW_values (); - // Now we loop over all - // quadrature points - for (unsigned int x=0; xdim - // components. - jump[face_no/2]+=std::fabs(u[x]-u_neighbor[x])*JxW[x]; - // We also sum up - // the scaled - // weights to - // obtain the - // measure of the - // face. - area[face_no/2]+=JxW[x]; - } + + dg.assemble_face_term2(fe_v_subface, + fe_v_face_neighbor, + ui_vi_matrix, + ue_vi_matrix, + ui_ve_matrix, + ue_ve_matrix); + + neighbor_child->get_dof_indices (dofs_neighbor); + + for (unsigned int i=0; ineighbor_is_coarser(face_no)) + // Case c). We simply ask, + // whether the neighbor is + // coarser. If not, then it + // is neither coarser nor + // finer, since finer + // neighbor would have been + // reated above withz case + // b). Of all the cases with + // thesame refinement + // situation of our cell and + // the neighbor we want to + // treat only one half, so + // that each face is + // considered only once. Thus + // we have the additional + // condition, that the cell + // with the lower index does + // the work. In the rare case + // that both cells have the + // same index, the cell with + // lower level is selected. + if (!cell->neighbor_is_coarser(face_no) && + (neighbor->index() > cell->index() || + (neighbor->level() < cell->level() && + neighbor->index() == cell->index()))) { - // Our current cell and - // the neighbor have - // the same refinement - // along the face under - // consideration. Apart - // from that, we do - // much the same as - // with one of the - // subcells in the - // above case. - unsigned int neighbor2=cell->neighbor_of_neighbor(face_no); - + // Here we know, that the + // neigbor is not coarser + // so we can use the + // usual @p + // neighbor_of_neighbor + // function. However, we + // could also use the + // more general @p + // neighbor_face_no + // function. + const unsigned int neighbor2=cell->neighbor_of_neighbor(face_no); + + ue_vi_matrix = 0; + ui_ve_matrix = 0; + ue_ve_matrix = 0; + fe_v_face.reinit (cell, face_no); fe_v_face_neighbor.reinit (neighbor, neighbor2); - - fe_v_face.get_function_values(solution2, u); - fe_v_face_neighbor.get_function_values(solution2, u_neighbor); - - const std::vector &JxW = fe_v_face.get_JxW_values (); - - for (unsigned int x=0; x neighbor_face_subface - = cell->neighbor_of_coarser_neighbor(face_no); - Assert (neighbor_face_subface.first::faces_per_cell, ExcInternalError()); - Assert (neighbor_face_subface.secondface(neighbor_face_subface.first)->number_of_children(), - ExcInternalError()); - Assert (neighbor->neighbor_child_on_subface(neighbor_face_subface.first, neighbor_face_subface.second) - == cell, ExcInternalError()); - - fe_v_face.reinit (cell, face_no); - fe_v_subface.reinit (neighbor, neighbor_face_subface.first, - neighbor_face_subface.second); - - fe_v_face.get_function_values(solution2, u); - fe_v_subface.get_function_values(solution2, u_neighbor); - - const std::vector &JxW = fe_v_face.get_JxW_values (); - - for (unsigned int x=0; xget_dof_indices (dofs_neighbor); + + for (unsigned int i=0; idim - // coordinate directions of the unit - // cell and compare the average jump - // over the faces orthogional to that - // direction with the average jumnps - // over faces orthogonal to the - // remining direction(s). If the first - // is larger than the latter by a given - // factor, we refine only along hat - // axis. Otherwise we leave the - // refinement flag unchanged, resulting - // in isotropic refinement. - for (unsigned int i=0; i anisotropic_threshold_ratio*(sum_of_average_jumps-average_jumps[i])) - cell->set_refine_flag(RefinementCase::cut_axis(i)); + for (unsigned int i=0; i -void DGMethod::output_results (const unsigned int cycle) const -{ - std::string refine_type; - if (anisotropic) - refine_type=".aniso"; - else - refine_type=".iso"; - - std::string filename = "grid-"; - filename += ('0' + cycle); - Assert (cycle < 10, ExcInternalError()); - - filename += refine_type + ".eps"; - std::cout << "Writing grid to <" << filename << ">..." << std::endl; - std::ofstream eps_output (filename.c_str()); - - GridOut grid_out; - grid_out.write_eps (triangulation, eps_output); - - filename = "grid-"; - filename += ('0' + cycle); - Assert (cycle < 10, ExcInternalError()); - - filename += refine_type + ".gnuplot"; - std::cout << "Writing grid to <" << filename << ">..." << std::endl; - std::ofstream gnuplot_grid_output (filename.c_str()); - - grid_out.write_gnuplot (triangulation, gnuplot_grid_output); - - filename = "sol-"; - filename += ('0' + cycle); - Assert (cycle < 10, ExcInternalError()); - - filename += refine_type + ".gnuplot"; - std::cout << "Writing solution to <" << filename << ">..." - << std::endl; - std::ofstream gnuplot_output (filename.c_str()); - - DataOut data_out; - data_out.attach_dof_handler (dof_handler); - data_out.add_data_vector (solution2, "u"); - - data_out.build_patches (degree); - - data_out.write_gnuplot(gnuplot_output); -} + } + + + // @sect3{Solver} + // + // For this simple problem we use the simple + // Richardson iteration again. The solver is + // completely unaffected by our anisotropic + // changes. + template + void DGMethod::solve (Vector &solution) + { + SolverControl solver_control (1000, 1e-12, false, false); + SolverRichardson<> solver (solver_control); + + PreconditionBlockSSOR > preconditioner; + + preconditioner.initialize(system_matrix, fe.dofs_per_cell); + + solver.solve (system_matrix, solution, right_hand_side, + preconditioner); + } + + + // @sect3{Refinement} + // + // We refine the grid according to the same + // simple refinement criterion used in step-12, + // namely an approximation to the + // gradient of the solution. + template + void DGMethod::refine_grid () + { + Vector gradient_indicator (triangulation.n_active_cells()); + + // We approximate the gradient, + DerivativeApproximation::approximate_gradient (mapping, + dof_handler, + solution2, + gradient_indicator); + + // and scale it to obtain an error indicator. + typename DoFHandler::active_cell_iterator + cell = dof_handler.begin_active(), + endc = dof_handler.end(); + for (unsigned int cell_no=0; cell!=endc; ++cell, ++cell_no) + gradient_indicator(cell_no)*=std::pow(cell->diameter(), 1+1.0*dim/2); + // Then we use this indicator to flag the 30 + // percent of the cells with highest error + // indicator to be refined. + GridRefinement::refine_and_coarsen_fixed_number (triangulation, + gradient_indicator, + 0.3, 0.1); + // Now the refinement flags are set for those + // cells with a large error indicator. If + // nothing is done to change this, those + // cells will be refined isotropically. If + // the @p anisotropic flag given to this + // function is set, we now call the + // set_anisotropic_flags() function, which + // uses the jump indicator to reset some of + // the refinement flags to anisotropic + // refinement. + if (anisotropic) + set_anisotropic_flags(); + // Now execute the refinement considering + // anisotropic as well as isotropic + // refinement flags. + triangulation.execute_coarsening_and_refinement (); + } + + // Once an error indicator has been evaluated + // and the cells with largerst error are + // flagged for refinement we want to loop over + // the flagged cells again to decide whether + // they need isotropic refinemnt or whether + // anisotropic refinement is more + // appropriate. This is the anisotropic jump + // indicator explained in the introduction. + template + void DGMethod::set_anisotropic_flags () + { + // We want to evaluate the jump over faces of + // the flagged cells, so we need some objects + // to evaluate values of the solution on + // faces. + UpdateFlags face_update_flags + = UpdateFlags(update_values | update_JxW_values); + + FEFaceValues fe_v_face (mapping, fe, face_quadrature, face_update_flags); + FESubfaceValues fe_v_subface (mapping, fe, face_quadrature, face_update_flags); + FEFaceValues fe_v_face_neighbor (mapping, fe, face_quadrature, update_values); + + // Now we need to loop over all active cells. + typename DoFHandler::active_cell_iterator cell=dof_handler.begin_active(), + endc=dof_handler.end(); + + for (; cell!=endc; ++cell) + // We only need to consider cells which are + // flaged for refinement. + if (cell->refine_flag_set()) + { + Point jump; + Point area; + for (unsigned int face_no=0; face_no::faces_per_cell; ++face_no) + { + typename DoFHandler::face_iterator face = cell->face(face_no); -template -void DGMethod::run () -{ - for (unsigned int cycle=0; cycle<6; ++cycle) - { - std::cout << "Cycle " << cycle << ':' << std::endl; + if (!face->at_boundary()) + { + Assert (cell->neighbor(face_no).state() == IteratorState::valid, ExcInternalError()); + typename DoFHandler::cell_iterator neighbor = cell->neighbor(face_no); + + std::vector u (fe_v_face.n_quadrature_points); + std::vector u_neighbor (fe_v_face.n_quadrature_points); + + // The four cases of different + // neighbor relations senn in + // the assembly routines are + // repeated much in the same + // way here. + if (face->has_children()) + { + // The neighbor is refined. + // First we store the + // information, which of + // the neighbor's faces + // points in the direction + // of our current + // cell. This property is + // inherited to the + // children. + unsigned int neighbor2=cell->neighbor_face_no(face_no); + // Now we loop over all subfaces, + for (unsigned int subface_no=0; subface_nonumber_of_children(); ++subface_no) + { + // get an iterator + // pointing to the cell + // behind the present + // subface... + typename DoFHandler::cell_iterator neighbor_child = cell->neighbor_child_on_subface(face_no,subface_no); + Assert (!neighbor_child->has_children(), ExcInternalError()); + // ... and reinit the + // respective + // FEFaceValues und + // FESubFaceValues + // objects. + fe_v_subface.reinit (cell, face_no, subface_no); + fe_v_face_neighbor.reinit (neighbor_child, neighbor2); + // We obtain the function values + fe_v_subface.get_function_values(solution2, u); + fe_v_face_neighbor.get_function_values(solution2, u_neighbor); + // as well as the + // quadrature weights, + // multiplied by the + // jacobian determinant. + const std::vector &JxW = fe_v_subface.get_JxW_values (); + // Now we loop over all + // quadrature points + for (unsigned int x=0; xdim + // components. + jump[face_no/2]+=std::fabs(u[x]-u_neighbor[x])*JxW[x]; + // We also sum up + // the scaled + // weights to + // obtain the + // measure of the + // face. + area[face_no/2]+=JxW[x]; + } + } + } + else + { + if (!cell->neighbor_is_coarser(face_no)) + { + // Our current cell and + // the neighbor have + // the same refinement + // along the face under + // consideration. Apart + // from that, we do + // much the same as + // with one of the + // subcells in the + // above case. + unsigned int neighbor2=cell->neighbor_of_neighbor(face_no); + + fe_v_face.reinit (cell, face_no); + fe_v_face_neighbor.reinit (neighbor, neighbor2); + + fe_v_face.get_function_values(solution2, u); + fe_v_face_neighbor.get_function_values(solution2, u_neighbor); + + const std::vector &JxW = fe_v_face.get_JxW_values (); + + for (unsigned int x=0; x neighbor_face_subface + = cell->neighbor_of_coarser_neighbor(face_no); + Assert (neighbor_face_subface.first::faces_per_cell, ExcInternalError()); + Assert (neighbor_face_subface.secondface(neighbor_face_subface.first)->number_of_children(), + ExcInternalError()); + Assert (neighbor->neighbor_child_on_subface(neighbor_face_subface.first, neighbor_face_subface.second) + == cell, ExcInternalError()); + + fe_v_face.reinit (cell, face_no); + fe_v_subface.reinit (neighbor, neighbor_face_subface.first, + neighbor_face_subface.second); + + fe_v_face.get_function_values(solution2, u); + fe_v_subface.get_function_values(solution2, u_neighbor); + + const std::vector &JxW = fe_v_face.get_JxW_values (); + + for (unsigned int x=0; x p1,p2; - p1(0)=0; - p1(0)=-1; + // Now we loop over the dim + // coordinate directions of the unit + // cell and compare the average jump + // over the faces orthogional to that + // direction with the average jumnps + // over faces orthogonal to the + // remining direction(s). If the first + // is larger than the latter by a given + // factor, we refine only along hat + // axis. Otherwise we leave the + // refinement flag unchanged, resulting + // in isotropic refinement. for (unsigned int i=0; i repetitions(dim,1); - repetitions[0]=2; - GridGenerator::subdivided_hyper_rectangle (triangulation, - repetitions, - p1, - p2); - - triangulation.refine_global (5-dim); + if (average_jumps[i] > anisotropic_threshold_ratio*(sum_of_average_jumps-average_jumps[i])) + cell->set_refine_flag(RefinementCase::cut_axis(i)); } - else - refine_grid (); - + } + + // @sect3{The Rest} + // + // The remaining part of the program is again + // unmodified. Only the creation of the + // original triangulation is changed in order + // to reproduce the new domain. + template + void DGMethod::output_results (const unsigned int cycle) const + { + std::string refine_type; + if (anisotropic) + refine_type=".aniso"; + else + refine_type=".iso"; + + std::string filename = "grid-"; + filename += ('0' + cycle); + Assert (cycle < 10, ExcInternalError()); + + filename += refine_type + ".eps"; + std::cout << "Writing grid to <" << filename << ">..." << std::endl; + std::ofstream eps_output (filename.c_str()); + + GridOut grid_out; + grid_out.write_eps (triangulation, eps_output); + + filename = "grid-"; + filename += ('0' + cycle); + Assert (cycle < 10, ExcInternalError()); + + filename += refine_type + ".gnuplot"; + std::cout << "Writing grid to <" << filename << ">..." << std::endl; + std::ofstream gnuplot_grid_output (filename.c_str()); + + grid_out.write_gnuplot (triangulation, gnuplot_grid_output); + + filename = "sol-"; + filename += ('0' + cycle); + Assert (cycle < 10, ExcInternalError()); + + filename += refine_type + ".gnuplot"; + std::cout << "Writing solution to <" << filename << ">..." + << std::endl; + std::ofstream gnuplot_output (filename.c_str()); + + DataOut data_out; + data_out.attach_dof_handler (dof_handler); + data_out.add_data_vector (solution2, "u"); + + data_out.build_patches (degree); + + data_out.write_gnuplot(gnuplot_output); + } + + + template + void DGMethod::run () + { + for (unsigned int cycle=0; cycle<6; ++cycle) + { + std::cout << "Cycle " << cycle << ':' << std::endl; - std::cout << " Number of active cells: " - << triangulation.n_active_cells() - << std::endl; + if (cycle == 0) + { + // Create the rectangular domain. + Point p1,p2; + p1(0)=0; + p1(0)=-1; + for (unsigned int i=0; i repetitions(dim,1); + repetitions[0]=2; + GridGenerator::subdivided_hyper_rectangle (triangulation, + repetitions, + p1, + p2); + + triangulation.refine_global (5-dim); + } + else + refine_grid (); - setup_system (); - std::cout << " Number of degrees of freedom: " - << dof_handler.n_dofs() - << std::endl; + std::cout << " Number of active cells: " + << triangulation.n_active_cells() + << std::endl; - Timer assemble_timer; - assemble_system2 (); - std::cout << "Time of assemble_system2: " - << assemble_timer() - << std::endl; - solve (solution2); + setup_system (); - output_results (cycle); - } + std::cout << " Number of degrees of freedom: " + << dof_handler.n_dofs() + << std::endl; + + Timer assemble_timer; + assemble_system2 (); + std::cout << "Time of assemble_system2: " + << assemble_timer() + << std::endl; + solve (solution2); + + output_results (cycle); + } + } } -int main () + + +int main () { try { + using namespace dealii; + using namespace Step30; + // If you want to run the program in 3D, // simply change the following line to // const unsigned int dim = 3;. const unsigned int dim = 2; - + { // First, we perform a run with // isotropic refinement. @@ -1155,7 +1163,7 @@ int main () DGMethod dgmethod_iso(false); dgmethod_iso.run (); } - + { // Now we do a second run, this time // with anisotropic refinement. @@ -1178,7 +1186,7 @@ int main () << std::endl; return 1; } - catch (...) + catch (...) { std::cerr << std::endl << std::endl << "----------------------------------------------------" @@ -1189,7 +1197,7 @@ int main () << std::endl; return 1; }; - + return 0; } diff --git a/deal.II/examples/step-31/step-31.cc b/deal.II/examples/step-31/step-31.cc index 9361676728..75e96b14b6 100644 --- a/deal.II/examples/step-31/step-31.cc +++ b/deal.II/examples/step-31/step-31.cc @@ -4,7 +4,7 @@ /* $Id$ */ /* */ -/* Copyright (C) 2007, 2008, 2009, 2010 by the deal.II authors */ +/* Copyright (C) 2007, 2008, 2009, 2010, 2011 by the deal.II authors */ /* */ /* This file is subject to QPL and may not be distributed */ /* without copyright and license information. Please refer */ @@ -73,2802 +73,2805 @@ // At the end of this top-matter, we import // all deal.II names into the global // namespace: -using namespace dealii; - - - // @sect3{Equation data} - - // Again, the next stage in the program is - // the definition of the equation data, that - // is, the various boundary conditions, the - // right hand sides and the initial condition - // (remember that we're about to solve a - // time-dependent system). The basic strategy - // for this definition is the same as in - // step-22. Regarding the details, though, - // there are some differences. - - // The first thing is that we don't set any - // non-homogenous boundary conditions on the - // velocity, since as is explained in the - // introduction we will use no-flux - // conditions - // $\mathbf{n}\cdot\mathbf{u}=0$. So what is - // left are dim-1 conditions for - // the tangential part of the normal - // component of the stress tensor, - // $\textbf{n} \cdot [p \textbf{1} - - // \eta\varepsilon(\textbf{u})]$; we assume - // homogenous values for these components, - // i.e. a natural boundary condition that - // requires no specific action (it appears as - // a zero term in the right hand side of the - // weak form). - // - // For the temperature T, we assume no - // thermal energy flux, i.e. $\mathbf{n} - // \cdot \kappa \nabla T=0$. This, again, is - // a boundary condition that does not require - // us to do anything in particular. - // - // Secondly, we have to set initial - // conditions for the temperature (no initial - // conditions are required for the velocity - // and pressure, since the Stokes equations - // for the quasi-stationary case we consider - // here have no time derivatives of the - // velocity or pressure). Here, we choose a - // very simple test case, where the initial - // temperature is zero, and all dynamics are - // driven by the temperature right hand side. - // - // Thirdly, we need to define the right hand - // side of the temperature equation. We - // choose it to be constant within three - // circles (or spheres in 3d) somewhere at - // the bottom of the domain, as explained in - // the introduction, and zero outside. - // - // Finally, or maybe firstly, at the top of - // this namespace, we define the various - // material constants we need ($\eta,\kappa$, - // density $\rho$ and the thermal expansion - // coefficient $\beta$): -namespace EquationData +namespace Step31 { - const double eta = 1; - const double kappa = 1e-6; - const double beta = 10; - const double density = 1; + using namespace dealii; + + + // @sect3{Equation data} + + // Again, the next stage in the program is + // the definition of the equation data, that + // is, the various boundary conditions, the + // right hand sides and the initial condition + // (remember that we're about to solve a + // time-dependent system). The basic strategy + // for this definition is the same as in + // step-22. Regarding the details, though, + // there are some differences. + + // The first thing is that we don't set any + // non-homogenous boundary conditions on the + // velocity, since as is explained in the + // introduction we will use no-flux + // conditions + // $\mathbf{n}\cdot\mathbf{u}=0$. So what is + // left are dim-1 conditions for + // the tangential part of the normal + // component of the stress tensor, + // $\textbf{n} \cdot [p \textbf{1} - + // \eta\varepsilon(\textbf{u})]$; we assume + // homogenous values for these components, + // i.e. a natural boundary condition that + // requires no specific action (it appears as + // a zero term in the right hand side of the + // weak form). + // + // For the temperature T, we assume no + // thermal energy flux, i.e. $\mathbf{n} + // \cdot \kappa \nabla T=0$. This, again, is + // a boundary condition that does not require + // us to do anything in particular. + // + // Secondly, we have to set initial + // conditions for the temperature (no initial + // conditions are required for the velocity + // and pressure, since the Stokes equations + // for the quasi-stationary case we consider + // here have no time derivatives of the + // velocity or pressure). Here, we choose a + // very simple test case, where the initial + // temperature is zero, and all dynamics are + // driven by the temperature right hand side. + // + // Thirdly, we need to define the right hand + // side of the temperature equation. We + // choose it to be constant within three + // circles (or spheres in 3d) somewhere at + // the bottom of the domain, as explained in + // the introduction, and zero outside. + // + // Finally, or maybe firstly, at the top of + // this namespace, we define the various + // material constants we need ($\eta,\kappa$, + // density $\rho$ and the thermal expansion + // coefficient $\beta$): + namespace EquationData + { + const double eta = 1; + const double kappa = 1e-6; + const double beta = 10; + const double density = 1; - template - class TemperatureInitialValues : public Function - { - public: - TemperatureInitialValues () : Function(1) {} + template + class TemperatureInitialValues : public Function + { + public: + TemperatureInitialValues () : Function(1) {} - virtual double value (const Point &p, - const unsigned int component = 0) const; + virtual double value (const Point &p, + const unsigned int component = 0) const; - virtual void vector_value (const Point &p, - Vector &value) const; - }; + virtual void vector_value (const Point &p, + Vector &value) const; + }; - template - double - TemperatureInitialValues::value (const Point &, - const unsigned int) const - { - return 0; - } + template + double + TemperatureInitialValues::value (const Point &, + const unsigned int) const + { + return 0; + } - template - void - TemperatureInitialValues::vector_value (const Point &p, - Vector &values) const - { - for (unsigned int c=0; cn_components; ++c) - values(c) = TemperatureInitialValues::value (p, c); - } + template + void + TemperatureInitialValues::vector_value (const Point &p, + Vector &values) const + { + for (unsigned int c=0; cn_components; ++c) + values(c) = TemperatureInitialValues::value (p, c); + } - template - class TemperatureRightHandSide : public Function - { - public: - TemperatureRightHandSide () : Function(1) {} + template + class TemperatureRightHandSide : public Function + { + public: + TemperatureRightHandSide () : Function(1) {} - virtual double value (const Point &p, - const unsigned int component = 0) const; + virtual double value (const Point &p, + const unsigned int component = 0) const; - virtual void vector_value (const Point &p, - Vector &value) const; - }; + virtual void vector_value (const Point &p, + Vector &value) const; + }; - template - double - TemperatureRightHandSide::value (const Point &p, - const unsigned int component) const - { - Assert (component == 0, - ExcMessage ("Invalid operation for a scalar function.")); - - Assert ((dim==2) || (dim==3), ExcNotImplemented()); - - static const Point source_centers[3] - = { (dim == 2 ? Point(.3,.1) : Point(.3,.5,.1)), - (dim == 2 ? Point(.45,.1) : Point(.45,.5,.1)), - (dim == 2 ? Point(.75,.1) : Point(.75,.5,.1)) }; - static const double source_radius - = (dim == 2 ? 1./32 : 1./8); - - return ((source_centers[0].distance (p) < source_radius) - || - (source_centers[1].distance (p) < source_radius) - || - (source_centers[2].distance (p) < source_radius) - ? - 1 - : - 0); - } + template + double + TemperatureRightHandSide::value (const Point &p, + const unsigned int component) const + { + Assert (component == 0, + ExcMessage ("Invalid operation for a scalar function.")); + + Assert ((dim==2) || (dim==3), ExcNotImplemented()); + + static const Point source_centers[3] + = { (dim == 2 ? Point(.3,.1) : Point(.3,.5,.1)), + (dim == 2 ? Point(.45,.1) : Point(.45,.5,.1)), + (dim == 2 ? Point(.75,.1) : Point(.75,.5,.1)) }; + static const double source_radius + = (dim == 2 ? 1./32 : 1./8); + + return ((source_centers[0].distance (p) < source_radius) + || + (source_centers[1].distance (p) < source_radius) + || + (source_centers[2].distance (p) < source_radius) + ? + 1 + : + 0); + } - template - void - TemperatureRightHandSide::vector_value (const Point &p, - Vector &values) const - { - for (unsigned int c=0; cn_components; ++c) - values(c) = TemperatureRightHandSide::value (p, c); + template + void + TemperatureRightHandSide::vector_value (const Point &p, + Vector &values) const + { + for (unsigned int c=0; cn_components; ++c) + values(c) = TemperatureRightHandSide::value (p, c); + } } -} - // @sect3{Linear solvers and preconditioners} - - // This section introduces some objects - // that are used for the solution of the - // linear equations of the Stokes system - // that we need to solve in each time - // step. Many of the ideas used here are - // the same as in step-20, where Schur - // complement based preconditioners and - // solvers have been introduced, with the - // actual interface taken from step-22 (in - // particular the discussion in the - // "Results" section of step-22, in which - // we introduce alternatives to the direct - // Schur complement approach). Note, - // however, that here we don't use the - // Schur complement to solve the Stokes - // equations, though an approximate Schur - // complement (the mass matrix on the - // pressure space) appears in the - // preconditioner. -namespace LinearSolvers -{ - - // @sect4{The InverseMatrix class template} - - // This class is an interface to - // calculate the action of an - // "inverted" matrix on a vector - // (using the vmult - // operation) in the same way as - // the corresponding class in - // step-22: when the product of an - // object of this class is - // requested, we solve a linear - // equation system with that matrix - // using the CG method, accelerated - // by a preconditioner of - // (templated) class - // Preconditioner. - // - // In a minor deviation from the - // implementation of the same class in - // step-22 (and step-20), we make the - // vmult function take any - // kind of vector type (it will yield - // compiler errors, however, if the matrix - // does not allow a matrix-vector product - // with this kind of vector). - // - // Secondly, we catch any exceptions that - // the solver may have thrown. The reason - // is as follows: When debugging a program - // like this one occasionally makes a - // mistake of passing an indefinite or - // non-symmetric matrix or preconditioner - // to the current class. The solver will, - // in that case, not converge and throw a - // run-time exception. If not caught here - // it will propagate up the call stack and - // may end up in main() where - // we output an error message that will say - // that the CG solver failed. The question - // then becomes: Which CG solver? The one - // that inverted the mass matrix? The one - // that inverted the top left block with - // the Laplace operator? Or a CG solver in - // one of the several other nested places - // where we use linear solvers in the - // current code? No indication about this - // is present in a run-time exception - // because it doesn't store the stack of - // calls through which we got to the place - // where the exception was generated. - // - // So rather than letting the exception - // propagate freely up to - // main() we realize that - // there is little that an outer function - // can do if the inner solver fails and - // rather convert the run-time exception - // into an assertion that fails and - // triggers a call to abort(), - // allowing us to trace back in a debugger - // how we got to the current place. - template - class InverseMatrix : public Subscriptor + // @sect3{Linear solvers and preconditioners} + + // This section introduces some objects + // that are used for the solution of the + // linear equations of the Stokes system + // that we need to solve in each time + // step. Many of the ideas used here are + // the same as in step-20, where Schur + // complement based preconditioners and + // solvers have been introduced, with the + // actual interface taken from step-22 (in + // particular the discussion in the + // "Results" section of step-22, in which + // we introduce alternatives to the direct + // Schur complement approach). Note, + // however, that here we don't use the + // Schur complement to solve the Stokes + // equations, though an approximate Schur + // complement (the mass matrix on the + // pressure space) appears in the + // preconditioner. + namespace LinearSolvers { - public: - InverseMatrix (const Matrix &m, - const Preconditioner &preconditioner); + // @sect4{The InverseMatrix class template} + + // This class is an interface to + // calculate the action of an + // "inverted" matrix on a vector + // (using the vmult + // operation) in the same way as + // the corresponding class in + // step-22: when the product of an + // object of this class is + // requested, we solve a linear + // equation system with that matrix + // using the CG method, accelerated + // by a preconditioner of + // (templated) class + // Preconditioner. + // + // In a minor deviation from the + // implementation of the same class in + // step-22 (and step-20), we make the + // vmult function take any + // kind of vector type (it will yield + // compiler errors, however, if the matrix + // does not allow a matrix-vector product + // with this kind of vector). + // + // Secondly, we catch any exceptions that + // the solver may have thrown. The reason + // is as follows: When debugging a program + // like this one occasionally makes a + // mistake of passing an indefinite or + // non-symmetric matrix or preconditioner + // to the current class. The solver will, + // in that case, not converge and throw a + // run-time exception. If not caught here + // it will propagate up the call stack and + // may end up in main() where + // we output an error message that will say + // that the CG solver failed. The question + // then becomes: Which CG solver? The one + // that inverted the mass matrix? The one + // that inverted the top left block with + // the Laplace operator? Or a CG solver in + // one of the several other nested places + // where we use linear solvers in the + // current code? No indication about this + // is present in a run-time exception + // because it doesn't store the stack of + // calls through which we got to the place + // where the exception was generated. + // + // So rather than letting the exception + // propagate freely up to + // main() we realize that + // there is little that an outer function + // can do if the inner solver fails and + // rather convert the run-time exception + // into an assertion that fails and + // triggers a call to abort(), + // allowing us to trace back in a debugger + // how we got to the current place. + template + class InverseMatrix : public Subscriptor + { + public: + InverseMatrix (const Matrix &m, + const Preconditioner &preconditioner); - template - void vmult (VectorType &dst, - const VectorType &src) const; - private: - const SmartPointer matrix; - const Preconditioner &preconditioner; - }; + template + void vmult (VectorType &dst, + const VectorType &src) const; + private: + const SmartPointer matrix; + const Preconditioner &preconditioner; + }; - template - InverseMatrix:: - InverseMatrix (const Matrix &m, - const Preconditioner &preconditioner) - : - matrix (&m), - preconditioner (preconditioner) - {} + template + InverseMatrix:: + InverseMatrix (const Matrix &m, + const Preconditioner &preconditioner) + : + matrix (&m), + preconditioner (preconditioner) + {} - template - template - void - InverseMatrix:: - vmult (VectorType &dst, - const VectorType &src) const - { - SolverControl solver_control (src.size(), 1e-7*src.l2_norm()); - SolverCG cg (solver_control); - dst = 0; + template + template + void + InverseMatrix:: + vmult (VectorType &dst, + const VectorType &src) const + { + SolverControl solver_control (src.size(), 1e-7*src.l2_norm()); + SolverCG cg (solver_control); + + dst = 0; - try - { - cg.solve (*matrix, dst, src, preconditioner); - } - catch (std::exception &e) - { - Assert (false, ExcMessage(e.what())); - } + try + { + cg.solve (*matrix, dst, src, preconditioner); + } + catch (std::exception &e) + { + Assert (false, ExcMessage(e.what())); + } + } + + // @sect4{Schur complement preconditioner} + + // This is the implementation of the + // Schur complement preconditioner as + // described in detail in the + // introduction. As opposed to step-20 + // and step-22, we solve the block system + // all-at-once using GMRES, and use the + // Schur complement of the block + // structured matrix to build a good + // preconditioner instead. + // + // Let's have a look at the ideal + // preconditioner matrix + // $P=\left(\begin{array}{cc} A & 0 \\ B + // & -S \end{array}\right)$ described in + // the introduction. If we apply this + // matrix in the solution of a linear + // system, convergence of an iterative + // GMRES solver will be governed by the + // matrix + // @f{eqnarray*} + // P^{-1}\left(\begin{array}{cc} A + // & B^T \\ B & 0 + // \end{array}\right) = + // \left(\begin{array}{cc} I & + // A^{-1} B^T \\ 0 & I + // \end{array}\right), + // @f} + // which indeed is very simple. A GMRES + // solver based on exact matrices would + // converge in one iteration, since all + // eigenvalues are equal (any Krylov + // method takes at most as many + // iterations as there are distinct + // eigenvalues). Such a preconditioner + // for the blocked Stokes system has been + // proposed by Silvester and Wathen + // ("Fast iterative solution of + // stabilised Stokes systems part II. + // Using general block preconditioners", + // SIAM J. Numer. Anal., 31 (1994), + // pp. 1352-1367). + // + // Replacing P by $\tilde{P}$ + // keeps that spirit alive: the product + // $P^{-1} A$ will still be close to a + // matrix with eigenvalues 1 with a + // distribution that does not depend on + // the problem size. This lets us hope to + // be able to get a number of GMRES + // iterations that is problem-size + // independent. + // + // The deal.II users who have already + // gone through the step-20 and step-22 + // tutorials can certainly imagine how + // we're going to implement this. We + // replace the exact inverse matrices in + // $P^{-1}$ by some approximate inverses + // built from the InverseMatrix class, + // and the inverse Schur complement will + // be approximated by the pressure mass + // matrix $M_p$ (weighted by $\eta^{-1}$ + // as mentioned in the introduction). As + // pointed out in the results section of + // step-22, we can replace the exact + // inverse of A by just the + // application of a preconditioner, in + // this case on a vector Laplace matrix + // as was explained in the + // introduction. This does increase the + // number of (outer) GMRES iterations, + // but is still significantly cheaper + // than an exact inverse, which would + // require between 20 and 35 CG + // iterations for each outer + // solver step (using the AMG + // preconditioner). + // + // Having the above explanations in mind, + // we define a preconditioner class with + // a vmult functionality, + // which is all we need for the + // interaction with the usual solver + // functions further below in the program + // code. + // + // First the declarations. These are + // similar to the definition of the Schur + // complement in step-20, with the + // difference that we need some more + // preconditioners in the constructor and + // that the matrices we use here are + // built upon Trilinos: + template + class BlockSchurPreconditioner : public Subscriptor + { + public: + BlockSchurPreconditioner ( + const TrilinosWrappers::BlockSparseMatrix &S, + const InverseMatrix &Mpinv, + const PreconditionerA &Apreconditioner); + + void vmult (TrilinosWrappers::BlockVector &dst, + const TrilinosWrappers::BlockVector &src) const; + + private: + const SmartPointer stokes_matrix; + const SmartPointer > m_inverse; + const PreconditionerA &a_preconditioner; + + mutable TrilinosWrappers::Vector tmp; + }; + + + + template + BlockSchurPreconditioner:: + BlockSchurPreconditioner(const TrilinosWrappers::BlockSparseMatrix &S, + const InverseMatrix &Mpinv, + const PreconditionerA &Apreconditioner) + : + stokes_matrix (&S), + m_inverse (&Mpinv), + a_preconditioner (Apreconditioner), + tmp (stokes_matrix->block(1,1).m()) + {} + + + // Next is the vmult + // function. We implement the action of + // $P^{-1}$ as described above in three + // successive steps. In formulas, we want + // to compute $Y=P^{-1}X$ where $X,Y$ are + // both vectors with two block components. + // + // The first step multiplies the velocity + // part of the vector by a preconditioner + // of the matrix A, i.e. we compute + // $Y_0={\tilde A}^{-1}X_0$. The resulting + // velocity vector is then multiplied by + // $B$ and subtracted from the pressure, + // i.e. we want to compute $X_1-BY_0$. + // This second step only acts on the + // pressure vector and is accomplished by + // the residual function of our matrix + // classes, except that the sign is + // wrong. Consequently, we change the sign + // in the temporary pressure vector and + // finally multiply by the inverse pressure + // mass matrix to get the final pressure + // vector, completing our work on the + // Stokes preconditioner: + template + void BlockSchurPreconditioner::vmult ( + TrilinosWrappers::BlockVector &dst, + const TrilinosWrappers::BlockVector &src) const + { + a_preconditioner.vmult (dst.block(0), src.block(0)); + stokes_matrix->block(1,0).residual(tmp, dst.block(0), src.block(1)); + tmp *= -1; + m_inverse->vmult (dst.block(1), tmp); + } } - // @sect4{Schur complement preconditioner} - - // This is the implementation of the - // Schur complement preconditioner as - // described in detail in the - // introduction. As opposed to step-20 - // and step-22, we solve the block system - // all-at-once using GMRES, and use the - // Schur complement of the block - // structured matrix to build a good - // preconditioner instead. - // - // Let's have a look at the ideal - // preconditioner matrix - // $P=\left(\begin{array}{cc} A & 0 \\ B - // & -S \end{array}\right)$ described in - // the introduction. If we apply this - // matrix in the solution of a linear - // system, convergence of an iterative - // GMRES solver will be governed by the - // matrix - // @f{eqnarray*} - // P^{-1}\left(\begin{array}{cc} A - // & B^T \\ B & 0 - // \end{array}\right) = - // \left(\begin{array}{cc} I & - // A^{-1} B^T \\ 0 & I - // \end{array}\right), - // @f} - // which indeed is very simple. A GMRES - // solver based on exact matrices would - // converge in one iteration, since all - // eigenvalues are equal (any Krylov - // method takes at most as many - // iterations as there are distinct - // eigenvalues). Such a preconditioner - // for the blocked Stokes system has been - // proposed by Silvester and Wathen - // ("Fast iterative solution of - // stabilised Stokes systems part II. - // Using general block preconditioners", - // SIAM J. Numer. Anal., 31 (1994), - // pp. 1352-1367). - // - // Replacing P by $\tilde{P}$ - // keeps that spirit alive: the product - // $P^{-1} A$ will still be close to a - // matrix with eigenvalues 1 with a - // distribution that does not depend on - // the problem size. This lets us hope to - // be able to get a number of GMRES - // iterations that is problem-size - // independent. - // - // The deal.II users who have already - // gone through the step-20 and step-22 - // tutorials can certainly imagine how - // we're going to implement this. We - // replace the exact inverse matrices in - // $P^{-1}$ by some approximate inverses - // built from the InverseMatrix class, - // and the inverse Schur complement will - // be approximated by the pressure mass - // matrix $M_p$ (weighted by $\eta^{-1}$ - // as mentioned in the introduction). As - // pointed out in the results section of - // step-22, we can replace the exact - // inverse of A by just the - // application of a preconditioner, in - // this case on a vector Laplace matrix - // as was explained in the - // introduction. This does increase the - // number of (outer) GMRES iterations, - // but is still significantly cheaper - // than an exact inverse, which would - // require between 20 and 35 CG - // iterations for each outer - // solver step (using the AMG - // preconditioner). + + + // @sect3{The BoussinesqFlowProblem class template} + + // The definition of the class that defines + // the top-level logic of solving the + // time-dependent Boussinesq problem is + // mainly based on the step-22 tutorial + // program. The main differences are that now + // we also have to solve for the temperature + // equation, which forces us to have a second + // DoFHandler object for the temperature + // variable as well as matrices, right hand + // sides, and solution vectors for the + // current and previous time steps. As + // mentioned in the introduction, all linear + // algebra objects are going to use wrappers + // of the corresponding Trilinos + // functionality. // - // Having the above explanations in mind, - // we define a preconditioner class with - // a vmult functionality, - // which is all we need for the - // interaction with the usual solver - // functions further below in the program - // code. + // The member functions of this class are + // reminiscent of step-21, where we also used + // a staggered scheme that first solve the + // flow equations (here the Stokes equations, + // in step-21 Darcy flow) and then update + // the advected quantity (here the + // temperature, there the saturation). The + // functions that are new are mainly + // concerned with determining the time step, + // as well as the proper size of the + // artificial viscosity stabilization. // - // First the declarations. These are - // similar to the definition of the Schur - // complement in step-20, with the - // difference that we need some more - // preconditioners in the constructor and - // that the matrices we use here are - // built upon Trilinos: - template - class BlockSchurPreconditioner : public Subscriptor + // The last three variables indicate whether + // the various matrices or preconditioners + // need to be rebuilt the next time the + // corresponding build functions are + // called. This allows us to move the + // corresponding if into the + // respective function and thereby keeping + // our main run() function clean + // and easy to read. + template + class BoussinesqFlowProblem { public: - BlockSchurPreconditioner ( - const TrilinosWrappers::BlockSparseMatrix &S, - const InverseMatrix &Mpinv, - const PreconditionerA &Apreconditioner); - - void vmult (TrilinosWrappers::BlockVector &dst, - const TrilinosWrappers::BlockVector &src) const; + BoussinesqFlowProblem (); + void run (); private: - const SmartPointer stokes_matrix; - const SmartPointer > m_inverse; - const PreconditionerA &a_preconditioner; - - mutable TrilinosWrappers::Vector tmp; + void setup_dofs (); + void assemble_stokes_preconditioner (); + void build_stokes_preconditioner (); + void assemble_stokes_system (); + void assemble_temperature_system (const double maximal_velocity); + void assemble_temperature_matrix (); + double get_maximal_velocity () const; + std::pair get_extrapolated_temperature_range () const; + void solve (); + void output_results () const; + void refine_mesh (const unsigned int max_grid_level); + + double + compute_viscosity(const std::vector &old_temperature, + const std::vector &old_old_temperature, + const std::vector > &old_temperature_grads, + const std::vector > &old_old_temperature_grads, + const std::vector &old_temperature_laplacians, + const std::vector &old_old_temperature_laplacians, + const std::vector > &old_velocity_values, + const std::vector > &old_old_velocity_values, + const std::vector &gamma_values, + const double global_u_infty, + const double global_T_variation, + const double cell_diameter) const; + + + Triangulation triangulation; + double global_Omega_diameter; + + const unsigned int stokes_degree; + FESystem stokes_fe; + DoFHandler stokes_dof_handler; + ConstraintMatrix stokes_constraints; + + std::vector stokes_block_sizes; + TrilinosWrappers::BlockSparseMatrix stokes_matrix; + TrilinosWrappers::BlockSparseMatrix stokes_preconditioner_matrix; + + TrilinosWrappers::BlockVector stokes_solution; + TrilinosWrappers::BlockVector old_stokes_solution; + TrilinosWrappers::BlockVector stokes_rhs; + + + const unsigned int temperature_degree; + FE_Q temperature_fe; + DoFHandler temperature_dof_handler; + ConstraintMatrix temperature_constraints; + + TrilinosWrappers::SparseMatrix temperature_mass_matrix; + TrilinosWrappers::SparseMatrix temperature_stiffness_matrix; + TrilinosWrappers::SparseMatrix temperature_matrix; + + TrilinosWrappers::Vector temperature_solution; + TrilinosWrappers::Vector old_temperature_solution; + TrilinosWrappers::Vector old_old_temperature_solution; + TrilinosWrappers::Vector temperature_rhs; + + + double time_step; + double old_time_step; + unsigned int timestep_number; + + std_cxx1x::shared_ptr Amg_preconditioner; + std_cxx1x::shared_ptr Mp_preconditioner; + + bool rebuild_stokes_matrix; + bool rebuild_temperature_matrices; + bool rebuild_stokes_preconditioner; }; + // @sect3{BoussinesqFlowProblem class implementation} - template - BlockSchurPreconditioner:: - BlockSchurPreconditioner(const TrilinosWrappers::BlockSparseMatrix &S, - const InverseMatrix &Mpinv, - const PreconditionerA &Apreconditioner) + // @sect4{BoussinesqFlowProblem::BoussinesqFlowProblem} + // + // The constructor of this class is an + // extension of the constructor in + // step-22. We need to add the various + // variables that concern the temperature. As + // discussed in the introduction, we are + // going to use $Q_2\times Q_1$ (Taylor-Hood) + // elements again for the Stokes part, and + // $Q_2$ elements for the + // temperature. However, by using variables + // that store the polynomial degree of the + // Stokes and temperature finite elements, it + // is easy to consistently modify the degree + // of the elements as well as all quadrature + // formulas used on them + // downstream. Moreover, we initialize the + // time stepping as well as the options for + // matrix assembly and preconditioning: + template + BoussinesqFlowProblem::BoussinesqFlowProblem () : - stokes_matrix (&S), - m_inverse (&Mpinv), - a_preconditioner (Apreconditioner), - tmp (stokes_matrix->block(1,1).m()) + triangulation (Triangulation::maximum_smoothing), + + stokes_degree (1), + stokes_fe (FE_Q(stokes_degree+1), dim, + FE_Q(stokes_degree), 1), + stokes_dof_handler (triangulation), + + temperature_degree (2), + temperature_fe (temperature_degree), + temperature_dof_handler (triangulation), + + time_step (0), + old_time_step (0), + timestep_number (0), + rebuild_stokes_matrix (true), + rebuild_temperature_matrices (true), + rebuild_stokes_preconditioner (true) {} - // Next is the vmult - // function. We implement the action of - // $P^{-1}$ as described above in three - // successive steps. In formulas, we want - // to compute $Y=P^{-1}X$ where $X,Y$ are - // both vectors with two block components. + + // @sect4{BoussinesqFlowProblem::get_maximal_velocity} + + // Starting the real functionality of this + // class is a helper function that determines + // the maximum ($L_\infty$) velocity in the + // domain (at the quadrature points, in + // fact). How it works should be relatively + // obvious to all who have gotten to this + // point of the tutorial. Note that since we + // are only interested in the velocity, + // rather than using + // stokes_fe_values.get_function_values + // to get the values of the entire Stokes + // solution (velocities and pressures) we use + // stokes_fe_values[velocities].get_function_values + // to extract only the velocities part. This + // has the additional benefit that we get it + // as a Tensor<1,dim>, rather than some + // components in a Vector, allowing + // us to process it right away using the + // norm() function to get the + // magnitude of the velocity. + // + // The only point worth thinking about a bit + // is how to choose the quadrature points we + // use here. Since the goal of this function + // is to find the maximal velocity over a + // domain by looking at quadrature points on + // each cell. So we should ask how we should + // best choose these quadrature points on + // each cell. To this end, recall that if we + // had a single $Q_1$ field (rather than the + // vector-valued field of higher order) then + // the maximum would be attained at a vertex + // of the mesh. In other words, we should use + // the QTrapez class that has quadrature + // points only at the vertices of cells. + // + // For higher order shape functions, the + // situation is more complicated: the maxima + // and minima may be attained at points + // between the support points of shape + // functions (for the usual $Q_p$ elements + // the support points are the equidistant + // Lagrange interpolation points); + // furthermore, since we are looking for the + // maximum magnitude of a vector-valued + // quantity, we can even less say with + // certainty where the set of potential + // maximal points are. Nevertheless, + // intuitively if not provably, the Lagrange + // interpolation points appear to be a better + // choice than the Gauss points. // - // The first step multiplies the velocity - // part of the vector by a preconditioner - // of the matrix A, i.e. we compute - // $Y_0={\tilde A}^{-1}X_0$. The resulting - // velocity vector is then multiplied by - // $B$ and subtracted from the pressure, - // i.e. we want to compute $X_1-BY_0$. - // This second step only acts on the - // pressure vector and is accomplished by - // the residual function of our matrix - // classes, except that the sign is - // wrong. Consequently, we change the sign - // in the temporary pressure vector and - // finally multiply by the inverse pressure - // mass matrix to get the final pressure - // vector, completing our work on the - // Stokes preconditioner: - template - void BlockSchurPreconditioner::vmult ( - TrilinosWrappers::BlockVector &dst, - const TrilinosWrappers::BlockVector &src) const + // There are now different methods to produce + // a quadrature formula with quadrature + // points equal to the interpolation points + // of the finite element. One option would be + // to use the + // FiniteElement::get_unit_support_points() + // function, reduce the output to a unique + // set of points to avoid duplicate function + // evaluations, and create a Quadrature + // object using these points. Another option, + // chosen here, is to use the QTrapez class + // and combine it with the QIterated class + // that repeats the QTrapez formula on a + // number of sub-cells in each coordinate + // direction. To cover all support points, we + // need to iterate it + // stokes_degree+1 times since + // this is the polynomial degree of the + // Stokes element in use: + template + double BoussinesqFlowProblem::get_maximal_velocity () const { - a_preconditioner.vmult (dst.block(0), src.block(0)); - stokes_matrix->block(1,0).residual(tmp, dst.block(0), src.block(1)); - tmp *= -1; - m_inverse->vmult (dst.block(1), tmp); - } -} + const QIterated quadrature_formula (QTrapez<1>(), + stokes_degree+1); + const unsigned int n_q_points = quadrature_formula.size(); + FEValues fe_values (stokes_fe, quadrature_formula, update_values); + std::vector > velocity_values(n_q_points); + double max_velocity = 0; + const FEValuesExtractors::Vector velocities (0); - // @sect3{The BoussinesqFlowProblem class template} - - // The definition of the class that defines - // the top-level logic of solving the - // time-dependent Boussinesq problem is - // mainly based on the step-22 tutorial - // program. The main differences are that now - // we also have to solve for the temperature - // equation, which forces us to have a second - // DoFHandler object for the temperature - // variable as well as matrices, right hand - // sides, and solution vectors for the - // current and previous time steps. As - // mentioned in the introduction, all linear - // algebra objects are going to use wrappers - // of the corresponding Trilinos - // functionality. - // - // The member functions of this class are - // reminiscent of step-21, where we also used - // a staggered scheme that first solve the - // flow equations (here the Stokes equations, - // in step-21 Darcy flow) and then update - // the advected quantity (here the - // temperature, there the saturation). The - // functions that are new are mainly - // concerned with determining the time step, - // as well as the proper size of the - // artificial viscosity stabilization. - // - // The last three variables indicate whether - // the various matrices or preconditioners - // need to be rebuilt the next time the - // corresponding build functions are - // called. This allows us to move the - // corresponding if into the - // respective function and thereby keeping - // our main run() function clean - // and easy to read. -template -class BoussinesqFlowProblem -{ - public: - BoussinesqFlowProblem (); - void run (); - - private: - void setup_dofs (); - void assemble_stokes_preconditioner (); - void build_stokes_preconditioner (); - void assemble_stokes_system (); - void assemble_temperature_system (const double maximal_velocity); - void assemble_temperature_matrix (); - double get_maximal_velocity () const; - std::pair get_extrapolated_temperature_range () const; - void solve (); - void output_results () const; - void refine_mesh (const unsigned int max_grid_level); + typename DoFHandler::active_cell_iterator + cell = stokes_dof_handler.begin_active(), + endc = stokes_dof_handler.end(); + for (; cell!=endc; ++cell) + { + fe_values.reinit (cell); + fe_values[velocities].get_function_values (stokes_solution, + velocity_values); - double - compute_viscosity(const std::vector &old_temperature, - const std::vector &old_old_temperature, - const std::vector > &old_temperature_grads, - const std::vector > &old_old_temperature_grads, - const std::vector &old_temperature_laplacians, - const std::vector &old_old_temperature_laplacians, - const std::vector > &old_velocity_values, - const std::vector > &old_old_velocity_values, - const std::vector &gamma_values, - const double global_u_infty, - const double global_T_variation, - const double cell_diameter) const; + for (unsigned int q=0; q triangulation; - double global_Omega_diameter; - const unsigned int stokes_degree; - FESystem stokes_fe; - DoFHandler stokes_dof_handler; - ConstraintMatrix stokes_constraints; - std::vector stokes_block_sizes; - TrilinosWrappers::BlockSparseMatrix stokes_matrix; - TrilinosWrappers::BlockSparseMatrix stokes_preconditioner_matrix; - TrilinosWrappers::BlockVector stokes_solution; - TrilinosWrappers::BlockVector old_stokes_solution; - TrilinosWrappers::BlockVector stokes_rhs; + // @sect4{BoussinesqFlowProblem::get_extrapolated_temperature_range} + // Next a function that determines the + // minimum and maximum temperature at + // quadrature points inside $\Omega$ when + // extrapolated from the two previous time + // steps to the current one. We need this + // information in the computation of the + // artificial viscosity parameter $\nu$ as + // discussed in the introduction. + // + // The formula for the extrapolated + // temperature is + // $\left(1+\frac{k_n}{k_{n-1}} + // \right)T^{n-1} + \frac{k_n}{k_{n-1}} + // T^{n-2}$. The way to compute it is to loop + // over all quadrature points and update the + // maximum and minimum value if the current + // value is bigger/smaller than the previous + // one. We initialize the variables that + // store the max and min before the loop over + // all quadrature points by the smallest and + // the largest number representable as a + // double. Then we know for a fact that it is + // larger/smaller than the minimum/maximum + // and that the loop over all quadrature + // points is ultimately going to update the + // initial value with the correct one. + // + // The only other complication worth + // mentioning here is that in the first time + // step, $T^{k-2}$ is not yet available of + // course. In that case, we can only use + // $T^{k-1}$ which we have from the initial + // temperature. As quadrature points, we use + // the same choice as in the previous + // function though with the difference that + // now the number of repetitions is + // determined by the polynomial degree of the + // temperature field. + template + std::pair + BoussinesqFlowProblem::get_extrapolated_temperature_range () const + { + const QIterated quadrature_formula (QTrapez<1>(), + temperature_degree); + const unsigned int n_q_points = quadrature_formula.size(); - const unsigned int temperature_degree; - FE_Q temperature_fe; - DoFHandler temperature_dof_handler; - ConstraintMatrix temperature_constraints; + FEValues fe_values (temperature_fe, quadrature_formula, + update_values); + std::vector old_temperature_values(n_q_points); + std::vector old_old_temperature_values(n_q_points); - TrilinosWrappers::SparseMatrix temperature_mass_matrix; - TrilinosWrappers::SparseMatrix temperature_stiffness_matrix; - TrilinosWrappers::SparseMatrix temperature_matrix; + if (timestep_number != 0) + { + double min_temperature = std::numeric_limits::max(), + max_temperature = -std::numeric_limits::max(); + + typename DoFHandler::active_cell_iterator + cell = temperature_dof_handler.begin_active(), + endc = temperature_dof_handler.end(); + for (; cell!=endc; ++cell) + { + fe_values.reinit (cell); + fe_values.get_function_values (old_temperature_solution, + old_temperature_values); + fe_values.get_function_values (old_old_temperature_solution, + old_old_temperature_values); + + for (unsigned int q=0; q::max(), + max_temperature = -std::numeric_limits::max(); + + typename DoFHandler::active_cell_iterator + cell = temperature_dof_handler.begin_active(), + endc = temperature_dof_handler.end(); + for (; cell!=endc; ++cell) + { + fe_values.reinit (cell); + fe_values.get_function_values (old_temperature_solution, + old_temperature_values); + + for (unsigned int q=0; q Amg_preconditioner; - std_cxx1x::shared_ptr Mp_preconditioner; + return std::make_pair(min_temperature, max_temperature); + } + } - bool rebuild_stokes_matrix; - bool rebuild_temperature_matrices; - bool rebuild_stokes_preconditioner; -}; - // @sect3{BoussinesqFlowProblem class implementation} + // @sect4{BoussinesqFlowProblem::compute_viscosity} - // @sect4{BoussinesqFlowProblem::BoussinesqFlowProblem} - // - // The constructor of this class is an - // extension of the constructor in - // step-22. We need to add the various - // variables that concern the temperature. As - // discussed in the introduction, we are - // going to use $Q_2\times Q_1$ (Taylor-Hood) - // elements again for the Stokes part, and - // $Q_2$ elements for the - // temperature. However, by using variables - // that store the polynomial degree of the - // Stokes and temperature finite elements, it - // is easy to consistently modify the degree - // of the elements as well as all quadrature - // formulas used on them - // downstream. Moreover, we initialize the - // time stepping as well as the options for - // matrix assembly and preconditioning: -template -BoussinesqFlowProblem::BoussinesqFlowProblem () - : - triangulation (Triangulation::maximum_smoothing), - - stokes_degree (1), - stokes_fe (FE_Q(stokes_degree+1), dim, - FE_Q(stokes_degree), 1), - stokes_dof_handler (triangulation), - - temperature_degree (2), - temperature_fe (temperature_degree), - temperature_dof_handler (triangulation), - - time_step (0), - old_time_step (0), - timestep_number (0), - rebuild_stokes_matrix (true), - rebuild_temperature_matrices (true), - rebuild_stokes_preconditioner (true) -{} - - - - // @sect4{BoussinesqFlowProblem::get_maximal_velocity} - - // Starting the real functionality of this - // class is a helper function that determines - // the maximum ($L_\infty$) velocity in the - // domain (at the quadrature points, in - // fact). How it works should be relatively - // obvious to all who have gotten to this - // point of the tutorial. Note that since we - // are only interested in the velocity, - // rather than using - // stokes_fe_values.get_function_values - // to get the values of the entire Stokes - // solution (velocities and pressures) we use - // stokes_fe_values[velocities].get_function_values - // to extract only the velocities part. This - // has the additional benefit that we get it - // as a Tensor<1,dim>, rather than some - // components in a Vector, allowing - // us to process it right away using the - // norm() function to get the - // magnitude of the velocity. - // - // The only point worth thinking about a bit - // is how to choose the quadrature points we - // use here. Since the goal of this function - // is to find the maximal velocity over a - // domain by looking at quadrature points on - // each cell. So we should ask how we should - // best choose these quadrature points on - // each cell. To this end, recall that if we - // had a single $Q_1$ field (rather than the - // vector-valued field of higher order) then - // the maximum would be attained at a vertex - // of the mesh. In other words, we should use - // the QTrapez class that has quadrature - // points only at the vertices of cells. - // - // For higher order shape functions, the - // situation is more complicated: the maxima - // and minima may be attained at points - // between the support points of shape - // functions (for the usual $Q_p$ elements - // the support points are the equidistant - // Lagrange interpolation points); - // furthermore, since we are looking for the - // maximum magnitude of a vector-valued - // quantity, we can even less say with - // certainty where the set of potential - // maximal points are. Nevertheless, - // intuitively if not provably, the Lagrange - // interpolation points appear to be a better - // choice than the Gauss points. - // - // There are now different methods to produce - // a quadrature formula with quadrature - // points equal to the interpolation points - // of the finite element. One option would be - // to use the - // FiniteElement::get_unit_support_points() - // function, reduce the output to a unique - // set of points to avoid duplicate function - // evaluations, and create a Quadrature - // object using these points. Another option, - // chosen here, is to use the QTrapez class - // and combine it with the QIterated class - // that repeats the QTrapez formula on a - // number of sub-cells in each coordinate - // direction. To cover all support points, we - // need to iterate it - // stokes_degree+1 times since - // this is the polynomial degree of the - // Stokes element in use: -template -double BoussinesqFlowProblem::get_maximal_velocity () const -{ - const QIterated quadrature_formula (QTrapez<1>(), - stokes_degree+1); - const unsigned int n_q_points = quadrature_formula.size(); + // The last of the tool functions computes + // the artificial viscosity parameter + // $\nu|_K$ on a cell $K$ as a function of + // the extrapolated temperature, its + // gradient and Hessian (second + // derivatives), the velocity, the right + // hand side $\gamma$ all on the quadrature + // points of the current cell, and various + // other parameters as described in detail + // in the introduction. + // + // There are some universal constants worth + // mentioning here. First, we need to fix + // $\beta$; we choose $\beta=0.015\cdot + // dim$, a choice discussed in detail in + // the results section of this tutorial + // program. The second is the exponent + // $\alpha$; $\alpha=1$ appears to work + // fine for the current program, even + // though some additional benefit might be + // expected from chosing $\alpha = + // 2$. Finally, there is one thing that + // requires special casing: In the first + // time step, the velocity equals zero, and + // the formula for $\nu|_K$ is not + // defined. In that case, we return + // $\nu|_K=5\cdot 10^3 \cdot h_K$, a choice + // admittedly more motivated by heuristics + // than anything else (it is in the same + // order of magnitude, however, as the + // value returned for most cells on the + // second time step). + // + // The rest of the function should be + // mostly obvious based on the material + // discussed in the introduction: + template + double + BoussinesqFlowProblem:: + compute_viscosity (const std::vector &old_temperature, + const std::vector &old_old_temperature, + const std::vector > &old_temperature_grads, + const std::vector > &old_old_temperature_grads, + const std::vector &old_temperature_laplacians, + const std::vector &old_old_temperature_laplacians, + const std::vector > &old_velocity_values, + const std::vector > &old_old_velocity_values, + const std::vector &gamma_values, + const double global_u_infty, + const double global_T_variation, + const double cell_diameter) const + { + const double beta = 0.015 * dim; + const double alpha = 1; - FEValues fe_values (stokes_fe, quadrature_formula, update_values); - std::vector > velocity_values(n_q_points); - double max_velocity = 0; + if (global_u_infty == 0) + return 5e-3 * cell_diameter; - const FEValuesExtractors::Vector velocities (0); + const unsigned int n_q_points = old_temperature.size(); - typename DoFHandler::active_cell_iterator - cell = stokes_dof_handler.begin_active(), - endc = stokes_dof_handler.end(); - for (; cell!=endc; ++cell) - { - fe_values.reinit (cell); - fe_values[velocities].get_function_values (stokes_solution, - velocity_values); + double max_residual = 0; + double max_velocity = 0; - for (unsigned int q=0; q u = (old_velocity_values[q] + + old_old_velocity_values[q]) / 2; - return max_velocity; -} + const double dT_dt = (old_temperature[q] - old_old_temperature[q]) + / old_time_step; + const double u_grad_T = u * (old_temperature_grads[q] + + old_old_temperature_grads[q]) / 2; + const double kappa_Delta_T = EquationData::kappa + * (old_temperature_laplacians[q] + + old_old_temperature_laplacians[q]) / 2; + const double residual + = std::abs((dT_dt + u_grad_T - kappa_Delta_T - gamma_values[q]) * + std::pow((old_temperature[q]+old_old_temperature[q]) / 2, + alpha-1.)); + max_residual = std::max (residual, max_residual); + max_velocity = std::max (std::sqrt (u*u), max_velocity); + } - // @sect4{BoussinesqFlowProblem::get_extrapolated_temperature_range} + const double c_R = std::pow (2., (4.-2*alpha)/dim); + const double global_scaling = c_R * global_u_infty * global_T_variation * + std::pow(global_Omega_diameter, alpha - 2.); - // Next a function that determines the - // minimum and maximum temperature at - // quadrature points inside $\Omega$ when - // extrapolated from the two previous time - // steps to the current one. We need this - // information in the computation of the - // artificial viscosity parameter $\nu$ as - // discussed in the introduction. - // - // The formula for the extrapolated - // temperature is - // $\left(1+\frac{k_n}{k_{n-1}} - // \right)T^{n-1} + \frac{k_n}{k_{n-1}} - // T^{n-2}$. The way to compute it is to loop - // over all quadrature points and update the - // maximum and minimum value if the current - // value is bigger/smaller than the previous - // one. We initialize the variables that - // store the max and min before the loop over - // all quadrature points by the smallest and - // the largest number representable as a - // double. Then we know for a fact that it is - // larger/smaller than the minimum/maximum - // and that the loop over all quadrature - // points is ultimately going to update the - // initial value with the correct one. - // - // The only other complication worth - // mentioning here is that in the first time - // step, $T^{k-2}$ is not yet available of - // course. In that case, we can only use - // $T^{k-1}$ which we have from the initial - // temperature. As quadrature points, we use - // the same choice as in the previous - // function though with the difference that - // now the number of repetitions is - // determined by the polynomial degree of the - // temperature field. -template -std::pair -BoussinesqFlowProblem::get_extrapolated_temperature_range () const -{ - const QIterated quadrature_formula (QTrapez<1>(), - temperature_degree); - const unsigned int n_q_points = quadrature_formula.size(); + return (beta * + max_velocity * + std::min (cell_diameter, + std::pow(cell_diameter,alpha) * + max_residual / global_scaling)); + } - FEValues fe_values (temperature_fe, quadrature_formula, - update_values); - std::vector old_temperature_values(n_q_points); - std::vector old_old_temperature_values(n_q_points); - if (timestep_number != 0) - { - double min_temperature = std::numeric_limits::max(), - max_temperature = -std::numeric_limits::max(); - typename DoFHandler::active_cell_iterator - cell = temperature_dof_handler.begin_active(), - endc = temperature_dof_handler.end(); - for (; cell!=endc; ++cell) - { - fe_values.reinit (cell); - fe_values.get_function_values (old_temperature_solution, - old_temperature_values); - fe_values.get_function_values (old_old_temperature_solution, - old_old_temperature_values); - - for (unsigned int q=0; qno_normal_flux_boundaries + // denotes the boundary indicators for which + // to set the no flux boundary conditions; + // here, this is boundary indicator zero. + // + // After having done so, we count the number + // of degrees of freedom in the various + // blocks: + template + void BoussinesqFlowProblem::setup_dofs () + { + std::vector stokes_sub_blocks (dim+1,0); + stokes_sub_blocks[dim] = 1; - return std::make_pair(min_temperature, max_temperature); + { + stokes_dof_handler.distribute_dofs (stokes_fe); + DoFRenumbering::component_wise (stokes_dof_handler, stokes_sub_blocks); + + stokes_constraints.clear (); + DoFTools::make_hanging_node_constraints (stokes_dof_handler, + stokes_constraints); + std::set no_normal_flux_boundaries; + no_normal_flux_boundaries.insert (0); + VectorTools::compute_no_normal_flux_constraints (stokes_dof_handler, 0, + no_normal_flux_boundaries, + stokes_constraints); + stokes_constraints.close (); } - else { - double min_temperature = std::numeric_limits::max(), - max_temperature = -std::numeric_limits::max(); - - typename DoFHandler::active_cell_iterator - cell = temperature_dof_handler.begin_active(), - endc = temperature_dof_handler.end(); - for (; cell!=endc; ++cell) - { - fe_values.reinit (cell); - fe_values.get_function_values (old_temperature_solution, - old_temperature_values); + temperature_dof_handler.distribute_dofs (temperature_fe); - for (unsigned int q=0; q stokes_dofs_per_block (2); + DoFTools::count_dofs_per_block (stokes_dof_handler, stokes_dofs_per_block, + stokes_sub_blocks); + + const unsigned int n_u = stokes_dofs_per_block[0], + n_p = stokes_dofs_per_block[1], + n_T = temperature_dof_handler.n_dofs(); + + std::cout << "Number of active cells: " + << triangulation.n_active_cells() + << " (on " + << triangulation.n_levels() + << " levels)" + << std::endl + << "Number of degrees of freedom: " + << n_u + n_p + n_T + << " (" << n_u << '+' << n_p << '+'<< n_T <<')' + << std::endl + << std::endl; - return std::make_pair(min_temperature, max_temperature); - } -} + // The next step is to create the sparsity + // pattern for the Stokes and temperature + // system matrices as well as the + // preconditioner matrix from which we + // build the Stokes preconditioner. As in + // step-22, we choose to create the pattern + // not as in the first few tutorial + // programs, but by using the blocked + // version of CompressedSimpleSparsityPattern. + // The reason for doing this is mainly + // memory, that is, the SparsityPattern + // class would consume too much memory when + // used in three spatial dimensions as we + // intend to do for this program. + // + // So, we first release the memory stored + // in the matrices, then set up an object + // of type + // BlockCompressedSimpleSparsityPattern + // consisting of $2\times 2$ blocks (for + // the Stokes system matrix and + // preconditioner) or + // CompressedSimpleSparsityPattern (for + // the temperature part). We then fill + // these objects with the nonzero + // pattern, taking into account that for + // the Stokes system matrix, there are no + // entries in the pressure-pressure block + // (but all velocity vector components + // couple with each other and with the + // pressure). Similarly, in the Stokes + // preconditioner matrix, only the + // diagonal blocks are nonzero, since we + // use the vector Laplacian as discussed + // in the introduction. This operator + // only couples each vector component of + // the Laplacian with itself, but not + // with the other vector + // components. (Application of the + // constraints resulting from the no-flux + // boundary conditions will couple vector + // components at the boundary again, + // however.) + // + // When generating the sparsity pattern, + // we directly apply the constraints from + // hanging nodes and no-flux boundary + // conditions. This approach was already + // used in step-27, but is different from + // the one in early tutorial programs + // where we first built the original + // sparsity pattern and only then added + // the entries resulting from + // constraints. The reason for doing so + // is that later during assembly we are + // going to distribute the constraints + // immediately when transferring local to + // global dofs. Consequently, there will + // be no data written at positions of + // constrained degrees of freedom, so we + // can let the + // DoFTools::make_sparsity_pattern + // function omit these entries by setting + // the last boolean flag to + // false. Once the sparsity + // pattern is ready, we can use it to + // initialize the Trilinos + // matrices. Since the Trilinos matrices + // store the sparsity pattern internally, + // there is no need to keep the sparsity + // pattern around after the + // initialization of the matrix. + stokes_block_sizes.resize (2); + stokes_block_sizes[0] = n_u; + stokes_block_sizes[1] = n_p; + { + stokes_matrix.clear (); + BlockCompressedSimpleSparsityPattern csp (2,2); + csp.block(0,0).reinit (n_u, n_u); + csp.block(0,1).reinit (n_u, n_p); + csp.block(1,0).reinit (n_p, n_u); + csp.block(1,1).reinit (n_p, n_p); - // @sect4{BoussinesqFlowProblem::compute_viscosity} + csp.collect_sizes (); - // The last of the tool functions computes - // the artificial viscosity parameter - // $\nu|_K$ on a cell $K$ as a function of - // the extrapolated temperature, its - // gradient and Hessian (second - // derivatives), the velocity, the right - // hand side $\gamma$ all on the quadrature - // points of the current cell, and various - // other parameters as described in detail - // in the introduction. - // - // There are some universal constants worth - // mentioning here. First, we need to fix - // $\beta$; we choose $\beta=0.015\cdot - // dim$, a choice discussed in detail in - // the results section of this tutorial - // program. The second is the exponent - // $\alpha$; $\alpha=1$ appears to work - // fine for the current program, even - // though some additional benefit might be - // expected from chosing $\alpha = - // 2$. Finally, there is one thing that - // requires special casing: In the first - // time step, the velocity equals zero, and - // the formula for $\nu|_K$ is not - // defined. In that case, we return - // $\nu|_K=5\cdot 10^3 \cdot h_K$, a choice - // admittedly more motivated by heuristics - // than anything else (it is in the same - // order of magnitude, however, as the - // value returned for most cells on the - // second time step). - // - // The rest of the function should be - // mostly obvious based on the material - // discussed in the introduction: -template -double -BoussinesqFlowProblem:: -compute_viscosity (const std::vector &old_temperature, - const std::vector &old_old_temperature, - const std::vector > &old_temperature_grads, - const std::vector > &old_old_temperature_grads, - const std::vector &old_temperature_laplacians, - const std::vector &old_old_temperature_laplacians, - const std::vector > &old_velocity_values, - const std::vector > &old_old_velocity_values, - const std::vector &gamma_values, - const double global_u_infty, - const double global_T_variation, - const double cell_diameter) const -{ - const double beta = 0.015 * dim; - const double alpha = 1; + Table<2,DoFTools::Coupling> coupling (dim+1, dim+1); - if (global_u_infty == 0) - return 5e-3 * cell_diameter; + for (unsigned int c=0; c u = (old_velocity_values[q] + - old_old_velocity_values[q]) / 2; + Amg_preconditioner.reset (); + Mp_preconditioner.reset (); + stokes_preconditioner_matrix.clear (); - const double dT_dt = (old_temperature[q] - old_old_temperature[q]) - / old_time_step; - const double u_grad_T = u * (old_temperature_grads[q] + - old_old_temperature_grads[q]) / 2; + BlockCompressedSimpleSparsityPattern csp (2,2); - const double kappa_Delta_T = EquationData::kappa - * (old_temperature_laplacians[q] + - old_old_temperature_laplacians[q]) / 2; + csp.block(0,0).reinit (n_u, n_u); + csp.block(0,1).reinit (n_u, n_p); + csp.block(1,0).reinit (n_p, n_u); + csp.block(1,1).reinit (n_p, n_p); - const double residual - = std::abs((dT_dt + u_grad_T - kappa_Delta_T - gamma_values[q]) * - std::pow((old_temperature[q]+old_old_temperature[q]) / 2, - alpha-1.)); + csp.collect_sizes (); - max_residual = std::max (residual, max_residual); - max_velocity = std::max (std::sqrt (u*u), max_velocity); - } + Table<2,DoFTools::Coupling> coupling (dim+1, dim+1); + for (unsigned int c=0; cno_normal_flux_boundaries - // denotes the boundary indicators for which - // to set the no flux boundary conditions; - // here, this is boundary indicator zero. - // - // After having done so, we count the number - // of degrees of freedom in the various - // blocks: -template -void BoussinesqFlowProblem::setup_dofs () -{ - std::vector stokes_sub_blocks (dim+1,0); - stokes_sub_blocks[dim] = 1; + temperature_matrix.reinit (csp); + temperature_mass_matrix.reinit (temperature_matrix); + temperature_stiffness_matrix.reinit (temperature_matrix); + } - { - stokes_dof_handler.distribute_dofs (stokes_fe); - DoFRenumbering::component_wise (stokes_dof_handler, stokes_sub_blocks); - - stokes_constraints.clear (); - DoFTools::make_hanging_node_constraints (stokes_dof_handler, - stokes_constraints); - std::set no_normal_flux_boundaries; - no_normal_flux_boundaries.insert (0); - VectorTools::compute_no_normal_flux_constraints (stokes_dof_handler, 0, - no_normal_flux_boundaries, - stokes_constraints); - stokes_constraints.close (); + // Lastly, we set the vectors for the + // Stokes solutions $\mathbf u^{n-1}$ and + // $\mathbf u^{n-2}$, as well as for the + // temperatures $T^{n}$, $T^{n-1}$ and + // $T^{n-2}$ (required for time stepping) + // and all the system right hand sides to + // their correct sizes and block + // structure: + stokes_solution.reinit (stokes_block_sizes); + old_stokes_solution.reinit (stokes_block_sizes); + stokes_rhs.reinit (stokes_block_sizes); + + temperature_solution.reinit (temperature_dof_handler.n_dofs()); + old_temperature_solution.reinit (temperature_dof_handler.n_dofs()); + old_old_temperature_solution.reinit (temperature_dof_handler.n_dofs()); + + temperature_rhs.reinit (temperature_dof_handler.n_dofs()); } - { - temperature_dof_handler.distribute_dofs (temperature_fe); - temperature_constraints.clear (); - DoFTools::make_hanging_node_constraints (temperature_dof_handler, - temperature_constraints); - temperature_constraints.close (); - } - std::vector stokes_dofs_per_block (2); - DoFTools::count_dofs_per_block (stokes_dof_handler, stokes_dofs_per_block, - stokes_sub_blocks); - - const unsigned int n_u = stokes_dofs_per_block[0], - n_p = stokes_dofs_per_block[1], - n_T = temperature_dof_handler.n_dofs(); - - std::cout << "Number of active cells: " - << triangulation.n_active_cells() - << " (on " - << triangulation.n_levels() - << " levels)" - << std::endl - << "Number of degrees of freedom: " - << n_u + n_p + n_T - << " (" << n_u << '+' << n_p << '+'<< n_T <<')' - << std::endl - << std::endl; - - // The next step is to create the sparsity - // pattern for the Stokes and temperature - // system matrices as well as the - // preconditioner matrix from which we - // build the Stokes preconditioner. As in - // step-22, we choose to create the pattern - // not as in the first few tutorial - // programs, but by using the blocked - // version of CompressedSimpleSparsityPattern. - // The reason for doing this is mainly - // memory, that is, the SparsityPattern - // class would consume too much memory when - // used in three spatial dimensions as we - // intend to do for this program. - // - // So, we first release the memory stored - // in the matrices, then set up an object - // of type - // BlockCompressedSimpleSparsityPattern - // consisting of $2\times 2$ blocks (for - // the Stokes system matrix and - // preconditioner) or - // CompressedSimpleSparsityPattern (for - // the temperature part). We then fill - // these objects with the nonzero - // pattern, taking into account that for - // the Stokes system matrix, there are no - // entries in the pressure-pressure block - // (but all velocity vector components - // couple with each other and with the - // pressure). Similarly, in the Stokes - // preconditioner matrix, only the - // diagonal blocks are nonzero, since we - // use the vector Laplacian as discussed - // in the introduction. This operator - // only couples each vector component of - // the Laplacian with itself, but not - // with the other vector - // components. (Application of the - // constraints resulting from the no-flux - // boundary conditions will couple vector - // components at the boundary again, - // however.) + + // @sect4{BoussinesqFlowProblem::assemble_stokes_preconditioner} // - // When generating the sparsity pattern, - // we directly apply the constraints from - // hanging nodes and no-flux boundary - // conditions. This approach was already - // used in step-27, but is different from - // the one in early tutorial programs - // where we first built the original - // sparsity pattern and only then added - // the entries resulting from - // constraints. The reason for doing so - // is that later during assembly we are - // going to distribute the constraints - // immediately when transferring local to - // global dofs. Consequently, there will - // be no data written at positions of - // constrained degrees of freedom, so we - // can let the - // DoFTools::make_sparsity_pattern - // function omit these entries by setting - // the last boolean flag to - // false. Once the sparsity - // pattern is ready, we can use it to - // initialize the Trilinos - // matrices. Since the Trilinos matrices - // store the sparsity pattern internally, - // there is no need to keep the sparsity - // pattern around after the - // initialization of the matrix. - stokes_block_sizes.resize (2); - stokes_block_sizes[0] = n_u; - stokes_block_sizes[1] = n_p; + // This function assembles the matrix we use + // for preconditioning the Stokes + // system. What we need are a vector Laplace + // matrix on the velocity components and a + // mass matrix weighted by $\eta^{-1}$ on the + // pressure component. We start by generating + // a quadrature object of appropriate order, + // the FEValues object that can give values + // and gradients at the quadrature points + // (together with quadrature weights). Next + // we create data structures for the cell + // matrix and the relation between local and + // global DoFs. The vectors + // phi_grad_u and + // phi_p are going to hold the + // values of the basis functions in order to + // faster build up the local matrices, as was + // already done in step-22. Before we start + // the loop over all active cells, we have to + // specify which components are pressure and + // which are velocity. + template + void + BoussinesqFlowProblem::assemble_stokes_preconditioner () { - stokes_matrix.clear (); - - BlockCompressedSimpleSparsityPattern csp (2,2); - - csp.block(0,0).reinit (n_u, n_u); - csp.block(0,1).reinit (n_u, n_p); - csp.block(1,0).reinit (n_p, n_u); - csp.block(1,1).reinit (n_p, n_p); + stokes_preconditioner_matrix = 0; - csp.collect_sizes (); + const QGauss quadrature_formula(stokes_degree+2); + FEValues stokes_fe_values (stokes_fe, quadrature_formula, + update_JxW_values | + update_values | + update_gradients); - Table<2,DoFTools::Coupling> coupling (dim+1, dim+1); + const unsigned int dofs_per_cell = stokes_fe.dofs_per_cell; + const unsigned int n_q_points = quadrature_formula.size(); - for (unsigned int c=0; c local_matrix (dofs_per_cell, dofs_per_cell); + std::vector local_dof_indices (dofs_per_cell); - DoFTools::make_sparsity_pattern (stokes_dof_handler, coupling, csp, - stokes_constraints, false); + std::vector > phi_grad_u (dofs_per_cell); + std::vector phi_p (dofs_per_cell); - stokes_matrix.reinit (csp); - } + const FEValuesExtractors::Vector velocities (0); + const FEValuesExtractors::Scalar pressure (dim); - { - Amg_preconditioner.reset (); - Mp_preconditioner.reset (); - stokes_preconditioner_matrix.clear (); - - BlockCompressedSimpleSparsityPattern csp (2,2); - - csp.block(0,0).reinit (n_u, n_u); - csp.block(0,1).reinit (n_u, n_p); - csp.block(1,0).reinit (n_p, n_u); - csp.block(1,1).reinit (n_p, n_p); - - csp.collect_sizes (); - - Table<2,DoFTools::Coupling> coupling (dim+1, dim+1); - for (unsigned int c=0; c::active_cell_iterator + cell = stokes_dof_handler.begin_active(), + endc = stokes_dof_handler.end(); + for (; cell!=endc; ++cell) + { + stokes_fe_values.reinit (cell); + local_matrix = 0; + + // The creation of the local matrix is + // rather simple. There are only a + // Laplace term (on the velocity) and a + // mass matrix weighted by $\eta^{-1}$ + // to be generated, so the creation of + // the local matrix is done in two + // lines. Once the local matrix is + // ready (loop over rows and columns in + // the local matrix on each quadrature + // point), we get the local DoF indices + // and write the local information into + // the global matrix. We do this as in + // step-27, i.e. we directly apply the + // constraints from hanging nodes + // locally. By doing so, we don't have + // to do that afterwards, and we don't + // also write into entries of the + // matrix that will actually be set to + // zero again later when eliminating + // constraints. + for (unsigned int q=0; qget_dof_indices (local_dof_indices); + stokes_constraints.distribute_local_to_global (local_matrix, + local_dof_indices, + stokes_preconditioner_matrix); + } } - // The creation of the temperature matrix - // (or, rather, matrices, since we - // provide a temperature mass matrix and - // a temperature stiffness matrix, that - // will be added together for time - // discretization) follows the generation - // of the Stokes matrix – except - // that it is much easier here since we - // do not need to take care of any blocks - // or coupling between components. Note - // how we initialize the three - // temperature matrices: We only use the - // sparsity pattern for reinitialization - // of the first matrix, whereas we use - // the previously generated matrix for - // the two remaining reinits. The reason - // for doing so is that reinitialization - // from an already generated matrix - // allows Trilinos to reuse the sparsity - // pattern instead of generating a new - // one for each copy. This saves both - // some time and memory. - { - temperature_mass_matrix.clear (); - temperature_stiffness_matrix.clear (); - temperature_matrix.clear (); - CompressedSimpleSparsityPattern csp (n_T, n_T); - DoFTools::make_sparsity_pattern (temperature_dof_handler, csp, - temperature_constraints, false); - temperature_matrix.reinit (csp); - temperature_mass_matrix.reinit (temperature_matrix); - temperature_stiffness_matrix.reinit (temperature_matrix); + // @sect4{BoussinesqFlowProblem::build_stokes_preconditioner} + // + // This function generates the inner + // preconditioners that are going to be used + // for the Schur complement block + // preconditioner. Since the preconditioners + // need only to be regenerated when the + // matrices change, this function does not + // have to do anything in case the matrices + // have not changed (i.e., the flag + // rebuild_stokes_preconditioner + // has the value + // false). Otherwise its first + // task is to call + // assemble_stokes_preconditioner + // to generate the preconditioner matrices. + // + // Next, we set up the preconditioner for + // the velocity-velocity matrix + // A. As explained in the + // introduction, we are going to use an + // AMG preconditioner based on a vector + // Laplace matrix $\hat{A}$ (which is + // spectrally close to the Stokes matrix + // A). Usually, the + // TrilinosWrappers::PreconditionAMG + // class can be seen as a good black-box + // preconditioner which does not need any + // special knowledge. In this case, + // however, we have to be careful: since + // we build an AMG for a vector problem, + // we have to tell the preconditioner + // setup which dofs belong to which + // vector component. We do this using the + // function + // DoFTools::extract_constant_modes, a + // function that generates a set of + // dim vectors, where each one + // has ones in the respective component + // of the vector problem and zeros + // elsewhere. Hence, these are the + // constant modes on each component, + // which explains the name of the + // variable. + template + void + BoussinesqFlowProblem::build_stokes_preconditioner () + { + if (rebuild_stokes_preconditioner == false) + return; + + std::cout << " Rebuilding Stokes preconditioner..." << std::flush; + + assemble_stokes_preconditioner (); + + Amg_preconditioner = std_cxx1x::shared_ptr + (new TrilinosWrappers::PreconditionAMG()); + + std::vector > constant_modes; + std::vector velocity_components (dim+1,true); + velocity_components[dim] = false; + DoFTools::extract_constant_modes (stokes_dof_handler, velocity_components, + constant_modes); + TrilinosWrappers::PreconditionAMG::AdditionalData amg_data; + amg_data.constant_modes = constant_modes; + + // Next, we set some more options of the + // AMG preconditioner. In particular, we + // need to tell the AMG setup that we use + // quadratic basis functions for the + // velocity matrix (this implies more + // nonzero elements in the matrix, so + // that a more rubust algorithm needs to + // be chosen internally). Moreover, we + // want to be able to control how the + // coarsening structure is build up. The + // way the Trilinos smoothed aggregation + // AMG does this is to look which matrix + // entries are of similar size as the + // diagonal entry in order to + // algebraically build a coarse-grid + // structure. By setting the parameter + // aggregation_threshold to + // 0.02, we specify that all entries that + // are more than two precent of size of + // some diagonal pivots in that row + // should form one coarse grid + // point. This parameter is rather + // ad-hoc, and some fine-tuning of it can + // influence the performance of the + // preconditioner. As a rule of thumb, + // larger values of + // aggregation_threshold + // will decrease the number of + // iterations, but increase the costs per + // iteration. A look at the Trilinos + // documentation will provide more + // information on these parameters. With + // this data set, we then initialize the + // preconditioner with the matrix we want + // it to apply to. + // + // Finally, we also initialize the + // preconditioner for the inversion of + // the pressure mass matrix. This matrix + // is symmetric and well-behaved, so we + // can chose a simple preconditioner. We + // stick with an incomple Cholesky (IC) + // factorization preconditioner, which is + // designed for symmetric matrices. We + // could have also chosen an SSOR + // preconditioner with relaxation factor + // around 1.2, but IC is cheaper for our + // example. We wrap the preconditioners + // into a std_cxx1x::shared_ptr + // pointer, which makes it easier to + // recreate the preconditioner next time + // around since we do not have to care + // about destroying the previously used + // object. + amg_data.elliptic = true; + amg_data.higher_order_elements = true; + amg_data.smoother_sweeps = 2; + amg_data.aggregation_threshold = 0.02; + Amg_preconditioner->initialize(stokes_preconditioner_matrix.block(0,0), + amg_data); + + Mp_preconditioner = std_cxx1x::shared_ptr + (new TrilinosWrappers::PreconditionIC()); + Mp_preconditioner->initialize(stokes_preconditioner_matrix.block(1,1)); + + std::cout << std::endl; + + rebuild_stokes_preconditioner = false; } - // Lastly, we set the vectors for the - // Stokes solutions $\mathbf u^{n-1}$ and - // $\mathbf u^{n-2}$, as well as for the - // temperatures $T^{n}$, $T^{n-1}$ and - // $T^{n-2}$ (required for time stepping) - // and all the system right hand sides to - // their correct sizes and block - // structure: - stokes_solution.reinit (stokes_block_sizes); - old_stokes_solution.reinit (stokes_block_sizes); - stokes_rhs.reinit (stokes_block_sizes); - - temperature_solution.reinit (temperature_dof_handler.n_dofs()); - old_temperature_solution.reinit (temperature_dof_handler.n_dofs()); - old_old_temperature_solution.reinit (temperature_dof_handler.n_dofs()); - - temperature_rhs.reinit (temperature_dof_handler.n_dofs()); -} - - - - // @sect4{BoussinesqFlowProblem::assemble_stokes_preconditioner} - // - // This function assembles the matrix we use - // for preconditioning the Stokes - // system. What we need are a vector Laplace - // matrix on the velocity components and a - // mass matrix weighted by $\eta^{-1}$ on the - // pressure component. We start by generating - // a quadrature object of appropriate order, - // the FEValues object that can give values - // and gradients at the quadrature points - // (together with quadrature weights). Next - // we create data structures for the cell - // matrix and the relation between local and - // global DoFs. The vectors - // phi_grad_u and - // phi_p are going to hold the - // values of the basis functions in order to - // faster build up the local matrices, as was - // already done in step-22. Before we start - // the loop over all active cells, we have to - // specify which components are pressure and - // which are velocity. -template -void -BoussinesqFlowProblem::assemble_stokes_preconditioner () -{ - stokes_preconditioner_matrix = 0; - - const QGauss quadrature_formula(stokes_degree+2); - FEValues stokes_fe_values (stokes_fe, quadrature_formula, - update_JxW_values | - update_values | - update_gradients); - const unsigned int dofs_per_cell = stokes_fe.dofs_per_cell; - const unsigned int n_q_points = quadrature_formula.size(); - FullMatrix local_matrix (dofs_per_cell, dofs_per_cell); - std::vector local_dof_indices (dofs_per_cell); - - std::vector > phi_grad_u (dofs_per_cell); - std::vector phi_p (dofs_per_cell); - - const FEValuesExtractors::Vector velocities (0); - const FEValuesExtractors::Scalar pressure (dim); + // @sect4{BoussinesqFlowProblem::assemble_stokes_system} + // + // The time lag scheme we use for advancing + // the coupled Stokes-temperature system + // forces us to split up the assembly (and + // the solution of linear systems) into two + // step. The first one is to create the + // Stokes system matrix and right hand + // side, and the second is to create matrix + // and right hand sides for the temperature + // dofs, which depends on the result of the + // linear system for the velocity. + // + // This function is called at the beginning + // of each time step. In the first time step + // or if the mesh has changed, indicated by + // the rebuild_stokes_matrix, we + // need to assemble the Stokes matrix; on the + // other hand, if the mesh hasn't changed and + // the matrix is already available, this is + // not necessary and all we need to do is + // assemble the right hand side vector which + // changes in each time step. + // + // Regarding the technical details of + // implementation, not much has changed from + // step-22. We reset matrix and vector, + // create a quadrature formula on the cells, + // and then create the respective FEValues + // object. For the update flags, we require + // basis function derivatives only in case of + // a full assembly, since they are not needed + // for the right hand side; as always, + // choosing the minimal set of flags + // depending on what is currently needed + // makes the call to FEValues::reinit further + // down in the program more efficient. + // + // There is one thing that needs to be + // commented – since we have a separate + // finite element and DoFHandler for the + // temperature, we need to generate a second + // FEValues object for the proper evaluation + // of the temperature solution. This isn't + // too complicated to realize here: just use + // the temperature structures and set an + // update flag for the basis function values + // which we need for evaluation of the + // temperature solution. The only important + // part to remember here is that the same + // quadrature formula is used for both + // FEValues objects to ensure that we get + // matching information when we loop over the + // quadrature points of the two objects. + // + // The declarations proceed with some + // shortcuts for array sizes, the creation + // of the local matrix and right hand side + // as well as the vector for the indices of + // the local dofs compared to the global + // system. + template + void BoussinesqFlowProblem::assemble_stokes_system () + { + std::cout << " Assembling..." << std::flush; + + if (rebuild_stokes_matrix == true) + stokes_matrix=0; + + stokes_rhs=0; + + const QGauss quadrature_formula (stokes_degree+2); + FEValues stokes_fe_values (stokes_fe, quadrature_formula, + update_values | + update_quadrature_points | + update_JxW_values | + (rebuild_stokes_matrix == true + ? + update_gradients + : + UpdateFlags(0))); + + FEValues temperature_fe_values (temperature_fe, quadrature_formula, + update_values); + + const unsigned int dofs_per_cell = stokes_fe.dofs_per_cell; + const unsigned int n_q_points = quadrature_formula.size(); + + FullMatrix local_matrix (dofs_per_cell, dofs_per_cell); + Vector local_rhs (dofs_per_cell); + + std::vector local_dof_indices (dofs_per_cell); + + // Next we need a vector that will contain + // the values of the temperature solution + // at the previous time level at the + // quadrature points to assemble the source + // term in the right hand side of the + // momentum equation. Let's call this vector + // old_solution_values. + // + // The set of vectors we create next hold + // the evaluations of the basis functions + // as well as their gradients and + // symmetrized gradients that will be used + // for creating the matrices. Putting these + // into their own arrays rather than asking + // the FEValues object for this information + // each time it is needed is an + // optimization to accelerate the assembly + // process, see step-22 for details. + // + // The last two declarations are used to + // extract the individual blocks + // (velocity, pressure, temperature) from + // the total FE system. + std::vector old_temperature_values(n_q_points); + + std::vector > phi_u (dofs_per_cell); + std::vector > grads_phi_u (dofs_per_cell); + std::vector div_phi_u (dofs_per_cell); + std::vector phi_p (dofs_per_cell); + + const FEValuesExtractors::Vector velocities (0); + const FEValuesExtractors::Scalar pressure (dim); + + // Now start the loop over all cells in + // the problem. We are working on two + // different DoFHandlers for this + // assembly routine, so we must have two + // different cell iterators for the two + // objects in use. This might seem a bit + // peculiar, since both the Stokes system + // and the temperature system use the + // same grid, but that's the only way to + // keep degrees of freedom in sync. The + // first statements within the loop are + // again all very familiar, doing the + // update of the finite element data as + // specified by the update flags, zeroing + // out the local arrays and getting the + // values of the old solution at the + // quadrature points. Then we are ready to + // loop over the quadrature points on the + // cell. + typename DoFHandler::active_cell_iterator + cell = stokes_dof_handler.begin_active(), + endc = stokes_dof_handler.end(); + typename DoFHandler::active_cell_iterator + temperature_cell = temperature_dof_handler.begin_active(); - typename DoFHandler::active_cell_iterator - cell = stokes_dof_handler.begin_active(), - endc = stokes_dof_handler.end(); - for (; cell!=endc; ++cell) - { - stokes_fe_values.reinit (cell); - local_matrix = 0; - - // The creation of the local matrix is - // rather simple. There are only a - // Laplace term (on the velocity) and a - // mass matrix weighted by $\eta^{-1}$ - // to be generated, so the creation of - // the local matrix is done in two - // lines. Once the local matrix is - // ready (loop over rows and columns in - // the local matrix on each quadrature - // point), we get the local DoF indices - // and write the local information into - // the global matrix. We do this as in - // step-27, i.e. we directly apply the - // constraints from hanging nodes - // locally. By doing so, we don't have - // to do that afterwards, and we don't - // also write into entries of the - // matrix that will actually be set to - // zero again later when eliminating - // constraints. - for (unsigned int q=0; qrebuild_matrices + // flag. + for (unsigned int k=0; kget_dof_indices (local_dof_indices); - stokes_constraints.distribute_local_to_global (local_matrix, - local_dof_indices, - stokes_preconditioner_matrix); - } -} + if (rebuild_stokes_matrix) + for (unsigned int i=0; i gravity = -( (dim == 2) ? (Point (0,1)) : + (Point (0,0,1)) ); + for (unsigned int i=0; ilocal_dof_indices. + // Again, we let the ConstraintMatrix + // class do the insertion of the cell + // matrix elements to the global + // matrix, which already condenses the + // hanging node constraints. + cell->get_dof_indices (local_dof_indices); + + if (rebuild_stokes_matrix == true) + stokes_constraints.distribute_local_to_global (local_matrix, + local_rhs, + local_dof_indices, + stokes_matrix, + stokes_rhs); + else + stokes_constraints.distribute_local_to_global (local_rhs, + local_dof_indices, + stokes_rhs); + } + rebuild_stokes_matrix = false; + std::cout << std::endl; + } - // @sect4{BoussinesqFlowProblem::build_stokes_preconditioner} - // - // This function generates the inner - // preconditioners that are going to be used - // for the Schur complement block - // preconditioner. Since the preconditioners - // need only to be regenerated when the - // matrices change, this function does not - // have to do anything in case the matrices - // have not changed (i.e., the flag - // rebuild_stokes_preconditioner - // has the value - // false). Otherwise its first - // task is to call - // assemble_stokes_preconditioner - // to generate the preconditioner matrices. - // - // Next, we set up the preconditioner for - // the velocity-velocity matrix - // A. As explained in the - // introduction, we are going to use an - // AMG preconditioner based on a vector - // Laplace matrix $\hat{A}$ (which is - // spectrally close to the Stokes matrix - // A). Usually, the - // TrilinosWrappers::PreconditionAMG - // class can be seen as a good black-box - // preconditioner which does not need any - // special knowledge. In this case, - // however, we have to be careful: since - // we build an AMG for a vector problem, - // we have to tell the preconditioner - // setup which dofs belong to which - // vector component. We do this using the - // function - // DoFTools::extract_constant_modes, a - // function that generates a set of - // dim vectors, where each one - // has ones in the respective component - // of the vector problem and zeros - // elsewhere. Hence, these are the - // constant modes on each component, - // which explains the name of the - // variable. -template -void -BoussinesqFlowProblem::build_stokes_preconditioner () -{ - if (rebuild_stokes_preconditioner == false) - return; - - std::cout << " Rebuilding Stokes preconditioner..." << std::flush; - - assemble_stokes_preconditioner (); - - Amg_preconditioner = std_cxx1x::shared_ptr - (new TrilinosWrappers::PreconditionAMG()); - - std::vector > constant_modes; - std::vector velocity_components (dim+1,true); - velocity_components[dim] = false; - DoFTools::extract_constant_modes (stokes_dof_handler, velocity_components, - constant_modes); - TrilinosWrappers::PreconditionAMG::AdditionalData amg_data; - amg_data.constant_modes = constant_modes; - - // Next, we set some more options of the - // AMG preconditioner. In particular, we - // need to tell the AMG setup that we use - // quadratic basis functions for the - // velocity matrix (this implies more - // nonzero elements in the matrix, so - // that a more rubust algorithm needs to - // be chosen internally). Moreover, we - // want to be able to control how the - // coarsening structure is build up. The - // way the Trilinos smoothed aggregation - // AMG does this is to look which matrix - // entries are of similar size as the - // diagonal entry in order to - // algebraically build a coarse-grid - // structure. By setting the parameter - // aggregation_threshold to - // 0.02, we specify that all entries that - // are more than two precent of size of - // some diagonal pivots in that row - // should form one coarse grid - // point. This parameter is rather - // ad-hoc, and some fine-tuning of it can - // influence the performance of the - // preconditioner. As a rule of thumb, - // larger values of - // aggregation_threshold - // will decrease the number of - // iterations, but increase the costs per - // iteration. A look at the Trilinos - // documentation will provide more - // information on these parameters. With - // this data set, we then initialize the - // preconditioner with the matrix we want - // it to apply to. - // - // Finally, we also initialize the - // preconditioner for the inversion of - // the pressure mass matrix. This matrix - // is symmetric and well-behaved, so we - // can chose a simple preconditioner. We - // stick with an incomple Cholesky (IC) - // factorization preconditioner, which is - // designed for symmetric matrices. We - // could have also chosen an SSOR - // preconditioner with relaxation factor - // around 1.2, but IC is cheaper for our - // example. We wrap the preconditioners - // into a std_cxx1x::shared_ptr - // pointer, which makes it easier to - // recreate the preconditioner next time - // around since we do not have to care - // about destroying the previously used - // object. - amg_data.elliptic = true; - amg_data.higher_order_elements = true; - amg_data.smoother_sweeps = 2; - amg_data.aggregation_threshold = 0.02; - Amg_preconditioner->initialize(stokes_preconditioner_matrix.block(0,0), - amg_data); - - Mp_preconditioner = std_cxx1x::shared_ptr - (new TrilinosWrappers::PreconditionIC()); - Mp_preconditioner->initialize(stokes_preconditioner_matrix.block(1,1)); - - std::cout << std::endl; - - rebuild_stokes_preconditioner = false; -} - // @sect4{BoussinesqFlowProblem::assemble_stokes_system} - // - // The time lag scheme we use for advancing - // the coupled Stokes-temperature system - // forces us to split up the assembly (and - // the solution of linear systems) into two - // step. The first one is to create the - // Stokes system matrix and right hand - // side, and the second is to create matrix - // and right hand sides for the temperature - // dofs, which depends on the result of the - // linear system for the velocity. - // - // This function is called at the beginning - // of each time step. In the first time step - // or if the mesh has changed, indicated by - // the rebuild_stokes_matrix, we - // need to assemble the Stokes matrix; on the - // other hand, if the mesh hasn't changed and - // the matrix is already available, this is - // not necessary and all we need to do is - // assemble the right hand side vector which - // changes in each time step. - // - // Regarding the technical details of - // implementation, not much has changed from - // step-22. We reset matrix and vector, - // create a quadrature formula on the cells, - // and then create the respective FEValues - // object. For the update flags, we require - // basis function derivatives only in case of - // a full assembly, since they are not needed - // for the right hand side; as always, - // choosing the minimal set of flags - // depending on what is currently needed - // makes the call to FEValues::reinit further - // down in the program more efficient. - // - // There is one thing that needs to be - // commented – since we have a separate - // finite element and DoFHandler for the - // temperature, we need to generate a second - // FEValues object for the proper evaluation - // of the temperature solution. This isn't - // too complicated to realize here: just use - // the temperature structures and set an - // update flag for the basis function values - // which we need for evaluation of the - // temperature solution. The only important - // part to remember here is that the same - // quadrature formula is used for both - // FEValues objects to ensure that we get - // matching information when we loop over the - // quadrature points of the two objects. - // - // The declarations proceed with some - // shortcuts for array sizes, the creation - // of the local matrix and right hand side - // as well as the vector for the indices of - // the local dofs compared to the global - // system. -template -void BoussinesqFlowProblem::assemble_stokes_system () -{ - std::cout << " Assembling..." << std::flush; - - if (rebuild_stokes_matrix == true) - stokes_matrix=0; - - stokes_rhs=0; - - const QGauss quadrature_formula (stokes_degree+2); - FEValues stokes_fe_values (stokes_fe, quadrature_formula, - update_values | - update_quadrature_points | - update_JxW_values | - (rebuild_stokes_matrix == true - ? - update_gradients - : - UpdateFlags(0))); - - FEValues temperature_fe_values (temperature_fe, quadrature_formula, - update_values); - - const unsigned int dofs_per_cell = stokes_fe.dofs_per_cell; - const unsigned int n_q_points = quadrature_formula.size(); - - FullMatrix local_matrix (dofs_per_cell, dofs_per_cell); - Vector local_rhs (dofs_per_cell); - - std::vector local_dof_indices (dofs_per_cell); - - // Next we need a vector that will contain - // the values of the temperature solution - // at the previous time level at the - // quadrature points to assemble the source - // term in the right hand side of the - // momentum equation. Let's call this vector - // old_solution_values. + // @sect4{BoussinesqFlowProblem::assemble_temperature_matrix} // - // The set of vectors we create next hold - // the evaluations of the basis functions - // as well as their gradients and - // symmetrized gradients that will be used - // for creating the matrices. Putting these - // into their own arrays rather than asking - // the FEValues object for this information - // each time it is needed is an - // optimization to accelerate the assembly - // process, see step-22 for details. + // This function assembles the matrix in + // the temperature equation. The + // temperature matrix consists of two + // parts, a mass matrix and the time step + // size times a stiffness matrix given by + // a Laplace term times the amount of + // diffusion. Since the matrix depends on + // the time step size (which varies from + // one step to another), the temperature + // matrix needs to be updated every time + // step. We could simply regenerate the + // matrices in every time step, but this + // is not really efficient since mass and + // Laplace matrix do only change when we + // change the mesh. Hence, we do this + // more efficiently by generating two + // separate matrices in this function, + // one for the mass matrix and one for + // the stiffness (diffusion) matrix. We + // will then sum up the matrix plus the + // stiffness matrix times the time step + // size once we know the actual time step. // - // The last two declarations are used to - // extract the individual blocks - // (velocity, pressure, temperature) from - // the total FE system. - std::vector old_temperature_values(n_q_points); - - std::vector > phi_u (dofs_per_cell); - std::vector > grads_phi_u (dofs_per_cell); - std::vector div_phi_u (dofs_per_cell); - std::vector phi_p (dofs_per_cell); - - const FEValuesExtractors::Vector velocities (0); - const FEValuesExtractors::Scalar pressure (dim); - - // Now start the loop over all cells in - // the problem. We are working on two - // different DoFHandlers for this - // assembly routine, so we must have two - // different cell iterators for the two - // objects in use. This might seem a bit - // peculiar, since both the Stokes system - // and the temperature system use the - // same grid, but that's the only way to - // keep degrees of freedom in sync. The - // first statements within the loop are - // again all very familiar, doing the - // update of the finite element data as - // specified by the update flags, zeroing - // out the local arrays and getting the - // values of the old solution at the - // quadrature points. Then we are ready to - // loop over the quadrature points on the - // cell. - typename DoFHandler::active_cell_iterator - cell = stokes_dof_handler.begin_active(), - endc = stokes_dof_handler.end(); - typename DoFHandler::active_cell_iterator - temperature_cell = temperature_dof_handler.begin_active(); - - for (; cell!=endc; ++cell, ++temperature_cell) - { - stokes_fe_values.reinit (cell); - temperature_fe_values.reinit (temperature_cell); - - local_matrix = 0; - local_rhs = 0; + // So the details for this first step are + // very simple. In case we need to + // rebuild the matrix (i.e., the mesh has + // changed), we zero the data structures, + // get a quadrature formula and a + // FEValues object, and create local + // matrices, local dof indices and + // evaluation structures for the basis + // functions. + template + void BoussinesqFlowProblem::assemble_temperature_matrix () + { + if (rebuild_temperature_matrices == false) + return; + + temperature_mass_matrix = 0; + temperature_stiffness_matrix = 0; + + QGauss quadrature_formula (temperature_degree+2); + FEValues temperature_fe_values (temperature_fe, quadrature_formula, + update_values | update_gradients | + update_JxW_values); + + const unsigned int dofs_per_cell = temperature_fe.dofs_per_cell; + const unsigned int n_q_points = quadrature_formula.size(); + + FullMatrix local_mass_matrix (dofs_per_cell, dofs_per_cell); + FullMatrix local_stiffness_matrix (dofs_per_cell, dofs_per_cell); + + std::vector local_dof_indices (dofs_per_cell); + + std::vector phi_T (dofs_per_cell); + std::vector > grad_phi_T (dofs_per_cell); + + // Now, let's start the loop over all cells + // in the triangulation. We need to zero + // out the local matrices, update the + // finite element evaluations, and then + // loop over the rows and columns of the + // matrices on each quadrature point, where + // we then create the mass matrix and the + // stiffness matrix (Laplace terms times + // the diffusion + // EquationData::kappa. Finally, + // we let the constraints object insert + // these values into the global matrix, and + // directly condense the constraints into + // the matrix. + typename DoFHandler::active_cell_iterator + cell = temperature_dof_handler.begin_active(), + endc = temperature_dof_handler.end(); + for (; cell!=endc; ++cell) + { + local_mass_matrix = 0; + local_stiffness_matrix = 0; - temperature_fe_values.get_function_values (old_temperature_solution, - old_temperature_values); + temperature_fe_values.reinit (cell); - for (unsigned int q=0; qrebuild_matrices - // flag. - for (unsigned int k=0; k gravity = -( (dim == 2) ? (Point (0,1)) : - (Point (0,0,1)) ); - for (unsigned int i=0; ilocal_dof_indices. - // Again, we let the ConstraintMatrix - // class do the insertion of the cell - // matrix elements to the global - // matrix, which already condenses the - // hanging node constraints. - cell->get_dof_indices (local_dof_indices); - - if (rebuild_stokes_matrix == true) - stokes_constraints.distribute_local_to_global (local_matrix, - local_rhs, - local_dof_indices, - stokes_matrix, - stokes_rhs); - else - stokes_constraints.distribute_local_to_global (local_rhs, - local_dof_indices, - stokes_rhs); - } + cell->get_dof_indices (local_dof_indices); - rebuild_stokes_matrix = false; + temperature_constraints.distribute_local_to_global (local_mass_matrix, + local_dof_indices, + temperature_mass_matrix); + temperature_constraints.distribute_local_to_global (local_stiffness_matrix, + local_dof_indices, + temperature_stiffness_matrix); + } - std::cout << std::endl; -} + rebuild_temperature_matrices = false; + } + // @sect4{BoussinesqFlowProblem::assemble_temperature_system} + // + // This function does the second part of + // the assembly work on the temperature + // matrix, the actual addition of + // pressure mass and stiffness matrix + // (where the time step size comes into + // play), as well as the creation of the + // velocity-dependent right hand + // side. The declarations for the right + // hand side assembly in this function + // are pretty much the same as the ones + // used in the other assembly routines, + // except that we restrict ourselves to + // vectors this time. We are going to + // calculate residuals on the temperature + // system, which means that we have to + // evaluate second derivatives, specified + // by the update flag + // update_hessians. + // + // The temperature equation is coupled to the + // Stokes system by means of the fluid + // velocity. These two parts of the solution + // are associated with different DoFHandlers, + // so we again need to create a second + // FEValues object for the evaluation of the + // velocity at the quadrature points. + template + void BoussinesqFlowProblem:: + assemble_temperature_system (const double maximal_velocity) + { + const bool use_bdf2_scheme = (timestep_number != 0); - // @sect4{BoussinesqFlowProblem::assemble_temperature_matrix} - // - // This function assembles the matrix in - // the temperature equation. The - // temperature matrix consists of two - // parts, a mass matrix and the time step - // size times a stiffness matrix given by - // a Laplace term times the amount of - // diffusion. Since the matrix depends on - // the time step size (which varies from - // one step to another), the temperature - // matrix needs to be updated every time - // step. We could simply regenerate the - // matrices in every time step, but this - // is not really efficient since mass and - // Laplace matrix do only change when we - // change the mesh. Hence, we do this - // more efficiently by generating two - // separate matrices in this function, - // one for the mass matrix and one for - // the stiffness (diffusion) matrix. We - // will then sum up the matrix plus the - // stiffness matrix times the time step - // size once we know the actual time step. - // - // So the details for this first step are - // very simple. In case we need to - // rebuild the matrix (i.e., the mesh has - // changed), we zero the data structures, - // get a quadrature formula and a - // FEValues object, and create local - // matrices, local dof indices and - // evaluation structures for the basis - // functions. -template -void BoussinesqFlowProblem::assemble_temperature_matrix () -{ - if (rebuild_temperature_matrices == false) - return; - - temperature_mass_matrix = 0; - temperature_stiffness_matrix = 0; - - QGauss quadrature_formula (temperature_degree+2); - FEValues temperature_fe_values (temperature_fe, quadrature_formula, - update_values | update_gradients | - update_JxW_values); - - const unsigned int dofs_per_cell = temperature_fe.dofs_per_cell; - const unsigned int n_q_points = quadrature_formula.size(); - - FullMatrix local_mass_matrix (dofs_per_cell, dofs_per_cell); - FullMatrix local_stiffness_matrix (dofs_per_cell, dofs_per_cell); - - std::vector local_dof_indices (dofs_per_cell); - - std::vector phi_T (dofs_per_cell); - std::vector > grad_phi_T (dofs_per_cell); - - // Now, let's start the loop over all cells - // in the triangulation. We need to zero - // out the local matrices, update the - // finite element evaluations, and then - // loop over the rows and columns of the - // matrices on each quadrature point, where - // we then create the mass matrix and the - // stiffness matrix (Laplace terms times - // the diffusion - // EquationData::kappa. Finally, - // we let the constraints object insert - // these values into the global matrix, and - // directly condense the constraints into - // the matrix. - typename DoFHandler::active_cell_iterator - cell = temperature_dof_handler.begin_active(), - endc = temperature_dof_handler.end(); - for (; cell!=endc; ++cell) - { - local_mass_matrix = 0; - local_stiffness_matrix = 0; + if (use_bdf2_scheme == true) + { + temperature_matrix.copy_from (temperature_mass_matrix); + temperature_matrix *= (2*time_step + old_time_step) / + (time_step + old_time_step); + temperature_matrix.add (time_step, temperature_stiffness_matrix); + } + else + { + temperature_matrix.copy_from (temperature_mass_matrix); + temperature_matrix.add (time_step, temperature_stiffness_matrix); + } - temperature_fe_values.reinit (cell); + temperature_rhs = 0; + + const QGauss quadrature_formula(temperature_degree+2); + FEValues temperature_fe_values (temperature_fe, quadrature_formula, + update_values | + update_gradients | + update_hessians | + update_quadrature_points | + update_JxW_values); + FEValues stokes_fe_values (stokes_fe, quadrature_formula, + update_values); + + const unsigned int dofs_per_cell = temperature_fe.dofs_per_cell; + const unsigned int n_q_points = quadrature_formula.size(); + + Vector local_rhs (dofs_per_cell); + FullMatrix local_matrix (dofs_per_cell, dofs_per_cell); + + std::vector local_dof_indices (dofs_per_cell); + + // Next comes the declaration of vectors + // to hold the old and older solution + // values (as a notation for time levels + // n-1 and n-2, + // respectively) and gradients at + // quadrature points of the current + // cell. We also declarate an object to + // hold the temperature right hande side + // values (gamma_values), + // and we again use shortcuts for the + // temperature basis + // functions. Eventually, we need to find + // the temperature extrema and the + // diameter of the computational domain + // which will be used for the definition + // of the stabilization parameter (we got + // the maximal velocity as an input to + // this function). + std::vector > old_velocity_values (n_q_points); + std::vector > old_old_velocity_values (n_q_points); + std::vector old_temperature_values (n_q_points); + std::vector old_old_temperature_values(n_q_points); + std::vector > old_temperature_grads(n_q_points); + std::vector > old_old_temperature_grads(n_q_points); + std::vector old_temperature_laplacians(n_q_points); + std::vector old_old_temperature_laplacians(n_q_points); + + EquationData::TemperatureRightHandSide temperature_right_hand_side; + std::vector gamma_values (n_q_points); + + std::vector phi_T (dofs_per_cell); + std::vector > grad_phi_T (dofs_per_cell); + + const std::pair + global_T_range = get_extrapolated_temperature_range(); + + const FEValuesExtractors::Vector velocities (0); + + // Now, let's start the loop over all cells + // in the triangulation. Again, we need two + // cell iterators that walk in parallel + // through the cells of the two involved + // DoFHandler objects for the Stokes and + // temperature part. Within the loop, we + // first set the local rhs to zero, and + // then get the values and derivatives of + // the old solution functions at the + // quadrature points, since they are going + // to be needed for the definition of the + // stabilization parameters and as + // coefficients in the equation, + // respectively. Note that since the + // temperature has its own DoFHandler and + // FEValues object we get the entire + // solution at the quadrature point (which + // is the scalar temperature field only + // anyway) whereas for the Stokes part we + // restrict ourselves to extracting the + // velocity part (and ignoring the pressure + // part) by using + // stokes_fe_values[velocities].get_function_values. + typename DoFHandler::active_cell_iterator + cell = temperature_dof_handler.begin_active(), + endc = temperature_dof_handler.end(); + typename DoFHandler::active_cell_iterator + stokes_cell = stokes_dof_handler.begin_active(); - for (unsigned int q=0; qdiameter()); + + for (unsigned int q=0; qget_dof_indices (local_dof_indices); + const double old_Ts + = (use_bdf2_scheme ? + (old_temperature_values[q] * + (time_step + old_time_step) / old_time_step + - + old_old_temperature_values[q] * + (time_step * time_step) / + (old_time_step * (time_step + old_time_step))) + : + old_temperature_values[q]); + + const Tensor<1,dim> ext_grad_T + = (use_bdf2_scheme ? + (old_temperature_grads[q] * + (1+time_step/old_time_step) + - + old_old_temperature_grads[q] * + time_step / old_time_step) + : + old_temperature_grads[q]); + + const Tensor<1,dim> extrapolated_u + = (use_bdf2_scheme ? + (old_velocity_values[q] * (1+time_step/old_time_step) - + old_old_velocity_values[q] * time_step/old_time_step) + : + old_velocity_values[q]); - temperature_constraints.distribute_local_to_global (local_mass_matrix, - local_dof_indices, - temperature_mass_matrix); - temperature_constraints.distribute_local_to_global (local_stiffness_matrix, - local_dof_indices, - temperature_stiffness_matrix); - } - - rebuild_temperature_matrices = false; -} + for (unsigned int i=0; iget_dof_indices (local_dof_indices); + temperature_constraints.distribute_local_to_global (local_rhs, + local_dof_indices, + temperature_rhs); + } + } - // @sect4{BoussinesqFlowProblem::assemble_temperature_system} - // - // This function does the second part of - // the assembly work on the temperature - // matrix, the actual addition of - // pressure mass and stiffness matrix - // (where the time step size comes into - // play), as well as the creation of the - // velocity-dependent right hand - // side. The declarations for the right - // hand side assembly in this function - // are pretty much the same as the ones - // used in the other assembly routines, - // except that we restrict ourselves to - // vectors this time. We are going to - // calculate residuals on the temperature - // system, which means that we have to - // evaluate second derivatives, specified - // by the update flag - // update_hessians. - // - // The temperature equation is coupled to the - // Stokes system by means of the fluid - // velocity. These two parts of the solution - // are associated with different DoFHandlers, - // so we again need to create a second - // FEValues object for the evaluation of the - // velocity at the quadrature points. -template -void BoussinesqFlowProblem:: - assemble_temperature_system (const double maximal_velocity) -{ - const bool use_bdf2_scheme = (timestep_number != 0); - if (use_bdf2_scheme == true) - { - temperature_matrix.copy_from (temperature_mass_matrix); - temperature_matrix *= (2*time_step + old_time_step) / - (time_step + old_time_step); - temperature_matrix.add (time_step, temperature_stiffness_matrix); - } - else - { - temperature_matrix.copy_from (temperature_mass_matrix); - temperature_matrix.add (time_step, temperature_stiffness_matrix); - } + // @sect4{BoussinesqFlowProblem::solve} + // + // This function solves the linear systems + // of equations. Following the + // introduction, we start with the Stokes + // system, where we need to generate our + // block Schur preconditioner. Since all + // the relevant actions are implemented in + // the class + // BlockSchurPreconditioner, + // all we have to do is to initialize the + // class appropriately. What we need to + // pass down is an + // InverseMatrix object for + // the pressure mass matrix, which we set + // up using the respective class together + // with the IC preconditioner we already + // generated, and the AMG preconditioner + // for the velocity-velocity matrix. Note + // that both Mp_preconditioner + // and Amg_preconditioner are + // only pointers, so we use * + // to pass down the actual preconditioner + // objects. + // + // Once the preconditioner is ready, we + // create a GMRES solver for the block + // system. Since we are working with + // Trilinos data structures, we have to set + // the respective template argument in the + // solver. GMRES needs to internally store + // temporary vectors for each iteration + // (see the discussion in the results + // section of step-22) – the more + // vectors it can use, the better it will + // generally perform. To keep memory + // demands in check, we set the number of + // vectors to 100. This means that up to + // 100 solver iterations, every temporary + // vector can be stored. If the solver + // needs to iterate more often to get the + // specified tolerance, it will work on a + // reduced set of vectors by restarting at + // every 100 iterations. + // + // With this all set up, we solve the system + // and distribute the constraints in the + // Stokes system, i.e. hanging nodes and + // no-flux boundary condition, in order to + // have the appropriate solution values even + // at constrained dofs. Finally, we write the + // number of iterations to the screen. + template + void BoussinesqFlowProblem::solve () + { + std::cout << " Solving..." << std::endl; - temperature_rhs = 0; - - const QGauss quadrature_formula(temperature_degree+2); - FEValues temperature_fe_values (temperature_fe, quadrature_formula, - update_values | - update_gradients | - update_hessians | - update_quadrature_points | - update_JxW_values); - FEValues stokes_fe_values (stokes_fe, quadrature_formula, - update_values); - - const unsigned int dofs_per_cell = temperature_fe.dofs_per_cell; - const unsigned int n_q_points = quadrature_formula.size(); - - Vector local_rhs (dofs_per_cell); - FullMatrix local_matrix (dofs_per_cell, dofs_per_cell); - - std::vector local_dof_indices (dofs_per_cell); - - // Next comes the declaration of vectors - // to hold the old and older solution - // values (as a notation for time levels - // n-1 and n-2, - // respectively) and gradients at - // quadrature points of the current - // cell. We also declarate an object to - // hold the temperature right hande side - // values (gamma_values), - // and we again use shortcuts for the - // temperature basis - // functions. Eventually, we need to find - // the temperature extrema and the - // diameter of the computational domain - // which will be used for the definition - // of the stabilization parameter (we got - // the maximal velocity as an input to - // this function). - std::vector > old_velocity_values (n_q_points); - std::vector > old_old_velocity_values (n_q_points); - std::vector old_temperature_values (n_q_points); - std::vector old_old_temperature_values(n_q_points); - std::vector > old_temperature_grads(n_q_points); - std::vector > old_old_temperature_grads(n_q_points); - std::vector old_temperature_laplacians(n_q_points); - std::vector old_old_temperature_laplacians(n_q_points); - - EquationData::TemperatureRightHandSide temperature_right_hand_side; - std::vector gamma_values (n_q_points); - - std::vector phi_T (dofs_per_cell); - std::vector > grad_phi_T (dofs_per_cell); - - const std::pair - global_T_range = get_extrapolated_temperature_range(); - - const FEValuesExtractors::Vector velocities (0); - - // Now, let's start the loop over all cells - // in the triangulation. Again, we need two - // cell iterators that walk in parallel - // through the cells of the two involved - // DoFHandler objects for the Stokes and - // temperature part. Within the loop, we - // first set the local rhs to zero, and - // then get the values and derivatives of - // the old solution functions at the - // quadrature points, since they are going - // to be needed for the definition of the - // stabilization parameters and as - // coefficients in the equation, - // respectively. Note that since the - // temperature has its own DoFHandler and - // FEValues object we get the entire - // solution at the quadrature point (which - // is the scalar temperature field only - // anyway) whereas for the Stokes part we - // restrict ourselves to extracting the - // velocity part (and ignoring the pressure - // part) by using - // stokes_fe_values[velocities].get_function_values. - typename DoFHandler::active_cell_iterator - cell = temperature_dof_handler.begin_active(), - endc = temperature_dof_handler.end(); - typename DoFHandler::active_cell_iterator - stokes_cell = stokes_dof_handler.begin_active(); - - for (; cell!=endc; ++cell, ++stokes_cell) { - local_rhs = 0; - - temperature_fe_values.reinit (cell); - stokes_fe_values.reinit (stokes_cell); - - temperature_fe_values.get_function_values (old_temperature_solution, - old_temperature_values); - temperature_fe_values.get_function_values (old_old_temperature_solution, - old_old_temperature_values); - - temperature_fe_values.get_function_gradients (old_temperature_solution, - old_temperature_grads); - temperature_fe_values.get_function_gradients (old_old_temperature_solution, - old_old_temperature_grads); - - temperature_fe_values.get_function_laplacians (old_temperature_solution, - old_temperature_laplacians); - temperature_fe_values.get_function_laplacians (old_old_temperature_solution, - old_old_temperature_laplacians); - - temperature_right_hand_side.value_list (temperature_fe_values.get_quadrature_points(), - gamma_values); - - stokes_fe_values[velocities].get_function_values (stokes_solution, - old_velocity_values); - stokes_fe_values[velocities].get_function_values (old_stokes_solution, - old_old_velocity_values); - - // Next, we calculate the artificial - // viscosity for stabilization - // according to the discussion in the - // introduction using the dedicated - // function. With that at hand, we - // can get into the loop over - // quadrature points and local rhs - // vector components. The terms here - // are quite lenghty, but their - // definition follows the - // time-discrete system developed in - // the introduction of this - // program. The BDF-2 scheme needs - // one more term from the old time - // step (and involves more - // complicated factors) than the - // backward Euler scheme that is used - // for the first time step. When all - // this is done, we distribute the - // local vector into the global one - // (including hanging node - // constraints). - const double nu - = compute_viscosity (old_temperature_values, - old_old_temperature_values, - old_temperature_grads, - old_old_temperature_grads, - old_temperature_laplacians, - old_old_temperature_laplacians, - old_velocity_values, - old_old_velocity_values, - gamma_values, - maximal_velocity, - global_T_range.second - global_T_range.first, - cell->diameter()); - - for (unsigned int q=0; q ext_grad_T - = (use_bdf2_scheme ? - (old_temperature_grads[q] * - (1+time_step/old_time_step) - - - old_old_temperature_grads[q] * - time_step / old_time_step) - : - old_temperature_grads[q]); - - const Tensor<1,dim> extrapolated_u - = (use_bdf2_scheme ? - (old_velocity_values[q] * (1+time_step/old_time_step) - - old_old_velocity_values[q] * time_step/old_time_step) - : - old_velocity_values[q]); - - for (unsigned int i=0; i + mp_inverse (stokes_preconditioner_matrix.block(1,1), *Mp_preconditioner); - cell->get_dof_indices (local_dof_indices); - temperature_constraints.distribute_local_to_global (local_rhs, - local_dof_indices, - temperature_rhs); - } -} + const LinearSolvers::BlockSchurPreconditioner + preconditioner (stokes_matrix, mp_inverse, *Amg_preconditioner); + SolverControl solver_control (stokes_matrix.m(), + 1e-6*stokes_rhs.l2_norm()); + SolverGMRES + gmres (solver_control, + SolverGMRES::AdditionalData(100)); + for (unsigned int i=0; iBlockSchurPreconditioner, - // all we have to do is to initialize the - // class appropriately. What we need to - // pass down is an - // InverseMatrix object for - // the pressure mass matrix, which we set - // up using the respective class together - // with the IC preconditioner we already - // generated, and the AMG preconditioner - // for the velocity-velocity matrix. Note - // that both Mp_preconditioner - // and Amg_preconditioner are - // only pointers, so we use * - // to pass down the actual preconditioner - // objects. - // - // Once the preconditioner is ready, we - // create a GMRES solver for the block - // system. Since we are working with - // Trilinos data structures, we have to set - // the respective template argument in the - // solver. GMRES needs to internally store - // temporary vectors for each iteration - // (see the discussion in the results - // section of step-22) – the more - // vectors it can use, the better it will - // generally perform. To keep memory - // demands in check, we set the number of - // vectors to 100. This means that up to - // 100 solver iterations, every temporary - // vector can be stored. If the solver - // needs to iterate more often to get the - // specified tolerance, it will work on a - // reduced set of vectors by restarting at - // every 100 iterations. - // - // With this all set up, we solve the system - // and distribute the constraints in the - // Stokes system, i.e. hanging nodes and - // no-flux boundary condition, in order to - // have the appropriate solution values even - // at constrained dofs. Finally, we write the - // number of iterations to the screen. -template -void BoussinesqFlowProblem::solve () -{ - std::cout << " Solving..." << std::endl; + gmres.solve(stokes_matrix, stokes_solution, stokes_rhs, preconditioner); - { - const LinearSolvers::InverseMatrix - mp_inverse (stokes_preconditioner_matrix.block(1,1), *Mp_preconditioner); + stokes_constraints.distribute (stokes_solution); - const LinearSolvers::BlockSchurPreconditioner - preconditioner (stokes_matrix, mp_inverse, *Amg_preconditioner); + std::cout << " " + << solver_control.last_step() + << " GMRES iterations for Stokes subsystem." + << std::endl; + } - SolverControl solver_control (stokes_matrix.m(), - 1e-6*stokes_rhs.l2_norm()); + // Once we know the Stokes solution, we can + // determine the new time step from the + // maximal velocity. We have to do this to + // satisfy the CFL condition since + // convection terms are treated explicitly + // in the temperature equation, as + // discussed in the introduction. The exact + // form of the formula used here for the + // time step is discussed in the results + // section of this program. + // + // There is a snatch here. The formula + // contains a division by the maximum value + // of the velocity. However, at the start + // of the computation, we have a constant + // temperature field (we start with a + // constant temperature, and it will be + // non-constant only after the first time + // step during which the source + // acts). Constant temperature means that + // no buoyancy acts, and so the velocity is + // zero. Dividing by it will not likely + // lead to anything good. + // + // To avoid the resulting infinite time + // step, we ask whether the maximal + // velocity is very small (in particular + // smaller than the values we encounter + // during any of the following time steps) + // and if so rather than dividing by zero + // we just divide by a small value, + // resulting in a large but finite time + // step. + old_time_step = time_step; + const double maximal_velocity = get_maximal_velocity(); + + if (maximal_velocity >= 0.01) + time_step = 1./(1.6*dim*std::sqrt(1.*dim)) / + temperature_degree * + GridTools::minimal_cell_diameter(triangulation) / + maximal_velocity; + else + time_step = 1./(1.6*dim*std::sqrt(1.*dim)) / + temperature_degree * + GridTools::minimal_cell_diameter(triangulation) / + .01; + + std::cout << " " << "Time step: " << time_step + << std::endl; - SolverGMRES - gmres (solver_control, - SolverGMRES::AdditionalData(100)); + temperature_solution = old_temperature_solution; + + // Next we set up the temperature system + // and the right hand side using the + // function + // assemble_temperature_system(). + // Knowing the matrix and right hand side + // of the temperature equation, we set up + // a preconditioner and a solver. The + // temperature matrix is a mass matrix + // (with eigenvalues around one) plus a + // Laplace matrix (with eigenvalues + // between zero and $ch^{-2}$) times a + // small number proportional to the time + // step $k_n$. Hence, the resulting + // symmetric and positive definite matrix + // has eigenvalues in the range + // $[1,1+k_nh^{-2}]$ (up to + // constants). This matrix is only + // moderately ill conditioned even for + // small mesh sizes and we get a + // reasonably good preconditioner by + // simple means, for example with an + // incomplete Cholesky decomposition + // preconditioner (IC) as we also use for + // preconditioning the pressure mass + // matrix solver. As a solver, we choose + // the conjugate gradient method CG. As + // before, we tell the solver to use + // Trilinos vectors via the template + // argument + // TrilinosWrappers::Vector. + // Finally, we solve, distribute the + // hanging node constraints and write out + // the number of iterations. + assemble_temperature_system (maximal_velocity); + { - for (unsigned int i=0; i cg (solver_control); + + TrilinosWrappers::PreconditionIC preconditioner; + preconditioner.initialize (temperature_matrix); + + cg.solve (temperature_matrix, temperature_solution, + temperature_rhs, preconditioner); + + temperature_constraints.distribute (temperature_solution); + + std::cout << " " + << solver_control.last_step() + << " CG iterations for temperature." + << std::endl; + + // At the end of this function, we step + // through the vector and read out the + // maximum and minimum temperature value, + // which we also want to output. This + // will come in handy when determining + // the correct constant in the choice of + // time step as discuss in the results + // section of this program. + double min_temperature = temperature_solution(0), + max_temperature = temperature_solution(0); + for (unsigned int i=0; i (min_temperature, + temperature_solution(i)); + max_temperature = std::max (max_temperature, + temperature_solution(i)); + } - gmres.solve(stokes_matrix, stokes_solution, stokes_rhs, preconditioner); + std::cout << " Temperature range: " + << min_temperature << ' ' << max_temperature + << std::endl; + } + } - stokes_constraints.distribute (stokes_solution); - std::cout << " " - << solver_control.last_step() - << " GMRES iterations for Stokes subsystem." - << std::endl; - } - // Once we know the Stokes solution, we can - // determine the new time step from the - // maximal velocity. We have to do this to - // satisfy the CFL condition since - // convection terms are treated explicitly - // in the temperature equation, as - // discussed in the introduction. The exact - // form of the formula used here for the - // time step is discussed in the results - // section of this program. + // @sect4{BoussinesqFlowProblem::output_results} // - // There is a snatch here. The formula - // contains a division by the maximum value - // of the velocity. However, at the start - // of the computation, we have a constant - // temperature field (we start with a - // constant temperature, and it will be - // non-constant only after the first time - // step during which the source - // acts). Constant temperature means that - // no buoyancy acts, and so the velocity is - // zero. Dividing by it will not likely - // lead to anything good. + // This function writes the solution to a VTK + // output file for visualization, which is + // done every tenth time step. This is + // usually quite a simple task, since the + // deal.II library provides functions that do + // almost all the job for us. In this case, + // the situation is a bit more complicated, + // since we want to visualize both the Stokes + // solution and the temperature as one data + // set, but we have done all the calculations + // based on two different DoFHandler objects, + // a situation the DataOut class usually used + // for output is not prepared to deal + // with. The way we're going to achieve this + // recombination is to create a joint + // DoFHandler that collects both components, + // the Stokes solution and the temperature + // solution. This can be nicely done by + // combining the finite elements from the two + // systems to form one FESystem, and let this + // collective system define a new DoFHandler + // object. To be sure that everything was + // done correctly, we perform a sanity check + // that ensures that we got all the dofs from + // both Stokes and temperature even in the + // combined system. // - // To avoid the resulting infinite time - // step, we ask whether the maximal - // velocity is very small (in particular - // smaller than the values we encounter - // during any of the following time steps) - // and if so rather than dividing by zero - // we just divide by a small value, - // resulting in a large but finite time - // step. - old_time_step = time_step; - const double maximal_velocity = get_maximal_velocity(); - - if (maximal_velocity >= 0.01) - time_step = 1./(1.6*dim*std::sqrt(1.*dim)) / - temperature_degree * - GridTools::minimal_cell_diameter(triangulation) / - maximal_velocity; - else - time_step = 1./(1.6*dim*std::sqrt(1.*dim)) / - temperature_degree * - GridTools::minimal_cell_diameter(triangulation) / - .01; - - std::cout << " " << "Time step: " << time_step - << std::endl; - - temperature_solution = old_temperature_solution; - - // Next we set up the temperature system - // and the right hand side using the - // function - // assemble_temperature_system(). - // Knowing the matrix and right hand side - // of the temperature equation, we set up - // a preconditioner and a solver. The - // temperature matrix is a mass matrix - // (with eigenvalues around one) plus a - // Laplace matrix (with eigenvalues - // between zero and $ch^{-2}$) times a - // small number proportional to the time - // step $k_n$. Hence, the resulting - // symmetric and positive definite matrix - // has eigenvalues in the range - // $[1,1+k_nh^{-2}]$ (up to - // constants). This matrix is only - // moderately ill conditioned even for - // small mesh sizes and we get a - // reasonably good preconditioner by - // simple means, for example with an - // incomplete Cholesky decomposition - // preconditioner (IC) as we also use for - // preconditioning the pressure mass - // matrix solver. As a solver, we choose - // the conjugate gradient method CG. As - // before, we tell the solver to use - // Trilinos vectors via the template - // argument - // TrilinosWrappers::Vector. - // Finally, we solve, distribute the - // hanging node constraints and write out - // the number of iterations. - assemble_temperature_system (maximal_velocity); + // Next, we create a vector that will collect + // the actual solution values. Since this + // vector is only going to be used for + // output, we create it as a deal.II vector + // that nicely cooperate with the data output + // classes. Remember that we used Trilinos + // vectors for assembly and solving. + template + void BoussinesqFlowProblem::output_results () const { + if (timestep_number % 10 != 0) + return; + + const FESystem joint_fe (stokes_fe, 1, + temperature_fe, 1); + DoFHandler joint_dof_handler (triangulation); + joint_dof_handler.distribute_dofs (joint_fe); + Assert (joint_dof_handler.n_dofs() == + stokes_dof_handler.n_dofs() + temperature_dof_handler.n_dofs(), + ExcInternalError()); + + Vector joint_solution (joint_dof_handler.n_dofs()); + + // Unfortunately, there is no + // straight-forward relation that tells + // us how to sort Stokes and temperature + // vector into the joint vector. The way + // we can get around this trouble is to + // rely on the information collected in + // the FESystem. For each dof in a cell, + // the joint finite element knows to + // which equation component (velocity + // component, pressure, or temperature) + // it belongs – that's the + // information we need! So we step + // through all cells (with iterators into + // all three DoFHandlers moving in + // synch), and for each joint cell dof, + // we read out that component using the + // FiniteElement::system_to_base_index + // function (see there for a description + // of what the various parts of its + // return value contain). We also need to + // keep track whether we're on a Stokes + // dof or a temperature dof, which is + // contained in + // joint_fe.system_to_base_index(i).first.first. + // Eventually, the dof_indices data + // structures on either of the three + // systems tell us how the relation + // between global vector and local dofs + // looks like on the present cell, which + // concludes this tedious work. + // + // There's one thing worth remembering + // when looking at the output: In our + // algorithm, we first solve for the + // Stokes system at time level n-1 + // in each time step and then for the + // temperature at time level n + // using the previously computed + // velocity. These are the two components + // we join for output, so these two parts + // of the output file are actually + // misaligned by one time step. Since we + // consider graphical output as only a + // qualititative means to understand a + // solution, we ignore this + // $\mathcal{O}(h)$ error. + { + std::vector local_joint_dof_indices (joint_fe.dofs_per_cell); + std::vector local_stokes_dof_indices (stokes_fe.dofs_per_cell); + std::vector local_temperature_dof_indices (temperature_fe.dofs_per_cell); - SolverControl solver_control (temperature_matrix.m(), - 1e-8*temperature_rhs.l2_norm()); - SolverCG cg (solver_control); - - TrilinosWrappers::PreconditionIC preconditioner; - preconditioner.initialize (temperature_matrix); - - cg.solve (temperature_matrix, temperature_solution, - temperature_rhs, preconditioner); - - temperature_constraints.distribute (temperature_solution); - - std::cout << " " - << solver_control.last_step() - << " CG iterations for temperature." - << std::endl; + typename DoFHandler::active_cell_iterator + joint_cell = joint_dof_handler.begin_active(), + joint_endc = joint_dof_handler.end(), + stokes_cell = stokes_dof_handler.begin_active(), + temperature_cell = temperature_dof_handler.begin_active(); + for (; joint_cell!=joint_endc; ++joint_cell, ++stokes_cell, ++temperature_cell) + { + joint_cell->get_dof_indices (local_joint_dof_indices); + stokes_cell->get_dof_indices (local_stokes_dof_indices); + temperature_cell->get_dof_indices (local_temperature_dof_indices); - // At the end of this function, we step - // through the vector and read out the - // maximum and minimum temperature value, - // which we also want to output. This - // will come in handy when determining - // the correct constant in the choice of - // time step as discuss in the results - // section of this program. - double min_temperature = temperature_solution(0), - max_temperature = temperature_solution(0); - for (unsigned int i=0; i (min_temperature, - temperature_solution(i)); - max_temperature = std::max (max_temperature, - temperature_solution(i)); - } + for (unsigned int i=0; idim + // components are the vector velocity, + // and then we have pressure and + // temperature. This information is read + // out using the + // DataComponentInterpretation helper + // class. Next, we attach the solution + // values together with the names of its + // components to the output object, and + // build patches according to the degree + // of freedom, which are (sub-) elements + // that describe the data for + // visualization programs. Finally, we + // set a file name (that includes the + // time step number) and write the vtk + // file. + std::vector joint_solution_names (dim, "velocity"); + joint_solution_names.push_back ("p"); + joint_solution_names.push_back ("T"); + + DataOut data_out; + + data_out.attach_dof_handler (joint_dof_handler); + + std::vector + data_component_interpretation + (dim+2, DataComponentInterpretation::component_is_scalar); + for (unsigned int i=0; i::type_dof_data, + data_component_interpretation); + data_out.build_patches (std::min(stokes_degree, temperature_degree)); + + std::ostringstream filename; + filename << "solution-" << Utilities::int_to_string(timestep_number, 4) << ".vtk"; + + std::ofstream output (filename.str().c_str()); + data_out.write_vtk (output); } -} - // @sect4{BoussinesqFlowProblem::output_results} - // - // This function writes the solution to a VTK - // output file for visualization, which is - // done every tenth time step. This is - // usually quite a simple task, since the - // deal.II library provides functions that do - // almost all the job for us. In this case, - // the situation is a bit more complicated, - // since we want to visualize both the Stokes - // solution and the temperature as one data - // set, but we have done all the calculations - // based on two different DoFHandler objects, - // a situation the DataOut class usually used - // for output is not prepared to deal - // with. The way we're going to achieve this - // recombination is to create a joint - // DoFHandler that collects both components, - // the Stokes solution and the temperature - // solution. This can be nicely done by - // combining the finite elements from the two - // systems to form one FESystem, and let this - // collective system define a new DoFHandler - // object. To be sure that everything was - // done correctly, we perform a sanity check - // that ensures that we got all the dofs from - // both Stokes and temperature even in the - // combined system. - // - // Next, we create a vector that will collect - // the actual solution values. Since this - // vector is only going to be used for - // output, we create it as a deal.II vector - // that nicely cooperate with the data output - // classes. Remember that we used Trilinos - // vectors for assembly and solving. -template -void BoussinesqFlowProblem::output_results () const -{ - if (timestep_number % 10 != 0) - return; - - const FESystem joint_fe (stokes_fe, 1, - temperature_fe, 1); - DoFHandler joint_dof_handler (triangulation); - joint_dof_handler.distribute_dofs (joint_fe); - Assert (joint_dof_handler.n_dofs() == - stokes_dof_handler.n_dofs() + temperature_dof_handler.n_dofs(), - ExcInternalError()); - - Vector joint_solution (joint_dof_handler.n_dofs()); - - // Unfortunately, there is no - // straight-forward relation that tells - // us how to sort Stokes and temperature - // vector into the joint vector. The way - // we can get around this trouble is to - // rely on the information collected in - // the FESystem. For each dof in a cell, - // the joint finite element knows to - // which equation component (velocity - // component, pressure, or temperature) - // it belongs – that's the - // information we need! So we step - // through all cells (with iterators into - // all three DoFHandlers moving in - // synch), and for each joint cell dof, - // we read out that component using the - // FiniteElement::system_to_base_index - // function (see there for a description - // of what the various parts of its - // return value contain). We also need to - // keep track whether we're on a Stokes - // dof or a temperature dof, which is - // contained in - // joint_fe.system_to_base_index(i).first.first. - // Eventually, the dof_indices data - // structures on either of the three - // systems tell us how the relation - // between global vector and local dofs - // looks like on the present cell, which - // concludes this tedious work. + // @sect4{BoussinesqFlowProblem::refine_mesh} + // + // This function takes care of the adaptive + // mesh refinement. The three tasks this + // function performs is to first find out + // which cells to refine/coarsen, then to + // actually do the refinement and eventually + // transfer the solution vectors between the + // two different grids. The first task is + // simply achieved by using the + // well-established Kelly error estimator on + // the temperature (it is the temperature + // we're mainly interested in for this + // program, and we need to be accurate in + // regions of high temperature gradients, + // also to not have too much numerical + // diffusion). The second task is to actually + // do the remeshing. That involves only basic + // functions as well, such as the + // refine_and_coarsen_fixed_fraction + // that refines those cells with the largest + // estimated error that together make up 80 + // per cent of the error, and coarsens those + // cells with the smallest error that make up + // for a combined 10 per cent of the + // error. + // + // If implemented like this, we would get a + // program that will not make much progress: + // Remember that we expect temperature fields + // that are nearly discontinuous (the + // diffusivity $\kappa$ is very small after + // all) and consequently we can expect that a + // freely adapted mesh will refine further + // and further into the areas of large + // gradients. This decrease in mesh size will + // then be accompanied by a decrease in time + // step, requiring an exceedingly large + // number of time steps to solve to a given + // final time. It will also lead to meshes + // that are much better at resolving + // discontinuities after several mesh + // refinement cycles than in the beginning. // - // There's one thing worth remembering - // when looking at the output: In our - // algorithm, we first solve for the - // Stokes system at time level n-1 - // in each time step and then for the - // temperature at time level n - // using the previously computed - // velocity. These are the two components - // we join for output, so these two parts - // of the output file are actually - // misaligned by one time step. Since we - // consider graphical output as only a - // qualititative means to understand a - // solution, we ignore this - // $\mathcal{O}(h)$ error. + // In particular to prevent the decrease in + // time step size and the correspondingly + // large number of time steps, we limit the + // maximal refinement depth of the mesh. To + // this end, after the refinement indicator + // has been applied to the cells, we simply + // loop over all cells on the finest level + // and unselect them from refinement if they + // would result in too high a mesh level. + template + void BoussinesqFlowProblem::refine_mesh (const unsigned int max_grid_level) { - std::vector local_joint_dof_indices (joint_fe.dofs_per_cell); - std::vector local_stokes_dof_indices (stokes_fe.dofs_per_cell); - std::vector local_temperature_dof_indices (temperature_fe.dofs_per_cell); - - typename DoFHandler::active_cell_iterator - joint_cell = joint_dof_handler.begin_active(), - joint_endc = joint_dof_handler.end(), - stokes_cell = stokes_dof_handler.begin_active(), - temperature_cell = temperature_dof_handler.begin_active(); - for (; joint_cell!=joint_endc; ++joint_cell, ++stokes_cell, ++temperature_cell) - { - joint_cell->get_dof_indices (local_joint_dof_indices); - stokes_cell->get_dof_indices (local_stokes_dof_indices); - temperature_cell->get_dof_indices (local_temperature_dof_indices); - - for (unsigned int i=0; i estimated_error_per_cell (triangulation.n_active_cells()); + + KellyErrorEstimator::estimate (temperature_dof_handler, + QGauss(temperature_degree+1), + typename FunctionMap::type(), + temperature_solution, + estimated_error_per_cell); + + GridRefinement::refine_and_coarsen_fixed_fraction (triangulation, + estimated_error_per_cell, + 0.8, 0.1); + if (triangulation.n_levels() > max_grid_level) + for (typename Triangulation::active_cell_iterator + cell = triangulation.begin_active(max_grid_level); + cell != triangulation.end(); ++cell) + cell->clear_refine_flag (); + + // As part of mesh refinement we need to + // transfer the solution vectors from the + // old mesh to the new one. To this end + // we use the SolutionTransfer class and + // we have to prepare the solution + // vectors that should be transfered to + // the new grid (we will lose the old + // grid once we have done the refinement + // so the transfer has to happen + // concurrently with refinement). What we + // definetely need are the current and + // the old temperature (BDF-2 time + // stepping requires two old + // solutions). Since the SolutionTransfer + // objects only support to transfer one + // object per dof handler, we need to + // collect the two temperature solutions + // in one data structure. Moreover, we + // choose to transfer the Stokes + // solution, too, since we need the + // velocity at two previous time steps, + // of which only one is calculated on the + // fly. + // + // Consequently, we initialize two + // SolutionTransfer objects for the + // Stokes and temperature DoFHandler + // objects, by attaching them to the old + // dof handlers. With this at place, we + // can prepare the triangulation and the + // data vectors for refinement (in this + // order). + std::vector x_temperature (2); + x_temperature[0] = temperature_solution; + x_temperature[1] = old_temperature_solution; + TrilinosWrappers::BlockVector x_stokes = stokes_solution; + + SolutionTransfer + temperature_trans(temperature_dof_handler); + SolutionTransfer + stokes_trans(stokes_dof_handler); + + triangulation.prepare_coarsening_and_refinement(); + temperature_trans.prepare_for_coarsening_and_refinement(x_temperature); + stokes_trans.prepare_for_coarsening_and_refinement(x_stokes); + + // Now everything is ready, so do the + // refinement and recreate the dof + // structure on the new grid, and + // initialize the matrix structures and + // the new vectors in the + // setup_dofs + // function. Next, we actually perform + // the interpolation of the solutions + // between the grids. We create another + // copy of temporary vectors for + // temperature (now corresponding to the + // new grid), and let the interpolate + // function do the job. Then, the + // resulting array of vectors is written + // into the respective vector member + // variables. For the Stokes vector, + // everything is just the same – + // except that we do not need another + // temporary vector since we just + // interpolate a single vector. In the + // end, we have to tell the program that + // the matrices and preconditioners need + // to be regenerated, since the mesh has + // changed. + triangulation.execute_coarsening_and_refinement (); + setup_dofs (); + + std::vector tmp (2); + tmp[0].reinit (temperature_solution); + tmp[1].reinit (temperature_solution); + temperature_trans.interpolate(x_temperature, tmp); + + temperature_solution = tmp[0]; + old_temperature_solution = tmp[1]; + + stokes_trans.interpolate (x_stokes, stokes_solution); + + rebuild_stokes_matrix = true; + rebuild_temperature_matrices = true; + rebuild_stokes_preconditioner = true; } - // Next, we proceed as we've done in - // step-22. We create solution names - // (that are going to appear in the - // visualization program for the - // individual components), and attach the - // joint dof handler to a DataOut - // object. The first dim - // components are the vector velocity, - // and then we have pressure and - // temperature. This information is read - // out using the - // DataComponentInterpretation helper - // class. Next, we attach the solution - // values together with the names of its - // components to the output object, and - // build patches according to the degree - // of freedom, which are (sub-) elements - // that describe the data for - // visualization programs. Finally, we - // set a file name (that includes the - // time step number) and write the vtk - // file. - std::vector joint_solution_names (dim, "velocity"); - joint_solution_names.push_back ("p"); - joint_solution_names.push_back ("T"); - - DataOut data_out; - - data_out.attach_dof_handler (joint_dof_handler); - - std::vector - data_component_interpretation - (dim+2, DataComponentInterpretation::component_is_scalar); - for (unsigned int i=0; i::type_dof_data, - data_component_interpretation); - data_out.build_patches (std::min(stokes_degree, temperature_degree)); - - std::ostringstream filename; - filename << "solution-" << Utilities::int_to_string(timestep_number, 4) << ".vtk"; - - std::ofstream output (filename.str().c_str()); - data_out.write_vtk (output); -} - - // @sect4{BoussinesqFlowProblem::refine_mesh} - // - // This function takes care of the adaptive - // mesh refinement. The three tasks this - // function performs is to first find out - // which cells to refine/coarsen, then to - // actually do the refinement and eventually - // transfer the solution vectors between the - // two different grids. The first task is - // simply achieved by using the - // well-established Kelly error estimator on - // the temperature (it is the temperature - // we're mainly interested in for this - // program, and we need to be accurate in - // regions of high temperature gradients, - // also to not have too much numerical - // diffusion). The second task is to actually - // do the remeshing. That involves only basic - // functions as well, such as the - // refine_and_coarsen_fixed_fraction - // that refines those cells with the largest - // estimated error that together make up 80 - // per cent of the error, and coarsens those - // cells with the smallest error that make up - // for a combined 10 per cent of the - // error. - // - // If implemented like this, we would get a - // program that will not make much progress: - // Remember that we expect temperature fields - // that are nearly discontinuous (the - // diffusivity $\kappa$ is very small after - // all) and consequently we can expect that a - // freely adapted mesh will refine further - // and further into the areas of large - // gradients. This decrease in mesh size will - // then be accompanied by a decrease in time - // step, requiring an exceedingly large - // number of time steps to solve to a given - // final time. It will also lead to meshes - // that are much better at resolving - // discontinuities after several mesh - // refinement cycles than in the beginning. - // - // In particular to prevent the decrease in - // time step size and the correspondingly - // large number of time steps, we limit the - // maximal refinement depth of the mesh. To - // this end, after the refinement indicator - // has been applied to the cells, we simply - // loop over all cells on the finest level - // and unselect them from refinement if they - // would result in too high a mesh level. -template -void BoussinesqFlowProblem::refine_mesh (const unsigned int max_grid_level) -{ - Vector estimated_error_per_cell (triangulation.n_active_cells()); - - KellyErrorEstimator::estimate (temperature_dof_handler, - QGauss(temperature_degree+1), - typename FunctionMap::type(), - temperature_solution, - estimated_error_per_cell); - - GridRefinement::refine_and_coarsen_fixed_fraction (triangulation, - estimated_error_per_cell, - 0.8, 0.1); - if (triangulation.n_levels() > max_grid_level) - for (typename Triangulation::active_cell_iterator - cell = triangulation.begin_active(max_grid_level); - cell != triangulation.end(); ++cell) - cell->clear_refine_flag (); - - // As part of mesh refinement we need to - // transfer the solution vectors from the - // old mesh to the new one. To this end - // we use the SolutionTransfer class and - // we have to prepare the solution - // vectors that should be transfered to - // the new grid (we will lose the old - // grid once we have done the refinement - // so the transfer has to happen - // concurrently with refinement). What we - // definetely need are the current and - // the old temperature (BDF-2 time - // stepping requires two old - // solutions). Since the SolutionTransfer - // objects only support to transfer one - // object per dof handler, we need to - // collect the two temperature solutions - // in one data structure. Moreover, we - // choose to transfer the Stokes - // solution, too, since we need the - // velocity at two previous time steps, - // of which only one is calculated on the - // fly. + // @sect4{BoussinesqFlowProblem::run} // - // Consequently, we initialize two - // SolutionTransfer objects for the - // Stokes and temperature DoFHandler - // objects, by attaching them to the old - // dof handlers. With this at place, we - // can prepare the triangulation and the - // data vectors for refinement (in this - // order). - std::vector x_temperature (2); - x_temperature[0] = temperature_solution; - x_temperature[1] = old_temperature_solution; - TrilinosWrappers::BlockVector x_stokes = stokes_solution; - - SolutionTransfer - temperature_trans(temperature_dof_handler); - SolutionTransfer - stokes_trans(stokes_dof_handler); - - triangulation.prepare_coarsening_and_refinement(); - temperature_trans.prepare_for_coarsening_and_refinement(x_temperature); - stokes_trans.prepare_for_coarsening_and_refinement(x_stokes); - - // Now everything is ready, so do the - // refinement and recreate the dof - // structure on the new grid, and - // initialize the matrix structures and - // the new vectors in the - // setup_dofs - // function. Next, we actually perform - // the interpolation of the solutions - // between the grids. We create another - // copy of temporary vectors for - // temperature (now corresponding to the - // new grid), and let the interpolate - // function do the job. Then, the - // resulting array of vectors is written - // into the respective vector member - // variables. For the Stokes vector, - // everything is just the same – - // except that we do not need another - // temporary vector since we just - // interpolate a single vector. In the - // end, we have to tell the program that - // the matrices and preconditioners need - // to be regenerated, since the mesh has - // changed. - triangulation.execute_coarsening_and_refinement (); - setup_dofs (); - - std::vector tmp (2); - tmp[0].reinit (temperature_solution); - tmp[1].reinit (temperature_solution); - temperature_trans.interpolate(x_temperature, tmp); - - temperature_solution = tmp[0]; - old_temperature_solution = tmp[1]; - - stokes_trans.interpolate (x_stokes, stokes_solution); - - rebuild_stokes_matrix = true; - rebuild_temperature_matrices = true; - rebuild_stokes_preconditioner = true; -} - - - - // @sect4{BoussinesqFlowProblem::run} - // - // This function performs all the - // essential steps in the Boussinesq - // program. It starts by setting up a - // grid (depending on the spatial - // dimension, we choose some - // different level of initial - // refinement and additional adaptive - // refinement steps, and then create - // a cube in dim - // dimensions and set up the dofs for - // the first time. Since we want to - // start the time stepping already - // with an adaptively refined grid, - // we perform some pre-refinement - // steps, consisting of all assembly, - // solution and refinement, but - // without actually advancing in - // time. Rather, we use the vilified - // goto statement to - // jump out of the time loop right - // after mesh refinement to start all - // over again on the new mesh - // beginning at the - // start_time_iteration - // label. - // - // Before we start, we project the - // initial values to the grid and - // obtain the first data for the - // old_temperature_solution - // vector. Then, we initialize time - // step number and time step and - // start the time loop. -template -void BoussinesqFlowProblem::run () -{ - const unsigned int initial_refinement = (dim == 2 ? 4 : 2); - const unsigned int n_pre_refinement_steps = (dim == 2 ? 4 : 3); - + // This function performs all the + // essential steps in the Boussinesq + // program. It starts by setting up a + // grid (depending on the spatial + // dimension, we choose some + // different level of initial + // refinement and additional adaptive + // refinement steps, and then create + // a cube in dim + // dimensions and set up the dofs for + // the first time. Since we want to + // start the time stepping already + // with an adaptively refined grid, + // we perform some pre-refinement + // steps, consisting of all assembly, + // solution and refinement, but + // without actually advancing in + // time. Rather, we use the vilified + // goto statement to + // jump out of the time loop right + // after mesh refinement to start all + // over again on the new mesh + // beginning at the + // start_time_iteration + // label. + // + // Before we start, we project the + // initial values to the grid and + // obtain the first data for the + // old_temperature_solution + // vector. Then, we initialize time + // step number and time step and + // start the time loop. + template + void BoussinesqFlowProblem::run () + { + const unsigned int initial_refinement = (dim == 2 ? 4 : 2); + const unsigned int n_pre_refinement_steps = (dim == 2 ? 4 : 3); - GridGenerator::hyper_cube (triangulation); - global_Omega_diameter = GridTools::diameter (triangulation); - triangulation.refine_global (initial_refinement); + GridGenerator::hyper_cube (triangulation); + global_Omega_diameter = GridTools::diameter (triangulation); - setup_dofs(); + triangulation.refine_global (initial_refinement); - unsigned int pre_refinement_step = 0; + setup_dofs(); - start_time_iteration: + unsigned int pre_refinement_step = 0; - VectorTools::project (temperature_dof_handler, - temperature_constraints, - QGauss(temperature_degree+2), - EquationData::TemperatureInitialValues(), - old_temperature_solution); + start_time_iteration: - timestep_number = 0; - time_step = old_time_step = 0; + VectorTools::project (temperature_dof_handler, + temperature_constraints, + QGauss(temperature_degree+2), + EquationData::TemperatureInitialValues(), + old_temperature_solution); - double time = 0; + timestep_number = 0; + time_step = old_time_step = 0; - do - { - std::cout << "Timestep " << timestep_number - << ": t=" << time - << std::endl; + double time = 0; - // The first steps in the time loop - // are all obvious – we - // assemble the Stokes system, the - // preconditioner, the temperature - // matrix (matrices and - // preconditioner do actually only - // change in case we've remeshed - // before), and then do the - // solve. Before going on - // with the next time step, we have - // to check whether we should first - // finish the pre-refinement steps or - // if we should remesh (every fifth - // time step), refining up to a level - // that is consistent with initial - // refinement and pre-refinement - // steps. Last in the loop is to - // advance the solutions, i.e. to - // copy the solutions to the next - // "older" time level. - assemble_stokes_system (); - build_stokes_preconditioner (); - assemble_temperature_matrix (); - - solve (); - - output_results (); - - std::cout << std::endl; - - if ((timestep_number == 0) && - (pre_refinement_step < n_pre_refinement_steps)) - { - refine_mesh (initial_refinement + n_pre_refinement_steps); - ++pre_refinement_step; - goto start_time_iteration; - } - else - if ((timestep_number > 0) && (timestep_number % 5 == 0)) - refine_mesh (initial_refinement + n_pre_refinement_steps); + do + { + std::cout << "Timestep " << timestep_number + << ": t=" << time + << std::endl; + + // The first steps in the time loop + // are all obvious – we + // assemble the Stokes system, the + // preconditioner, the temperature + // matrix (matrices and + // preconditioner do actually only + // change in case we've remeshed + // before), and then do the + // solve. Before going on + // with the next time step, we have + // to check whether we should first + // finish the pre-refinement steps or + // if we should remesh (every fifth + // time step), refining up to a level + // that is consistent with initial + // refinement and pre-refinement + // steps. Last in the loop is to + // advance the solutions, i.e. to + // copy the solutions to the next + // "older" time level. + assemble_stokes_system (); + build_stokes_preconditioner (); + assemble_temperature_matrix (); + + solve (); + + output_results (); + + std::cout << std::endl; + + if ((timestep_number == 0) && + (pre_refinement_step < n_pre_refinement_steps)) + { + refine_mesh (initial_refinement + n_pre_refinement_steps); + ++pre_refinement_step; + goto start_time_iteration; + } + else + if ((timestep_number > 0) && (timestep_number % 5 == 0)) + refine_mesh (initial_refinement + n_pre_refinement_steps); - time += time_step; - ++timestep_number; + time += time_step; + ++timestep_number; - old_stokes_solution = stokes_solution; - old_old_temperature_solution = old_temperature_solution; - old_temperature_solution = temperature_solution; - } - // Do all the above until we arrive at - // time 100. - while (time <= 100); + old_stokes_solution = stokes_solution; + old_old_temperature_solution = old_temperature_solution; + old_temperature_solution = temperature_solution; + } + // Do all the above until we arrive at + // time 100. + while (time <= 100); + } } @@ -2888,6 +2891,9 @@ int main (int argc, char *argv[]) { try { + using namespace dealii; + using namespace Step31; + deallog.depth_console (0); Utilities::System::MPI_InitFinalize mpi_initialization (argc, argv); diff --git a/deal.II/examples/step-33/step-33.cc b/deal.II/examples/step-33/step-33.cc index 2405b6d406..ceba990a78 100644 --- a/deal.II/examples/step-33/step-33.cc +++ b/deal.II/examples/step-33/step-33.cc @@ -3,7 +3,7 @@ /* $Id$ */ /* */ -/* Copyright (C) 2007, 2008, 2009, 2010 by the deal.II authors and David Neckels */ +/* Copyright (C) 2007, 2008, 2009, 2010, 2011 by the deal.II authors and David Neckels */ /* */ /* This file is subject to QPL and may not be distributed */ /* without copyright and license information. Please refer */ @@ -76,3029 +76,3033 @@ #include #include - // To end this section, introduce everythin - // in the dealii library into the current - // namespace: -using namespace dealii; - - - // @sect3{Euler equation specifics} - - // Here we define the flux function for this - // particular system of conservation laws, as - // well as pretty much everything else that's - // specific to the Euler equations for gas - // dynamics, for reasons discussed in the - // introduction. We group all this into a - // structure that defines everything that has - // to do with the flux. All members of this - // structure are static, i.e. the structure - // has no actual state specified by instance - // member variables. The better way to do - // this, rather than a structure with all - // static members would be to use a namespace - // -- but namespaces can't be templatized and - // we want some of the member variables of - // the structure to depend on the space - // dimension, which we in our usual way - // introduce using a template parameter. -template -struct EulerEquations + // To end this section, introduce everything + // in the dealii library into the namespace + // into which the contents of this program + // will go: +namespace Step33 { - // @sect4{Component description} - - // First a few variables that - // describe the various components of our - // solution vector in a generic way. This - // includes the number of components in the - // system (Euler's equations have one entry - // for momenta in each spatial direction, - // plus the energy and density components, - // for a total of dim+2 - // components), as well as functions that - // describe the index within the solution - // vector of the first momentum component, - // the density component, and the energy - // density component. Note that all these - // %numbers depend on the space dimension; - // defining them in a generic way (rather - // than by implicit convention) makes our - // code more flexible and makes it easier - // to later extend it, for example by - // adding more components to the equations. - static const unsigned int n_components = dim + 2; - static const unsigned int first_momentum_component = 0; - static const unsigned int density_component = dim; - static const unsigned int energy_component = dim+1; - - // When generating graphical - // output way down in this - // program, we need to specify - // the names of the solution - // variables as well as how the - // various components group into - // vector and scalar fields. We - // could describe this there, but - // in order to keep things that - // have to do with the Euler - // equation localized here and - // the rest of the program as - // generic as possible, we - // provide this sort of - // information in the following - // two functions: - static - std::vector - component_names () - { - std::vector names (dim, "momentum"); - names.push_back ("density"); - names.push_back ("energy_density"); + using namespace dealii; + + + // @sect3{Euler equation specifics} + + // Here we define the flux function for this + // particular system of conservation laws, as + // well as pretty much everything else that's + // specific to the Euler equations for gas + // dynamics, for reasons discussed in the + // introduction. We group all this into a + // structure that defines everything that has + // to do with the flux. All members of this + // structure are static, i.e. the structure + // has no actual state specified by instance + // member variables. The better way to do + // this, rather than a structure with all + // static members would be to use a namespace + // -- but namespaces can't be templatized and + // we want some of the member variables of + // the structure to depend on the space + // dimension, which we in our usual way + // introduce using a template parameter. + template + struct EulerEquations + { + // @sect4{Component description} + + // First a few variables that + // describe the various components of our + // solution vector in a generic way. This + // includes the number of components in the + // system (Euler's equations have one entry + // for momenta in each spatial direction, + // plus the energy and density components, + // for a total of dim+2 + // components), as well as functions that + // describe the index within the solution + // vector of the first momentum component, + // the density component, and the energy + // density component. Note that all these + // %numbers depend on the space dimension; + // defining them in a generic way (rather + // than by implicit convention) makes our + // code more flexible and makes it easier + // to later extend it, for example by + // adding more components to the equations. + static const unsigned int n_components = dim + 2; + static const unsigned int first_momentum_component = 0; + static const unsigned int density_component = dim; + static const unsigned int energy_component = dim+1; + + // When generating graphical + // output way down in this + // program, we need to specify + // the names of the solution + // variables as well as how the + // various components group into + // vector and scalar fields. We + // could describe this there, but + // in order to keep things that + // have to do with the Euler + // equation localized here and + // the rest of the program as + // generic as possible, we + // provide this sort of + // information in the following + // two functions: + static + std::vector + component_names () + { + std::vector names (dim, "momentum"); + names.push_back ("density"); + names.push_back ("energy_density"); - return names; - } + return names; + } - static - std::vector - component_interpretation () - { - std::vector + static + std::vector + component_interpretation () + { + std::vector + data_component_interpretation + (dim, DataComponentInterpretation::component_is_part_of_vector); + data_component_interpretation + .push_back (DataComponentInterpretation::component_is_scalar); data_component_interpretation - (dim, DataComponentInterpretation::component_is_part_of_vector); - data_component_interpretation - .push_back (DataComponentInterpretation::component_is_scalar); - data_component_interpretation - .push_back (DataComponentInterpretation::component_is_scalar); + .push_back (DataComponentInterpretation::component_is_scalar); - return data_component_interpretation; - } + return data_component_interpretation; + } - // @sect4{Transformations between variables} - - // Next, we define the gas - // constant. We will set it to 1.4 - // in its definition immediately - // following the declaration of - // this class (unlike integer - // variables, like the ones above, - // static const floating point - // member variables cannot be - // initialized within the class - // declaration in C++). This value - // of 1.4 is representative of a - // gas that consists of molecules - // composed of two atoms, such as - // air which consists up to small - // traces almost entirely of $N_2$ - // and $O_2$. - static const double gas_gamma; - - - // In the following, we will need to - // compute the kinetic energy and the - // pressure from a vector of conserved - // variables. This we can do based on the - // energy density and the kinetic energy - // $\frac 12 \rho |\mathbf v|^2 = - // \frac{|\rho \mathbf v|^2}{2\rho}$ - // (note that the independent variables - // contain the momentum components $\rho - // v_i$, not the velocities $v_i$). - // - // There is one slight problem: We will - // need to call the following functions - // with input arguments of type - // std::vector@ and - // Vector@. The - // problem is that the former has an - // access operator - // operator[] whereas the - // latter, for historical reasons, has - // operator(). We wouldn't - // be able to write the function in a - // generic way if we were to use one or - // the other of these. Fortunately, we - // can use the following trick: instead - // of writing v[i] or - // v(i), we can use - // *(v.begin() + i), i.e. we - // generate an iterator that points to - // the ith element, and then - // dereference it. This works for both - // kinds of vectors -- not the prettiest - // solution, but one that works. - template - static - number - compute_kinetic_energy (const InputVector &W) - { - number kinetic_energy = 0; - for (unsigned int d=0; dstd::vector@ and + // Vector@. The + // problem is that the former has an + // access operator + // operator[] whereas the + // latter, for historical reasons, has + // operator(). We wouldn't + // be able to write the function in a + // generic way if we were to use one or + // the other of these. Fortunately, we + // can use the following trick: instead + // of writing v[i] or + // v(i), we can use + // *(v.begin() + i), i.e. we + // generate an iterator that points to + // the ith element, and then + // dereference it. This works for both + // kinds of vectors -- not the prettiest + // solution, but one that works. + template + static + number + compute_kinetic_energy (const InputVector &W) + { + number kinetic_energy = 0; + for (unsigned int d=0; d - static - number - compute_pressure (const InputVector &W) - { - return ((gas_gamma-1.0) * - (*(W.begin() + energy_component) - - compute_kinetic_energy(W))); - } + template + static + number + compute_pressure (const InputVector &W) + { + return ((gas_gamma-1.0) * + (*(W.begin() + energy_component) - + compute_kinetic_energy(W))); + } - // @sect4{EulerEquations::compute_flux_matrix} - - // We define the flux function - // $F(W)$ as one large matrix. - // Each row of this matrix - // represents a scalar - // conservation law for the - // component in that row. The - // exact form of this matrix is - // given in the - // introduction. Note that we - // know the size of the matrix: - // it has as many rows as the - // system has components, and - // dim columns; - // rather than using a FullMatrix - // object for such a matrix - // (which has a variable number - // of rows and columns and must - // therefore allocate memory on - // the heap each time such a - // matrix is created), we use a - // rectangular array of numbers - // right away. - // - // We templatize the numerical type of - // the flux function so that we may use - // the automatic differentiation type - // here. Similarly, we will call the - // function with different input vector - // data types, so we templatize on it as - // well: - template - static - void compute_flux_matrix (const InputVector &W, - number (&flux)[n_components][dim]) - { - // First compute the pressure that - // appears in the flux matrix, and - // then compute the first - // dim columns of the - // matrix that correspond to the - // momentum terms: - const number pressure = compute_pressure (W); + // @sect4{EulerEquations::compute_flux_matrix} + + // We define the flux function + // $F(W)$ as one large matrix. + // Each row of this matrix + // represents a scalar + // conservation law for the + // component in that row. The + // exact form of this matrix is + // given in the + // introduction. Note that we + // know the size of the matrix: + // it has as many rows as the + // system has components, and + // dim columns; + // rather than using a FullMatrix + // object for such a matrix + // (which has a variable number + // of rows and columns and must + // therefore allocate memory on + // the heap each time such a + // matrix is created), we use a + // rectangular array of numbers + // right away. + // + // We templatize the numerical type of + // the flux function so that we may use + // the automatic differentiation type + // here. Similarly, we will call the + // function with different input vector + // data types, so we templatize on it as + // well: + template + static + void compute_flux_matrix (const InputVector &W, + number (&flux)[n_components][dim]) + { + // First compute the pressure that + // appears in the flux matrix, and + // then compute the first + // dim columns of the + // matrix that correspond to the + // momentum terms: + const number pressure = compute_pressure (W); + + for (unsigned int d=0; d + static + void numerical_normal_flux (const Point &normal, + const InputVector &Wplus, + const InputVector &Wminus, + const double alpha, + Sacado::Fad::DFad (&normal_flux)[n_components]) + { + Sacado::Fad::DFad iflux[n_components][dim]; + Sacado::Fad::DFad oflux[n_components][dim]; + compute_flux_matrix (Wplus, iflux); + compute_flux_matrix (Wminus, oflux); - // @sect4{EulerEquations::compute_normal_flux} - - // On the boundaries of the - // domain and across hanging - // nodes we use a numerical flux - // function to enforce boundary - // conditions. This routine is - // the basic Lax-Friedrich's flux - // with a stabilization parameter - // $\alpha$. It's form has also - // been given already in the - // introduction: - template - static - void numerical_normal_flux (const Point &normal, - const InputVector &Wplus, - const InputVector &Wminus, - const double alpha, - Sacado::Fad::DFad (&normal_flux)[n_components]) - { - Sacado::Fad::DFad iflux[n_components][dim]; - Sacado::Fad::DFad oflux[n_components][dim]; + for (unsigned int di=0; di + static + void compute_forcing_vector (const InputVector &W, + number (&forcing)[n_components]) + { + const double gravity = -1.0; - normal_flux[di] += 0.5*alpha*(Wplus[di] - Wminus[di]); - } - } + for (unsigned int c=0; c - static - void compute_forcing_vector (const InputVector &W, - number (&forcing)[n_components]) - { - const double gravity = -1.0; - for (unsigned int c=0; cWminus will of course be + // modified, so it shouldn't be a + // const argument. Yet it is + // in the implementation below, and needs + // to be in order to allow the code to + // compile. The reason is that we call + // this function at a place where + // Wminus is of type + // Table@<2,Sacado::Fad::DFad@ + // @>, this being 2d table with + // indices representing the quadrature + // point and the vector component, + // respectively. We call this function + // with Wminus[q] as last + // argument; subscripting a 2d table + // yields a temporary accessor object + // representing a 1d vector, just what we + // want here. The problem is that a + // temporary accessor object can't be + // bound to a non-const reference + // argument of a function, as we would + // like here, according to the C++ 1998 + // and 2003 standards (something that + // will be fixed with the next standard + // in the form of rvalue references). We + // get away with making the output + // argument here a constant because it is + // the accessor object that's + // constant, not the table it points to: + // that one can still be written to. The + // hack is unpleasant nevertheless + // because it restricts the kind of data + // types that may be used as template + // argument to this function: a regular + // vector isn't going to do because that + // one can not be written to when marked + // const. With no good + // solution around at the moment, we'll + // go with the pragmatic, even if not + // pretty, solution shown here: + template + static + void + compute_Wminus (const BoundaryKind (&boundary_kind)[n_components], + const Point &normal_vector, + const DataVector &Wplus, + const Vector &boundary_values, + const DataVector &Wminus) + { + for (unsigned int c = 0; c < n_components; c++) + switch (boundary_kind[c]) + { + case inflow_boundary: + { + Wminus[c] = boundary_values(c); + break; + } + case outflow_boundary: + { + Wminus[c] = Wplus[c]; + break; + } - // The next part is to actually decide - // what to do at each kind of - // boundary. To this end, remember from - // the introduction that boundary - // conditions are specified by choosing a - // value $\mathbf w^-$ on the outside of - // a boundary given an inhomogeneity - // $\mathbf j$ and possibly the - // solution's value $\mathbf w^+$ on the - // inside. Both are then passed to the - // numerical flux $\mathbf - // H(\mathbf{w}^+, \mathbf{w}^-, - // \mathbf{n})$ to define boundary - // contributions to the bilinear form. - // - // Boundary conditions can in some cases - // be specified for each component of the - // solution vector independently. For - // example, if component $c$ is marked - // for inflow, then $w^-_c = j_c$. If it - // is an outflow, then $w^-_c = - // w^+_c$. These two simple cases are - // handled first in the function below. - // - // There is a little snag that makes this - // function unpleasant from a C++ - // language viewpoint: The output vector - // Wminus will of course be - // modified, so it shouldn't be a - // const argument. Yet it is - // in the implementation below, and needs - // to be in order to allow the code to - // compile. The reason is that we call - // this function at a place where - // Wminus is of type - // Table@<2,Sacado::Fad::DFad@ - // @>, this being 2d table with - // indices representing the quadrature - // point and the vector component, - // respectively. We call this function - // with Wminus[q] as last - // argument; subscripting a 2d table - // yields a temporary accessor object - // representing a 1d vector, just what we - // want here. The problem is that a - // temporary accessor object can't be - // bound to a non-const reference - // argument of a function, as we would - // like here, according to the C++ 1998 - // and 2003 standards (something that - // will be fixed with the next standard - // in the form of rvalue references). We - // get away with making the output - // argument here a constant because it is - // the accessor object that's - // constant, not the table it points to: - // that one can still be written to. The - // hack is unpleasant nevertheless - // because it restricts the kind of data - // types that may be used as template - // argument to this function: a regular - // vector isn't going to do because that - // one can not be written to when marked - // const. With no good - // solution around at the moment, we'll - // go with the pragmatic, even if not - // pretty, solution shown here: - template - static - void - compute_Wminus (const BoundaryKind (&boundary_kind)[n_components], - const Point &normal_vector, - const DataVector &Wplus, - const Vector &boundary_values, - const DataVector &Wminus) - { - for (unsigned int c = 0; c < n_components; c++) - switch (boundary_kind[c]) - { - case inflow_boundary: - { - Wminus[c] = boundary_values(c); - break; - } + // Prescribed pressure boundary + // conditions are a bit more + // complicated by the fact that + // even though the pressure is + // prescribed, we really are + // setting the energy component + // here, which will depend on + // velocity and pressure. So + // even though this seems like + // a Dirichlet type boundary + // condition, we get + // sensitivities of energy to + // velocity and density (unless + // these are also prescribed): + case pressure_boundary: + { + const typename DataVector::value_type + density = (boundary_kind[density_component] == + inflow_boundary + ? + boundary_values(density_component) + : + Wplus[density_component]); - case outflow_boundary: - { - Wminus[c] = Wplus[c]; - break; - } + typename DataVector::value_type kinetic_energy = 0; + for (unsigned int d=0; d vdotn = 0; - for (unsigned int d = 0; d < dim; d++) { - vdotn += Wplus[d]*normal_vector[d]; + case no_penetration_boundary: + { + // We prescribe the + // velocity (we are dealing with a + // particular component here so + // that the average of the + // velocities is orthogonal to the + // surface normal. This creates + // sensitivies of across the + // velocity components. + Sacado::Fad::DFad vdotn = 0; + for (unsigned int d = 0; d < dim; d++) { + vdotn += Wplus[d]*normal_vector[d]; + } + + Wminus[c] = Wplus[c] - 2.0*vdotn*normal_vector[c]; + break; } - Wminus[c] = Wplus[c] - 2.0*vdotn*normal_vector[c]; - break; + default: + Assert (false, ExcNotImplemented()); } + } + + + // @sect4{EulerEquations::compute_refinement_indicators} + + // In this class, we also want to specify + // how to refine the mesh. The class + // ConservationLaw that will + // use all the information we provide + // here in the EulerEquation + // class is pretty agnostic about the + // particular conservation law it solves: + // as doesn't even really care how many + // components a solution vector + // has. Consequently, it can't know what + // a reasonable refinement indicator + // would be. On the other hand, here we + // do, or at least we can come up with a + // reasonable choice: we simply look at + // the gradient of the density, and + // compute + // $\eta_K=\log\left(1+|\nabla\rho(x_K)|\right)$, + // where $x_K$ is the center of cell $K$. + // + // There are certainly a number of + // equally reasonable refinement + // indicators, but this one does, and it + // is easy to compute: + static + void + compute_refinement_indicators (const DoFHandler &dof_handler, + const Mapping &mapping, + const Vector &solution, + Vector &refinement_indicators) + { + const unsigned int dofs_per_cell = dof_handler.get_fe().dofs_per_cell; + std::vector dofs (dofs_per_cell); - default: - Assert (false, ExcNotImplemented()); + const QMidpoint quadrature_formula; + const UpdateFlags update_flags = update_gradients; + FEValues fe_v (mapping, dof_handler.get_fe(), + quadrature_formula, update_flags); + + std::vector > > + dU (1, std::vector >(n_components)); + + typename DoFHandler::active_cell_iterator + cell = dof_handler.begin_active(), + endc = dof_handler.end(); + for (unsigned int cell_no=0; cell!=endc; ++cell, ++cell_no) + { + fe_v.reinit(cell); + fe_v.get_function_grads (solution, dU); + + refinement_indicators(cell_no) + = std::log(1+ + std::sqrt(dU[0][density_component] * + dU[0][density_component])); } - } + } - // @sect4{EulerEquations::compute_refinement_indicators} - - // In this class, we also want to specify - // how to refine the mesh. The class - // ConservationLaw that will - // use all the information we provide - // here in the EulerEquation - // class is pretty agnostic about the - // particular conservation law it solves: - // as doesn't even really care how many - // components a solution vector - // has. Consequently, it can't know what - // a reasonable refinement indicator - // would be. On the other hand, here we - // do, or at least we can come up with a - // reasonable choice: we simply look at - // the gradient of the density, and - // compute - // $\eta_K=\log\left(1+|\nabla\rho(x_K)|\right)$, - // where $x_K$ is the center of cell $K$. - // - // There are certainly a number of - // equally reasonable refinement - // indicators, but this one does, and it - // is easy to compute: - static - void - compute_refinement_indicators (const DoFHandler &dof_handler, - const Mapping &mapping, - const Vector &solution, - Vector &refinement_indicators) + + // @sect4{EulerEquations::Postprocessor} + + // Finally, we declare a class that + // implements a postprocessing of data + // components. The problem this class + // solves is that the variables in the + // formulation of the Euler equations we + // use are in conservative rather than + // physical form: they are momentum + // densities $\mathbf m=\rho\mathbf v$, + // density $\rho$, and energy density + // $E$. What we would like to also put + // into our output file are velocities + // $\mathbf v=\frac{\mathbf m}{\rho}$ and + // pressure $p=(\gamma-1)(E-\frac{1}{2} + // \rho |\mathbf v|^2)$. + // + // In addition, we would like to add the + // possibility to generate schlieren + // plots. Schlieren plots are a way to + // visualize shocks and other sharp + // interfaces. The word "schlieren" is a + // German word that may be translated as + // "striae" -- it may be simpler to + // explain it by an example, however: + // schlieren is what you see when you, + // for example, pour highly concentrated + // alcohol, or a transparent saline + // solution, into water; the two have the + // same color, but they have different + // refractive indices and so before they + // are fully mixed light goes through the + // mixture along bent rays that lead to + // brightness variations if you look at + // it. That's "schlieren". A similar + // effect happens in compressible flow + // because the refractive index + // depends on the pressure (and therefore + // the density) of the gas. + // + // The origin of the word refers to + // two-dimensional projections of a + // three-dimensional volume (we see a 2d + // picture of the 3d fluid). In + // computational fluid dynamics, we can + // get an idea of this effect by + // considering what causes it: density + // variations. Schlieren plots are + // therefore produced by plotting + // $s=|\nabla \rho|^2$; obviously, $s$ is + // large in shocks and at other highly + // dynamic places. If so desired by the + // user (by specifying this in the input + // file), we would like to generate these + // schlieren plots in addition to the + // other derived quantities listed above. + // + // The implementation of the algorithms + // to compute derived quantities from the + // ones that solve our problem, and to + // output them into data file, rests on + // the DataPostprocessor class. It has + // extensive documentation, and other + // uses of the class can also be found in + // step-29. We therefore refrain from + // extensive comments. + class Postprocessor : public DataPostprocessor { - const unsigned int dofs_per_cell = dof_handler.get_fe().dofs_per_cell; - std::vector dofs (dofs_per_cell); + public: + Postprocessor (const bool do_schlieren_plot); - const QMidpoint quadrature_formula; - const UpdateFlags update_flags = update_gradients; - FEValues fe_v (mapping, dof_handler.get_fe(), - quadrature_formula, update_flags); + virtual + void + compute_derived_quantities_vector (const std::vector > &uh, + const std::vector > > &duh, + const std::vector > > &dduh, + const std::vector > &normals, + const std::vector > &evaluation_points, + std::vector > &computed_quantities) const; - std::vector > > - dU (1, std::vector >(n_components)); + virtual std::vector get_names () const; - typename DoFHandler::active_cell_iterator - cell = dof_handler.begin_active(), - endc = dof_handler.end(); - for (unsigned int cell_no=0; cell!=endc; ++cell, ++cell_no) - { - fe_v.reinit(cell); - fe_v.get_function_grads (solution, dU); + virtual + std::vector + get_data_component_interpretation () const; - refinement_indicators(cell_no) - = std::log(1+ - std::sqrt(dU[0][density_component] * - dU[0][density_component])); - } - } + virtual UpdateFlags get_needed_update_flags () const; + virtual unsigned int n_output_variables() const; + private: + const bool do_schlieren_plot; + }; + }; - // @sect4{EulerEquations::Postprocessor} - // Finally, we declare a class that - // implements a postprocessing of data - // components. The problem this class - // solves is that the variables in the - // formulation of the Euler equations we - // use are in conservative rather than - // physical form: they are momentum - // densities $\mathbf m=\rho\mathbf v$, - // density $\rho$, and energy density - // $E$. What we would like to also put - // into our output file are velocities - // $\mathbf v=\frac{\mathbf m}{\rho}$ and - // pressure $p=(\gamma-1)(E-\frac{1}{2} - // \rho |\mathbf v|^2)$. - // - // In addition, we would like to add the - // possibility to generate schlieren - // plots. Schlieren plots are a way to - // visualize shocks and other sharp - // interfaces. The word "schlieren" is a - // German word that may be translated as - // "striae" -- it may be simpler to - // explain it by an example, however: - // schlieren is what you see when you, - // for example, pour highly concentrated - // alcohol, or a transparent saline - // solution, into water; the two have the - // same color, but they have different - // refractive indices and so before they - // are fully mixed light goes through the - // mixture along bent rays that lead to - // brightness variations if you look at - // it. That's "schlieren". A similar - // effect happens in compressible flow - // because the refractive index - // depends on the pressure (and therefore - // the density) of the gas. - // - // The origin of the word refers to - // two-dimensional projections of a - // three-dimensional volume (we see a 2d - // picture of the 3d fluid). In - // computational fluid dynamics, we can - // get an idea of this effect by - // considering what causes it: density - // variations. Schlieren plots are - // therefore produced by plotting - // $s=|\nabla \rho|^2$; obviously, $s$ is - // large in shocks and at other highly - // dynamic places. If so desired by the - // user (by specifying this in the input - // file), we would like to generate these - // schlieren plots in addition to the - // other derived quantities listed above. - // - // The implementation of the algorithms - // to compute derived quantities from the - // ones that solve our problem, and to - // output them into data file, rests on - // the DataPostprocessor class. It has - // extensive documentation, and other - // uses of the class can also be found in - // step-29. We therefore refrain from - // extensive comments. - class Postprocessor : public DataPostprocessor - { - public: - Postprocessor (const bool do_schlieren_plot); + template + const double EulerEquations::gas_gamma = 1.4; - virtual - void - compute_derived_quantities_vector (const std::vector > &uh, - const std::vector > > &duh, - const std::vector > > &dduh, - const std::vector > &normals, - const std::vector > &evaluation_points, - std::vector > &computed_quantities) const; - virtual std::vector get_names () const; - virtual - std::vector - get_data_component_interpretation () const; + template + EulerEquations::Postprocessor:: + Postprocessor (const bool do_schlieren_plot) + : + do_schlieren_plot (do_schlieren_plot) + {} - virtual UpdateFlags get_needed_update_flags () const; - virtual unsigned int n_output_variables() const; + // This is the only function worth commenting + // on. When generating graphical output, the + // DataOut and related classes will call this + // function on each cell, with values, + // gradients, hessians, and normal vectors + // (in case we're working on faces) at each + // quadrature point. Note that the data at + // each quadrature point is itself + // vector-valued, namely the conserved + // variables. What we're going to do here is + // to compute the quantities we're interested + // in at each quadrature point. Note that for + // this we can ignore the hessians ("dduh") + // and normal vectors; to avoid compiler + // warnings about unused variables, we + // comment out their names. + template + void + EulerEquations::Postprocessor:: + compute_derived_quantities_vector (const std::vector > &uh, + const std::vector > > &duh, + const std::vector > > &/*dduh*/, + const std::vector > &/*normals*/, + const std::vector > &/*evaluation_points*/, + std::vector > &computed_quantities) const + { + // At the beginning of the function, let us + // make sure that all variables have the + // correct sizes, so that we can access + // individual vector elements without + // having to wonder whether we might read + // or write invalid elements; we also check + // that the duh vector only + // contains data if we really need it (the + // system knows about this because we say + // so in the + // get_needed_update_flags() + // function below). For the inner vectors, + // we check that at least the first element + // of the outer vector has the correct + // inner size: + const unsigned int n_quadrature_points = uh.size(); + + if (do_schlieren_plot == true) + Assert (duh.size() == n_quadrature_points, + ExcInternalError()) + else + Assert (duh.size() == 0, + ExcInternalError()); - private: - const bool do_schlieren_plot; - }; -}; - - -template -const double EulerEquations::gas_gamma = 1.4; - - - -template -EulerEquations::Postprocessor:: -Postprocessor (const bool do_schlieren_plot) - : - do_schlieren_plot (do_schlieren_plot) -{} - - - // This is the only function worth commenting - // on. When generating graphical output, the - // DataOut and related classes will call this - // function on each cell, with values, - // gradients, hessians, and normal vectors - // (in case we're working on faces) at each - // quadrature point. Note that the data at - // each quadrature point is itself - // vector-valued, namely the conserved - // variables. What we're going to do here is - // to compute the quantities we're interested - // in at each quadrature point. Note that for - // this we can ignore the hessians ("dduh") - // and normal vectors; to avoid compiler - // warnings about unused variables, we - // comment out their names. -template -void -EulerEquations::Postprocessor:: -compute_derived_quantities_vector (const std::vector > &uh, - const std::vector > > &duh, - const std::vector > > &/*dduh*/, - const std::vector > &/*normals*/, - const std::vector > &/*evaluation_points*/, - std::vector > &computed_quantities) const -{ - // At the beginning of the function, let us - // make sure that all variables have the - // correct sizes, so that we can access - // individual vector elements without - // having to wonder whether we might read - // or write invalid elements; we also check - // that the duh vector only - // contains data if we really need it (the - // system knows about this because we say - // so in the - // get_needed_update_flags() - // function below). For the inner vectors, - // we check that at least the first element - // of the outer vector has the correct - // inner size: - const unsigned int n_quadrature_points = uh.size(); - - if (do_schlieren_plot == true) - Assert (duh.size() == n_quadrature_points, - ExcInternalError()) - else - Assert (duh.size() == 0, - ExcInternalError()); - - Assert (computed_quantities.size() == n_quadrature_points, - ExcInternalError()); - - Assert (uh[0].size() == n_components, - ExcInternalError()); - - if (do_schlieren_plot == true) - Assert (computed_quantities[0].size() == dim+2, ExcInternalError()) - else - Assert (computed_quantities[0].size() == dim+1, ExcInternalError()); - - // Then loop over all quadrature points and - // do our work there. The code should be - // pretty self-explanatory. The order of - // output variables is first - // dim velocities, then the - // pressure, and if so desired the - // schlieren plot. Note that we try to be - // generic about the order of variables in - // the input vector, using the - // first_momentum_component - // and density_component - // information: - for (unsigned int q=0; q (uh[q]); + if (do_schlieren_plot == true) + Assert (computed_quantities[0].size() == dim+2, ExcInternalError()) + else + Assert (computed_quantities[0].size() == dim+1, ExcInternalError()); + + // Then loop over all quadrature points and + // do our work there. The code should be + // pretty self-explanatory. The order of + // output variables is first + // dim velocities, then the + // pressure, and if so desired the + // schlieren plot. Note that we try to be + // generic about the order of variables in + // the input vector, using the + // first_momentum_component + // and density_component + // information: + for (unsigned int q=0; q (uh[q]); -template -std::vector -EulerEquations::Postprocessor:: -get_names () const -{ - std::vector names; - for (unsigned int d=0; d + std::vector + EulerEquations::Postprocessor:: + get_names () const + { + std::vector names; + for (unsigned int d=0; d -std::vector -EulerEquations::Postprocessor:: -get_data_component_interpretation () const -{ - std::vector - interpretation (dim, - DataComponentInterpretation::component_is_part_of_vector); - interpretation.push_back (DataComponentInterpretation:: - component_is_scalar); + template + std::vector + EulerEquations::Postprocessor:: + get_data_component_interpretation () const + { + std::vector + interpretation (dim, + DataComponentInterpretation::component_is_part_of_vector); - if (do_schlieren_plot == true) interpretation.push_back (DataComponentInterpretation:: component_is_scalar); - return interpretation; -} + if (do_schlieren_plot == true) + interpretation.push_back (DataComponentInterpretation:: + component_is_scalar); + return interpretation; + } -template -UpdateFlags -EulerEquations::Postprocessor:: -get_needed_update_flags () const -{ - if (do_schlieren_plot == true) - return update_values | update_gradients; - else - return update_values; -} + template + UpdateFlags + EulerEquations::Postprocessor:: + get_needed_update_flags () const + { + if (do_schlieren_plot == true) + return update_values | update_gradients; + else + return update_values; + } -template -unsigned int -EulerEquations::Postprocessor:: -n_output_variables () const -{ - if (do_schlieren_plot == true) - return dim+2; - else - return dim+1; -} + template + unsigned int + EulerEquations::Postprocessor:: + n_output_variables () const + { + if (do_schlieren_plot == true) + return dim+2; + else + return dim+1; + } - // @sect3{Run time parameter handling} - - // Our next job is to define a few - // classes that will contain run-time - // parameters (for example solver - // tolerances, number of iterations, - // stabilization parameter, and the - // like). One could do this in the - // main class, but we separate it - // from that one to make the program - // more modular and easier to read: - // Everything that has to do with - // run-time parameters will be in the - // following namespace, whereas the - // program logic is in the main - // class. - // - // We will split the run-time - // parameters into a few separate - // structures, which we will all put - // into a namespace - // Parameters. Of these - // classes, there are a few that - // group the parameters for - // individual groups, such as for - // solvers, mesh refinement, or - // output. Each of these classes have - // functions - // declare_parameters() - // and - // parse_parameters() - // that declare parameter subsections - // and entries in a ParameterHandler - // object, and retrieve actual - // parameter values from such an - // object, respectively. These - // classes declare all their - // parameters in subsections of the - // ParameterHandler. - // - // The final class of the following - // namespace combines all the - // previous classes by deriving from - // them and taking care of a few more - // entries at the top level of the - // input file, as well as a few odd - // other entries in subsections that - // are too short to warrent a - // structure by themselves. - // - // It is worth pointing out one thing here: - // None of the classes below have a - // constructor that would initialize the - // various member variables. This isn't a - // problem, however, since we will read all - // variables declared in these classes from - // the input file (or indirectly: a - // ParameterHandler object will read it from - // there, and we will get the values from - // this object), and they will be initialized - // this way. In case a certain variable is - // not specified at all in the input file, - // this isn't a problem either: The - // ParameterHandler class will in this case - // simply take the default value that was - // specified when declaring an entry in the - // declare_parameters() - // functions of the classes below. -namespace Parameters -{ - // @sect4{Parameters::Solver} + // @sect3{Run time parameter handling} + + // Our next job is to define a few + // classes that will contain run-time + // parameters (for example solver + // tolerances, number of iterations, + // stabilization parameter, and the + // like). One could do this in the + // main class, but we separate it + // from that one to make the program + // more modular and easier to read: + // Everything that has to do with + // run-time parameters will be in the + // following namespace, whereas the + // program logic is in the main + // class. // - // The first of these classes deals - // with parameters for the linear - // inner solver. It offers - // parameters that indicate which - // solver to use (GMRES as a solver - // for general non-symmetric - // indefinite systems, or a sparse - // direct solver), the amount of - // output to be produced, as well - // as various parameters that tweak - // the thresholded incomplete LU - // decomposition (ILUT) that we use - // as a preconditioner for GMRES. + // We will split the run-time + // parameters into a few separate + // structures, which we will all put + // into a namespace + // Parameters. Of these + // classes, there are a few that + // group the parameters for + // individual groups, such as for + // solvers, mesh refinement, or + // output. Each of these classes have + // functions + // declare_parameters() + // and + // parse_parameters() + // that declare parameter subsections + // and entries in a ParameterHandler + // object, and retrieve actual + // parameter values from such an + // object, respectively. These + // classes declare all their + // parameters in subsections of the + // ParameterHandler. // - // In particular, the ILUT takes - // the following parameters: - // - ilut_fill: the number of extra - // entries to add when forming the ILU - // decomposition - // - ilut_atol, ilut_rtol: When - // forming the preconditioner, for - // certain problems bad conditioning - // (or just bad luck) can cause the - // preconditioner to be very poorly - // conditioned. Hence it can help to - // add diagonal perturbations to the - // original matrix and form the - // preconditioner for this slightly - // better matrix. ATOL is an absolute - // perturbation that is added to the - // diagonal before forming the prec, - // and RTOL is a scaling factor $rtol - // \geq 1$. - // - ilut_drop: The ILUT will - // drop any values that - // have magnitude less than this value. - // This is a way to manage the amount - // of memory used by this - // preconditioner. + // The final class of the following + // namespace combines all the + // previous classes by deriving from + // them and taking care of a few more + // entries at the top level of the + // input file, as well as a few odd + // other entries in subsections that + // are too short to warrent a + // structure by themselves. // - // The meaning of each parameter is - // also briefly described in the - // third argument of the - // ParameterHandler::declare_entry - // call in - // declare_parameters(). - struct Solver + // It is worth pointing out one thing here: + // None of the classes below have a + // constructor that would initialize the + // various member variables. This isn't a + // problem, however, since we will read all + // variables declared in these classes from + // the input file (or indirectly: a + // ParameterHandler object will read it from + // there, and we will get the values from + // this object), and they will be initialized + // this way. In case a certain variable is + // not specified at all in the input file, + // this isn't a problem either: The + // ParameterHandler class will in this case + // simply take the default value that was + // specified when declaring an entry in the + // declare_parameters() + // functions of the classes below. + namespace Parameters { - enum SolverType { gmres, direct }; - SolverType solver; - enum OutputType { quiet, verbose }; - OutputType output; + // @sect4{Parameters::Solver} + // + // The first of these classes deals + // with parameters for the linear + // inner solver. It offers + // parameters that indicate which + // solver to use (GMRES as a solver + // for general non-symmetric + // indefinite systems, or a sparse + // direct solver), the amount of + // output to be produced, as well + // as various parameters that tweak + // the thresholded incomplete LU + // decomposition (ILUT) that we use + // as a preconditioner for GMRES. + // + // In particular, the ILUT takes + // the following parameters: + // - ilut_fill: the number of extra + // entries to add when forming the ILU + // decomposition + // - ilut_atol, ilut_rtol: When + // forming the preconditioner, for + // certain problems bad conditioning + // (or just bad luck) can cause the + // preconditioner to be very poorly + // conditioned. Hence it can help to + // add diagonal perturbations to the + // original matrix and form the + // preconditioner for this slightly + // better matrix. ATOL is an absolute + // perturbation that is added to the + // diagonal before forming the prec, + // and RTOL is a scaling factor $rtol + // \geq 1$. + // - ilut_drop: The ILUT will + // drop any values that + // have magnitude less than this value. + // This is a way to manage the amount + // of memory used by this + // preconditioner. + // + // The meaning of each parameter is + // also briefly described in the + // third argument of the + // ParameterHandler::declare_entry + // call in + // declare_parameters(). + struct Solver + { + enum SolverType { gmres, direct }; + SolverType solver; + + enum OutputType { quiet, verbose }; + OutputType output; - double linear_residual; - int max_iterations; + double linear_residual; + int max_iterations; - double ilut_fill; - double ilut_atol; - double ilut_rtol; - double ilut_drop; + double ilut_fill; + double ilut_atol; + double ilut_rtol; + double ilut_drop; - static void declare_parameters (ParameterHandler &prm); - void parse_parameters (ParameterHandler &prm); - }; + static void declare_parameters (ParameterHandler &prm); + void parse_parameters (ParameterHandler &prm); + }; - void Solver::declare_parameters (ParameterHandler &prm) - { - prm.enter_subsection("linear solver"); + void Solver::declare_parameters (ParameterHandler &prm) { - prm.declare_entry("output", "quiet", - Patterns::Selection("quiet|verbose"), - "State whether output from solver runs should be printed. " - "Choices are ."); - prm.declare_entry("method", "gmres", - Patterns::Selection("gmres|direct"), - "The kind of solver for the linear system. " - "Choices are ."); - prm.declare_entry("residual", "1e-10", - Patterns::Double(), - "Linear solver residual"); - prm.declare_entry("max iters", "300", - Patterns::Integer(), - "Maximum solver iterations"); - prm.declare_entry("ilut fill", "2", - Patterns::Double(), - "Ilut preconditioner fill"); - prm.declare_entry("ilut absolute tolerance", "1e-9", - Patterns::Double(), - "Ilut preconditioner tolerance"); - prm.declare_entry("ilut relative tolerance", "1.1", - Patterns::Double(), - "Ilut relative tolerance"); - prm.declare_entry("ilut drop tolerance", "1e-10", - Patterns::Double(), - "Ilut drop tolerance"); + prm.enter_subsection("linear solver"); + { + prm.declare_entry("output", "quiet", + Patterns::Selection("quiet|verbose"), + "State whether output from solver runs should be printed. " + "Choices are ."); + prm.declare_entry("method", "gmres", + Patterns::Selection("gmres|direct"), + "The kind of solver for the linear system. " + "Choices are ."); + prm.declare_entry("residual", "1e-10", + Patterns::Double(), + "Linear solver residual"); + prm.declare_entry("max iters", "300", + Patterns::Integer(), + "Maximum solver iterations"); + prm.declare_entry("ilut fill", "2", + Patterns::Double(), + "Ilut preconditioner fill"); + prm.declare_entry("ilut absolute tolerance", "1e-9", + Patterns::Double(), + "Ilut preconditioner tolerance"); + prm.declare_entry("ilut relative tolerance", "1.1", + Patterns::Double(), + "Ilut relative tolerance"); + prm.declare_entry("ilut drop tolerance", "1e-10", + Patterns::Double(), + "Ilut drop tolerance"); + } + prm.leave_subsection(); } - prm.leave_subsection(); - } - void Solver::parse_parameters (ParameterHandler &prm) - { - prm.enter_subsection("linear solver"); + void Solver::parse_parameters (ParameterHandler &prm) { - const std::string op = prm.get("output"); - if (op == "verbose") - output = verbose; - if (op == "quiet") - output = quiet; - - const std::string sv = prm.get("method"); - if (sv == "direct") - solver = direct; - else if (sv == "gmres") - solver = gmres; - - linear_residual = prm.get_double("residual"); - max_iterations = prm.get_integer("max iters"); - ilut_fill = prm.get_double("ilut fill"); - ilut_atol = prm.get_double("ilut absolute tolerance"); - ilut_rtol = prm.get_double("ilut relative tolerance"); - ilut_drop = prm.get_double("ilut drop tolerance"); + prm.enter_subsection("linear solver"); + { + const std::string op = prm.get("output"); + if (op == "verbose") + output = verbose; + if (op == "quiet") + output = quiet; + + const std::string sv = prm.get("method"); + if (sv == "direct") + solver = direct; + else if (sv == "gmres") + solver = gmres; + + linear_residual = prm.get_double("residual"); + max_iterations = prm.get_integer("max iters"); + ilut_fill = prm.get_double("ilut fill"); + ilut_atol = prm.get_double("ilut absolute tolerance"); + ilut_rtol = prm.get_double("ilut relative tolerance"); + ilut_drop = prm.get_double("ilut drop tolerance"); + } + prm.leave_subsection(); } - prm.leave_subsection(); - } - // @sect4{Parameters::Refinement} - // - // Similarly, here are a few parameters - // that determine how the mesh is to be - // refined (and if it is to be refined at - // all). For what exactly the shock - // parameters do, see the mesh refinement - // functions further down. - struct Refinement - { - bool do_refine; - double shock_val; - double shock_levels; - - static void declare_parameters (ParameterHandler &prm); - void parse_parameters (ParameterHandler &prm); - }; + // @sect4{Parameters::Refinement} + // + // Similarly, here are a few parameters + // that determine how the mesh is to be + // refined (and if it is to be refined at + // all). For what exactly the shock + // parameters do, see the mesh refinement + // functions further down. + struct Refinement + { + bool do_refine; + double shock_val; + double shock_levels; + static void declare_parameters (ParameterHandler &prm); + void parse_parameters (ParameterHandler &prm); + }; - void Refinement::declare_parameters (ParameterHandler &prm) - { - prm.enter_subsection("refinement"); + void Refinement::declare_parameters (ParameterHandler &prm) { - prm.declare_entry("refinement", "true", - Patterns::Bool(), - "Whether to perform mesh refinement or not"); - prm.declare_entry("refinement fraction", "0.1", - Patterns::Double(), - "Fraction of high refinement"); - prm.declare_entry("unrefinement fraction", "0.1", - Patterns::Double(), - "Fraction of low unrefinement"); - prm.declare_entry("max elements", "1000000", - Patterns::Double(), - "maximum number of elements"); - prm.declare_entry("shock value", "4.0", - Patterns::Double(), - "value for shock indicator"); - prm.declare_entry("shock levels", "3.0", - Patterns::Double(), - "number of shock refinement levels"); + + prm.enter_subsection("refinement"); + { + prm.declare_entry("refinement", "true", + Patterns::Bool(), + "Whether to perform mesh refinement or not"); + prm.declare_entry("refinement fraction", "0.1", + Patterns::Double(), + "Fraction of high refinement"); + prm.declare_entry("unrefinement fraction", "0.1", + Patterns::Double(), + "Fraction of low unrefinement"); + prm.declare_entry("max elements", "1000000", + Patterns::Double(), + "maximum number of elements"); + prm.declare_entry("shock value", "4.0", + Patterns::Double(), + "value for shock indicator"); + prm.declare_entry("shock levels", "3.0", + Patterns::Double(), + "number of shock refinement levels"); + } + prm.leave_subsection(); } - prm.leave_subsection(); - } - void Refinement::parse_parameters (ParameterHandler &prm) - { - prm.enter_subsection("refinement"); + void Refinement::parse_parameters (ParameterHandler &prm) { - do_refine = prm.get_bool ("refinement"); - shock_val = prm.get_double("shock value"); - shock_levels = prm.get_double("shock levels"); + prm.enter_subsection("refinement"); + { + do_refine = prm.get_bool ("refinement"); + shock_val = prm.get_double("shock value"); + shock_levels = prm.get_double("shock levels"); + } + prm.leave_subsection(); } - prm.leave_subsection(); - } - // @sect4{Parameters::Flux} - // - // Next a section on flux modifications to - // make it more stable. In particular, two - // options are offered to stabilize the - // Lax-Friedrichs flux: either choose - // $\mathbf{H}(\mathbf{a},\mathbf{b},\mathbf{n}) - // = - // \frac{1}{2}(\mathbf{F}(\mathbf{a})\cdot - // \mathbf{n} + \mathbf{F}(\mathbf{b})\cdot - // \mathbf{n} + \alpha (\mathbf{a} - - // \mathbf{b}))$ where $\alpha$ is either a - // fixed number specified in the input - // file, or where $\alpha$ is a mesh - // dependent value. In the latter case, it - // is chosen as $\frac{h}{2\delta T}$ with - // $h$ the diameter of the face to which - // the flux is applied, and $\delta T$ - // the current time step. - struct Flux - { - enum StabilizationKind { constant, mesh_dependent }; - StabilizationKind stabilization_kind; + // @sect4{Parameters::Flux} + // + // Next a section on flux modifications to + // make it more stable. In particular, two + // options are offered to stabilize the + // Lax-Friedrichs flux: either choose + // $\mathbf{H}(\mathbf{a},\mathbf{b},\mathbf{n}) + // = + // \frac{1}{2}(\mathbf{F}(\mathbf{a})\cdot + // \mathbf{n} + \mathbf{F}(\mathbf{b})\cdot + // \mathbf{n} + \alpha (\mathbf{a} - + // \mathbf{b}))$ where $\alpha$ is either a + // fixed number specified in the input + // file, or where $\alpha$ is a mesh + // dependent value. In the latter case, it + // is chosen as $\frac{h}{2\delta T}$ with + // $h$ the diameter of the face to which + // the flux is applied, and $\delta T$ + // the current time step. + struct Flux + { + enum StabilizationKind { constant, mesh_dependent }; + StabilizationKind stabilization_kind; - double stabilization_value; + double stabilization_value; - static void declare_parameters (ParameterHandler &prm); - void parse_parameters (ParameterHandler &prm); - }; + static void declare_parameters (ParameterHandler &prm); + void parse_parameters (ParameterHandler &prm); + }; - void Flux::declare_parameters (ParameterHandler &prm) - { - prm.enter_subsection("flux"); + void Flux::declare_parameters (ParameterHandler &prm) { - prm.declare_entry("stab", "mesh", - Patterns::Selection("constant|mesh"), - "Whether to use a constant stabilization parameter or " - "a mesh-dependent one"); - prm.declare_entry("stab value", "1", - Patterns::Double(), - "alpha stabilization"); + prm.enter_subsection("flux"); + { + prm.declare_entry("stab", "mesh", + Patterns::Selection("constant|mesh"), + "Whether to use a constant stabilization parameter or " + "a mesh-dependent one"); + prm.declare_entry("stab value", "1", + Patterns::Double(), + "alpha stabilization"); + } + prm.leave_subsection(); } - prm.leave_subsection(); - } - void Flux::parse_parameters (ParameterHandler &prm) - { - prm.enter_subsection("flux"); + void Flux::parse_parameters (ParameterHandler &prm) { - const std::string stab = prm.get("stab"); - if (stab == "constant") - stabilization_kind = constant; - else if (stab == "mesh") - stabilization_kind = mesh_dependent; - else - AssertThrow (false, ExcNotImplemented()); + prm.enter_subsection("flux"); + { + const std::string stab = prm.get("stab"); + if (stab == "constant") + stabilization_kind = constant; + else if (stab == "mesh") + stabilization_kind = mesh_dependent; + else + AssertThrow (false, ExcNotImplemented()); - stabilization_value = prm.get_double("stab value"); + stabilization_value = prm.get_double("stab value"); + } + prm.leave_subsection(); } - prm.leave_subsection(); - } - // @sect4{Parameters::Output} - // - // Then a section on output parameters. We - // offer to produce Schlieren plots (the - // squared gradient of the density, a tool - // to visualize shock fronts), and a time - // interval between graphical output in - // case we don't want an output file every - // time step. - struct Output - { - bool schlieren_plot; - double output_step; + // @sect4{Parameters::Output} + // + // Then a section on output parameters. We + // offer to produce Schlieren plots (the + // squared gradient of the density, a tool + // to visualize shock fronts), and a time + // interval between graphical output in + // case we don't want an output file every + // time step. + struct Output + { + bool schlieren_plot; + double output_step; - static void declare_parameters (ParameterHandler &prm); - void parse_parameters (ParameterHandler &prm); - }; + static void declare_parameters (ParameterHandler &prm); + void parse_parameters (ParameterHandler &prm); + }; - void Output::declare_parameters (ParameterHandler &prm) - { - prm.enter_subsection("output"); + void Output::declare_parameters (ParameterHandler &prm) { - prm.declare_entry("schlieren plot", "true", - Patterns::Bool (), - "Whether or not to produce schlieren plots"); - prm.declare_entry("step", "-1", - Patterns::Double(), - "Output once per this period"); + prm.enter_subsection("output"); + { + prm.declare_entry("schlieren plot", "true", + Patterns::Bool (), + "Whether or not to produce schlieren plots"); + prm.declare_entry("step", "-1", + Patterns::Double(), + "Output once per this period"); + } + prm.leave_subsection(); } - prm.leave_subsection(); - } - void Output::parse_parameters (ParameterHandler &prm) - { - prm.enter_subsection("output"); + void Output::parse_parameters (ParameterHandler &prm) { - schlieren_plot = prm.get_bool("schlieren plot"); - output_step = prm.get_double("step"); + prm.enter_subsection("output"); + { + schlieren_plot = prm.get_bool("schlieren plot"); + output_step = prm.get_double("step"); + } + prm.leave_subsection(); } - prm.leave_subsection(); - } - // @sect4{Parameters::AllParameters} - // - // Finally the class that brings it all - // together. It declares a number of - // parameters itself, mostly ones at the - // top level of the parameter file as well - // as several in section too small to - // warrant their own classes. It also - // contains everything that is actually - // space dimension dependent, like initial - // or boundary conditions. - // - // Since this class is derived from all the - // ones above, the - // declare_parameters() and - // parse_parameters() - // functions call the respective functions - // of the base classes as well. - // - // Note that this class also handles the - // declaration of initial and boundary - // conditions specified in the input - // file. To this end, in both cases, - // there are entries like "w_0 value" - // which represent an expression in terms - // of $x,y,z$ that describe the initial - // or boundary condition as a formula - // that will later be parsed by the - // FunctionParser class. Similar - // expressions exist for "w_1", "w_2", - // etc, denoting the dim+2 - // conserved variables of the Euler - // system. Similarly, we allow up to - // max_n_boundaries boundary - // indicators to be used in the input - // file, and each of these boundary - // indicators can be associated with an - // inflow, outflow, or pressure boundary - // condition, with inhomogenous boundary - // conditions being specified for each - // component and each boundary indicator - // separately. - // - // The data structure used to store the - // boundary indicators is a bit - // complicated. It is an array of - // max_n_boundaries elements - // indicating the range of boundary - // indicators that will be accepted. For - // each entry in this array, we store a - // pair of data in the - // BoundaryCondition - // structure: first, an array of size - // n_components that for - // each component of the solution vector - // indicates whether it is an inflow, - // outflow, or other kind of boundary, - // and second a FunctionParser object - // that describes all components of the - // solution vector for this boundary id - // at once. - // - // The BoundaryCondition - // structure requires a constructor since - // we need to tell the function parser - // object at construction time how many - // vector components it is to - // describe. This initialization can - // therefore not wait till we actually - // set the formulas the FunctionParser - // object represents later in - // AllParameters::parse_parameters() - // - // For the same reason of having to tell - // Function objects their vector size at - // construction time, we have to have a - // constructor of the - // AllParameters class that - // at least initializes the other - // FunctionParser object, i.e. the one - // describing initial conditions. - template - struct AllParameters : public Solver, - public Refinement, - public Flux, - public Output - { - static const unsigned int max_n_boundaries = 10; + // @sect4{Parameters::AllParameters} + // + // Finally the class that brings it all + // together. It declares a number of + // parameters itself, mostly ones at the + // top level of the parameter file as well + // as several in section too small to + // warrant their own classes. It also + // contains everything that is actually + // space dimension dependent, like initial + // or boundary conditions. + // + // Since this class is derived from all the + // ones above, the + // declare_parameters() and + // parse_parameters() + // functions call the respective functions + // of the base classes as well. + // + // Note that this class also handles the + // declaration of initial and boundary + // conditions specified in the input + // file. To this end, in both cases, + // there are entries like "w_0 value" + // which represent an expression in terms + // of $x,y,z$ that describe the initial + // or boundary condition as a formula + // that will later be parsed by the + // FunctionParser class. Similar + // expressions exist for "w_1", "w_2", + // etc, denoting the dim+2 + // conserved variables of the Euler + // system. Similarly, we allow up to + // max_n_boundaries boundary + // indicators to be used in the input + // file, and each of these boundary + // indicators can be associated with an + // inflow, outflow, or pressure boundary + // condition, with inhomogenous boundary + // conditions being specified for each + // component and each boundary indicator + // separately. + // + // The data structure used to store the + // boundary indicators is a bit + // complicated. It is an array of + // max_n_boundaries elements + // indicating the range of boundary + // indicators that will be accepted. For + // each entry in this array, we store a + // pair of data in the + // BoundaryCondition + // structure: first, an array of size + // n_components that for + // each component of the solution vector + // indicates whether it is an inflow, + // outflow, or other kind of boundary, + // and second a FunctionParser object + // that describes all components of the + // solution vector for this boundary id + // at once. + // + // The BoundaryCondition + // structure requires a constructor since + // we need to tell the function parser + // object at construction time how many + // vector components it is to + // describe. This initialization can + // therefore not wait till we actually + // set the formulas the FunctionParser + // object represents later in + // AllParameters::parse_parameters() + // + // For the same reason of having to tell + // Function objects their vector size at + // construction time, we have to have a + // constructor of the + // AllParameters class that + // at least initializes the other + // FunctionParser object, i.e. the one + // describing initial conditions. + template + struct AllParameters : public Solver, + public Refinement, + public Flux, + public Output + { + static const unsigned int max_n_boundaries = 10; - struct BoundaryConditions - { - typename EulerEquations::BoundaryKind - kind[EulerEquations::n_components]; + struct BoundaryConditions + { + typename EulerEquations::BoundaryKind + kind[EulerEquations::n_components]; - FunctionParser values; + FunctionParser values; - BoundaryConditions (); - }; + BoundaryConditions (); + }; - AllParameters (); + AllParameters (); - double diffusion_power; + double diffusion_power; - double time_step, final_time; - double theta; - bool is_stationary; + double time_step, final_time; + double theta; + bool is_stationary; - std::string mesh_filename; + std::string mesh_filename; - FunctionParser initial_conditions; - BoundaryConditions boundary_conditions[max_n_boundaries]; + FunctionParser initial_conditions; + BoundaryConditions boundary_conditions[max_n_boundaries]; - static void declare_parameters (ParameterHandler &prm); - void parse_parameters (ParameterHandler &prm); - }; + static void declare_parameters (ParameterHandler &prm); + void parse_parameters (ParameterHandler &prm); + }; - template - AllParameters::BoundaryConditions::BoundaryConditions () - : - values (EulerEquations::n_components) - {} + template + AllParameters::BoundaryConditions::BoundaryConditions () + : + values (EulerEquations::n_components) + {} - template - AllParameters::AllParameters () - : - initial_conditions (EulerEquations::n_components) - {} + template + AllParameters::AllParameters () + : + initial_conditions (EulerEquations::n_components) + {} - template - void - AllParameters::declare_parameters (ParameterHandler &prm) - { - prm.declare_entry("mesh", "grid.inp", - Patterns::Anything(), - "intput file name"); - - prm.declare_entry("diffusion power", "2.0", - Patterns::Double(), - "power of mesh size for diffusion"); - - prm.enter_subsection("time stepping"); + template + void + AllParameters::declare_parameters (ParameterHandler &prm) { - prm.declare_entry("time step", "0.1", - Patterns::Double(0), - "simulation time step"); - prm.declare_entry("final time", "10.0", - Patterns::Double(0), - "simulation end time"); - prm.declare_entry("theta scheme value", "0.5", - Patterns::Double(0,1), - "value for theta that interpolated between explicit " - "Euler (theta=0), Crank-Nicolson (theta=0.5), and " - "implicit Euler (theta=1)."); - } - prm.leave_subsection(); + prm.declare_entry("mesh", "grid.inp", + Patterns::Anything(), + "intput file name"); + prm.declare_entry("diffusion power", "2.0", + Patterns::Double(), + "power of mesh size for diffusion"); - for (unsigned int b=0; b::n_components; ++di) - { - prm.declare_entry("w_" + Utilities::int_to_string(di), - "outflow", - Patterns::Selection("inflow|outflow|pressure"), - ""); - - prm.declare_entry("w_" + Utilities::int_to_string(di) + - " value", "0.0", - Patterns::Anything(), - "expression in x,y,z"); - } + for (unsigned int di=0; di::n_components; ++di) + { + prm.declare_entry("w_" + Utilities::int_to_string(di), + "outflow", + Patterns::Selection("inflow|outflow|pressure"), + ""); + + prm.declare_entry("w_" + Utilities::int_to_string(di) + + " value", "0.0", + Patterns::Anything(), + "expression in x,y,z"); + } + } + prm.leave_subsection(); } - prm.leave_subsection(); + + prm.enter_subsection("initial condition"); + { + for (unsigned int di=0; di::n_components; ++di) + prm.declare_entry("w_" + Utilities::int_to_string(di) + " value", + "0.0", + Patterns::Anything(), + "expression in x,y,z"); } + prm.leave_subsection(); - prm.enter_subsection("initial condition"); - { - for (unsigned int di=0; di::n_components; ++di) - prm.declare_entry("w_" + Utilities::int_to_string(di) + " value", - "0.0", - Patterns::Anything(), - "expression in x,y,z"); + Parameters::Solver::declare_parameters (prm); + Parameters::Refinement::declare_parameters (prm); + Parameters::Flux::declare_parameters (prm); + Parameters::Output::declare_parameters (prm); } - prm.leave_subsection(); - - Parameters::Solver::declare_parameters (prm); - Parameters::Refinement::declare_parameters (prm); - Parameters::Flux::declare_parameters (prm); - Parameters::Output::declare_parameters (prm); - } - template - void - AllParameters::parse_parameters (ParameterHandler &prm) - { - mesh_filename = prm.get("mesh"); - diffusion_power = prm.get_double("diffusion power"); - - prm.enter_subsection("time stepping"); + template + void + AllParameters::parse_parameters (ParameterHandler &prm) { - time_step = prm.get_double("time step"); - if (time_step == 0) - { - is_stationary = true; - time_step = 1.0; - final_time = 1.0; - } - else - is_stationary = false; - - final_time = prm.get_double("final time"); - theta = prm.get_double("theta scheme value"); - } - prm.leave_subsection(); + mesh_filename = prm.get("mesh"); + diffusion_power = prm.get_double("diffusion power"); - for (unsigned int boundary_id=0; boundary_id - expressions(EulerEquations::n_components, "0.0"); + prm.enter_subsection("boundary_" + + Utilities::int_to_string(boundary_id)); + { + std::vector + expressions(EulerEquations::n_components, "0.0"); - const bool no_penetration = prm.get_bool("no penetration"); + const bool no_penetration = prm.get_bool("no penetration"); - for (unsigned int di=0; di::n_components; ++di) - { - const std::string boundary_type - = prm.get("w_" + Utilities::int_to_string(di)); - - if ((di < dim) && (no_penetration == true)) - boundary_conditions[boundary_id].kind[di] - = EulerEquations::no_penetration_boundary; - else if (boundary_type == "inflow") - boundary_conditions[boundary_id].kind[di] - = EulerEquations::inflow_boundary; - else if (boundary_type == "pressure") - boundary_conditions[boundary_id].kind[di] - = EulerEquations::pressure_boundary; - else if (boundary_type == "outflow") - boundary_conditions[boundary_id].kind[di] - = EulerEquations::outflow_boundary; - else - AssertThrow (false, ExcNotImplemented()); - - expressions[di] = prm.get("w_" + Utilities::int_to_string(di) + - " value"); - } + for (unsigned int di=0; di::n_components; ++di) + { + const std::string boundary_type + = prm.get("w_" + Utilities::int_to_string(di)); + + if ((di < dim) && (no_penetration == true)) + boundary_conditions[boundary_id].kind[di] + = EulerEquations::no_penetration_boundary; + else if (boundary_type == "inflow") + boundary_conditions[boundary_id].kind[di] + = EulerEquations::inflow_boundary; + else if (boundary_type == "pressure") + boundary_conditions[boundary_id].kind[di] + = EulerEquations::pressure_boundary; + else if (boundary_type == "outflow") + boundary_conditions[boundary_id].kind[di] + = EulerEquations::outflow_boundary; + else + AssertThrow (false, ExcNotImplemented()); + + expressions[di] = prm.get("w_" + Utilities::int_to_string(di) + + " value"); + } - boundary_conditions[boundary_id].values - .initialize (FunctionParser::default_variable_names(), - expressions, - std::map()); + boundary_conditions[boundary_id].values + .initialize (FunctionParser::default_variable_names(), + expressions, + std::map()); + } + prm.leave_subsection(); } - prm.leave_subsection(); + + prm.enter_subsection("initial condition"); + { + std::vector expressions (EulerEquations::n_components, + "0.0"); + for (unsigned int di = 0; di < EulerEquations::n_components; di++) + expressions[di] = prm.get("w_" + Utilities::int_to_string(di) + + " value"); + initial_conditions.initialize (FunctionParser::default_variable_names(), + expressions, + std::map()); } + prm.leave_subsection(); - prm.enter_subsection("initial condition"); - { - std::vector expressions (EulerEquations::n_components, - "0.0"); - for (unsigned int di = 0; di < EulerEquations::n_components; di++) - expressions[di] = prm.get("w_" + Utilities::int_to_string(di) + - " value"); - initial_conditions.initialize (FunctionParser::default_variable_names(), - expressions, - std::map()); + Parameters::Solver::parse_parameters (prm); + Parameters::Refinement::parse_parameters (prm); + Parameters::Flux::parse_parameters (prm); + Parameters::Output::parse_parameters (prm); } - prm.leave_subsection(); - - Parameters::Solver::parse_parameters (prm); - Parameters::Refinement::parse_parameters (prm); - Parameters::Flux::parse_parameters (prm); - Parameters::Output::parse_parameters (prm); } -} - - - // @sect3{Conservation law class} - - // Here finally comes the class that - // actually does something with all - // the Euler equation and parameter - // specifics we've defined above. The - // public interface is pretty much - // the same as always (the - // constructor now takes the name of - // a file from which to read - // parameters, which is passed on the - // command line). The private - // function interface is also pretty - // similar to the usual arrangement, - // with the - // assemble_system - // function split into three parts: - // one that contains the main loop - // over all cells and that then calls - // the other two for integrals over - // cells and faces, respectively. -template -class ConservationLaw -{ - public: - ConservationLaw (const char *input_filename); - void run (); - - private: - void setup_system (); - - void assemble_system (); - void assemble_cell_term (const FEValues &fe_v, - const std::vector &dofs); - void assemble_face_term (const unsigned int face_no, - const FEFaceValuesBase &fe_v, - const FEFaceValuesBase &fe_v_neighbor, - const std::vector &dofs, - const std::vector &dofs_neighbor, - const bool external_face, - const unsigned int boundary_id, - const double face_diameter); - - std::pair solve (Vector &solution); - - void compute_refinement_indicators (Vector &indicator) const; - void refine_grid (const Vector &indicator); - - void output_results () const; - - - - // The first few member variables - // are also rather standard. Note - // that we define a mapping - // object to be used throughout - // the program when assembling - // terms (we will hand it to - // every FEValues and - // FEFaceValues object); the - // mapping we use is just the - // standard $Q_1$ mapping -- - // nothing fancy, in other words - // -- but declaring one here and - // using it throughout the - // program will make it simpler - // later on to change it if that - // should become necessary. This - // is, in fact, rather pertinent: - // it is known that for - // transsonic simulations with - // the Euler equations, - // computations do not converge - // even as $h\rightarrow 0$ if - // the boundary approximation is - // not of sufficiently high - // order. - Triangulation triangulation; - const MappingQ1 mapping; - - const FESystem fe; - DoFHandler dof_handler; - - const QGauss quadrature; - const QGauss face_quadrature; - - // Next come a number of data - // vectors that correspond to the - // solution of the previous time - // step - // (old_solution), - // the best guess of the current - // solution - // (current_solution; - // we say guess because - // the Newton iteration to - // compute it may not have - // converged yet, whereas - // old_solution - // refers to the fully converged - // final result of the previous - // time step), and a predictor - // for the solution at the next - // time step, computed by - // extrapolating the current and - // previous solution one time - // step into the future: - Vector old_solution; - Vector current_solution; - Vector predictor; - - Vector right_hand_side; - - // This final set of member variables - // (except for the object holding all - // run-time parameters at the very - // bottom and a screen output stream - // that only prints something if - // verbose output has been requested) - // deals with the inteface we have in - // this program to the Trilinos library - // that provides us with linear - // solvers. Similarly to including - // PETSc matrices in step-17, - // step-18, and step-19, all we - // need to do is to create a Trilinos - // sparse matrix instead of the - // standard deal.II class. The system - // matrix is used for the Jacobian in - // each Newton step. Since we do not - // intend to run this program in - // parallel (which wouldn't be too hard - // with Trilinos data structures, - // though), we don't have to think - // about anything else like - // distributing the degrees of freedom. - TrilinosWrappers::SparseMatrix system_matrix; - - Parameters::AllParameters parameters; - ConditionalOStream verbose_cout; -}; - - - // @sect4{ConservationLaw::ConservationLaw} - // - // There is nothing much to say about - // the constructor. Essentially, it - // reads the input file and fills the - // parameter object with the parsed - // values: -template -ConservationLaw::ConservationLaw (const char *input_filename) - : - mapping (), - fe (FE_Q(1), EulerEquations::n_components), - dof_handler (triangulation), - quadrature (2), - face_quadrature (2), - verbose_cout (std::cout, false) -{ - ParameterHandler prm; - Parameters::AllParameters::declare_parameters (prm); - prm.read_input (input_filename); - parameters.parse_parameters (prm); - - verbose_cout.set_condition (parameters.output == Parameters::Solver::verbose); -} + // @sect3{Conservation law class} + + // Here finally comes the class that + // actually does something with all + // the Euler equation and parameter + // specifics we've defined above. The + // public interface is pretty much + // the same as always (the + // constructor now takes the name of + // a file from which to read + // parameters, which is passed on the + // command line). The private + // function interface is also pretty + // similar to the usual arrangement, + // with the + // assemble_system + // function split into three parts: + // one that contains the main loop + // over all cells and that then calls + // the other two for integrals over + // cells and faces, respectively. + template + class ConservationLaw + { + public: + ConservationLaw (const char *input_filename); + void run (); + + private: + void setup_system (); + + void assemble_system (); + void assemble_cell_term (const FEValues &fe_v, + const std::vector &dofs); + void assemble_face_term (const unsigned int face_no, + const FEFaceValuesBase &fe_v, + const FEFaceValuesBase &fe_v_neighbor, + const std::vector &dofs, + const std::vector &dofs_neighbor, + const bool external_face, + const unsigned int boundary_id, + const double face_diameter); + + std::pair solve (Vector &solution); + + void compute_refinement_indicators (Vector &indicator) const; + void refine_grid (const Vector &indicator); + + void output_results () const; + + + + // The first few member variables + // are also rather standard. Note + // that we define a mapping + // object to be used throughout + // the program when assembling + // terms (we will hand it to + // every FEValues and + // FEFaceValues object); the + // mapping we use is just the + // standard $Q_1$ mapping -- + // nothing fancy, in other words + // -- but declaring one here and + // using it throughout the + // program will make it simpler + // later on to change it if that + // should become necessary. This + // is, in fact, rather pertinent: + // it is known that for + // transsonic simulations with + // the Euler equations, + // computations do not converge + // even as $h\rightarrow 0$ if + // the boundary approximation is + // not of sufficiently high + // order. + Triangulation triangulation; + const MappingQ1 mapping; + + const FESystem fe; + DoFHandler dof_handler; + + const QGauss quadrature; + const QGauss face_quadrature; + + // Next come a number of data + // vectors that correspond to the + // solution of the previous time + // step + // (old_solution), + // the best guess of the current + // solution + // (current_solution; + // we say guess because + // the Newton iteration to + // compute it may not have + // converged yet, whereas + // old_solution + // refers to the fully converged + // final result of the previous + // time step), and a predictor + // for the solution at the next + // time step, computed by + // extrapolating the current and + // previous solution one time + // step into the future: + Vector old_solution; + Vector current_solution; + Vector predictor; + + Vector right_hand_side; + + // This final set of member variables + // (except for the object holding all + // run-time parameters at the very + // bottom and a screen output stream + // that only prints something if + // verbose output has been requested) + // deals with the inteface we have in + // this program to the Trilinos library + // that provides us with linear + // solvers. Similarly to including + // PETSc matrices in step-17, + // step-18, and step-19, all we + // need to do is to create a Trilinos + // sparse matrix instead of the + // standard deal.II class. The system + // matrix is used for the Jacobian in + // each Newton step. Since we do not + // intend to run this program in + // parallel (which wouldn't be too hard + // with Trilinos data structures, + // though), we don't have to think + // about anything else like + // distributing the degrees of freedom. + TrilinosWrappers::SparseMatrix system_matrix; + + Parameters::AllParameters parameters; + ConditionalOStream verbose_cout; + }; - // @sect4{ConservationLaw::setup_system} - // - // The following (easy) function is called - // each time the mesh is changed. All it - // does is to resize the Trilinos matrix - // according to a sparsity pattern that we - // generate as in all the previous tutorial - // programs. -template -void ConservationLaw::setup_system () -{ - CompressedSparsityPattern sparsity_pattern (dof_handler.n_dofs(), - dof_handler.n_dofs()); - DoFTools::make_sparsity_pattern (dof_handler, sparsity_pattern); + // @sect4{ConservationLaw::ConservationLaw} + // + // There is nothing much to say about + // the constructor. Essentially, it + // reads the input file and fills the + // parameter object with the parsed + // values: + template + ConservationLaw::ConservationLaw (const char *input_filename) + : + mapping (), + fe (FE_Q(1), EulerEquations::n_components), + dof_handler (triangulation), + quadrature (2), + face_quadrature (2), + verbose_cout (std::cout, false) + { + ParameterHandler prm; + Parameters::AllParameters::declare_parameters (prm); - system_matrix.reinit (sparsity_pattern); -} + prm.read_input (input_filename); + parameters.parse_parameters (prm); + verbose_cout.set_condition (parameters.output == Parameters::Solver::verbose); + } - // @sect4{ConservationLaw::assemble_system} - // - // This and the following two - // functions are the meat of this - // program: They assemble the linear - // system that results from applying - // Newton's method to the nonlinear - // system of conservation - // equations. - // - // This first function puts all of - // the assembly pieces together in a - // routine that dispatches the - // correct piece for each cell/face. - // The actual implementation of the - // assembly on these objects is done - // in the following functions. - // - // At the top of the function we do the - // usual housekeeping: allocate FEValues, - // FEFaceValues, and FESubfaceValues - // objects necessary to do the integrations - // on cells, faces, and subfaces (in case - // of adjoining cells on different - // refinement levels). Note that we don't - // need all information (like values, - // gradients, or real locations of - // quadrature points) for all of these - // objects, so we only let the FEValues - // classes whatever is actually necessary - // by specifying the minimal set of - // UpdateFlags. For example, when using a - // FEFaceValues object for the neighboring - // cell we only need the shape values: - // Given a specific face, the quadrature - // points and JxW values are - // the same as for the current cells, and - // the normal vectors are known to be the - // negative of the normal vectors of the - // current cell. -template -void ConservationLaw::assemble_system () -{ - const unsigned int dofs_per_cell = dof_handler.get_fe().dofs_per_cell; - - std::vector dof_indices (dofs_per_cell); - std::vector dof_indices_neighbor (dofs_per_cell); - - const UpdateFlags update_flags = update_values - | update_gradients - | update_q_points - | update_JxW_values, - face_update_flags = update_values - | update_q_points - | update_JxW_values - | update_normal_vectors, - neighbor_face_update_flags = update_values; - - FEValues fe_v (mapping, fe, quadrature, - update_flags); - FEFaceValues fe_v_face (mapping, fe, face_quadrature, - face_update_flags); - FESubfaceValues fe_v_subface (mapping, fe, face_quadrature, - face_update_flags); - FEFaceValues fe_v_face_neighbor (mapping, fe, face_quadrature, - neighbor_face_update_flags); - FESubfaceValues fe_v_subface_neighbor (mapping, fe, face_quadrature, - neighbor_face_update_flags); - - // Then loop over all cells, initialize the - // FEValues object for the current cell and - // call the function that assembles the - // problem on this cell. - typename DoFHandler::active_cell_iterator - cell = dof_handler.begin_active(), - endc = dof_handler.end(); - for (; cell!=endc; ++cell) - { - fe_v.reinit (cell); - cell->get_dof_indices (dof_indices); - - assemble_cell_term(fe_v, dof_indices); - - // Then loop over all the faces of this - // cell. If a face is part of the - // external boundary, then assemble - // boundary conditions there (the fifth - // argument to - // assemble_face_terms - // indicates whether we are working on - // an external or internal face; if it - // is an external face, the fourth - // argument denoting the degrees of - // freedom indices of the neighbor is - // ignored, so we pass an empty - // vector): - for (unsigned int face_no=0; face_no::faces_per_cell; - ++face_no) - if (cell->at_boundary(face_no)) - { - fe_v_face.reinit (cell, face_no); - assemble_face_term (face_no, fe_v_face, - fe_v_face, - dof_indices, - std::vector(), - true, - cell->face(face_no)->boundary_indicator(), - cell->face(face_no)->diameter()); - } - // The alternative is that we are - // dealing with an internal face. There - // are two cases that we need to - // distinguish: that this is a normal - // face between two cells at the same - // refinement level, and that it is a - // face between two cells of the - // different refinement levels. - // - // In the first case, there is nothing - // we need to do: we are using a - // continuous finite element, and face - // terms do not appear in the bilinear - // form in this case. The second case - // usually does not lead to face terms - // either if we enforce hanging node - // constraints strongly (as in all - // previous tutorial programs so far - // whenever we used continuous finite - // elements -- this enforcement is done - // by the ConstraintMatrix class - // together with - // DoFTools::make_hanging_node_constraints). In - // the current program, however, we opt - // to enforce continuity weakly at - // faces between cells of different - // refinement level, for two reasons: - // (i) because we can, and more - // importantly (ii) because we would - // have to thread the automatic - // differentiation we use to compute - // the elements of the Newton matrix - // from the residual through the - // operations of the ConstraintMatrix - // class. This would be possible, but - // is not trivial, and so we choose - // this alternative approach. - // - // What needs to be decided is which - // side of an interface between two - // cells of different refinement level - // we are sitting on. - // - // Let's take the case where the - // neighbor is more refined first. We - // then have to loop over the children - // of the face of the current cell and - // integrate on each of them. We - // sprinkle a couple of assertions into - // the code to ensure that our - // reasoning trying to figure out which - // of the neighbor's children's faces - // coincides with a given subface of - // the current cell's faces is correct - // -- a bit of defensive programming - // never hurts. - // - // We then call the function that - // integrates over faces; since this is - // an internal face, the fifth argument - // is false, and the sixth one is - // ignored so we pass an invalid value - // again: - else - { - if (cell->neighbor(face_no)->has_children()) - { - const unsigned int neighbor2= - cell->neighbor_of_neighbor(face_no); - - for (unsigned int subface_no=0; - subface_no < cell->face(face_no)->n_children(); - ++subface_no) - { - const typename DoFHandler::active_cell_iterator - neighbor_child - = cell->neighbor_child_on_subface (face_no, subface_no); - - Assert (neighbor_child->face(neighbor2) == - cell->face(face_no)->child(subface_no), - ExcInternalError()); - Assert (neighbor_child->has_children() == false, - ExcInternalError()); - - fe_v_subface.reinit (cell, face_no, subface_no); - fe_v_face_neighbor.reinit (neighbor_child, neighbor2); - - neighbor_child->get_dof_indices (dof_indices_neighbor); - - assemble_face_term (face_no, fe_v_subface, - fe_v_face_neighbor, - dof_indices, - dof_indices_neighbor, - false, - numbers::invalid_unsigned_int, - neighbor_child->face(neighbor2)->diameter()); - } - } - // The other possibility we have - // to care for is if the neighbor - // is coarser than the current - // cell (in particular, because - // of the usual restriction of - // only one hanging node per - // face, the neighbor must be - // exactly one level coarser than - // the current cell, something - // that we check with an - // assertion). Again, we then - // integrate over this interface: - else if (cell->neighbor(face_no)->level() != cell->level()) - { - const typename DoFHandler::cell_iterator - neighbor = cell->neighbor(face_no); - Assert(neighbor->level() == cell->level()-1, - ExcInternalError()); - - neighbor->get_dof_indices (dof_indices_neighbor); - - const std::pair - faceno_subfaceno = cell->neighbor_of_coarser_neighbor(face_no); - const unsigned int neighbor_face_no = faceno_subfaceno.first, - neighbor_subface_no = faceno_subfaceno.second; - - Assert (neighbor->neighbor_child_on_subface (neighbor_face_no, - neighbor_subface_no) - == cell, - ExcInternalError()); - - fe_v_face.reinit (cell, face_no); - fe_v_subface_neighbor.reinit (neighbor, - neighbor_face_no, - neighbor_subface_no); - - assemble_face_term (face_no, fe_v_face, - fe_v_subface_neighbor, - dof_indices, - dof_indices_neighbor, - false, - numbers::invalid_unsigned_int, - cell->face(face_no)->diameter()); - } - } - } + // @sect4{ConservationLaw::setup_system} + // + // The following (easy) function is called + // each time the mesh is changed. All it + // does is to resize the Trilinos matrix + // according to a sparsity pattern that we + // generate as in all the previous tutorial + // programs. + template + void ConservationLaw::setup_system () + { + CompressedSparsityPattern sparsity_pattern (dof_handler.n_dofs(), + dof_handler.n_dofs()); + DoFTools::make_sparsity_pattern (dof_handler, sparsity_pattern); - // After all this assembling, notify the - // Trilinos matrix object that the matrix - // is done: - system_matrix.compress(); -} + system_matrix.reinit (sparsity_pattern); + } - // @sect4{ConservationLaw::assemble_cell_term} - // - // This function assembles the cell term by - // computing the cell part of the residual, - // adding its negative to the right hand side - // vector, and adding its derivative with - // respect to the local variables to the - // Jacobian (i.e. the Newton matrix). Recall - // that the cell contributions to the - // residual read $F_i = - // \left(\frac{\mathbf{w}_{n+1} - - // \mathbf{w}_n}{\delta - // t},\mathbf{z}_i\right)_K - - // \left(\mathbf{F}(\tilde{\mathbf{w}}), - // \nabla\mathbf{z}_i\right)_K + - // h^{\eta}(\nabla \mathbf{w} , \nabla - // \mathbf{z}_i)_K - - // (\mathbf{G}(\tilde{\mathbf w}), - // \mathbf{z}_i)_K$ where $\tilde{\mathbf w}$ - // is represented by the variable - // W_theta, $\mathbf{z}_i$ is - // the $i$th test function, and the scalar - // product - // $\left(\mathbf{F}(\tilde{\mathbf{w}}), - // \nabla\mathbf{z}\right)_K$ is understood - // as $\int_K - // \sum_{c=1}^{\text{n\_components}} - // \sum_{d=1}^{\text{dim}} - // \mathbf{F}(\tilde{\mathbf{w}})_{cd} - // \frac{\partial z_c}{x_d}$. - // - // At the top of this function, we do the - // usual housekeeping in terms of allocating - // some local variables that we will need - // later. In particular, we will allocate - // variables that will hold the values of the - // current solution $W_{n+1}^k$ after the - // $k$th Newton iteration (variable - // W), the previous time step's - // solution $W_{n}$ (variable - // W_old), as well as the linear - // combination $\theta W_{n+1}^k + - // (1-\theta)W_n$ that results from choosing - // different time stepping schemes (variable - // W_theta). - // - // In addition to these, we need the - // gradients of the current variables. It is - // a bit of a shame that we have to compute - // these; we almost don't. The nice thing - // about a simple conservation law is that - // the flux doesn't generally involve any - // gradients. We do need these, however, for - // the diffusion stabilization. - // - // The actual format in which we store these - // variables requires some - // explanation. First, we need values at each - // quadrature point for each of the - // EulerEquations::n_components - // components of the solution vector. This - // makes for a two-dimensional table for - // which we use deal.II's Table class (this - // is more efficient than - // std::vector@ - // @> because it only needs to - // allocate memory once, rather than once for - // each element of the outer - // vector). Similarly, the gradient is a - // three-dimensional table, which the Table - // class also supports. - // - // Secondly, we want to use automatic - // differentiation. To this end, we use the - // Sacado::Fad::DFad template for everything - // that is a computed from the variables with - // respect to which we would like to compute - // derivatives. This includes the current - // solution and gradient at the quadrature - // points (which are linear combinations of - // the degrees of freedom) as well as - // everything that is computed from them such - // as the residual, but not the previous time - // step's solution. These variables are all - // found in the first part of the function, - // along with a variable that we will use to - // store the derivatives of a single - // component of the residual: -template -void -ConservationLaw:: -assemble_cell_term (const FEValues &fe_v, - const std::vector &dof_indices) -{ - const unsigned int dofs_per_cell = fe_v.dofs_per_cell; - const unsigned int n_q_points = fe_v.n_quadrature_points; - - Table<2,Sacado::Fad::DFad > - W (n_q_points, EulerEquations::n_components); - - Table<2,double> - W_old (n_q_points, EulerEquations::n_components); - - Table<2,Sacado::Fad::DFad > - W_theta (n_q_points, EulerEquations::n_components); - - Table<3,Sacado::Fad::DFad > - grad_W (n_q_points, EulerEquations::n_components, dim); - - std::vector residual_derivatives (dofs_per_cell); - - // Next, we have to define the independent - // variables that we will try to determine - // by solving a Newton step. These - // independent variables are the values of - // the local degrees of freedom which we - // extract here: - std::vector > independent_local_dof_values(dofs_per_cell); - for (unsigned int i=0; iindependent_local_dof_values[i] - // as the $i$th independent variable out of - // a total of dofs_per_cell: - for (unsigned int i=0; iW, - // W_old, - // W_theta, and - // grad_W, which we can - // compute from the local DoF values by - // using the formula $W(x_q)=\sum_i \mathbf - // W_i \Phi_i(x_q)$, where $\mathbf W_i$ is - // the $i$th entry of the (local part of - // the) solution vector, and $\Phi_i(x_q)$ - // the value of the $i$th vector-valued - // shape function evaluated at quadrature - // point $x_q$. The gradient can be - // computed in a similar way. + // This and the following two + // functions are the meat of this + // program: They assemble the linear + // system that results from applying + // Newton's method to the nonlinear + // system of conservation + // equations. // - // Ideally, we could compute this - // information using a call into something - // like FEValues::get_function_values and - // FEValues::get_function_grads, but since - // (i) we would have to extend the FEValues - // class for this, and (ii) we don't want - // to make the entire - // old_solution vector fad - // types, only the local cell variables, we - // explicitly code the loop above. Before - // this, we add another loop that - // initializes all the fad variables to - // zero: - for (unsigned int q=0; q::n_components; ++c) + // This first function puts all of + // the assembly pieces together in a + // routine that dispatches the + // correct piece for each cell/face. + // The actual implementation of the + // assembly on these objects is done + // in the following functions. + // + // At the top of the function we do the + // usual housekeeping: allocate FEValues, + // FEFaceValues, and FESubfaceValues + // objects necessary to do the integrations + // on cells, faces, and subfaces (in case + // of adjoining cells on different + // refinement levels). Note that we don't + // need all information (like values, + // gradients, or real locations of + // quadrature points) for all of these + // objects, so we only let the FEValues + // classes whatever is actually necessary + // by specifying the minimal set of + // UpdateFlags. For example, when using a + // FEFaceValues object for the neighboring + // cell we only need the shape values: + // Given a specific face, the quadrature + // points and JxW values are + // the same as for the current cells, and + // the normal vectors are known to be the + // negative of the normal vectors of the + // current cell. + template + void ConservationLaw::assemble_system () + { + const unsigned int dofs_per_cell = dof_handler.get_fe().dofs_per_cell; + + std::vector dof_indices (dofs_per_cell); + std::vector dof_indices_neighbor (dofs_per_cell); + + const UpdateFlags update_flags = update_values + | update_gradients + | update_q_points + | update_JxW_values, + face_update_flags = update_values + | update_q_points + | update_JxW_values + | update_normal_vectors, + neighbor_face_update_flags = update_values; + + FEValues fe_v (mapping, fe, quadrature, + update_flags); + FEFaceValues fe_v_face (mapping, fe, face_quadrature, + face_update_flags); + FESubfaceValues fe_v_subface (mapping, fe, face_quadrature, + face_update_flags); + FEFaceValues fe_v_face_neighbor (mapping, fe, face_quadrature, + neighbor_face_update_flags); + FESubfaceValues fe_v_subface_neighbor (mapping, fe, face_quadrature, + neighbor_face_update_flags); + + // Then loop over all cells, initialize the + // FEValues object for the current cell and + // call the function that assembles the + // problem on this cell. + typename DoFHandler::active_cell_iterator + cell = dof_handler.begin_active(), + endc = dof_handler.end(); + for (; cell!=endc; ++cell) { - W[q][c] = 0; - W_old[q][c] = 0; - W_theta[q][c] = 0; - for (unsigned int d=0; dget_dof_indices (dof_indices); + + assemble_cell_term(fe_v, dof_indices); + + // Then loop over all the faces of this + // cell. If a face is part of the + // external boundary, then assemble + // boundary conditions there (the fifth + // argument to + // assemble_face_terms + // indicates whether we are working on + // an external or internal face; if it + // is an external face, the fourth + // argument denoting the degrees of + // freedom indices of the neighbor is + // ignored, so we pass an empty + // vector): + for (unsigned int face_no=0; face_no::faces_per_cell; + ++face_no) + if (cell->at_boundary(face_no)) + { + fe_v_face.reinit (cell, face_no); + assemble_face_term (face_no, fe_v_face, + fe_v_face, + dof_indices, + std::vector(), + true, + cell->face(face_no)->boundary_indicator(), + cell->face(face_no)->diameter()); + } - for (unsigned int q=0; qneighbor(face_no)->has_children()) + { + const unsigned int neighbor2= + cell->neighbor_of_neighbor(face_no); + + for (unsigned int subface_no=0; + subface_no < cell->face(face_no)->n_children(); + ++subface_no) + { + const typename DoFHandler::active_cell_iterator + neighbor_child + = cell->neighbor_child_on_subface (face_no, subface_no); + + Assert (neighbor_child->face(neighbor2) == + cell->face(face_no)->child(subface_no), + ExcInternalError()); + Assert (neighbor_child->has_children() == false, + ExcInternalError()); + + fe_v_subface.reinit (cell, face_no, subface_no); + fe_v_face_neighbor.reinit (neighbor_child, neighbor2); + + neighbor_child->get_dof_indices (dof_indices_neighbor); + + assemble_face_term (face_no, fe_v_subface, + fe_v_face_neighbor, + dof_indices, + dof_indices_neighbor, + false, + numbers::invalid_unsigned_int, + neighbor_child->face(neighbor2)->diameter()); + } + } - for (unsigned int d = 0; d < dim; d++) - grad_W[q][c][d] += independent_local_dof_values[i] * - fe_v.shape_grad_component(i, q, c)[d]; + // The other possibility we have + // to care for is if the neighbor + // is coarser than the current + // cell (in particular, because + // of the usual restriction of + // only one hanging node per + // face, the neighbor must be + // exactly one level coarser than + // the current cell, something + // that we check with an + // assertion). Again, we then + // integrate over this interface: + else if (cell->neighbor(face_no)->level() != cell->level()) + { + const typename DoFHandler::cell_iterator + neighbor = cell->neighbor(face_no); + Assert(neighbor->level() == cell->level()-1, + ExcInternalError()); + + neighbor->get_dof_indices (dof_indices_neighbor); + + const std::pair + faceno_subfaceno = cell->neighbor_of_coarser_neighbor(face_no); + const unsigned int neighbor_face_no = faceno_subfaceno.first, + neighbor_subface_no = faceno_subfaceno.second; + + Assert (neighbor->neighbor_child_on_subface (neighbor_face_no, + neighbor_subface_no) + == cell, + ExcInternalError()); + + fe_v_face.reinit (cell, face_no); + fe_v_subface_neighbor.reinit (neighbor, + neighbor_face_no, + neighbor_subface_no); + + assemble_face_term (face_no, fe_v_face, + fe_v_subface_neighbor, + dof_indices, + dof_indices_neighbor, + false, + numbers::invalid_unsigned_int, + cell->face(face_no)->diameter()); + } + } } - - // Next, in order to compute the cell - // contributions, we need to evaluate - // $F(\tilde{\mathbf w})$ and - // $G(\tilde{\mathbf w})$ at all quadrature - // points. To store these, we also need to - // allocate a bit of memory. Note that we - // compute the flux matrices and right hand - // sides in terms of autodifferentiation - // variables, so that the Jacobian - // contributions can later easily be - // computed from it: - typedef Sacado::Fad::DFad FluxMatrix[EulerEquations::n_components][dim]; - FluxMatrix *flux = new FluxMatrix[n_q_points]; - - typedef Sacado::Fad::DFad ForcingVector[EulerEquations::n_components]; - ForcingVector *forcing = new ForcingVector[n_q_points]; - - for (unsigned int q=0; q::compute_flux_matrix (W_theta[q], flux[q]); - EulerEquations::compute_forcing_vector (W_theta[q], forcing[q]); - } + // After all this assembling, notify the + // Trilinos matrix object that the matrix + // is done: + system_matrix.compress(); + } - // We now have all of the pieces in place, - // so perform the assembly. We have an - // outer loop through the components of the - // system, and an inner loop over the - // quadrature points, where we accumulate - // contributions to the $i$th residual - // $F_i$. The general formula for this - // residual is given in the introduction - // and at the top of this function. We can, - // however, simplify it a bit taking into - // account that the $i$th (vector-valued) - // test function $\mathbf{z}_i$ has in - // reality only a single nonzero component - // (more on this topic can be found in the - // @ref vector_valued module). It will be - // represented by the variable - // component_i below. With - // this, the residual term can be - // re-written as $F_i = - // \left(\frac{(\mathbf{w}_{n+1} - - // \mathbf{w}_n)_{\text{component\_i}}}{\delta - // t},(\mathbf{z}_i)_{\text{component\_i}}\right)_K$ - // $- \sum_{d=1}^{\text{dim}} - // \left(\mathbf{F} - // (\tilde{\mathbf{w}})_{\text{component\_i},d}, - // \frac{\partial(\mathbf{z}_i)_{\text{component\_i}}} - // {\partial x_d}\right)_K$ $+ - // \sum_{d=1}^{\text{dim}} h^{\eta} - // \left(\frac{\partial - // \mathbf{w}_{\text{component\_i}}}{\partial - // x_d} , \frac{\partial - // (\mathbf{z}_i)_{\text{component\_i}}}{\partial - // x_d} \right)_K$ - // $-(\mathbf{G}(\tilde{\mathbf{w}} - // )_{\text{component\_i}}, - // (\mathbf{z}_i)_{\text{component\_i}})_K$, - // where integrals are understood to be - // evaluated through summation over - // quadrature points. + // @sect4{ConservationLaw::assemble_cell_term} // - // We initialy sum all contributions of the - // residual in the positive sense, so that - // we don't need to negative the Jacobian - // entries. Then, when we sum into the - // right_hand_side vector, - // we negate this residual. - for (unsigned int i=0; i F_i = 0; - - const unsigned int - component_i = fe_v.get_fe().system_to_component_index(i).first; - - // The residual for each row (i) will be accumulating - // into this fad variable. At the end of the assembly - // for this row, we will query for the sensitivities - // to this variable and add them into the Jacobian. - - for (unsigned int point=0; pointW_theta, $\mathbf{z}_i$ is + // the $i$th test function, and the scalar + // product + // $\left(\mathbf{F}(\tilde{\mathbf{w}}), + // \nabla\mathbf{z}\right)_K$ is understood + // as $\int_K + // \sum_{c=1}^{\text{n\_components}} + // \sum_{d=1}^{\text{dim}} + // \mathbf{F}(\tilde{\mathbf{w}})_{cd} + // \frac{\partial z_c}{x_d}$. + // + // At the top of this function, we do the + // usual housekeeping in terms of allocating + // some local variables that we will need + // later. In particular, we will allocate + // variables that will hold the values of the + // current solution $W_{n+1}^k$ after the + // $k$th Newton iteration (variable + // W), the previous time step's + // solution $W_{n}$ (variable + // W_old), as well as the linear + // combination $\theta W_{n+1}^k + + // (1-\theta)W_n$ that results from choosing + // different time stepping schemes (variable + // W_theta). + // + // In addition to these, we need the + // gradients of the current variables. It is + // a bit of a shame that we have to compute + // these; we almost don't. The nice thing + // about a simple conservation law is that + // the flux doesn't generally involve any + // gradients. We do need these, however, for + // the diffusion stabilization. + // + // The actual format in which we store these + // variables requires some + // explanation. First, we need values at each + // quadrature point for each of the + // EulerEquations::n_components + // components of the solution vector. This + // makes for a two-dimensional table for + // which we use deal.II's Table class (this + // is more efficient than + // std::vector@ + // @> because it only needs to + // allocate memory once, rather than once for + // each element of the outer + // vector). Similarly, the gradient is a + // three-dimensional table, which the Table + // class also supports. + // + // Secondly, we want to use automatic + // differentiation. To this end, we use the + // Sacado::Fad::DFad template for everything + // that is a computed from the variables with + // respect to which we would like to compute + // derivatives. This includes the current + // solution and gradient at the quadrature + // points (which are linear combinations of + // the degrees of freedom) as well as + // everything that is computed from them such + // as the residual, but not the previous time + // step's solution. These variables are all + // found in the first part of the function, + // along with a variable that we will use to + // store the derivatives of a single + // component of the residual: + template + void + ConservationLaw:: + assemble_cell_term (const FEValues &fe_v, + const std::vector &dof_indices) + { + const unsigned int dofs_per_cell = fe_v.dofs_per_cell; + const unsigned int n_q_points = fe_v.n_quadrature_points; - for (unsigned int d=0; d > + W (n_q_points, EulerEquations::n_components); - for (unsigned int d=0; ddiameter(), - parameters.diffusion_power) * - grad_W[point][component_i][d] * - fe_v.shape_grad_component(i, point, component_i)[d] * - fe_v.JxW(point); + Table<2,double> + W_old (n_q_points, EulerEquations::n_components); - F_i -= forcing[point][component_i] * - fe_v.shape_value_component(i, point, component_i) * - fe_v.JxW(point); - } + Table<2,Sacado::Fad::DFad > + W_theta (n_q_points, EulerEquations::n_components); - // At the end of the loop, we have to - // add the sensitivities to the - // matrix and subtract the residual - // from the right hand side. Trilinos - // FAD data type gives us access to - // the derivatives using - // F_i.fastAccessDx(k), - // so we store the data in a - // temporary array. This information - // about the whole row of local dofs - // is then added to the Trilinos - // matrix at once (which supports the - // data types we have chosen). - for (unsigned int k=0; k > + grad_W (n_q_points, EulerEquations::n_components, dim); - delete[] forcing; - delete[] flux; -} + std::vector residual_derivatives (dofs_per_cell); + // Next, we have to define the independent + // variables that we will try to determine + // by solving a Newton step. These + // independent variables are the values of + // the local degrees of freedom which we + // extract here: + std::vector > independent_local_dof_values(dofs_per_cell); + for (unsigned int i=0; i -void -ConservationLaw::assemble_face_term(const unsigned int face_no, - const FEFaceValuesBase &fe_v, - const FEFaceValuesBase &fe_v_neighbor, - const std::vector &dof_indices, - const std::vector &dof_indices_neighbor, - const bool external_face, - const unsigned int boundary_id, - const double face_diameter) -{ - const unsigned int n_q_points = fe_v.n_quadrature_points; - const unsigned int dofs_per_cell = fe_v.dofs_per_cell; + // The next step incorporates all the + // magic: we declare a subset of the + // autodifferentiation variables as + // independent degrees of freedom, whereas + // all the other ones remain dependent + // functions. These are precisely the local + // degrees of freedom just extracted. All + // calculations that reference them (either + // directly or indirectly) will accumulate + // sensitivies with respect to these + // variables. + // + // In order to mark the variables as + // independent, the following does the + // trick, marking + // independent_local_dof_values[i] + // as the $i$th independent variable out of + // a total of dofs_per_cell: + for (unsigned int i=0; iW, + // W_old, + // W_theta, and + // grad_W, which we can + // compute from the local DoF values by + // using the formula $W(x_q)=\sum_i \mathbf + // W_i \Phi_i(x_q)$, where $\mathbf W_i$ is + // the $i$th entry of the (local part of + // the) solution vector, and $\Phi_i(x_q)$ + // the value of the $i$th vector-valued + // shape function evaluated at quadrature + // point $x_q$. The gradient can be + // computed in a similar way. + // + // Ideally, we could compute this + // information using a call into something + // like FEValues::get_function_values and + // FEValues::get_function_grads, but since + // (i) we would have to extend the FEValues + // class for this, and (ii) we don't want + // to make the entire + // old_solution vector fad + // types, only the local cell variables, we + // explicitly code the loop above. Before + // this, we add another loop that + // initializes all the fad variables to + // zero: + for (unsigned int q=0; q::n_components; ++c) + { + W[q][c] = 0; + W_old[q][c] = 0; + W_theta[q][c] = 0; + for (unsigned int d=0; d > - independent_local_dof_values (dofs_per_cell), - independent_neighbor_dof_values (external_face == false ? - dofs_per_cell : - 0); + for (unsigned int q=0; q FluxMatrix[EulerEquations::n_components][dim]; + FluxMatrix *flux = new FluxMatrix[n_q_points]; + + typedef Sacado::Fad::DFad ForcingVector[EulerEquations::n_components]; + ForcingVector *forcing = new ForcingVector[n_q_points]; + + for (unsigned int q=0; q::compute_flux_matrix (W_theta[q], flux[q]); + EulerEquations::compute_forcing_vector (W_theta[q], forcing[q]); } - // Next, we need to define the values of - // the conservative variables $\tilde - // {\mathbf W}$ on this side of the face - // ($\tilde {\mathbf W}^+$) and on the - // opposite side ($\tilde {\mathbf - // W}^-$). The former can be computed in - // exactly the same way as in the previous - // function, but note that the - // fe_v variable now is of - // type FEFaceValues or FESubfaceValues: - Table<2,Sacado::Fad::DFad > - Wplus (n_q_points, EulerEquations::n_components), - Wminus (n_q_points, EulerEquations::n_components); - - for (unsigned int q=0; qcomponent_i below. With + // this, the residual term can be + // re-written as $F_i = + // \left(\frac{(\mathbf{w}_{n+1} - + // \mathbf{w}_n)_{\text{component\_i}}}{\delta + // t},(\mathbf{z}_i)_{\text{component\_i}}\right)_K$ + // $- \sum_{d=1}^{\text{dim}} + // \left(\mathbf{F} + // (\tilde{\mathbf{w}})_{\text{component\_i},d}, + // \frac{\partial(\mathbf{z}_i)_{\text{component\_i}}} + // {\partial x_d}\right)_K$ $+ + // \sum_{d=1}^{\text{dim}} h^{\eta} + // \left(\frac{\partial + // \mathbf{w}_{\text{component\_i}}}{\partial + // x_d} , \frac{\partial + // (\mathbf{z}_i)_{\text{component\_i}}}{\partial + // x_d} \right)_K$ + // $-(\mathbf{G}(\tilde{\mathbf{w}} + // )_{\text{component\_i}}, + // (\mathbf{z}_i)_{\text{component\_i}})_K$, + // where integrals are understood to be + // evaluated through summation over + // quadrature points. + // + // We initialy sum all contributions of the + // residual in the positive sense, so that + // we don't need to negative the Jacobian + // entries. Then, when we sum into the + // right_hand_side vector, + // we negate this residual. + for (unsigned int i=0; i::max_n_boundaries, - ExcIndexRange (boundary_id, 0, - Parameters::AllParameters::max_n_boundaries)); - - std::vector > - boundary_values(n_q_points, Vector(EulerEquations::n_components)); - parameters.boundary_conditions[boundary_id] - .values.vector_value_list(fe_v.get_quadrature_points(), - boundary_values); - - for (unsigned int q = 0; q < n_q_points; q++) - EulerEquations::compute_Wminus (parameters.boundary_conditions[boundary_id].kind, - fe_v.normal_vector(q), - Wplus[q], - boundary_values[q], - Wminus[q]); - } - - - // Now that we have $\mathbf w^+$ and - // $\mathbf w^-$, we can go about computing - // the numerical flux function $\mathbf - // H(\mathbf w^+,\mathbf w^-, \mathbf n)$ - // for each quadrature point. Before - // calling the function that does so, we - // also need to determine the - // Lax-Friedrich's stability parameter: - typedef Sacado::Fad::DFad NormalFlux[EulerEquations::n_components]; - NormalFlux *normal_fluxes = new NormalFlux[n_q_points]; - - double alpha; + Sacado::Fad::DFad F_i = 0; - switch(parameters.stabilization_kind) - { - case Parameters::Flux::constant: - alpha = parameters.stabilization_value; - break; - case Parameters::Flux::mesh_dependent: - alpha = face_diameter/(2.0*parameters.time_step); - break; - default: - Assert (false, ExcNotImplemented()); - alpha = 1; - } + const unsigned int + component_i = fe_v.get_fe().system_to_component_index(i).first; - for (unsigned int q=0; q::numerical_normal_flux(fe_v.normal_vector(q), - Wplus[q], Wminus[q], alpha, - normal_fluxes[q]); - - // Now assemble the face term in exactly - // the same way as for the cell - // contributions in the previous - // function. The only difference is that if - // this is an internal face, we also have - // to take into account the sensitivies of - // the residual contributions to the - // degrees of freedom on the neighboring - // cell: - std::vector residual_derivatives (dofs_per_cell); - for (unsigned int i=0; i F_i = 0; + // The residual for each row (i) will be accumulating + // into this fad variable. At the end of the assembly + // for this row, we will query for the sensitivities + // to this variable and add them into the Jacobian. - for (unsigned int point=0; pointdiameter(), + parameters.diffusion_power) * + grad_W[point][component_i][d] * + fe_v.shape_grad_component(i, point, component_i)[d] * + fe_v.JxW(point); + + F_i -= forcing[point][component_i] * fe_v.shape_value_component(i, point, component_i) * fe_v.JxW(point); } + // At the end of the loop, we have to + // add the sensitivities to the + // matrix and subtract the residual + // from the right hand side. Trilinos + // FAD data type gives us access to + // the derivatives using + // F_i.fastAccessDx(k), + // so we store the data in a + // temporary array. This information + // about the whole row of local dofs + // is then added to the Trilinos + // matrix at once (which supports the + // data types we have chosen). for (unsigned int k=0; k -std::pair -ConservationLaw::solve (Vector &newton_update) -{ - switch (parameters.solver) - { - // If the parameter file specified - // that a direct solver shall be - // used, then we'll get here. The - // process is straightforward, since - // deal.II provides a wrapper class - // to the Amesos direct solver within - // Trilinos. All we have to do is to - // create a solver control object - // (which is just a dummy object - // here, since we won't perform any - // iterations), and then create the - // direct solver object. When - // actually doing the solve, note - // that we don't pass a - // preconditioner. That wouldn't make - // much sense for a direct solver - // anyway. At the end we return the - // solver control statistics — - // which will tell that no iterations - // have been performed and that the - // final linear residual is zero, - // absent any better information that - // may be provided here: - case Parameters::Solver::direct: - { - SolverControl solver_control (1,0); - TrilinosWrappers::SolverDirect direct (solver_control, - parameters.output == - Parameters::Solver::verbose); + // @sect4{ConservationLaw::assemble_face_term} + // + // Here, we do essentially the same as in the + // previous function. t the top, we introduce + // the independent variables. Because the + // current function is also used if we are + // working on an internal face between two + // cells, the independent variables are not + // only the degrees of freedom on the current + // cell but in the case of an interior face + // also the ones on the neighbor. + template + void + ConservationLaw::assemble_face_term(const unsigned int face_no, + const FEFaceValuesBase &fe_v, + const FEFaceValuesBase &fe_v_neighbor, + const std::vector &dof_indices, + const std::vector &dof_indices_neighbor, + const bool external_face, + const unsigned int boundary_id, + const double face_diameter) + { + const unsigned int n_q_points = fe_v.n_quadrature_points; + const unsigned int dofs_per_cell = fe_v.dofs_per_cell; - direct.solve (system_matrix, newton_update, right_hand_side); + std::vector > + independent_local_dof_values (dofs_per_cell), + independent_neighbor_dof_values (external_face == false ? + dofs_per_cell : + 0); - return std::pair (solver_control.last_step(), - solver_control.last_value()); - } + const unsigned int n_independent_variables = (external_face == false ? + 2 * dofs_per_cell : + dofs_per_cell); - // Likewise, if we are to use an - // iterative solver, we use Aztec's - // GMRES solver. We could use the - // Trilinos wrapper classes for - // iterative solvers and - // preconditioners here as well, but - // we choose to use an Aztec solver - // directly. For the given problem, - // Aztec's internal preconditioner - // implementations are superior over - // the ones deal.II has wrapper - // classes to, so we use ILU-T - // preconditioning within the AztecOO - // solver and set a bunch of options - // that can be changed from the - // parameter file. - // - // There are two more practicalities: - // Since we have built our right hand - // side and solution vector as - // deal.II Vector objects (as opposed - // to the matrix, which is a Trilinos - // object), we must hand the solvers - // Trilinos Epetra vectors. Luckily, - // they support the concept of a - // 'view', so we just send in a - // pointer to our deal.II vectors. We - // have to provide an Epetra_Map for - // the vector that sets the parallel - // distribution, which is just a - // dummy object in serial. The - // easiest way is to ask the matrix - // for its map, and we're going to be - // ready for matrix-vector products - // with it. - // - // Secondly, the Aztec solver wants - // us to pass a Trilinos - // Epetra_CrsMatrix in, not the - // deal.II wrapper class itself. So - // we access to the actual Trilinos - // matrix in the Trilinos wrapper - // class by the command - // trilinos_matrix(). Trilinos wants - // the matrix to be non-constant, so - // we have to manually remove the - // constantness using a const_cast. - case Parameters::Solver::gmres: + for (unsigned int i = 0; i < dofs_per_cell; i++) { - Epetra_Vector x(View, system_matrix.domain_partitioner(), - newton_update.begin()); - Epetra_Vector b(View, system_matrix.range_partitioner(), - right_hand_side.begin()); - - AztecOO solver; - solver.SetAztecOption(AZ_output, - (parameters.output == - Parameters::Solver::quiet - ? - AZ_none - : - AZ_all)); - solver.SetAztecOption(AZ_solver, AZ_gmres); - solver.SetRHS(&b); - solver.SetLHS(&x); - - solver.SetAztecOption(AZ_precond, AZ_dom_decomp); - solver.SetAztecOption(AZ_subdomain_solve, AZ_ilut); - solver.SetAztecOption(AZ_overlap, 0); - solver.SetAztecOption(AZ_reorder, 0); + independent_local_dof_values[i] = current_solution(dof_indices[i]); + independent_local_dof_values[i].diff(i, n_independent_variables); + } - solver.SetAztecParam(AZ_drop, parameters.ilut_drop); - solver.SetAztecParam(AZ_ilut_fill, parameters.ilut_fill); - solver.SetAztecParam(AZ_athresh, parameters.ilut_atol); - solver.SetAztecParam(AZ_rthresh, parameters.ilut_rtol); + if (external_face == false) + for (unsigned int i = 0; i < dofs_per_cell; i++) + { + independent_neighbor_dof_values[i] + = current_solution(dof_indices_neighbor[i]); + independent_neighbor_dof_values[i] + .diff(i+dofs_per_cell, n_independent_variables); + } - solver.SetUserMatrix(const_cast - (&system_matrix.trilinos_matrix())); - solver.Iterate(parameters.max_iterations, parameters.linear_residual); + // Next, we need to define the values of + // the conservative variables $\tilde + // {\mathbf W}$ on this side of the face + // ($\tilde {\mathbf W}^+$) and on the + // opposite side ($\tilde {\mathbf + // W}^-$). The former can be computed in + // exactly the same way as in the previous + // function, but note that the + // fe_v variable now is of + // type FEFaceValues or FESubfaceValues: + Table<2,Sacado::Fad::DFad > + Wplus (n_q_points, EulerEquations::n_components), + Wminus (n_q_points, EulerEquations::n_components); + + for (unsigned int q=0; q (solver.NumIters(), - solver.TrueResidual()); + // Computing $\tilde {\mathbf W}^-$ is a + // bit more complicated. If this is an + // internal face, we can compute it as + // above by simply using the independent + // variables from the neighbor: + if (external_face == false) + { + for (unsigned int q=0; q::max_n_boundaries, + ExcIndexRange (boundary_id, 0, + Parameters::AllParameters::max_n_boundaries)); + + std::vector > + boundary_values(n_q_points, Vector(EulerEquations::n_components)); + parameters.boundary_conditions[boundary_id] + .values.vector_value_list(fe_v.get_quadrature_points(), + boundary_values); + + for (unsigned int q = 0; q < n_q_points; q++) + EulerEquations::compute_Wminus (parameters.boundary_conditions[boundary_id].kind, + fe_v.normal_vector(q), + Wplus[q], + boundary_values[q], + Wminus[q]); } - } - Assert (false, ExcNotImplemented()); - return std::pair (0,0); -} + // Now that we have $\mathbf w^+$ and + // $\mathbf w^-$, we can go about computing + // the numerical flux function $\mathbf + // H(\mathbf w^+,\mathbf w^-, \mathbf n)$ + // for each quadrature point. Before + // calling the function that does so, we + // also need to determine the + // Lax-Friedrich's stability parameter: + typedef Sacado::Fad::DFad NormalFlux[EulerEquations::n_components]; + NormalFlux *normal_fluxes = new NormalFlux[n_q_points]; - // @sect4{ConservationLaw::compute_refinement_indicators} + double alpha; - // This function is real simple: We don't - // pretend that we know here what a good - // refinement indicator would be. Rather, we - // assume that the EulerEquation - // class would know about this, and so we - // simply defer to the respective function - // we've implemented there: -template -void -ConservationLaw:: -compute_refinement_indicators (Vector &refinement_indicators) const -{ - EulerEquations::compute_refinement_indicators (dof_handler, - mapping, - predictor, - refinement_indicators); -} + switch(parameters.stabilization_kind) + { + case Parameters::Flux::constant: + alpha = parameters.stabilization_value; + break; + case Parameters::Flux::mesh_dependent: + alpha = face_diameter/(2.0*parameters.time_step); + break; + default: + Assert (false, ExcNotImplemented()); + alpha = 1; + } + for (unsigned int q=0; q::numerical_normal_flux(fe_v.normal_vector(q), + Wplus[q], Wminus[q], alpha, + normal_fluxes[q]); + + // Now assemble the face term in exactly + // the same way as for the cell + // contributions in the previous + // function. The only difference is that if + // this is an internal face, we also have + // to take into account the sensitivies of + // the residual contributions to the + // degrees of freedom on the neighboring + // cell: + std::vector residual_derivatives (dofs_per_cell); + for (unsigned int i=0; i F_i = 0; + for (unsigned int point=0; point -void -ConservationLaw::refine_grid (const Vector &refinement_indicators) -{ - typename DoFHandler::active_cell_iterator - cell = dof_handler.begin_active(), - endc = dof_handler.end(); + for (unsigned int k=0; kclear_coarsen_flag(); - cell->clear_refine_flag(); + if (external_face == false) + { + for (unsigned int k=0; klevel() < parameters.shock_levels) && - (std::fabs(refinement_indicators(cell_no)) > parameters.shock_val)) - cell->set_refine_flag(); - else - if ((cell->level() > 0) && - (std::fabs(refinement_indicators(cell_no)) < 0.75*parameters.shock_val)) - cell->set_coarsen_flag(); - } + right_hand_side(dof_indices[i]) -= F_i.val(); + } - // Then we need to transfer the - // various solution vectors from - // the old to the new grid while we - // do the refinement. The - // SolutionTransfer class is our - // friend here; it has a fairly - // extensive documentation, - // including examples, so we won't - // comment much on the following - // code. The last three lines - // simply re-set the sizes of some - // other vectors to the now correct - // size: - std::vector > transfer_in; - std::vector > transfer_out; + delete[] normal_fluxes; + } - transfer_in.push_back(old_solution); - transfer_in.push_back(predictor); - triangulation.prepare_coarsening_and_refinement(); + // @sect4{ConservationLaw::solve} + // + // Here, we actually solve the linear system, + // using either of Trilinos' Aztec or Amesos + // linear solvers. The result of the + // computation will be written into the + // argument vector passed to this + // function. The result is a pair of number + // of iterations and the final linear + // residual. - SolutionTransfer soltrans(dof_handler); - soltrans.prepare_for_coarsening_and_refinement(transfer_in); + template + std::pair + ConservationLaw::solve (Vector &newton_update) + { + switch (parameters.solver) + { + // If the parameter file specified + // that a direct solver shall be + // used, then we'll get here. The + // process is straightforward, since + // deal.II provides a wrapper class + // to the Amesos direct solver within + // Trilinos. All we have to do is to + // create a solver control object + // (which is just a dummy object + // here, since we won't perform any + // iterations), and then create the + // direct solver object. When + // actually doing the solve, note + // that we don't pass a + // preconditioner. That wouldn't make + // much sense for a direct solver + // anyway. At the end we return the + // solver control statistics — + // which will tell that no iterations + // have been performed and that the + // final linear residual is zero, + // absent any better information that + // may be provided here: + case Parameters::Solver::direct: + { + SolverControl solver_control (1,0); + TrilinosWrappers::SolverDirect direct (solver_control, + parameters.output == + Parameters::Solver::verbose); - triangulation.execute_coarsening_and_refinement (); + direct.solve (system_matrix, newton_update, right_hand_side); - dof_handler.clear(); - dof_handler.distribute_dofs (fe); + return std::pair (solver_control.last_step(), + solver_control.last_value()); + } - { - Vector new_old_solution(1); - Vector new_predictor(1); + // Likewise, if we are to use an + // iterative solver, we use Aztec's + // GMRES solver. We could use the + // Trilinos wrapper classes for + // iterative solvers and + // preconditioners here as well, but + // we choose to use an Aztec solver + // directly. For the given problem, + // Aztec's internal preconditioner + // implementations are superior over + // the ones deal.II has wrapper + // classes to, so we use ILU-T + // preconditioning within the AztecOO + // solver and set a bunch of options + // that can be changed from the + // parameter file. + // + // There are two more practicalities: + // Since we have built our right hand + // side and solution vector as + // deal.II Vector objects (as opposed + // to the matrix, which is a Trilinos + // object), we must hand the solvers + // Trilinos Epetra vectors. Luckily, + // they support the concept of a + // 'view', so we just send in a + // pointer to our deal.II vectors. We + // have to provide an Epetra_Map for + // the vector that sets the parallel + // distribution, which is just a + // dummy object in serial. The + // easiest way is to ask the matrix + // for its map, and we're going to be + // ready for matrix-vector products + // with it. + // + // Secondly, the Aztec solver wants + // us to pass a Trilinos + // Epetra_CrsMatrix in, not the + // deal.II wrapper class itself. So + // we access to the actual Trilinos + // matrix in the Trilinos wrapper + // class by the command + // trilinos_matrix(). Trilinos wants + // the matrix to be non-constant, so + // we have to manually remove the + // constantness using a const_cast. + case Parameters::Solver::gmres: + { + Epetra_Vector x(View, system_matrix.domain_partitioner(), + newton_update.begin()); + Epetra_Vector b(View, system_matrix.range_partitioner(), + right_hand_side.begin()); + + AztecOO solver; + solver.SetAztecOption(AZ_output, + (parameters.output == + Parameters::Solver::quiet + ? + AZ_none + : + AZ_all)); + solver.SetAztecOption(AZ_solver, AZ_gmres); + solver.SetRHS(&b); + solver.SetLHS(&x); + + solver.SetAztecOption(AZ_precond, AZ_dom_decomp); + solver.SetAztecOption(AZ_subdomain_solve, AZ_ilut); + solver.SetAztecOption(AZ_overlap, 0); + solver.SetAztecOption(AZ_reorder, 0); + + solver.SetAztecParam(AZ_drop, parameters.ilut_drop); + solver.SetAztecParam(AZ_ilut_fill, parameters.ilut_fill); + solver.SetAztecParam(AZ_athresh, parameters.ilut_atol); + solver.SetAztecParam(AZ_rthresh, parameters.ilut_rtol); + + solver.SetUserMatrix(const_cast + (&system_matrix.trilinos_matrix())); + + solver.Iterate(parameters.max_iterations, parameters.linear_residual); + + return std::pair (solver.NumIters(), + solver.TrueResidual()); + } + } - transfer_out.push_back(new_old_solution); - transfer_out.push_back(new_predictor); - transfer_out[0].reinit(dof_handler.n_dofs()); - transfer_out[1].reinit(dof_handler.n_dofs()); + Assert (false, ExcNotImplemented()); + return std::pair (0,0); } - soltrans.interpolate(transfer_in, transfer_out); - old_solution.reinit (transfer_out[0].size()); - old_solution = transfer_out[0]; + // @sect4{ConservationLaw::compute_refinement_indicators} - predictor.reinit (transfer_out[1].size()); - predictor = transfer_out[1]; + // This function is real simple: We don't + // pretend that we know here what a good + // refinement indicator would be. Rather, we + // assume that the EulerEquation + // class would know about this, and so we + // simply defer to the respective function + // we've implemented there: + template + void + ConservationLaw:: + compute_refinement_indicators (Vector &refinement_indicators) const + { + EulerEquations::compute_refinement_indicators (dof_handler, + mapping, + predictor, + refinement_indicators); + } - current_solution.reinit(dof_handler.n_dofs()); - current_solution = old_solution; - right_hand_side.reinit (dof_handler.n_dofs()); -} - // @sect4{ConservationLaw::output_results} - - // This function now is rather - // straightforward. All the magic, including - // transforming data from conservative - // variables to physical ones has been - // abstracted and moved into the - // EulerEquations class so that it can be - // replaced in case we want to solve some - // other hyperbolic conservation law. - // - // Note that the number of the output file is - // determined by keeping a counter in the - // form of a static variable that is set to - // zero the first time we come to this - // function and is incremented by one at the - // end of each invokation. -template -void ConservationLaw::output_results () const -{ - typename EulerEquations::Postprocessor - postprocessor (parameters.schlieren_plot); + // @sect4{ConservationLaw::refine_grid} - DataOut data_out; - data_out.attach_dof_handler (dof_handler); + // Here, we use the refinement indicators + // computed before and refine the mesh. At + // the beginning, we loop over all cells and + // mark those that we think should be + // refined: + template + void + ConservationLaw::refine_grid (const Vector &refinement_indicators) + { + typename DoFHandler::active_cell_iterator + cell = dof_handler.begin_active(), + endc = dof_handler.end(); - data_out.add_data_vector (current_solution, - EulerEquations::component_names (), - DataOut::type_dof_data, - EulerEquations::component_interpretation ()); + for (unsigned int cell_no=0; cell!=endc; ++cell, ++cell_no) + { + cell->clear_coarsen_flag(); + cell->clear_refine_flag(); - data_out.add_data_vector (current_solution, postprocessor); + if ((cell->level() < parameters.shock_levels) && + (std::fabs(refinement_indicators(cell_no)) > parameters.shock_val)) + cell->set_refine_flag(); + else + if ((cell->level() > 0) && + (std::fabs(refinement_indicators(cell_no)) < 0.75*parameters.shock_val)) + cell->set_coarsen_flag(); + } - data_out.build_patches (); + // Then we need to transfer the + // various solution vectors from + // the old to the new grid while we + // do the refinement. The + // SolutionTransfer class is our + // friend here; it has a fairly + // extensive documentation, + // including examples, so we won't + // comment much on the following + // code. The last three lines + // simply re-set the sizes of some + // other vectors to the now correct + // size: + std::vector > transfer_in; + std::vector > transfer_out; - static unsigned int output_file_number = 0; - std::string filename = "solution-" + - Utilities::int_to_string (output_file_number, 3) + - ".vtk"; - std::ofstream output (filename.c_str()); - data_out.write_vtk (output); + transfer_in.push_back(old_solution); + transfer_in.push_back(predictor); - ++output_file_number; -} + triangulation.prepare_coarsening_and_refinement(); + SolutionTransfer soltrans(dof_handler); + soltrans.prepare_for_coarsening_and_refinement(transfer_in); + triangulation.execute_coarsening_and_refinement (); + dof_handler.clear(); + dof_handler.distribute_dofs (fe); - // @sect4{ConservationLaw::run} + { + Vector new_old_solution(1); + Vector new_predictor(1); - // This function contains the top-level logic - // of this program: initialization, the time - // loop, and the inner Newton iteration. - // - // At the beginning, we read the mesh file - // specified by the parameter file, setup the - // DoFHandler and various vectors, and then - // interpolate the given initial conditions - // on this mesh. We then perform a number of - // mesh refinements, based on the initial - // conditions, to obtain a mesh that is - // already well adapted to the starting - // solution. At the end of this process, we - // output the initial solution. -template -void ConservationLaw::run () -{ - { - GridIn grid_in; - grid_in.attach_triangulation(triangulation); + transfer_out.push_back(new_old_solution); + transfer_out.push_back(new_predictor); + transfer_out[0].reinit(dof_handler.n_dofs()); + transfer_out[1].reinit(dof_handler.n_dofs()); + } - std::ifstream input_file(parameters.mesh_filename.c_str()); - Assert (input_file, ExcFileNotOpen(parameters.mesh_filename.c_str())); + soltrans.interpolate(transfer_in, transfer_out); - grid_in.read_ucd(input_file); - } + old_solution.reinit (transfer_out[0].size()); + old_solution = transfer_out[0]; - dof_handler.clear(); - dof_handler.distribute_dofs (fe); + predictor.reinit (transfer_out[1].size()); + predictor = transfer_out[1]; - // Size all of the fields. - old_solution.reinit (dof_handler.n_dofs()); - current_solution.reinit (dof_handler.n_dofs()); - predictor.reinit (dof_handler.n_dofs()); - right_hand_side.reinit (dof_handler.n_dofs()); + current_solution.reinit(dof_handler.n_dofs()); + current_solution = old_solution; + right_hand_side.reinit (dof_handler.n_dofs()); + } - setup_system(); - VectorTools::interpolate(dof_handler, - parameters.initial_conditions, old_solution); - current_solution = old_solution; - predictor = old_solution; + // @sect4{ConservationLaw::output_results} - if (parameters.do_refine == true) - for (unsigned int i=0; i refinement_indicators (triangulation.n_active_cells()); + // This function now is rather + // straightforward. All the magic, including + // transforming data from conservative + // variables to physical ones has been + // abstracted and moved into the + // EulerEquations class so that it can be + // replaced in case we want to solve some + // other hyperbolic conservation law. + // + // Note that the number of the output file is + // determined by keeping a counter in the + // form of a static variable that is set to + // zero the first time we come to this + // function and is incremented by one at the + // end of each invokation. + template + void ConservationLaw::output_results () const + { + typename EulerEquations::Postprocessor + postprocessor (parameters.schlieren_plot); - compute_refinement_indicators(refinement_indicators); - refine_grid(refinement_indicators); + DataOut data_out; + data_out.attach_dof_handler (dof_handler); - setup_system(); + data_out.add_data_vector (current_solution, + EulerEquations::component_names (), + DataOut::type_dof_data, + EulerEquations::component_interpretation ()); - VectorTools::interpolate(dof_handler, - parameters.initial_conditions, old_solution); - current_solution = old_solution; - predictor = old_solution; - } + data_out.add_data_vector (current_solution, postprocessor); - output_results (); + data_out.build_patches (); - // We then enter into the main time - // stepping loop. At the top we simply - // output some status information so one - // can keep track of where a computation - // is, as well as the header for a table - // that indicates progress of the nonlinear - // inner iteration: - Vector newton_update (dof_handler.n_dofs()); + static unsigned int output_file_number = 0; + std::string filename = "solution-" + + Utilities::int_to_string (output_file_number, 3) + + ".vtk"; + std::ofstream output (filename.c_str()); + data_out.write_vtk (output); - double time = 0; - double next_output = time + parameters.output_step; + ++output_file_number; + } - predictor = old_solution; - while (time < parameters.final_time) - { - std::cout << "T=" << time << std::endl - << " Number of active cells: " - << triangulation.n_active_cells() - << std::endl - << " Number of degrees of freedom: " - << dof_handler.n_dofs() - << std::endl - << std::endl; - std::cout << " NonLin Res Lin Iter Lin Res" << std::endl - << " _____________________________________" << std::endl; - - // Then comes the inner Newton - // iteration to solve the nonlinear - // problem in each time step. The way - // it works is to reset matrix and - // right hand side to zero, then - // assemble the linear system. If the - // norm of the right hand side is small - // enough, then we declare that the - // Newton iteration has - // converged. Otherwise, we solve the - // linear system, update the current - // solution with the Newton increment, - // and output convergence - // information. At the end, we check - // that the number of Newton iterations - // is not beyond a limit of 10 -- if it - // is, it appears likely that - // iterations are diverging and further - // iterations would do no good. If that - // happens, we throw an exception that - // will be caught in - // main() with status - // information being displayed before - // the program aborts. - // - // Note that the way we write the - // AssertThrow macro below is by and - // large equivalent to writing - // something like if - // (!(nonlin_iter @<= 10)) throw - // ExcMessage ("No convergence in - // nonlinear solver");. The only - // significant difference is that - // AssertThrow also makes sure that the - // exception being thrown carries with - // it information about the location - // (file name and line number) where it - // was generated. This is not overly - // critical here, because there is only - // a single place where this sort of - // exception can happen; however, it is - // generally a very useful tool when - // one wants to find out where an error - // occurred. - unsigned int nonlin_iter = 0; - current_solution = predictor; - while (true) - { - system_matrix = 0; - right_hand_side = 0; - assemble_system (); - const double res_norm = right_hand_side.l2_norm(); - if (std::fabs(res_norm) < 1e-10) - { - std::printf(" %-16.3e (converged)\n\n", res_norm); - break; - } - else - { - newton_update = 0; + // @sect4{ConservationLaw::run} - std::pair convergence - = solve (newton_update); + // This function contains the top-level logic + // of this program: initialization, the time + // loop, and the inner Newton iteration. + // + // At the beginning, we read the mesh file + // specified by the parameter file, setup the + // DoFHandler and various vectors, and then + // interpolate the given initial conditions + // on this mesh. We then perform a number of + // mesh refinements, based on the initial + // conditions, to obtain a mesh that is + // already well adapted to the starting + // solution. At the end of this process, we + // output the initial solution. + template + void ConservationLaw::run () + { + { + GridIn grid_in; + grid_in.attach_triangulation(triangulation); - current_solution += newton_update; + std::ifstream input_file(parameters.mesh_filename.c_str()); + Assert (input_file, ExcFileNotOpen(parameters.mesh_filename.c_str())); - std::printf(" %-16.3e %04d %-5.2e\n", - res_norm, convergence.first, convergence.second); - } + grid_in.read_ucd(input_file); + } - ++nonlin_iter; - AssertThrow (nonlin_iter <= 10, - ExcMessage ("No convergence in nonlinear solver")); - } + dof_handler.clear(); + dof_handler.distribute_dofs (fe); - // We only get to this point if the - // Newton iteration has converged, so - // do various post convergence tasks - // here: - // - // First, we update the time - // and produce graphical output - // if so desired. Then we - // update a predictor for the - // solution at the next time - // step by approximating - // $\mathbf w^{n+1}\approx - // \mathbf w^n + \delta t - // \frac{\partial \mathbf - // w}{\partial t} \approx - // \mathbf w^n + \delta t \; - // \frac{\mathbf w^n-\mathbf - // w^{n-1}}{\delta t} = 2 - // \mathbf w^n - \mathbf - // w^{n-1}$ to try and make - // adaptivity work better. The - // idea is to try and refine - // ahead of a front, rather - // than stepping into a coarse - // set of elements and smearing - // the old_solution. This - // simple time extrapolator - // does the job. With this, we - // then refine the mesh if so - // desired by the user, and - // finally continue on with the - // next time step: - time += parameters.time_step; - - if (parameters.output_step < 0) - output_results (); - else if (time >= next_output) - { - output_results (); - next_output += parameters.output_step; - } + // Size all of the fields. + old_solution.reinit (dof_handler.n_dofs()); + current_solution.reinit (dof_handler.n_dofs()); + predictor.reinit (dof_handler.n_dofs()); + right_hand_side.reinit (dof_handler.n_dofs()); - predictor = current_solution; - predictor.sadd (2.0, -1.0, old_solution); + setup_system(); - old_solution = current_solution; + VectorTools::interpolate(dof_handler, + parameters.initial_conditions, old_solution); + current_solution = old_solution; + predictor = old_solution; - if (parameters.do_refine == true) + if (parameters.do_refine == true) + for (unsigned int i=0; i refinement_indicators (triangulation.n_active_cells()); - compute_refinement_indicators(refinement_indicators); + compute_refinement_indicators(refinement_indicators); refine_grid(refinement_indicators); + setup_system(); - newton_update.reinit (dof_handler.n_dofs()); + VectorTools::interpolate(dof_handler, + parameters.initial_conditions, old_solution); + current_solution = old_solution; + predictor = old_solution; } - } + + output_results (); + + // We then enter into the main time + // stepping loop. At the top we simply + // output some status information so one + // can keep track of where a computation + // is, as well as the header for a table + // that indicates progress of the nonlinear + // inner iteration: + Vector newton_update (dof_handler.n_dofs()); + + double time = 0; + double next_output = time + parameters.output_step; + + predictor = old_solution; + while (time < parameters.final_time) + { + std::cout << "T=" << time << std::endl + << " Number of active cells: " + << triangulation.n_active_cells() + << std::endl + << " Number of degrees of freedom: " + << dof_handler.n_dofs() + << std::endl + << std::endl; + + std::cout << " NonLin Res Lin Iter Lin Res" << std::endl + << " _____________________________________" << std::endl; + + // Then comes the inner Newton + // iteration to solve the nonlinear + // problem in each time step. The way + // it works is to reset matrix and + // right hand side to zero, then + // assemble the linear system. If the + // norm of the right hand side is small + // enough, then we declare that the + // Newton iteration has + // converged. Otherwise, we solve the + // linear system, update the current + // solution with the Newton increment, + // and output convergence + // information. At the end, we check + // that the number of Newton iterations + // is not beyond a limit of 10 -- if it + // is, it appears likely that + // iterations are diverging and further + // iterations would do no good. If that + // happens, we throw an exception that + // will be caught in + // main() with status + // information being displayed before + // the program aborts. + // + // Note that the way we write the + // AssertThrow macro below is by and + // large equivalent to writing + // something like if + // (!(nonlin_iter @<= 10)) throw + // ExcMessage ("No convergence in + // nonlinear solver");. The only + // significant difference is that + // AssertThrow also makes sure that the + // exception being thrown carries with + // it information about the location + // (file name and line number) where it + // was generated. This is not overly + // critical here, because there is only + // a single place where this sort of + // exception can happen; however, it is + // generally a very useful tool when + // one wants to find out where an error + // occurred. + unsigned int nonlin_iter = 0; + current_solution = predictor; + while (true) + { + system_matrix = 0; + + right_hand_side = 0; + assemble_system (); + + const double res_norm = right_hand_side.l2_norm(); + if (std::fabs(res_norm) < 1e-10) + { + std::printf(" %-16.3e (converged)\n\n", res_norm); + break; + } + else + { + newton_update = 0; + + std::pair convergence + = solve (newton_update); + + current_solution += newton_update; + + std::printf(" %-16.3e %04d %-5.2e\n", + res_norm, convergence.first, convergence.second); + } + + ++nonlin_iter; + AssertThrow (nonlin_iter <= 10, + ExcMessage ("No convergence in nonlinear solver")); + } + + // We only get to this point if the + // Newton iteration has converged, so + // do various post convergence tasks + // here: + // + // First, we update the time + // and produce graphical output + // if so desired. Then we + // update a predictor for the + // solution at the next time + // step by approximating + // $\mathbf w^{n+1}\approx + // \mathbf w^n + \delta t + // \frac{\partial \mathbf + // w}{\partial t} \approx + // \mathbf w^n + \delta t \; + // \frac{\mathbf w^n-\mathbf + // w^{n-1}}{\delta t} = 2 + // \mathbf w^n - \mathbf + // w^{n-1}$ to try and make + // adaptivity work better. The + // idea is to try and refine + // ahead of a front, rather + // than stepping into a coarse + // set of elements and smearing + // the old_solution. This + // simple time extrapolator + // does the job. With this, we + // then refine the mesh if so + // desired by the user, and + // finally continue on with the + // next time step: + time += parameters.time_step; + + if (parameters.output_step < 0) + output_results (); + else if (time >= next_output) + { + output_results (); + next_output += parameters.output_step; + } + + predictor = current_solution; + predictor.sadd (2.0, -1.0, old_solution); + + old_solution = current_solution; + + if (parameters.do_refine == true) + { + Vector refinement_indicators (triangulation.n_active_cells()); + compute_refinement_indicators(refinement_indicators); + + refine_grid(refinement_indicators); + setup_system(); + + newton_update.reinit (dof_handler.n_dofs()); + } + } + } } // @sect3{main()} @@ -3111,16 +3115,20 @@ void ConservationLaw::run () // line. int main (int argc, char *argv[]) { - deallog.depth_console(0); - if (argc != 2) - { - std::cout << "Usage:" << argv[0] << " input_file" << std::endl; - std::exit(1); - } - try { + using namespace dealii; + using namespace Step33; + + deallog.depth_console(0); + if (argc != 2) + { + std::cout << "Usage:" << argv[0] << " input_file" << std::endl; + std::exit(1); + } + Utilities::System::MPI_InitFinalize mpi_initialization (argc, argv); + ConservationLaw<2> cons (argv[1]); cons.run (); } diff --git a/deal.II/examples/step-34/step-34.cc b/deal.II/examples/step-34/step-34.cc index 17e54a2782..2090179ec2 100644 --- a/deal.II/examples/step-34/step-34.cc +++ b/deal.II/examples/step-34/step-34.cc @@ -2,7 +2,7 @@ // $Id$ // Version: $Name$ // -// Copyright (C) 2009, 2010 by the deal.II authors +// Copyright (C) 2009, 2010, 2011 by the deal.II authors // // This file is subject to QPL and may not be distributed // without copyright and license information. Please refer @@ -62,1390 +62,1394 @@ // The last part of this preamble is to // import everything in the dealii namespace - // into the global one for ease of use: -using namespace dealii; - - - // @sect3{Single and double layer operator kernels} - - // First, let us define a bit of the - // boundary integral equation - // machinery. - - // The following two functions are - // the actual calculations of the - // single and double layer potential - // kernels, that is $G$ and $\nabla - // G$. They are well defined only if - // the vector $R = - // \mathbf{y}-\mathbf{x}$ is - // different from zero. -namespace LaplaceKernel + // into the one into which everything in this + // program will go: +namespace Step34 { - template - double single_layer(const Point &R) - { - switch(dim) - { - case 2: - return (-std::log(R.norm()) / (2*numbers::PI) ); - - case 3: - return (1./( R.norm()*4*numbers::PI ) ); + using namespace dealii; - default: - Assert(false, ExcInternalError()); - return 0.; - } - } + // @sect3{Single and double layer operator kernels} + // First, let us define a bit of the + // boundary integral equation + // machinery. - template - Point double_layer(const Point &R) + // The following two functions are + // the actual calculations of the + // single and double layer potential + // kernels, that is $G$ and $\nabla + // G$. They are well defined only if + // the vector $R = + // \mathbf{y}-\mathbf{x}$ is + // different from zero. + namespace LaplaceKernel { - switch(dim) - { - case 2: - return R / ( -2*numbers::PI * R.square()); - case 3: - return R / ( -4*numbers::PI * R.square() * R.norm() ); - - default: - Assert(false, ExcInternalError()); - return Point(); - } - } -} + template + double single_layer(const Point &R) + { + switch(dim) + { + case 2: + return (-std::log(R.norm()) / (2*numbers::PI) ); + case 3: + return (1./( R.norm()*4*numbers::PI ) ); - // @sect3{The BEMProblem class} - - // The structure of a boundary - // element method code is very - // similar to the structure of a - // finite element code, and so the - // member functions of this class are - // like those of most of the other - // tutorial programs. In particular, - // by now you should be familiar with - // reading parameters from an - // external file, and with the - // splitting of the different tasks - // into different modules. The same - // applies to boundary element - // methods, and we won't comment too - // much on them, except on the - // differences. -template -class BEMProblem -{ - public: - BEMProblem(const unsigned int fe_degree = 1, - const unsigned int mapping_degree = 1); - - void run(); + default: + Assert(false, ExcInternalError()); + return 0.; + } + } - private: - void read_parameters (const std::string &filename); - void read_domain(); + template + Point double_layer(const Point &R) + { + switch(dim) + { + case 2: + return R / ( -2*numbers::PI * R.square()); + case 3: + return R / ( -4*numbers::PI * R.square() * R.norm() ); + + default: + Assert(false, ExcInternalError()); + return Point(); + } + } + } - void refine_and_resize(); - // The only really different - // function that we find here is - // the assembly routine. We wrote - // this function in the most - // possible general way, in order - // to allow for easy - // generalization to higher order - // methods and to different - // fundamental solutions (e.g., - // Stokes or Maxwell). - // - // The most noticeable difference - // is the fact that the final - // matrix is full, and that we - // have a nested loop inside the - // usual loop on cells that - // visits all support points of - // the degrees of freedom. - // Moreover, when the support - // point lies inside the cell - // which we are visiting, then - // the integral we perform - // becomes singular. - // - // The practical consequence is - // that we have two sets of - // quadrature formulas, finite - // element values and temporary - // storage, one for standard - // integration and one for the - // singular integration, which - // are used where necessary. - void assemble_system(); - - // There are two options for the - // solution of this problem. The - // first is to use a direct - // solver, and the second is to - // use an iterative solver. We - // opt for the second option. - // - // The matrix that we assemble is - // not symmetric, and we opt to - // use the GMRES method; however - // the construction of an - // efficient preconditioner for - // boundary element methods is - // not a trivial issue. Here we - // use a non preconditioned GMRES - // solver. The options for the - // iterative solver, such as the - // tolerance, the maximum number - // of iterations, are selected - // through the parameter file. - void solve_system(); - - // Once we obtained the solution, - // we compute the $L^2$ error of - // the computed potential as well - // as the $L^\infty$ error of the - // approximation of the solid - // angle. The mesh we are using - // is an approximation of a - // smooth curve, therefore the - // computed diagonal matrix of - // fraction of angles or solid - // angles $\alpha(\mathbf{x})$ - // should be constantly equal to - // $\frac 12$. In this routine we - // output the error on the - // potential and the error in the - // approximation of the computed - // angle. Notice that the latter - // error is actually not the - // error in the computation of - // the angle, but a measure of - // how well we are approximating - // the sphere and the circle. - // - // Experimenting a little with - // the computation of the angles - // gives very accurate results - // for simpler geometries. To - // verify this you can comment - // out, in the read_domain() - // method, the - // tria.set_boundary(1, boundary) - // line, and check the alpha that - // is generated by the - // program. By removing this - // call, whenever the mesh is - // refined new nodes will be - // placed along the straight - // lines that made up the coarse - // mesh, rather than be pulled - // onto the surface that we - // really want to approximate. In - // the three dimensional case, - // the coarse grid of the sphere - // is obtained starting from a - // cube, and the obtained values - // of alphas are exactly $\frac - // 12$ on the nodes of the faces, - // $\frac 34$ on the nodes of the - // edges and $\frac 78$ on the 8 - // nodes of the vertices. - void compute_errors(const unsigned int cycle); - - // Once we obtained a solution on - // the codimension one domain, we - // want to interpolate it to the - // rest of the space. This is - // done by performing again the - // convolution of the solution - // with the kernel in the - // compute_exterior_solution() - // function. - // - // We would like to plot the - // velocity variable which is the - // gradient of the potential - // solution. The potential - // solution is only known on the - // boundary, but we use the - // convolution with the - // fundamental solution to - // interpolate it on a standard - // dim dimensional continuous - // finite element space. The plot - // of the gradient of the - // extrapolated solution will - // give us the velocity we want. - // - // In addition to the solution on - // the exterior domain, we also - // output the solution on the - // domain's boundary in the - // output_results() function, of - // course. - void compute_exterior_solution(); - - void output_results(const unsigned int cycle); - - // To allow for dimension - // independent programming, we - // specialize this single - // function to extract the - // singular quadrature formula - // needed to integrate the - // singular kernels in the - // interior of the cells. - const Quadrature & get_singular_quadrature( - const typename DoFHandler::active_cell_iterator &cell, - const unsigned int index) const; - - - // The usual deal.II classes can - // be used for boundary element - // methods by specifying the - // "codimension" of the - // problem. This is done by - // setting the optional second - // template arguments to - // Triangulation, FiniteElement - // and DoFHandler to the - // dimension of the embedding - // space. In our case we generate - // either 1 or 2 dimensional - // meshes embedded in 2 or 3 - // dimensional spaces. - // - // The optional argument by - // default is equal to the first - // argument, and produces the - // usual finite element classes - // that we saw in all previous - // examples. - // - // The class is constructed in a - // way to allow for arbitrary - // order of approximation of both - // the domain (through high order - // mapping) and the finite - // element space. The order of - // the finite element space and - // of the mapping can be selected - // in the constructor of the class. - - Triangulation tria; - FE_Q fe; - DoFHandler dh; - MappingQ mapping; - - // In BEM methods, the matrix - // that is generated is - // dense. Depending on the size - // of the problem, the final - // system might be solved by - // direct LU decomposition, or by - // iterative methods. In this - // example we use an - // unpreconditioned GMRES - // method. Building a - // preconditioner for BEM method - // is non trivial, and we don't - // treat this subject here. - - FullMatrix system_matrix; - Vector system_rhs; - - // The next two variables will - // denote the solution $\phi$ as - // well as a vector that will - // hold the values of - // $\alpha(\mathbf x)$ (the - // fraction of $\Omega$ visible - // from a point $\mathbf x$) at - // the support points of our - // shape functions. - - Vector phi; - Vector alpha; - - // The convergence table is used - // to output errors in the exact - // solution and in the computed - // alphas. - - ConvergenceTable convergence_table; - - // The following variables are - // the ones that we fill through - // a parameter file. The new - // objects that we use in this - // example are the - // Functions::ParsedFunction - // object and the - // QuadratureSelector object. - // - // The Functions::ParsedFunction - // class allows us to easily and - // quickly define new function - // objects via parameter files, - // with custom definitions which - // can be very complex (see the - // documentation of that class - // for all the available - // options). - // - // We will allocate the - // quadrature object using the - // QuadratureSelector class that - // allows us to generate - // quadrature formulas based on - // an identifying string and on - // the possible degree of the - // formula itself. We used this - // to allow custom selection of - // the quadrature formulas for - // the standard integration, and - // to define the order of the - // singular quadrature rule. - // - // We also define a couple of - // parameters which are used in - // case we wanted to extend the - // solution to the entire domain. - - Functions::ParsedFunction wind; - Functions::ParsedFunction exact_solution; - - unsigned int singular_quadrature_order; - std_cxx1x::shared_ptr > quadrature; - - SolverControl solver_control; - - unsigned int n_cycles; - unsigned int external_refinement; - - bool run_in_this_dimension; - bool extend_solution; -}; - - - // @sect4{BEMProblem::BEMProblem and BEMProblem::read_parameters} - - // The constructor initializes the - // variuous object in much the same - // way as done in the finite element - // programs such as step-4 or - // step-6. The only new ingredient - // here is the ParsedFunction object, - // which needs, at construction time, - // the specification of the number of - // components. - // - // For the exact solution the number - // of vector components is one, and - // no action is required since one is - // the default value for a - // ParsedFunction object. The wind, - // however, requires dim components - // to be specified. Notice that when - // declaring entries in a parameter - // file for the expression of the - // Functions::ParsedFunction, we need - // to specify the number of - // components explicitly, since the - // function - // Functions::ParsedFunction::declare_parameters - // is static, and has no knowledge of - // the number of components. -template -BEMProblem::BEMProblem(const unsigned int fe_degree, - const unsigned int mapping_degree) - : - fe(fe_degree), - dh(tria), - mapping(mapping_degree, true), - wind(dim) -{} - - -template -void BEMProblem::read_parameters (const std::string &filename) -{ - deallog << std::endl << "Parsing parameter file " << filename << std::endl - << "for a " << dim << " dimensional simulation. " << std::endl; - - ParameterHandler prm; - - prm.declare_entry("Number of cycles", "4", - Patterns::Integer()); - prm.declare_entry("External refinement", "5", - Patterns::Integer()); - prm.declare_entry("Extend solution on the -2,2 box", "true", - Patterns::Bool()); - prm.declare_entry("Run 2d simulation", "true", - Patterns::Bool()); - prm.declare_entry("Run 3d simulation", "true", - Patterns::Bool()); - - prm.enter_subsection("Quadrature rules"); + // @sect3{The BEMProblem class} + + // The structure of a boundary + // element method code is very + // similar to the structure of a + // finite element code, and so the + // member functions of this class are + // like those of most of the other + // tutorial programs. In particular, + // by now you should be familiar with + // reading parameters from an + // external file, and with the + // splitting of the different tasks + // into different modules. The same + // applies to boundary element + // methods, and we won't comment too + // much on them, except on the + // differences. + template + class BEMProblem { - prm.declare_entry("Quadrature type", "gauss", - Patterns::Selection(QuadratureSelector<(dim-1)>::get_quadrature_names())); - prm.declare_entry("Quadrature order", "4", Patterns::Integer()); - prm.declare_entry("Singular quadrature order", "5", Patterns::Integer()); - } - prm.leave_subsection(); - - // For both two and three - // dimensions, we set the default - // input data to be such that the - // solution is $x+y$ or - // $x+y+z$. The actually computed - // solution will have value zero at - // infinity. In this case, this - // coincide with the exact - // solution, and no additional - // corrections are needed, but you - // should be aware of the fact that - // we arbitrarily set - // $\phi_\infty$, and the exact - // solution we pass to the program - // needs to have the same value at - // infinity for the error to be - // computed correctly. + public: + BEMProblem(const unsigned int fe_degree = 1, + const unsigned int mapping_degree = 1); + + void run(); + + private: + + void read_parameters (const std::string &filename); + + void read_domain(); + + void refine_and_resize(); + + // The only really different + // function that we find here is + // the assembly routine. We wrote + // this function in the most + // possible general way, in order + // to allow for easy + // generalization to higher order + // methods and to different + // fundamental solutions (e.g., + // Stokes or Maxwell). + // + // The most noticeable difference + // is the fact that the final + // matrix is full, and that we + // have a nested loop inside the + // usual loop on cells that + // visits all support points of + // the degrees of freedom. + // Moreover, when the support + // point lies inside the cell + // which we are visiting, then + // the integral we perform + // becomes singular. + // + // The practical consequence is + // that we have two sets of + // quadrature formulas, finite + // element values and temporary + // storage, one for standard + // integration and one for the + // singular integration, which + // are used where necessary. + void assemble_system(); + + // There are two options for the + // solution of this problem. The + // first is to use a direct + // solver, and the second is to + // use an iterative solver. We + // opt for the second option. + // + // The matrix that we assemble is + // not symmetric, and we opt to + // use the GMRES method; however + // the construction of an + // efficient preconditioner for + // boundary element methods is + // not a trivial issue. Here we + // use a non preconditioned GMRES + // solver. The options for the + // iterative solver, such as the + // tolerance, the maximum number + // of iterations, are selected + // through the parameter file. + void solve_system(); + + // Once we obtained the solution, + // we compute the $L^2$ error of + // the computed potential as well + // as the $L^\infty$ error of the + // approximation of the solid + // angle. The mesh we are using + // is an approximation of a + // smooth curve, therefore the + // computed diagonal matrix of + // fraction of angles or solid + // angles $\alpha(\mathbf{x})$ + // should be constantly equal to + // $\frac 12$. In this routine we + // output the error on the + // potential and the error in the + // approximation of the computed + // angle. Notice that the latter + // error is actually not the + // error in the computation of + // the angle, but a measure of + // how well we are approximating + // the sphere and the circle. + // + // Experimenting a little with + // the computation of the angles + // gives very accurate results + // for simpler geometries. To + // verify this you can comment + // out, in the read_domain() + // method, the + // tria.set_boundary(1, boundary) + // line, and check the alpha that + // is generated by the + // program. By removing this + // call, whenever the mesh is + // refined new nodes will be + // placed along the straight + // lines that made up the coarse + // mesh, rather than be pulled + // onto the surface that we + // really want to approximate. In + // the three dimensional case, + // the coarse grid of the sphere + // is obtained starting from a + // cube, and the obtained values + // of alphas are exactly $\frac + // 12$ on the nodes of the faces, + // $\frac 34$ on the nodes of the + // edges and $\frac 78$ on the 8 + // nodes of the vertices. + void compute_errors(const unsigned int cycle); + + // Once we obtained a solution on + // the codimension one domain, we + // want to interpolate it to the + // rest of the space. This is + // done by performing again the + // convolution of the solution + // with the kernel in the + // compute_exterior_solution() + // function. + // + // We would like to plot the + // velocity variable which is the + // gradient of the potential + // solution. The potential + // solution is only known on the + // boundary, but we use the + // convolution with the + // fundamental solution to + // interpolate it on a standard + // dim dimensional continuous + // finite element space. The plot + // of the gradient of the + // extrapolated solution will + // give us the velocity we want. + // + // In addition to the solution on + // the exterior domain, we also + // output the solution on the + // domain's boundary in the + // output_results() function, of + // course. + void compute_exterior_solution(); + + void output_results(const unsigned int cycle); + + // To allow for dimension + // independent programming, we + // specialize this single + // function to extract the + // singular quadrature formula + // needed to integrate the + // singular kernels in the + // interior of the cells. + const Quadrature & get_singular_quadrature( + const typename DoFHandler::active_cell_iterator &cell, + const unsigned int index) const; + + + // The usual deal.II classes can + // be used for boundary element + // methods by specifying the + // "codimension" of the + // problem. This is done by + // setting the optional second + // template arguments to + // Triangulation, FiniteElement + // and DoFHandler to the + // dimension of the embedding + // space. In our case we generate + // either 1 or 2 dimensional + // meshes embedded in 2 or 3 + // dimensional spaces. + // + // The optional argument by + // default is equal to the first + // argument, and produces the + // usual finite element classes + // that we saw in all previous + // examples. + // + // The class is constructed in a + // way to allow for arbitrary + // order of approximation of both + // the domain (through high order + // mapping) and the finite + // element space. The order of + // the finite element space and + // of the mapping can be selected + // in the constructor of the class. + + Triangulation tria; + FE_Q fe; + DoFHandler dh; + MappingQ mapping; + + // In BEM methods, the matrix + // that is generated is + // dense. Depending on the size + // of the problem, the final + // system might be solved by + // direct LU decomposition, or by + // iterative methods. In this + // example we use an + // unpreconditioned GMRES + // method. Building a + // preconditioner for BEM method + // is non trivial, and we don't + // treat this subject here. + + FullMatrix system_matrix; + Vector system_rhs; + + // The next two variables will + // denote the solution $\phi$ as + // well as a vector that will + // hold the values of + // $\alpha(\mathbf x)$ (the + // fraction of $\Omega$ visible + // from a point $\mathbf x$) at + // the support points of our + // shape functions. + + Vector phi; + Vector alpha; + + // The convergence table is used + // to output errors in the exact + // solution and in the computed + // alphas. + + ConvergenceTable convergence_table; + + // The following variables are + // the ones that we fill through + // a parameter file. The new + // objects that we use in this + // example are the + // Functions::ParsedFunction + // object and the + // QuadratureSelector object. + // + // The Functions::ParsedFunction + // class allows us to easily and + // quickly define new function + // objects via parameter files, + // with custom definitions which + // can be very complex (see the + // documentation of that class + // for all the available + // options). + // + // We will allocate the + // quadrature object using the + // QuadratureSelector class that + // allows us to generate + // quadrature formulas based on + // an identifying string and on + // the possible degree of the + // formula itself. We used this + // to allow custom selection of + // the quadrature formulas for + // the standard integration, and + // to define the order of the + // singular quadrature rule. + // + // We also define a couple of + // parameters which are used in + // case we wanted to extend the + // solution to the entire domain. + + Functions::ParsedFunction wind; + Functions::ParsedFunction exact_solution; + + unsigned int singular_quadrature_order; + std_cxx1x::shared_ptr > quadrature; + + SolverControl solver_control; + + unsigned int n_cycles; + unsigned int external_refinement; + + bool run_in_this_dimension; + bool extend_solution; + }; + + + // @sect4{BEMProblem::BEMProblem and BEMProblem::read_parameters} + + // The constructor initializes the + // variuous object in much the same + // way as done in the finite element + // programs such as step-4 or + // step-6. The only new ingredient + // here is the ParsedFunction object, + // which needs, at construction time, + // the specification of the number of + // components. // - // The use of the - // Functions::ParsedFunction object - // is pretty straight forward. The + // For the exact solution the number + // of vector components is one, and + // no action is required since one is + // the default value for a + // ParsedFunction object. The wind, + // however, requires dim components + // to be specified. Notice that when + // declaring entries in a parameter + // file for the expression of the + // Functions::ParsedFunction, we need + // to specify the number of + // components explicitly, since the + // function // Functions::ParsedFunction::declare_parameters - // function takes an additional - // integer argument that specifies - // the number of components of the - // given function. Its default - // value is one. When the - // corresponding - // Functions::ParsedFunction::parse_parameters - // method is called, the calling - // object has to have the same - // number of components defined - // here, otherwise an exception is - // thrown. - // - // When declaring entries, we - // declare both 2 and three - // dimensional functions. However - // only the dim-dimensional one is - // ultimately parsed. This allows - // us to have only one parameter - // file for both 2 and 3 - // dimensional problems. - // - // Notice that from a mathematical - // point of view, the wind function - // on the boundary should satisfy - // the condition - // $\int_{\partial\Omega} - // \mathbf{v}\cdot \mathbf{n} d - // \Gamma = 0$, for the problem to - // have a solution. If this - // condition is not satisfied, then - // no solution can be found, and - // the solver will not converge. - prm.enter_subsection("Wind function 2d"); - { - Functions::ParsedFunction<2>::declare_parameters(prm, 2); - prm.set("Function expression", "1; 1"); - } - prm.leave_subsection(); - - prm.enter_subsection("Wind function 3d"); - { - Functions::ParsedFunction<3>::declare_parameters(prm, 3); - prm.set("Function expression", "1; 1; 1"); - } - prm.leave_subsection(); + // is static, and has no knowledge of + // the number of components. + template + BEMProblem::BEMProblem(const unsigned int fe_degree, + const unsigned int mapping_degree) + : + fe(fe_degree), + dh(tria), + mapping(mapping_degree, true), + wind(dim) + {} - prm.enter_subsection("Exact solution 2d"); - { - Functions::ParsedFunction<2>::declare_parameters(prm); - prm.set("Function expression", "x+y"); - } - prm.leave_subsection(); - prm.enter_subsection("Exact solution 3d"); - { - Functions::ParsedFunction<3>::declare_parameters(prm); - prm.set("Function expression", "x+y+z"); - } - prm.leave_subsection(); - - - // In the solver section, we set - // all SolverControl - // parameters. The object will then - // be fed to the GMRES solver in - // the solve_system() function. - prm.enter_subsection("Solver"); - SolverControl::declare_parameters(prm); - prm.leave_subsection(); - - // After declaring all these - // parameters to the - // ParameterHandler object, let's - // read an input file that will - // give the parameters their - // values. We then proceed to - // extract these values from the - // ParameterHandler object: - prm.read_input(filename); - - n_cycles = prm.get_integer("Number of cycles"); - external_refinement = prm.get_integer("External refinement"); - extend_solution = prm.get_bool("Extend solution on the -2,2 box"); - - prm.enter_subsection("Quadrature rules"); + template + void BEMProblem::read_parameters (const std::string &filename) { - quadrature = - std_cxx1x::shared_ptr > - (new QuadratureSelector (prm.get("Quadrature type"), - prm.get_integer("Quadrature order"))); - singular_quadrature_order = prm.get_integer("Singular quadrature order"); - } - prm.leave_subsection(); + deallog << std::endl << "Parsing parameter file " << filename << std::endl + << "for a " << dim << " dimensional simulation. " << std::endl; + + ParameterHandler prm; + + prm.declare_entry("Number of cycles", "4", + Patterns::Integer()); + prm.declare_entry("External refinement", "5", + Patterns::Integer()); + prm.declare_entry("Extend solution on the -2,2 box", "true", + Patterns::Bool()); + prm.declare_entry("Run 2d simulation", "true", + Patterns::Bool()); + prm.declare_entry("Run 3d simulation", "true", + Patterns::Bool()); + + prm.enter_subsection("Quadrature rules"); + { + prm.declare_entry("Quadrature type", "gauss", + Patterns::Selection(QuadratureSelector<(dim-1)>::get_quadrature_names())); + prm.declare_entry("Quadrature order", "4", Patterns::Integer()); + prm.declare_entry("Singular quadrature order", "5", Patterns::Integer()); + } + prm.leave_subsection(); + + // For both two and three + // dimensions, we set the default + // input data to be such that the + // solution is $x+y$ or + // $x+y+z$. The actually computed + // solution will have value zero at + // infinity. In this case, this + // coincide with the exact + // solution, and no additional + // corrections are needed, but you + // should be aware of the fact that + // we arbitrarily set + // $\phi_\infty$, and the exact + // solution we pass to the program + // needs to have the same value at + // infinity for the error to be + // computed correctly. + // + // The use of the + // Functions::ParsedFunction object + // is pretty straight forward. The + // Functions::ParsedFunction::declare_parameters + // function takes an additional + // integer argument that specifies + // the number of components of the + // given function. Its default + // value is one. When the + // corresponding + // Functions::ParsedFunction::parse_parameters + // method is called, the calling + // object has to have the same + // number of components defined + // here, otherwise an exception is + // thrown. + // + // When declaring entries, we + // declare both 2 and three + // dimensional functions. However + // only the dim-dimensional one is + // ultimately parsed. This allows + // us to have only one parameter + // file for both 2 and 3 + // dimensional problems. + // + // Notice that from a mathematical + // point of view, the wind function + // on the boundary should satisfy + // the condition + // $\int_{\partial\Omega} + // \mathbf{v}\cdot \mathbf{n} d + // \Gamma = 0$, for the problem to + // have a solution. If this + // condition is not satisfied, then + // no solution can be found, and + // the solver will not converge. + prm.enter_subsection("Wind function 2d"); + { + Functions::ParsedFunction<2>::declare_parameters(prm, 2); + prm.set("Function expression", "1; 1"); + } + prm.leave_subsection(); - prm.enter_subsection(std::string("Wind function ")+ - Utilities::int_to_string(dim)+std::string("d")); - { - wind.parse_parameters(prm); - } - prm.leave_subsection(); + prm.enter_subsection("Wind function 3d"); + { + Functions::ParsedFunction<3>::declare_parameters(prm, 3); + prm.set("Function expression", "1; 1; 1"); + } + prm.leave_subsection(); - prm.enter_subsection(std::string("Exact solution ")+ - Utilities::int_to_string(dim)+std::string("d")); - { - exact_solution.parse_parameters(prm); - } - prm.leave_subsection(); - - prm.enter_subsection("Solver"); - solver_control.parse_parameters(prm); - prm.leave_subsection(); - - - // Finally, here's another example - // of how to use parameter files in - // dimension independent - // programming. If we wanted to - // switch off one of the two - // simulations, we could do this by - // setting the corresponding "Run - // 2d simulation" or "Run 3d - // simulation" flag to false: - run_in_this_dimension = prm.get_bool("Run " + - Utilities::int_to_string(dim) + - "d simulation"); -} + prm.enter_subsection("Exact solution 2d"); + { + Functions::ParsedFunction<2>::declare_parameters(prm); + prm.set("Function expression", "x+y"); + } + prm.leave_subsection(); + prm.enter_subsection("Exact solution 3d"); + { + Functions::ParsedFunction<3>::declare_parameters(prm); + prm.set("Function expression", "x+y+z"); + } + prm.leave_subsection(); + + + // In the solver section, we set + // all SolverControl + // parameters. The object will then + // be fed to the GMRES solver in + // the solve_system() function. + prm.enter_subsection("Solver"); + SolverControl::declare_parameters(prm); + prm.leave_subsection(); + + // After declaring all these + // parameters to the + // ParameterHandler object, let's + // read an input file that will + // give the parameters their + // values. We then proceed to + // extract these values from the + // ParameterHandler object: + prm.read_input(filename); + + n_cycles = prm.get_integer("Number of cycles"); + external_refinement = prm.get_integer("External refinement"); + extend_solution = prm.get_bool("Extend solution on the -2,2 box"); + + prm.enter_subsection("Quadrature rules"); + { + quadrature = + std_cxx1x::shared_ptr > + (new QuadratureSelector (prm.get("Quadrature type"), + prm.get_integer("Quadrature order"))); + singular_quadrature_order = prm.get_integer("Singular quadrature order"); + } + prm.leave_subsection(); - // @sect4{BEMProblem::read_domain} - - // A boundary element method - // triangulation is basically the - // same as a (dim-1) dimensional - // triangulation, with the difference - // that the vertices belong to a - // (dim) dimensional space. - // - // Some of the mesh formats supported - // in deal.II use by default three - // dimensional points to describe - // meshes. These are the formats - // which are compatible with the - // boundary element method - // capabilities of deal.II. In - // particular we can use either UCD - // or GMSH formats. In both cases, we - // have to be particularly careful - // with the orientation of the mesh, - // because, unlike in the standard - // finite element case, no reordering - // or compatibility check is - // performed here. All meshes are - // considered as oriented, because - // they are embedded in a higher - // dimensional space. (See the - // documentation of the GridIn and of - // the Triangulation for further - // details on orientation of cells in - // a triangulation.) In our case, the - // normals to the mesh are external - // to both the circle in 2d or the - // sphere in 3d. - // - // The other detail that is required - // for appropriate refinement of the - // boundary element mesh, is an - // accurate description of the - // manifold that the mesh is - // approximating. We already saw this - // several times for the boundary of - // standard finite element meshes - // (for example in step-5 and - // step-6), and here the principle - // and usage is the same, except that - // the HyperBallBoundary class takes - // an additional template parameter - // that specifies the embedding space - // dimension. The function object - // still has to be static to live at - // least as long as the triangulation - // object to which it is attached. - -template -void BEMProblem::read_domain() -{ - static const Point center = Point(); - static const HyperBallBoundary boundary(center,1.); + prm.enter_subsection(std::string("Wind function ")+ + Utilities::int_to_string(dim)+std::string("d")); + { + wind.parse_parameters(prm); + } + prm.leave_subsection(); - std::ifstream in; - switch (dim) + prm.enter_subsection(std::string("Exact solution ")+ + Utilities::int_to_string(dim)+std::string("d")); { - case 2: - in.open ("coarse_circle.inp"); - break; + exact_solution.parse_parameters(prm); + } + prm.leave_subsection(); + + prm.enter_subsection("Solver"); + solver_control.parse_parameters(prm); + prm.leave_subsection(); + + + // Finally, here's another example + // of how to use parameter files in + // dimension independent + // programming. If we wanted to + // switch off one of the two + // simulations, we could do this by + // setting the corresponding "Run + // 2d simulation" or "Run 3d + // simulation" flag to false: + run_in_this_dimension = prm.get_bool("Run " + + Utilities::int_to_string(dim) + + "d simulation"); + } - case 3: - in.open ("coarse_sphere.inp"); - break; - default: - Assert (false, ExcNotImplemented()); - } + // @sect4{BEMProblem::read_domain} - GridIn gi; - gi.attach_triangulation (tria); - gi.read_ucd (in); + // A boundary element method + // triangulation is basically the + // same as a (dim-1) dimensional + // triangulation, with the difference + // that the vertices belong to a + // (dim) dimensional space. + // + // Some of the mesh formats supported + // in deal.II use by default three + // dimensional points to describe + // meshes. These are the formats + // which are compatible with the + // boundary element method + // capabilities of deal.II. In + // particular we can use either UCD + // or GMSH formats. In both cases, we + // have to be particularly careful + // with the orientation of the mesh, + // because, unlike in the standard + // finite element case, no reordering + // or compatibility check is + // performed here. All meshes are + // considered as oriented, because + // they are embedded in a higher + // dimensional space. (See the + // documentation of the GridIn and of + // the Triangulation for further + // details on orientation of cells in + // a triangulation.) In our case, the + // normals to the mesh are external + // to both the circle in 2d or the + // sphere in 3d. + // + // The other detail that is required + // for appropriate refinement of the + // boundary element mesh, is an + // accurate description of the + // manifold that the mesh is + // approximating. We already saw this + // several times for the boundary of + // standard finite element meshes + // (for example in step-5 and + // step-6), and here the principle + // and usage is the same, except that + // the HyperBallBoundary class takes + // an additional template parameter + // that specifies the embedding space + // dimension. The function object + // still has to be static to live at + // least as long as the triangulation + // object to which it is attached. - tria.set_boundary(1, boundary); -} + template + void BEMProblem::read_domain() + { + static const Point center = Point(); + static const HyperBallBoundary boundary(center,1.); + std::ifstream in; + switch (dim) + { + case 2: + in.open ("coarse_circle.inp"); + break; - // @sect4{BEMProblem::refine_and_resize} + case 3: + in.open ("coarse_sphere.inp"); + break; - // This function globally refines the - // mesh, distributes degrees of - // freedom, and resizes matrices and - // vectors. + default: + Assert (false, ExcNotImplemented()); + } -template -void BEMProblem::refine_and_resize() -{ - tria.refine_global(1); + GridIn gi; + gi.attach_triangulation (tria); + gi.read_ucd (in); - dh.distribute_dofs(fe); + tria.set_boundary(1, boundary); + } - const unsigned int n_dofs = dh.n_dofs(); - system_matrix.reinit(n_dofs, n_dofs); + // @sect4{BEMProblem::refine_and_resize} - system_rhs.reinit(n_dofs); - phi.reinit(n_dofs); - alpha.reinit(n_dofs); -} + // This function globally refines the + // mesh, distributes degrees of + // freedom, and resizes matrices and + // vectors. + + template + void BEMProblem::refine_and_resize() + { + tria.refine_global(1); + dh.distribute_dofs(fe); - // @sect4{BEMProblem::assemble_system} + const unsigned int n_dofs = dh.n_dofs(); - // The following is the main function - // of this program, assembling the - // matrix that corresponds to the - // boundary integral equation. -template -void BEMProblem::assemble_system() -{ + system_matrix.reinit(n_dofs, n_dofs); - // First we initialize an FEValues - // object with the quadrature - // formula for the integration of - // the kernel in non singular - // cells. This quadrature is - // selected with the parameter - // file, and needs to be quite - // precise, since the functions we - // are integrating are not - // polynomial functions. - FEValues fe_v(mapping, fe, *quadrature, - update_values | - update_cell_normal_vectors | - update_quadrature_points | - update_JxW_values); - - const unsigned int n_q_points = fe_v.n_quadrature_points; - - std::vector local_dof_indices(fe.dofs_per_cell); - - std::vector > cell_wind(n_q_points, Vector(dim) ); - double normal_wind; - - // Unlike in finite element - // methods, if we use a collocation - // boundary element method, then in - // each assembly loop we only - // assemble the information that - // refers to the coupling between - // one degree of freedom (the - // degree associated with support - // point $i$) and the current - // cell. This is done using a - // vector of fe.dofs_per_cell - // elements, which will then be - // distributed to the matrix in the - // global row $i$. The following - // object will hold this - // information: - Vector local_matrix_row_i(fe.dofs_per_cell); - - // The index $i$ runs on the - // collocation points, which are - // the support points of the $i$th - // basis function, while $j$ runs - // on inner integration points. - - // We construct a vector - // of support points which will be - // used in the local integrations: - std::vector > support_points(dh.n_dofs()); - DoFTools::map_dofs_to_support_points( mapping, dh, support_points); - - - // After doing so, we can start the - // integration loop over all cells, - // where we first initialize the - // FEValues object and get the - // values of $\mathbf{\tilde v}$ at - // the quadrature points (this - // vector field should be constant, - // but it doesn't hurt to be more - // general): - typename DoFHandler::active_cell_iterator - cell = dh.begin_active(), - endc = dh.end(); - - for(cell = dh.begin_active(); cell != endc; ++cell) - { - fe_v.reinit(cell); - cell->get_dof_indices(local_dof_indices); - - const std::vector > &q_points = fe_v.get_quadrature_points(); - const std::vector > &normals = fe_v.get_cell_normal_vectors(); - wind.vector_value_list(q_points, cell_wind); - - // We then form the integral over - // the current cell for all - // degrees of freedom (note that - // this includes degrees of - // freedom not located on the - // current cell, a deviation from - // the usual finite element - // integrals). The integral that - // we need to perform is singular - // if one of the local degrees of - // freedom is the same as the - // support point $i$. A the - // beginning of the loop we - // therefore check wether this is - // the case, and we store which - // one is the singular index: - for(unsigned int i=0; i + void BEMProblem::assemble_system() + { - const Point R = q_points[q] - support_points[i]; + // First we initialize an FEValues + // object with the quadrature + // formula for the integration of + // the kernel in non singular + // cells. This quadrature is + // selected with the parameter + // file, and needs to be quite + // precise, since the functions we + // are integrating are not + // polynomial functions. + FEValues fe_v(mapping, fe, *quadrature, + update_values | + update_cell_normal_vectors | + update_quadrature_points | + update_JxW_values); + + const unsigned int n_q_points = fe_v.n_quadrature_points; + + std::vector local_dof_indices(fe.dofs_per_cell); + + std::vector > cell_wind(n_q_points, Vector(dim) ); + double normal_wind; + + // Unlike in finite element + // methods, if we use a collocation + // boundary element method, then in + // each assembly loop we only + // assemble the information that + // refers to the coupling between + // one degree of freedom (the + // degree associated with support + // point $i$) and the current + // cell. This is done using a + // vector of fe.dofs_per_cell + // elements, which will then be + // distributed to the matrix in the + // global row $i$. The following + // object will hold this + // information: + Vector local_matrix_row_i(fe.dofs_per_cell); + + // The index $i$ runs on the + // collocation points, which are + // the support points of the $i$th + // basis function, while $j$ runs + // on inner integration points. + + // We construct a vector + // of support points which will be + // used in the local integrations: + std::vector > support_points(dh.n_dofs()); + DoFTools::map_dofs_to_support_points( mapping, dh, support_points); + + + // After doing so, we can start the + // integration loop over all cells, + // where we first initialize the + // FEValues object and get the + // values of $\mathbf{\tilde v}$ at + // the quadrature points (this + // vector field should be constant, + // but it doesn't hurt to be more + // general): + typename DoFHandler::active_cell_iterator + cell = dh.begin_active(), + endc = dh.end(); + + for (cell = dh.begin_active(); cell != endc; ++cell) + { + fe_v.reinit(cell); + cell->get_dof_indices(local_dof_indices); + + const std::vector > &q_points = fe_v.get_quadrature_points(); + const std::vector > &normals = fe_v.get_cell_normal_vectors(); + wind.vector_value_list(q_points, cell_wind); + + // We then form the integral over + // the current cell for all + // degrees of freedom (note that + // this includes degrees of + // freedom not located on the + // current cell, a deviation from + // the usual finite element + // integrals). The integral that + // we need to perform is singular + // if one of the local degrees of + // freedom is the same as the + // support point $i$. A the + // beginning of the loop we + // therefore check wether this is + // the case, and we store which + // one is the singular index: + for (unsigned int i=0; i & singular_quadrature = - get_singular_quadrature(cell, singular_index); - - FEValues fe_v_singular (mapping, fe, singular_quadrature, - update_jacobians | - update_values | - update_cell_normal_vectors | - update_quadrature_points ); - - fe_v_singular.reinit(cell); - - std::vector > singular_cell_wind( singular_quadrature.size(), - Vector(dim) ); - - const std::vector > &singular_normals = fe_v_singular.get_cell_normal_vectors(); - const std::vector > &singular_q_points = fe_v_singular.get_quadrature_points(); - - wind.vector_value_list(singular_q_points, singular_cell_wind); - - for(unsigned int q=0; q R = singular_q_points[q] - support_points[i]; - double normal_wind = 0; - for(unsigned int d=0; d R = q_points[q] - support_points[i]; + + system_rhs(i) += ( LaplaceKernel::single_layer(R) * + normal_wind * + fe_v.JxW(q) ); + + for (unsigned int j=0; j & singular_quadrature = + get_singular_quadrature(cell, singular_index); + + FEValues fe_v_singular (mapping, fe, singular_quadrature, + update_jacobians | + update_values | + update_cell_normal_vectors | + update_quadrature_points ); + + fe_v_singular.reinit(cell); + + std::vector > singular_cell_wind( singular_quadrature.size(), + Vector(dim) ); + + const std::vector > &singular_normals = fe_v_singular.get_cell_normal_vectors(); + const std::vector > &singular_q_points = fe_v_singular.get_quadrature_points(); + + wind.vector_value_list(singular_q_points, singular_cell_wind); + + for (unsigned int q=0; q R = singular_q_points[q] - support_points[i]; + double normal_wind = 0; + for (unsigned int d=0; d ones(dh.n_dofs()); - ones.add(-1.); - - system_matrix.vmult(alpha, ones); - alpha.add(1); - for(unsigned int i = 0; i ones(dh.n_dofs()); + ones.add(-1.); + + system_matrix.vmult(alpha, ones); + alpha.add(1); + for (unsigned int i = 0; i -void BEMProblem::solve_system() -{ + // The next function simply solves + // the linear system. + template + void BEMProblem::solve_system() + { SolverGMRES > solver (solver_control); solver.solve (system_matrix, phi, system_rhs, PreconditionIdentity()); -} + } - // @sect4{BEMProblem::compute_errors} + // @sect4{BEMProblem::compute_errors} - // The computation of the errors is - // exactly the same in all other - // example programs, and we won't - // comment too much. Notice how the - // same methods that are used in the - // finite element methods can be used - // here. -template -void BEMProblem::compute_errors(const unsigned int cycle) -{ - Vector difference_per_cell (tria.n_active_cells()); - VectorTools::integrate_difference (mapping, dh, phi, - exact_solution, - difference_per_cell, - QGauss<(dim-1)>(2*fe.degree+1), - VectorTools::L2_norm); - const double L2_error = difference_per_cell.l2_norm(); - - - // The error in the alpha vector - // can be computed directly using - // the Vector::linfty_norm() - // function, since on each node, - // the value should be $\frac - // 12$. All errors are then output - // and appended to our - // ConvergenceTable object for - // later computation of convergence - // rates: - Vector difference_per_node(alpha); - difference_per_node.add(-.5); - - const double alpha_error = difference_per_node.linfty_norm(); - const unsigned int n_active_cells=tria.n_active_cells(); - const unsigned int n_dofs=dh.n_dofs(); - - deallog << "Cycle " << cycle << ':' - << std::endl - << " Number of active cells: " - << n_active_cells - << std::endl - << " Number of degrees of freedom: " - << n_dofs - << std::endl; - - convergence_table.add_value("cycle", cycle); - convergence_table.add_value("cells", n_active_cells); - convergence_table.add_value("dofs", n_dofs); - convergence_table.add_value("L2(phi)", L2_error); - convergence_table.add_value("Linfty(alpha)", alpha_error); -} + // The computation of the errors is + // exactly the same in all other + // example programs, and we won't + // comment too much. Notice how the + // same methods that are used in the + // finite element methods can be used + // here. + template + void BEMProblem::compute_errors(const unsigned int cycle) + { + Vector difference_per_cell (tria.n_active_cells()); + VectorTools::integrate_difference (mapping, dh, phi, + exact_solution, + difference_per_cell, + QGauss<(dim-1)>(2*fe.degree+1), + VectorTools::L2_norm); + const double L2_error = difference_per_cell.l2_norm(); + + + // The error in the alpha vector + // can be computed directly using + // the Vector::linfty_norm() + // function, since on each node, + // the value should be $\frac + // 12$. All errors are then output + // and appended to our + // ConvergenceTable object for + // later computation of convergence + // rates: + Vector difference_per_node(alpha); + difference_per_node.add(-.5); + + const double alpha_error = difference_per_node.linfty_norm(); + const unsigned int n_active_cells=tria.n_active_cells(); + const unsigned int n_dofs=dh.n_dofs(); + + deallog << "Cycle " << cycle << ':' + << std::endl + << " Number of active cells: " + << n_active_cells + << std::endl + << " Number of degrees of freedom: " + << n_dofs + << std::endl; + + convergence_table.add_value("cycle", cycle); + convergence_table.add_value("cells", n_active_cells); + convergence_table.add_value("dofs", n_dofs); + convergence_table.add_value("L2(phi)", L2_error); + convergence_table.add_value("Linfty(alpha)", alpha_error); + } - // Singular integration requires a - // careful selection of the - // quadrature rules. In particular - // the deal.II library provides - // quadrature rules which are - // taylored for logarithmic - // singularities (QGaussLog, - // QGaussLogR), as well as for 1/R - // singularities (QGaussOneOverR). - // - // Singular integration is typically - // obtained by constructing weighted - // quadrature formulas with singular - // weights, so that it is possible to - // write - // - // \f[ - // \int_K f(x) s(x) dx = \sum_{i=1}^N w_i f(q_i) - // \f] - // - // where $s(x)$ is a given - // singularity, and the weights and - // quadrature points $w_i,q_i$ are - // carefully selected to make the - // formula above an equality for a - // certain class of functions $f(x)$. - // - // In all the finite element examples - // we have seen so far, the weight of - // the quadrature itself (namely, the - // function $s(x)$), was always - // constantly equal to 1. For - // singular integration, we have two - // choices: we can use the definition - // above, factoring out the - // singularity from the integrand - // (i.e., integrating $f(x)$ with the - // special quadrature rule), or we - // can ask the quadrature rule to - // "normalize" the weights $w_i$ with - // $s(q_i)$: - // - // \f[ - // \int_K f(x) s(x) dx = - // \int_K g(x) dx = \sum_{i=1}^N \frac{w_i}{s(q_i)} g(q_i) - // \f] - // - // We use this second option, through - // the @p factor_out_singularity - // parameter of both QGaussLogR and - // QGaussOneOverR. - // - // These integrals are somewhat - // delicate, especially in two - // dimensions, due to the - // transformation from the real to - // the reference cell, where the - // variable of integration is scaled - // with the determinant of the - // transformation. - // - // In two dimensions this process - // does not result only in a factor - // appearing as a constant factor on - // the entire integral, but also on - // an additional integral alltogether - // that needs to be evaluated: - // - // \f[ - // \int_0^1 f(x)\ln(x/\alpha) dx = - // \int_0^1 f(x)\ln(x) dx - \int_0^1 f(x) \ln(\alpha) dx. - // \f] - // - // This process is taken care of by - // the constructor of the QGaussLogR - // class, which adds additional - // quadrature points and weights to - // take into consideration also the - // second part of the integral. - // - // A similar reasoning should be done - // in the three dimensional case, - // since the singular quadrature is - // taylored on the inverse of the - // radius $r$ in the reference cell, - // while our singular function lives - // in real space, however in the - // three dimensional case everything - // is simpler because the singularity - // scales linearly with the - // determinant of the - // transformation. This allows us to - // build the singular two dimensional - // quadrature rules only once and, - // reuse them over all cells. - // - // In the one dimensional singular - // integration this is not possible, - // since we need to know the scaling - // parameter for the quadrature, - // which is not known a priori. Here, - // the quadrature rule itself depends - // also on the size of the current - // cell. For this reason, it is - // necessary to create a new - // quadrature for each singular - // integration. - // - // The different quadrature rules are - // built inside the - // get_singular_quadrature, which is - // specialized for dim=2 and dim=3, - // and they are retrieved inside the - // assemble_system function. The - // index given as an argument is the - // index of the unit support point - // where the singularity is located. - -template<> -const Quadrature<2> & BEMProblem<3>::get_singular_quadrature( - const DoFHandler<2,3>::active_cell_iterator &, - const unsigned int index) const -{ - Assert(index < fe.dofs_per_cell, - ExcIndexRange(0, fe.dofs_per_cell, index)); - - static std::vector > quadratures; - if(quadratures.size() == 0) - for(unsigned int i=0; i(singular_quadrature_order, - fe.get_unit_support_points()[i], - true)); - return quadratures[index]; -} + // Singular integration requires a + // careful selection of the + // quadrature rules. In particular + // the deal.II library provides + // quadrature rules which are + // taylored for logarithmic + // singularities (QGaussLog, + // QGaussLogR), as well as for 1/R + // singularities (QGaussOneOverR). + // + // Singular integration is typically + // obtained by constructing weighted + // quadrature formulas with singular + // weights, so that it is possible to + // write + // + // \f[ + // \int_K f(x) s(x) dx = \sum_{i=1}^N w_i f(q_i) + // \f] + // + // where $s(x)$ is a given + // singularity, and the weights and + // quadrature points $w_i,q_i$ are + // carefully selected to make the + // formula above an equality for a + // certain class of functions $f(x)$. + // + // In all the finite element examples + // we have seen so far, the weight of + // the quadrature itself (namely, the + // function $s(x)$), was always + // constantly equal to 1. For + // singular integration, we have two + // choices: we can use the definition + // above, factoring out the + // singularity from the integrand + // (i.e., integrating $f(x)$ with the + // special quadrature rule), or we + // can ask the quadrature rule to + // "normalize" the weights $w_i$ with + // $s(q_i)$: + // + // \f[ + // \int_K f(x) s(x) dx = + // \int_K g(x) dx = \sum_{i=1}^N \frac{w_i}{s(q_i)} g(q_i) + // \f] + // + // We use this second option, through + // the @p factor_out_singularity + // parameter of both QGaussLogR and + // QGaussOneOverR. + // + // These integrals are somewhat + // delicate, especially in two + // dimensions, due to the + // transformation from the real to + // the reference cell, where the + // variable of integration is scaled + // with the determinant of the + // transformation. + // + // In two dimensions this process + // does not result only in a factor + // appearing as a constant factor on + // the entire integral, but also on + // an additional integral alltogether + // that needs to be evaluated: + // + // \f[ + // \int_0^1 f(x)\ln(x/\alpha) dx = + // \int_0^1 f(x)\ln(x) dx - \int_0^1 f(x) \ln(\alpha) dx. + // \f] + // + // This process is taken care of by + // the constructor of the QGaussLogR + // class, which adds additional + // quadrature points and weights to + // take into consideration also the + // second part of the integral. + // + // A similar reasoning should be done + // in the three dimensional case, + // since the singular quadrature is + // taylored on the inverse of the + // radius $r$ in the reference cell, + // while our singular function lives + // in real space, however in the + // three dimensional case everything + // is simpler because the singularity + // scales linearly with the + // determinant of the + // transformation. This allows us to + // build the singular two dimensional + // quadrature rules only once and, + // reuse them over all cells. + // + // In the one dimensional singular + // integration this is not possible, + // since we need to know the scaling + // parameter for the quadrature, + // which is not known a priori. Here, + // the quadrature rule itself depends + // also on the size of the current + // cell. For this reason, it is + // necessary to create a new + // quadrature for each singular + // integration. + // + // The different quadrature rules are + // built inside the + // get_singular_quadrature, which is + // specialized for dim=2 and dim=3, + // and they are retrieved inside the + // assemble_system function. The + // index given as an argument is the + // index of the unit support point + // where the singularity is located. + + template<> + const Quadrature<2> & BEMProblem<3>::get_singular_quadrature( + const DoFHandler<2,3>::active_cell_iterator &, + const unsigned int index) const + { + Assert(index < fe.dofs_per_cell, + ExcIndexRange(0, fe.dofs_per_cell, index)); + + static std::vector > quadratures; + if (quadratures.size() == 0) + for (unsigned int i=0; i(singular_quadrature_order, + fe.get_unit_support_points()[i], + true)); + return quadratures[index]; + } -template<> -const Quadrature<1> & BEMProblem<2>::get_singular_quadrature( - const DoFHandler<1,2>::active_cell_iterator &cell, - const unsigned int index) const -{ - Assert(index < fe.dofs_per_cell, - ExcIndexRange(0, fe.dofs_per_cell, index)); + template<> + const Quadrature<1> & BEMProblem<2>::get_singular_quadrature( + const DoFHandler<1,2>::active_cell_iterator &cell, + const unsigned int index) const + { + Assert(index < fe.dofs_per_cell, + ExcIndexRange(0, fe.dofs_per_cell, index)); - static Quadrature<1> * q_pointer = NULL; - if(q_pointer) delete q_pointer; + static Quadrature<1> * q_pointer = NULL; + if (q_pointer) delete q_pointer; - q_pointer = new QGaussLogR<1>(singular_quadrature_order, - fe.get_unit_support_points()[index], - 1./cell->measure(), true); - return (*q_pointer); -} + q_pointer = new QGaussLogR<1>(singular_quadrature_order, + fe.get_unit_support_points()[index], + 1./cell->measure(), true); + return (*q_pointer); + } - // @sect4{BEMProblem::compute_exterior_solution} - - // We'd like to also know something - // about the value of the potential - // $\phi$ in the exterior domain: - // after all our motivation to - // consider the boundary integral - // problem was that we wanted to know - // the velocity in the exterior - // domain! - // - // To this end, let us assume here - // that the boundary element domain - // is contained in the box - // $[-2,2]^{\text{dim}}$, and we - // extrapolate the actual solution - // inside this box using the - // convolution with the fundamental - // solution. The formula for this is - // given in the introduction. - // - // The reconstruction of the solution - // in the entire space is done on a - // continuous finite element grid of - // dimension dim. These are the usual - // ones, and we don't comment any - // further on them. At the end of the - // function, we output this exterior - // solution in, again, much the usual - // way. -template -void BEMProblem::compute_exterior_solution() -{ - Triangulation external_tria; - GridGenerator::hyper_cube(external_tria, -2, 2); + // @sect4{BEMProblem::compute_exterior_solution} - FE_Q external_fe(1); - DoFHandler external_dh (external_tria); - Vector external_phi; + // We'd like to also know something + // about the value of the potential + // $\phi$ in the exterior domain: + // after all our motivation to + // consider the boundary integral + // problem was that we wanted to know + // the velocity in the exterior + // domain! + // + // To this end, let us assume here + // that the boundary element domain + // is contained in the box + // $[-2,2]^{\text{dim}}$, and we + // extrapolate the actual solution + // inside this box using the + // convolution with the fundamental + // solution. The formula for this is + // given in the introduction. + // + // The reconstruction of the solution + // in the entire space is done on a + // continuous finite element grid of + // dimension dim. These are the usual + // ones, and we don't comment any + // further on them. At the end of the + // function, we output this exterior + // solution in, again, much the usual + // way. + template + void BEMProblem::compute_exterior_solution() + { + Triangulation external_tria; + GridGenerator::hyper_cube(external_tria, -2, 2); - external_tria.refine_global(external_refinement); - external_dh.distribute_dofs(external_fe); - external_phi.reinit(external_dh.n_dofs()); + FE_Q external_fe(1); + DoFHandler external_dh (external_tria); + Vector external_phi; - typename DoFHandler::active_cell_iterator - cell = dh.begin_active(), - endc = dh.end(); + external_tria.refine_global(external_refinement); + external_dh.distribute_dofs(external_fe); + external_phi.reinit(external_dh.n_dofs()); + typename DoFHandler::active_cell_iterator + cell = dh.begin_active(), + endc = dh.end(); - FEValues fe_v(mapping, fe, *quadrature, - update_values | - update_cell_normal_vectors | - update_quadrature_points | - update_JxW_values); - const unsigned int n_q_points = fe_v.n_quadrature_points; + FEValues fe_v(mapping, fe, *quadrature, + update_values | + update_cell_normal_vectors | + update_quadrature_points | + update_JxW_values); - std::vector dofs(fe.dofs_per_cell); + const unsigned int n_q_points = fe_v.n_quadrature_points; - std::vector local_phi(n_q_points); - std::vector normal_wind(n_q_points); - std::vector > local_wind(n_q_points, Vector(dim) ); + std::vector dofs(fe.dofs_per_cell); - std::vector > external_support_points(external_dh.n_dofs()); - DoFTools::map_dofs_to_support_points(StaticMappingQ1::mapping, - external_dh, external_support_points); + std::vector local_phi(n_q_points); + std::vector normal_wind(n_q_points); + std::vector > local_wind(n_q_points, Vector(dim) ); - for(cell = dh.begin_active(); cell != endc; ++cell) - { - fe_v.reinit(cell); + std::vector > external_support_points(external_dh.n_dofs()); + DoFTools::map_dofs_to_support_points(StaticMappingQ1::mapping, + external_dh, external_support_points); - const std::vector > &q_points = fe_v.get_quadrature_points(); - const std::vector > &normals = fe_v.get_cell_normal_vectors(); + for (cell = dh.begin_active(); cell != endc; ++cell) + { + fe_v.reinit(cell); - cell->get_dof_indices(dofs); - fe_v.get_function_values(phi, local_phi); + const std::vector > &q_points = fe_v.get_quadrature_points(); + const std::vector > &normals = fe_v.get_cell_normal_vectors(); - wind.vector_value_list(q_points, local_wind); + cell->get_dof_indices(dofs); + fe_v.get_function_values(phi, local_phi); - for(unsigned int q=0; q R = q_points[q] - external_support_points[i]; + for (unsigned int i=0; i R = q_points[q] - external_support_points[i]; - DataOut data_out; + external_phi(i) += ( ( LaplaceKernel::single_layer(R) * + normal_wind[q] + + + (LaplaceKernel::double_layer(R) * + normals[q] ) * + local_phi[q] ) * + fe_v.JxW(q) ); + } + } - data_out.attach_dof_handler(external_dh); - data_out.add_data_vector(external_phi, "external_phi"); - data_out.build_patches(); + DataOut data_out; - const std::string - filename = Utilities::int_to_string(dim) + "d_external.vtk"; - std::ofstream file(filename.c_str()); + data_out.attach_dof_handler(external_dh); + data_out.add_data_vector(external_phi, "external_phi"); + data_out.build_patches(); - data_out.write_vtk(file); -} + const std::string + filename = Utilities::int_to_string(dim) + "d_external.vtk"; + std::ofstream file(filename.c_str()); + + data_out.write_vtk(file); + } - // @sect4{BEMProblem::output_results} + // @sect4{BEMProblem::output_results} - // Outputting the results of our - // computations is a rather - // mechanical tasks. All the - // components of this function have - // been discussed before. -template -void BEMProblem::output_results(const unsigned int cycle) -{ - DataOut > dataout; + // Outputting the results of our + // computations is a rather + // mechanical tasks. All the + // components of this function have + // been discussed before. + template + void BEMProblem::output_results(const unsigned int cycle) + { + DataOut > dataout; - dataout.attach_dof_handler(dh); - dataout.add_data_vector(phi, "phi"); - dataout.add_data_vector(alpha, "alpha"); - dataout.build_patches(mapping, - mapping.get_degree(), - DataOut >::curved_inner_cells); + dataout.attach_dof_handler(dh); + dataout.add_data_vector(phi, "phi"); + dataout.add_data_vector(alpha, "alpha"); + dataout.build_patches(mapping, + mapping.get_degree(), + DataOut >::curved_inner_cells); - std::string filename = ( Utilities::int_to_string(dim) + - "d_boundary_solution_" + - Utilities::int_to_string(cycle) + - ".vtk" ); - std::ofstream file(filename.c_str()); + std::string filename = ( Utilities::int_to_string(dim) + + "d_boundary_solution_" + + Utilities::int_to_string(cycle) + + ".vtk" ); + std::ofstream file(filename.c_str()); - dataout.write_vtk(file); + dataout.write_vtk(file); - if(cycle == n_cycles-1) - { - convergence_table.set_precision("L2(phi)", 3); - convergence_table.set_precision("Linfty(alpha)", 3); - - convergence_table.set_scientific("L2(phi)", true); - convergence_table.set_scientific("Linfty(alpha)", true); - - convergence_table - .evaluate_convergence_rates("L2(phi)", ConvergenceTable::reduction_rate_log2); - convergence_table - .evaluate_convergence_rates("Linfty(alpha)", ConvergenceTable::reduction_rate_log2); - deallog << std::endl; - convergence_table.write_text(std::cout); - } -} + if (cycle == n_cycles-1) + { + convergence_table.set_precision("L2(phi)", 3); + convergence_table.set_precision("Linfty(alpha)", 3); + + convergence_table.set_scientific("L2(phi)", true); + convergence_table.set_scientific("Linfty(alpha)", true); + + convergence_table + .evaluate_convergence_rates("L2(phi)", ConvergenceTable::reduction_rate_log2); + convergence_table + .evaluate_convergence_rates("Linfty(alpha)", ConvergenceTable::reduction_rate_log2); + deallog << std::endl; + convergence_table.write_text(std::cout); + } + } - // @sect4{BEMProblem::run} + // @sect4{BEMProblem::run} - // This is the main function. It - // should be self explanatory in its - // briefness: -template -void BEMProblem::run() -{ + // This is the main function. It + // should be self explanatory in its + // briefness: + template + void BEMProblem::run() + { - read_parameters("parameters.prm"); + read_parameters("parameters.prm"); - if(run_in_this_dimension == false) - { - deallog << "Run in dimension " << dim - << " explicitly disabled in parameter file. " - << std::endl; - return; - } + if (run_in_this_dimension == false) + { + deallog << "Run in dimension " << dim + << " explicitly disabled in parameter file. " + << std::endl; + return; + } - read_domain(); + read_domain(); - for(unsigned int cycle=0; cycle laplace_problem_2d(degree, mapping_degree); diff --git a/deal.II/examples/step-35/step-35.cc b/deal.II/examples/step-35/step-35.cc index 6a1c0b70c6..4402ddd083 100644 --- a/deal.II/examples/step-35/step-35.cc +++ b/deal.II/examples/step-35/step-35.cc @@ -1,7 +1,7 @@ /* $Id$ */ /* Version: $Name: $ */ /* */ -/* Copyright (C) 2007, 2008, 2009, 2010 by the deal.II authors */ +/* Copyright (C) 2007, 2008, 2009, 2010, 2011 by the deal.II authors */ /* Author: Abner Salgado, Texas A&M University 2009 */ /* */ /* This file is subject to QPL and may not be distributed */ @@ -63,1459 +63,1463 @@ #include #include - // Finally we import all the deal.II - // names to the global namespace -using namespace dealii; - - - - // @sect3{Run time parameters} - // - // Since our method has several - // parameters that can be fine-tuned - // we put them into an external file, - // so that they can be determined at - // run-time. - // - // This includes, in particular, the - // formulation of the equation for - // the auxiliary variable $\phi$, for - // which we declare an - // enum. Next, we - // declare a class that is going to - // read and store all the parameters - // that our program needs to run. -namespace RunTimeParameters + // Finally this is as in all previous + // programs: +namespace Step35 { - enum MethodFormulation + using namespace dealii; + + + + // @sect3{Run time parameters} + // + // Since our method has several + // parameters that can be fine-tuned + // we put them into an external file, + // so that they can be determined at + // run-time. + // + // This includes, in particular, the + // formulation of the equation for + // the auxiliary variable $\phi$, for + // which we declare an + // enum. Next, we + // declare a class that is going to + // read and store all the parameters + // that our program needs to run. + namespace RunTimeParameters { - METHOD_STANDARD, - METHOD_ROTATIONAL - }; - - class Data_Storage - { - public: - Data_Storage(); - ~Data_Storage(); - void read_data (const char *filename); - MethodFormulation form; - double initial_time, - final_time, - Reynolds; - double dt; - unsigned int n_global_refines, - pressure_degree; - unsigned int vel_max_iterations, - vel_Krylov_size, - vel_off_diagonals, - vel_update_prec; - double vel_eps, - vel_diag_strength; - bool verbose; - unsigned int output_interval; - protected: - ParameterHandler prm; - }; - - // In the constructor of this class - // we declare all the - // parameters. The details of how - // this works have been discussed - // elsewhere, for example in - // step-19 and step-29. - Data_Storage::Data_Storage() - { - prm.declare_entry ("Method_Form", "rotational", - Patterns::Selection ("rotational|standard"), - " Used to select the type of method that we are going " - "to use. "); - prm.enter_subsection ("Physical data"); + enum MethodFormulation { - prm.declare_entry ("initial_time", "0.", - Patterns::Double (0.), - " The initial time of the simulation. "); - prm.declare_entry ("final_time", "1.", - Patterns::Double (0.), - " The final time of the simulation. "); - prm.declare_entry ("Reynolds", "1.", - Patterns::Double (0.), - " The Reynolds number. "); - } - prm.leave_subsection(); + METHOD_STANDARD, + METHOD_ROTATIONAL + }; - prm.enter_subsection ("Time step data"); + class Data_Storage { - prm.declare_entry ("dt", "5e-4", - Patterns::Double (0.), - " The time step size. "); - } - prm.leave_subsection(); + public: + Data_Storage(); + ~Data_Storage(); + void read_data (const char *filename); + MethodFormulation form; + double initial_time, + final_time, + Reynolds; + double dt; + unsigned int n_global_refines, + pressure_degree; + unsigned int vel_max_iterations, + vel_Krylov_size, + vel_off_diagonals, + vel_update_prec; + double vel_eps, + vel_diag_strength; + bool verbose; + unsigned int output_interval; + protected: + ParameterHandler prm; + }; - prm.enter_subsection ("Space discretization"); + // In the constructor of this class + // we declare all the + // parameters. The details of how + // this works have been discussed + // elsewhere, for example in + // step-19 and step-29. + Data_Storage::Data_Storage() { - prm.declare_entry ("n_of_refines", "0", - Patterns::Integer (0, 15), - " The number of global refines we do on the mesh. "); - prm.declare_entry ("pressure_fe_degree", "1", - Patterns::Integer (1, 5), - " The polynomial degree for the pressure space. "); - } - prm.leave_subsection(); + prm.declare_entry ("Method_Form", "rotational", + Patterns::Selection ("rotational|standard"), + " Used to select the type of method that we are going " + "to use. "); + prm.enter_subsection ("Physical data"); + { + prm.declare_entry ("initial_time", "0.", + Patterns::Double (0.), + " The initial time of the simulation. "); + prm.declare_entry ("final_time", "1.", + Patterns::Double (0.), + " The final time of the simulation. "); + prm.declare_entry ("Reynolds", "1.", + Patterns::Double (0.), + " The Reynolds number. "); + } + prm.leave_subsection(); - prm.enter_subsection ("Data solve velocity"); - { - prm.declare_entry ("max_iterations", "1000", - Patterns::Integer (1, 1000), - " The maximal number of iterations GMRES must make. "); - prm.declare_entry ("eps", "1e-12", - Patterns::Double (0.), - " The stopping criterion. "); - prm.declare_entry ("Krylov_size", "30", - Patterns::Integer(1), - " The size of the Krylov subspace to be used. "); - prm.declare_entry ("off_diagonals", "60", - Patterns::Integer(0), - " The number of off-diagonal elements ILU must " - "compute. "); - prm.declare_entry ("diag_strength", "0.01", - Patterns::Double (0.), - " Diagonal strengthening coefficient. "); - prm.declare_entry ("update_prec", "15", + prm.enter_subsection ("Time step data"); + { + prm.declare_entry ("dt", "5e-4", + Patterns::Double (0.), + " The time step size. "); + } + prm.leave_subsection(); + + prm.enter_subsection ("Space discretization"); + { + prm.declare_entry ("n_of_refines", "0", + Patterns::Integer (0, 15), + " The number of global refines we do on the mesh. "); + prm.declare_entry ("pressure_fe_degree", "1", + Patterns::Integer (1, 5), + " The polynomial degree for the pressure space. "); + } + prm.leave_subsection(); + + prm.enter_subsection ("Data solve velocity"); + { + prm.declare_entry ("max_iterations", "1000", + Patterns::Integer (1, 1000), + " The maximal number of iterations GMRES must make. "); + prm.declare_entry ("eps", "1e-12", + Patterns::Double (0.), + " The stopping criterion. "); + prm.declare_entry ("Krylov_size", "30", + Patterns::Integer(1), + " The size of the Krylov subspace to be used. "); + prm.declare_entry ("off_diagonals", "60", + Patterns::Integer(0), + " The number of off-diagonal elements ILU must " + "compute. "); + prm.declare_entry ("diag_strength", "0.01", + Patterns::Double (0.), + " Diagonal strengthening coefficient. "); + prm.declare_entry ("update_prec", "15", + Patterns::Integer(1), + " This number indicates how often we need to " + "update the preconditioner"); + } + prm.leave_subsection(); + + prm.declare_entry ("verbose", "true", + Patterns::Bool(), + " This indicates whether the output of the solution " + "process should be verbose. "); + + prm.declare_entry ("output_interval", "1", Patterns::Integer(1), - " This number indicates how often we need to " - "update the preconditioner"); + " This indicates between how many time steps we print " + "the solution. "); } - prm.leave_subsection(); - prm.declare_entry ("verbose", "true", - Patterns::Bool(), - " This indicates whether the output of the solution " - "process should be verbose. "); - prm.declare_entry ("output_interval", "1", - Patterns::Integer(1), - " This indicates between how many time steps we print " - "the solution. "); - } + Data_Storage::~Data_Storage() + {} - Data_Storage::~Data_Storage() - {} + void Data_Storage::read_data (const char *filename) + { + std::ifstream file (filename); + AssertThrow (file, ExcFileNotOpen (filename)); + prm.read_input (file); - void Data_Storage::read_data (const char *filename) - { - std::ifstream file (filename); - AssertThrow (file, ExcFileNotOpen (filename)); + if (prm.get ("Method_Form") == std::string ("rotational")) + form = METHOD_ROTATIONAL; + else + form = METHOD_STANDARD; - prm.read_input (file); + prm.enter_subsection ("Physical data"); + { + initial_time = prm.get_double ("initial_time"); + final_time = prm.get_double ("final_time"); + Reynolds = prm.get_double ("Reynolds"); + } + prm.leave_subsection(); - if (prm.get ("Method_Form") == std::string ("rotational")) - form = METHOD_ROTATIONAL; - else - form = METHOD_STANDARD; + prm.enter_subsection ("Time step data"); + { + dt = prm.get_double ("dt"); + } + prm.leave_subsection(); - prm.enter_subsection ("Physical data"); - { - initial_time = prm.get_double ("initial_time"); - final_time = prm.get_double ("final_time"); - Reynolds = prm.get_double ("Reynolds"); - } - prm.leave_subsection(); + prm.enter_subsection ("Space discretization"); + { + n_global_refines = prm.get_integer ("n_of_refines"); + pressure_degree = prm.get_integer ("pressure_fe_degree"); + } + prm.leave_subsection(); - prm.enter_subsection ("Time step data"); - { - dt = prm.get_double ("dt"); + prm.enter_subsection ("Data solve velocity"); + { + vel_max_iterations = prm.get_integer ("max_iterations"); + vel_eps = prm.get_double ("eps"); + vel_Krylov_size = prm.get_integer ("Krylov_size"); + vel_off_diagonals = prm.get_integer ("off_diagonals"); + vel_diag_strength = prm.get_double ("diag_strength"); + vel_update_prec = prm.get_integer ("update_prec"); + } + prm.leave_subsection(); + + verbose = prm.get_bool ("verbose"); + + output_interval = prm.get_integer ("output_interval"); } - prm.leave_subsection(); + } + - prm.enter_subsection ("Space discretization"); + + // @sect3{Equation data} + + // In the next namespace, we declare + // the initial and boundary + // conditions: + namespace EquationData + { + // As we have chosen a completely + // decoupled formulation, we will + // not take advantage of deal.II's + // capabilities to handle vector + // valued problems. We do, however, + // want to use an interface for the + // equation data that is somehow + // dimension independent. To be + // able to do that, our functions + // should be able to know on which + // spatial component we are + // currently working, and we should + // be able to have a common + // interface to do that. The + // following class is an attempt in + // that direction. + template + class MultiComponentFunction: public Function { - n_global_refines = prm.get_integer ("n_of_refines"); - pressure_degree = prm.get_integer ("pressure_fe_degree"); - } - prm.leave_subsection(); + public: + MultiComponentFunction (const double initial_time = 0.); + void set_component (const unsigned int d); + protected: + unsigned int comp; + }; - prm.enter_subsection ("Data solve velocity"); + template + MultiComponentFunction:: + MultiComponentFunction (const double initial_time) + : + Function (1, initial_time), comp(0) + {} + + + template + void MultiComponentFunction::set_component(const unsigned int d) { - vel_max_iterations = prm.get_integer ("max_iterations"); - vel_eps = prm.get_double ("eps"); - vel_Krylov_size = prm.get_integer ("Krylov_size"); - vel_off_diagonals = prm.get_integer ("off_diagonals"); - vel_diag_strength = prm.get_double ("diag_strength"); - vel_update_prec = prm.get_integer ("update_prec"); + Assert (d + class Velocity : public MultiComponentFunction + { + public: + Velocity (const double initial_time = 0.0); + virtual double value (const Point &p, + const unsigned int component = 0) const; + virtual void value_list (const std::vector< Point > &points, + std::vector &values, + const unsigned int component = 0) const; + }; - // @sect3{Equation data} - // In the next namespace, we declare - // the initial and boundary - // conditions: -namespace EquationData -{ - // As we have chosen a completely - // decoupled formulation, we will - // not take advantage of deal.II's - // capabilities to handle vector - // valued problems. We do, however, - // want to use an interface for the - // equation data that is somehow - // dimension independent. To be - // able to do that, our functions - // should be able to know on which - // spatial component we are - // currently working, and we should - // be able to have a common - // interface to do that. The - // following class is an attempt in - // that direction. - template - class MultiComponentFunction: public Function - { - public: - MultiComponentFunction (const double initial_time = 0.); - void set_component (const unsigned int d); - protected: - unsigned int comp; - }; + template + Velocity::Velocity (const double initial_time) + : + MultiComponentFunction (initial_time) + {} - template - MultiComponentFunction:: - MultiComponentFunction (const double initial_time) - : - Function (1, initial_time), comp(0) - {} + template + void Velocity::value_list (const std::vector > &points, + std::vector &values, + const unsigned int) const + { + const unsigned int n_points = points.size(); + Assert (values.size() == n_points, + ExcDimensionMismatch (values.size(), n_points)); + for (unsigned int i=0; i::value (points[i]); + } - template - void MultiComponentFunction::set_component(const unsigned int d) - { - Assert (d + double Velocity::value (const Point &p, + const unsigned int) const + { + if (this->comp == 0) + { + const double Um = 1.5; + const double H = 4.1; + return 4.*Um*p(1)*(H - p(1))/(H*H); + } + else + return 0.; + } - // With this class defined, we - // declare classes that describe - // the boundary conditions for - // velocity and pressure: - template - class Velocity : public MultiComponentFunction - { - public: - Velocity (const double initial_time = 0.0); - virtual double value (const Point &p, - const unsigned int component = 0) const; - virtual void value_list (const std::vector< Point > &points, - std::vector &values, - const unsigned int component = 0) const; - }; + template + class Pressure: public Function + { + public: + Pressure (const double initial_time = 0.0); + virtual double value (const Point &p, + const unsigned int component = 0) const; - template - Velocity::Velocity (const double initial_time) - : - MultiComponentFunction (initial_time) - {} + virtual void value_list (const std::vector< Point > &points, + std::vector &values, + const unsigned int component = 0) const; + }; + template + Pressure::Pressure (const double initial_time) + : + Function (1, initial_time) + {} - template - void Velocity::value_list (const std::vector > &points, - std::vector &values, - const unsigned int) const - { - const unsigned int n_points = points.size(); - Assert (values.size() == n_points, - ExcDimensionMismatch (values.size(), n_points)); - for (unsigned int i=0; i::value (points[i]); - } + template + double Pressure::value (const Point &p, + const unsigned int) const + { + return 25.-p(0); + } - template - double Velocity::value (const Point &p, - const unsigned int) const - { - if (this->comp == 0) - { - const double Um = 1.5; - const double H = 4.1; - return 4.*Um*p(1)*(H - p(1))/(H*H); - } - else - return 0.; + template + void Pressure::value_list (const std::vector > &points, + std::vector &values, + const unsigned int) const + { + const unsigned int n_points = points.size(); + Assert (values.size() == n_points, ExcDimensionMismatch (values.size(), n_points)); + for (unsigned int i=0; i::value (points[i]); + } } + // @sect3{The NavierStokesProjection class} + + // Now for the main class of the program. It + // implements the various versions of the + // projection method for Navier-Stokes + // equations. The names for all the methods + // and member variables should be + // self-explanatory, taking into account the + // implementation details given in the + // introduction. template - class Pressure: public Function + class NavierStokesProjection { public: - Pressure (const double initial_time = 0.0); - - virtual double value (const Point &p, - const unsigned int component = 0) const; + NavierStokesProjection (const RunTimeParameters::Data_Storage &data); - virtual void value_list (const std::vector< Point > &points, - std::vector &values, - const unsigned int component = 0) const; + void run (const bool verbose = false, + const unsigned int n_plots = 10); + protected: + RunTimeParameters::MethodFormulation type; + + const unsigned int deg; + const double dt; + const double t_0, T, Re; + + EquationData::Velocity vel_exact; + std::map boundary_values; + std::vector boundary_indicators; + + Triangulation triangulation; + + FE_Q fe_velocity; + FE_Q fe_pressure; + + DoFHandler dof_handler_velocity; + DoFHandler dof_handler_pressure; + + QGauss quadrature_pressure; + QGauss quadrature_velocity; + + SparsityPattern sparsity_pattern_velocity; + SparsityPattern sparsity_pattern_pressure; + SparsityPattern sparsity_pattern_pres_vel; + + SparseMatrix vel_Laplace_plus_Mass; + SparseMatrix vel_it_matrix[dim]; + SparseMatrix vel_Mass; + SparseMatrix vel_Laplace; + SparseMatrix vel_Advection; + SparseMatrix pres_Laplace; + SparseMatrix pres_Mass; + SparseMatrix pres_Diff[dim]; + SparseMatrix pres_iterative; + + Vector pres_n; + Vector pres_n_minus_1; + Vector phi_n; + Vector phi_n_minus_1; + Vector u_n[dim]; + Vector u_n_minus_1[dim]; + Vector u_star[dim]; + Vector force[dim]; + Vector v_tmp; + Vector pres_tmp; + Vector rot_u; + + SparseILU prec_velocity[dim]; + SparseILU prec_pres_Laplace; + SparseDirectUMFPACK prec_mass; + SparseDirectUMFPACK prec_vel_mass; + + DeclException2 (ExcInvalidTimeStep, + double, double, + << " The time step " << arg1 << " is out of range." + << std::endl + << " The permitted range is (0," << arg2 << "]"); + + void create_triangulation_and_dofs (const unsigned int n_refines); + + void initialize(); + + void interpolate_velocity (); + + void diffusion_step (const bool reinit_prec); + + void projection_step (const bool reinit_prec); + + void update_pressure (const bool reinit_prec); + + private: + unsigned int vel_max_its; + unsigned int vel_Krylov_size; + unsigned int vel_off_diagonals; + unsigned int vel_update_prec; + double vel_eps; + double vel_diag_strength; + + void initialize_velocity_matrices(); + + void initialize_pressure_matrices(); + + // The next few structures and functions + // are for doing various things in + // parallel. They follow the scheme laid + // out in @ref threads, using the + // WorkStream class. As explained there, + // this requires us to declare two + // structures for each of the assemblers, + // a per-task data and a scratch data + // structure. These are then handed over + // to functions that assemble local + // contributions and that copy these + // local contributions to the global + // objects. + // + // One of the things that are specific to + // this program is that we don't just + // have a single DoFHandler object that + // represents both the velocities and the + // pressure, but we use individual + // DoFHandler objects for these two kinds + // of variables. We pay for this + // optimization when we want to assemble + // terms that involve both variables, + // such as the divergence of the velocity + // and the gradient of the pressure, + // times the respective test + // functions. When doing so, we can't + // just anymore use a single FEValues + // object, but rather we need two, and + // they need to be initialized with cell + // iterators that point to the same cell + // in the triangulation but different + // DoFHandlers. + // + // To do this in practice, we declare a + // "synchronous" iterator -- an object + // that internally consists of several + // (in our case two) iterators, and each + // time the synchronous iteration is + // moved up one step, each of the + // iterators stored internally is moved + // up one step as well, thereby always + // staying in sync. As it so happens, + // there is a deal.II class that + // facilitates this sort of thing. + typedef std_cxx1x::tuple< typename DoFHandler::active_cell_iterator, + typename DoFHandler::active_cell_iterator + > IteratorTuple; + + typedef SynchronousIterators IteratorPair; + + void initialize_gradient_operator(); + + struct InitGradPerTaskData + { + unsigned int d; + unsigned int vel_dpc; + unsigned int pres_dpc; + FullMatrix local_grad; + std::vector vel_local_dof_indices; + std::vector pres_local_dof_indices; + + InitGradPerTaskData (const unsigned int dd, + const unsigned int vdpc, + const unsigned int pdpc) + : + d(dd), + vel_dpc (vdpc), + pres_dpc (pdpc), + local_grad (vdpc, pdpc), + vel_local_dof_indices (vdpc), + pres_local_dof_indices (pdpc) + {} + }; + + struct InitGradScratchData + { + unsigned int nqp; + FEValues fe_val_vel; + FEValues fe_val_pres; + InitGradScratchData (const FE_Q &fe_v, + const FE_Q &fe_p, + const QGauss &quad, + const UpdateFlags flags_v, + const UpdateFlags flags_p) + : + nqp (quad.size()), + fe_val_vel (fe_v, quad, flags_v), + fe_val_pres (fe_p, quad, flags_p) + {} + InitGradScratchData (const InitGradScratchData &data) + : + nqp (data.nqp), + fe_val_vel (data.fe_val_vel.get_fe(), + data.fe_val_vel.get_quadrature(), + data.fe_val_vel.get_update_flags()), + fe_val_pres (data.fe_val_pres.get_fe(), + data.fe_val_pres.get_quadrature(), + data.fe_val_pres.get_update_flags()) + {} + }; + + void assemble_one_cell_of_gradient (const IteratorPair &SI, + InitGradScratchData &scratch, + InitGradPerTaskData &data); + + void copy_gradient_local_to_global (const InitGradPerTaskData &data); + + // The same general layout also applies + // to the following classes and functions + // implementing the assembly of the + // advection term: + void assemble_advection_term(); + + struct AdvectionPerTaskData + { + FullMatrix local_advection; + std::vector local_dof_indices; + AdvectionPerTaskData (const unsigned int dpc) + : + local_advection (dpc, dpc), + local_dof_indices (dpc) + {} + }; + + struct AdvectionScratchData + { + unsigned int nqp; + unsigned int dpc; + std::vector< Point > u_star_local; + std::vector< Tensor<1,dim> > grad_u_star; + std::vector u_star_tmp; + FEValues fe_val; + AdvectionScratchData (const FE_Q &fe, + const QGauss &quad, + const UpdateFlags flags) + : + nqp (quad.size()), + dpc (fe.dofs_per_cell), + u_star_local (nqp), + grad_u_star (nqp), + u_star_tmp (nqp), + fe_val (fe, quad, flags) + {} + + AdvectionScratchData (const AdvectionScratchData &data) + : + nqp (data.nqp), + dpc (data.dpc), + u_star_local (nqp), + grad_u_star (nqp), + u_star_tmp (nqp), + fe_val (data.fe_val.get_fe(), + data.fe_val.get_quadrature(), + data.fe_val.get_update_flags()) + {} + }; + + void assemble_one_cell_of_advection (const typename DoFHandler::active_cell_iterator &cell, + AdvectionScratchData &scratch, + AdvectionPerTaskData &data); + + void copy_advection_local_to_global (const AdvectionPerTaskData &data); + + // The final few functions implement the + // diffusion solve as well as + // postprocessing the output, including + // computing the curl of the velocity: + void diffusion_component_solve (const unsigned int d); + + void output_results (const unsigned int step); + + void assemble_vorticity (const bool reinit_prec); }; - template - Pressure::Pressure (const double initial_time) - : - Function (1, initial_time) - {} - template - double Pressure::value (const Point &p, - const unsigned int) const - { - return 25.-p(0); - } + // @sect4{ NavierStokesProjection::NavierStokesProjection } + // In the constructor, we just read + // all the data from the + // Data_Storage object + // that is passed as an argument, + // verify that the data we read is + // reasonable and, finally, create + // the triangulation and load the + // initial data. template - void Pressure::value_list (const std::vector > &points, - std::vector &values, - const unsigned int) const + NavierStokesProjection::NavierStokesProjection(const RunTimeParameters::Data_Storage &data) + : + type (data.form), + deg (data.pressure_degree), + dt (data.dt), + t_0 (data.initial_time), + T (data.final_time), + Re (data.Reynolds), + vel_exact (data.initial_time), + fe_velocity (deg+1), + fe_pressure (deg), + dof_handler_velocity (triangulation), + dof_handler_pressure (triangulation), + quadrature_pressure (deg+1), + quadrature_velocity (deg+2), + vel_max_its (data.vel_max_iterations), + vel_Krylov_size (data.vel_Krylov_size), + vel_off_diagonals (data.vel_off_diagonals), + vel_update_prec (data.vel_update_prec), + vel_eps (data.vel_eps), + vel_diag_strength (data.vel_diag_strength) { - const unsigned int n_points = points.size(); - Assert (values.size() == n_points, ExcDimensionMismatch (values.size(), n_points)); - for (unsigned int i=0; i::value (points[i]); + if(deg < 1) + std::cout << " WARNING: The chosen pair of finite element spaces is not stable." + << std::endl + << " The obtained results will be nonsense" + << std::endl; + + AssertThrow (! ( (dt <= 0.) || (dt > .5*T)), ExcInvalidTimeStep (dt, .5*T)); + + create_triangulation_and_dofs (data.n_global_refines); + initialize(); } -} + // @sect4{ NavierStokesProjection::create_triangulation_and_dofs } - // @sect3{The NavierStokesProjection class} + // The method that creates the + // triangulation and refines it the + // needed number of times. After + // creating the triangulation, it + // creates the mesh dependent data, + // i.e. it distributes degrees of + // freedom and renumbers them, and + // initializes the matrices and + // vectors that we will use. + template + void + NavierStokesProjection:: + create_triangulation_and_dofs (const unsigned int n_refines) + { + GridIn grid_in; + grid_in.attach_triangulation (triangulation); - // Now for the main class of the program. It - // implements the various versions of the - // projection method for Navier-Stokes - // equations. The names for all the methods - // and member variables should be - // self-explanatory, taking into account the - // implementation details given in the - // introduction. -template -class NavierStokesProjection -{ - public: - NavierStokesProjection (const RunTimeParameters::Data_Storage &data); - - void run (const bool verbose = false, - const unsigned int n_plots = 10); - protected: - RunTimeParameters::MethodFormulation type; - - const unsigned int deg; - const double dt; - const double t_0, T, Re; - - EquationData::Velocity vel_exact; - std::map boundary_values; - std::vector boundary_indicators; - - Triangulation triangulation; - - FE_Q fe_velocity; - FE_Q fe_pressure; - - DoFHandler dof_handler_velocity; - DoFHandler dof_handler_pressure; - - QGauss quadrature_pressure; - QGauss quadrature_velocity; - - SparsityPattern sparsity_pattern_velocity; - SparsityPattern sparsity_pattern_pressure; - SparsityPattern sparsity_pattern_pres_vel; - - SparseMatrix vel_Laplace_plus_Mass; - SparseMatrix vel_it_matrix[dim]; - SparseMatrix vel_Mass; - SparseMatrix vel_Laplace; - SparseMatrix vel_Advection; - SparseMatrix pres_Laplace; - SparseMatrix pres_Mass; - SparseMatrix pres_Diff[dim]; - SparseMatrix pres_iterative; - - Vector pres_n; - Vector pres_n_minus_1; - Vector phi_n; - Vector phi_n_minus_1; - Vector u_n[dim]; - Vector u_n_minus_1[dim]; - Vector u_star[dim]; - Vector force[dim]; - Vector v_tmp; - Vector pres_tmp; - Vector rot_u; - - SparseILU prec_velocity[dim]; - SparseILU prec_pres_Laplace; - SparseDirectUMFPACK prec_mass; - SparseDirectUMFPACK prec_vel_mass; - - DeclException2 (ExcInvalidTimeStep, - double, double, - << " The time step " << arg1 << " is out of range." - << std::endl - << " The permitted range is (0," << arg2 << "]"); - - void create_triangulation_and_dofs (const unsigned int n_refines); - - void initialize(); - - void interpolate_velocity (); - - void diffusion_step (const bool reinit_prec); - - void projection_step (const bool reinit_prec); - - void update_pressure (const bool reinit_prec); - - private: - unsigned int vel_max_its; - unsigned int vel_Krylov_size; - unsigned int vel_off_diagonals; - unsigned int vel_update_prec; - double vel_eps; - double vel_diag_strength; - - void initialize_velocity_matrices(); - - void initialize_pressure_matrices(); - - // The next few structures and functions - // are for doing various things in - // parallel. They follow the scheme laid - // out in @ref threads, using the - // WorkStream class. As explained there, - // this requires us to declare two - // structures for each of the assemblers, - // a per-task data and a scratch data - // structure. These are then handed over - // to functions that assemble local - // contributions and that copy these - // local contributions to the global - // objects. - // - // One of the things that are specific to - // this program is that we don't just - // have a single DoFHandler object that - // represents both the velocities and the - // pressure, but we use individual - // DoFHandler objects for these two kinds - // of variables. We pay for this - // optimization when we want to assemble - // terms that involve both variables, - // such as the divergence of the velocity - // and the gradient of the pressure, - // times the respective test - // functions. When doing so, we can't - // just anymore use a single FEValues - // object, but rather we need two, and - // they need to be initialized with cell - // iterators that point to the same cell - // in the triangulation but different - // DoFHandlers. - // - // To do this in practice, we declare a - // "synchronous" iterator -- an object - // that internally consists of several - // (in our case two) iterators, and each - // time the synchronous iteration is - // moved up one step, each of the - // iterators stored internally is moved - // up one step as well, thereby always - // staying in sync. As it so happens, - // there is a deal.II class that - // facilitates this sort of thing. - typedef std_cxx1x::tuple< typename DoFHandler::active_cell_iterator, - typename DoFHandler::active_cell_iterator - > IteratorTuple; - - typedef SynchronousIterators IteratorPair; - - void initialize_gradient_operator(); - - struct InitGradPerTaskData { - unsigned int d; - unsigned int vel_dpc; - unsigned int pres_dpc; - FullMatrix local_grad; - std::vector vel_local_dof_indices; - std::vector pres_local_dof_indices; - - InitGradPerTaskData (const unsigned int dd, - const unsigned int vdpc, - const unsigned int pdpc) - : - d(dd), - vel_dpc (vdpc), - pres_dpc (pdpc), - local_grad (vdpc, pdpc), - vel_local_dof_indices (vdpc), - pres_local_dof_indices (pdpc) - {} - }; + std::string filename = "nsbench2.inp"; + std::ifstream file (filename.c_str()); + Assert (file, ExcFileNotOpen (filename.c_str())); + grid_in.read_ucd (file); + } - struct InitGradScratchData - { - unsigned int nqp; - FEValues fe_val_vel; - FEValues fe_val_pres; - InitGradScratchData (const FE_Q &fe_v, - const FE_Q &fe_p, - const QGauss &quad, - const UpdateFlags flags_v, - const UpdateFlags flags_p) - : - nqp (quad.size()), - fe_val_vel (fe_v, quad, flags_v), - fe_val_pres (fe_p, quad, flags_p) - {} - InitGradScratchData (const InitGradScratchData &data) - : - nqp (data.nqp), - fe_val_vel (data.fe_val_vel.get_fe(), - data.fe_val_vel.get_quadrature(), - data.fe_val_vel.get_update_flags()), - fe_val_pres (data.fe_val_pres.get_fe(), - data.fe_val_pres.get_quadrature(), - data.fe_val_pres.get_update_flags()) - {} - }; + std::cout << "Number of refines = " << n_refines + << std::endl; + triangulation.refine_global (n_refines); + std::cout << "Number of active cells: " << triangulation.n_active_cells() + << std::endl; - void assemble_one_cell_of_gradient (const IteratorPair &SI, - InitGradScratchData &scratch, - InitGradPerTaskData &data); + boundary_indicators = triangulation.get_boundary_indicators(); - void copy_gradient_local_to_global (const InitGradPerTaskData &data); + dof_handler_velocity.distribute_dofs (fe_velocity); + DoFRenumbering::boost::Cuthill_McKee (dof_handler_velocity); + dof_handler_pressure.distribute_dofs (fe_pressure); + DoFRenumbering::boost::Cuthill_McKee (dof_handler_pressure); - // The same general layout also applies - // to the following classes and functions - // implementing the assembly of the - // advection term: - void assemble_advection_term(); + initialize_velocity_matrices(); + initialize_pressure_matrices(); + initialize_gradient_operator(); - struct AdvectionPerTaskData - { - FullMatrix local_advection; - std::vector local_dof_indices; - AdvectionPerTaskData (const unsigned int dpc) - : - local_advection (dpc, dpc), - local_dof_indices (dpc) - {} - }; - - struct AdvectionScratchData - { - unsigned int nqp; - unsigned int dpc; - std::vector< Point > u_star_local; - std::vector< Tensor<1,dim> > grad_u_star; - std::vector u_star_tmp; - FEValues fe_val; - AdvectionScratchData (const FE_Q &fe, - const QGauss &quad, - const UpdateFlags flags) - : - nqp (quad.size()), - dpc (fe.dofs_per_cell), - u_star_local (nqp), - grad_u_star (nqp), - u_star_tmp (nqp), - fe_val (fe, quad, flags) - {} - - AdvectionScratchData (const AdvectionScratchData &data) - : - nqp (data.nqp), - dpc (data.dpc), - u_star_local (nqp), - grad_u_star (nqp), - u_star_tmp (nqp), - fe_val (data.fe_val.get_fe(), - data.fe_val.get_quadrature(), - data.fe_val.get_update_flags()) - {} - }; + pres_n.reinit (dof_handler_pressure.n_dofs()); + pres_n_minus_1.reinit (dof_handler_pressure.n_dofs()); + phi_n.reinit (dof_handler_pressure.n_dofs()); + phi_n_minus_1.reinit (dof_handler_pressure.n_dofs()); + pres_tmp.reinit (dof_handler_pressure.n_dofs()); + for(unsigned int d=0; d::active_cell_iterator &cell, - AdvectionScratchData &scratch, - AdvectionPerTaskData &data); - - void copy_advection_local_to_global (const AdvectionPerTaskData &data); - - // The final few functions implement the - // diffusion solve as well as - // postprocessing the output, including - // computing the curl of the velocity: - void diffusion_component_solve (const unsigned int d); - - void output_results (const unsigned int step); - - void assemble_vorticity (const bool reinit_prec); -}; - - - - // @sect4{ NavierStokesProjection::NavierStokesProjection } - - // In the constructor, we just read - // all the data from the - // Data_Storage object - // that is passed as an argument, - // verify that the data we read is - // reasonable and, finally, create - // the triangulation and load the - // initial data. -template -NavierStokesProjection::NavierStokesProjection(const RunTimeParameters::Data_Storage &data) - : - type (data.form), - deg (data.pressure_degree), - dt (data.dt), - t_0 (data.initial_time), - T (data.final_time), - Re (data.Reynolds), - vel_exact (data.initial_time), - fe_velocity (deg+1), - fe_pressure (deg), - dof_handler_velocity (triangulation), - dof_handler_pressure (triangulation), - quadrature_pressure (deg+1), - quadrature_velocity (deg+2), - vel_max_its (data.vel_max_iterations), - vel_Krylov_size (data.vel_Krylov_size), - vel_off_diagonals (data.vel_off_diagonals), - vel_update_prec (data.vel_update_prec), - vel_eps (data.vel_eps), - vel_diag_strength (data.vel_diag_strength) -{ - if(deg < 1) - std::cout << " WARNING: The chosen pair of finite element spaces is not stable." + std::cout << "dim (X_h) = " << (dof_handler_velocity.n_dofs()*dim) + << std::endl + << "dim (M_h) = " << dof_handler_pressure.n_dofs() + << std::endl + << "Re = " << Re << std::endl - << " The obtained results will be nonsense" << std::endl; + } - AssertThrow (! ( (dt <= 0.) || (dt > .5*T)), ExcInvalidTimeStep (dt, .5*T)); - - create_triangulation_and_dofs (data.n_global_refines); - initialize(); -} + // @sect4{ NavierStokesProjection::initialize } - // @sect4{ NavierStokesProjection::create_triangulation_and_dofs } - - // The method that creates the - // triangulation and refines it the - // needed number of times. After - // creating the triangulation, it - // creates the mesh dependent data, - // i.e. it distributes degrees of - // freedom and renumbers them, and - // initializes the matrices and - // vectors that we will use. -template -void -NavierStokesProjection:: -create_triangulation_and_dofs (const unsigned int n_refines) -{ - GridIn grid_in; - grid_in.attach_triangulation (triangulation); - + // This method creates the constant + // matrices and loads the initial + // data + template + void + NavierStokesProjection::initialize() { - std::string filename = "nsbench2.inp"; - std::ifstream file (filename.c_str()); - Assert (file, ExcFileNotOpen (filename.c_str())); - grid_in.read_ucd (file); + vel_Laplace_plus_Mass = 0.; + vel_Laplace_plus_Mass.add (1./Re, vel_Laplace); + vel_Laplace_plus_Mass.add (1.5/dt, vel_Mass); + + EquationData::Pressure pres (t_0); + VectorTools::interpolate (dof_handler_pressure, pres, pres_n_minus_1); + pres.advance_time (dt); + VectorTools::interpolate (dof_handler_pressure, pres, pres_n); + phi_n = 0.; + phi_n_minus_1 = 0.; + for(unsigned int d=0; d(), u_n_minus_1[d]); + vel_exact.advance_time (dt); + VectorTools::interpolate (dof_handler_velocity, ZeroFunction(), u_n[d]); + } } - std::cout << "Number of refines = " << n_refines - << std::endl; - triangulation.refine_global (n_refines); - std::cout << "Number of active cells: " << triangulation.n_active_cells() - << std::endl; - - boundary_indicators = triangulation.get_boundary_indicators(); - - dof_handler_velocity.distribute_dofs (fe_velocity); - DoFRenumbering::boost::Cuthill_McKee (dof_handler_velocity); - dof_handler_pressure.distribute_dofs (fe_pressure); - DoFRenumbering::boost::Cuthill_McKee (dof_handler_pressure); - - initialize_velocity_matrices(); - initialize_pressure_matrices(); - initialize_gradient_operator(); - - pres_n.reinit (dof_handler_pressure.n_dofs()); - pres_n_minus_1.reinit (dof_handler_pressure.n_dofs()); - phi_n.reinit (dof_handler_pressure.n_dofs()); - phi_n_minus_1.reinit (dof_handler_pressure.n_dofs()); - pres_tmp.reinit (dof_handler_pressure.n_dofs()); - for(unsigned int d=0; d -void -NavierStokesProjection::run (const bool verbose, - const unsigned int output_interval) -{ - ConditionalOStream verbose_cout (std::cout, verbose); + // @sect4{ NavierStokesProjection::run } + + // This is the time marching + // function, which starting at + // t_0 advances in time + // using the projection method with + // time step dt until + // T. + // + // Its second parameter, verbose + // indicates whether the function should + // output information what it is doing at any + // given moment: for example, it will say + // whether we are working on the diffusion, + // projection substep; updating + // preconditioners etc. Rather than + // implementing this output using code like + // @code + // if (verbose) + // std::cout << "something"; + // @endcode + // we use the ConditionalOStream class to + // do that for us. That class takes an + // output stream and a condition that + // indicates whether the things you pass + // to it should be passed through to the + // given output stream, or should just + // be ignored. This way, above code + // simply becomes + // @code + // verbose_cout << "something"; + // @endcode + // and does the right thing in either + // case. + template + void + NavierStokesProjection::run (const bool verbose, + const unsigned int output_interval) + { + ConditionalOStream verbose_cout (std::cout, verbose); - const unsigned int n_steps = static_cast((T - t_0)/dt); - vel_exact.set_time (2.*dt); - output_results(1); - for (unsigned int n = 2; n<=n_steps; ++n) - { - if (n % output_interval == 0) - { - verbose_cout << "Plotting Solution" << std::endl; - output_results(n); - } - std::cout << "Step = " << n << " Time = " << (n*dt) << std::endl; - verbose_cout << " Interpolating the velocity " << std::endl; - - interpolate_velocity(); - verbose_cout << " Diffusion Step" << std::endl; - if (n % vel_update_prec == 0) - verbose_cout << " With reinitialization of the preconditioner" - << std::endl; - diffusion_step ((n%vel_update_prec == 0) || (n == 2)); - verbose_cout << " Projection Step" << std::endl; - projection_step ( (n == 2)); - verbose_cout << " Updating the Pressure" << std::endl; - update_pressure ( (n == 2)); - vel_exact.advance_time(dt); - } - output_results (n_steps); -} + const unsigned int n_steps = static_cast((T - t_0)/dt); + vel_exact.set_time (2.*dt); + output_results(1); + for (unsigned int n = 2; n<=n_steps; ++n) + { + if (n % output_interval == 0) + { + verbose_cout << "Plotting Solution" << std::endl; + output_results(n); + } + std::cout << "Step = " << n << " Time = " << (n*dt) << std::endl; + verbose_cout << " Interpolating the velocity " << std::endl; + + interpolate_velocity(); + verbose_cout << " Diffusion Step" << std::endl; + if (n % vel_update_prec == 0) + verbose_cout << " With reinitialization of the preconditioner" + << std::endl; + diffusion_step ((n%vel_update_prec == 0) || (n == 2)); + verbose_cout << " Projection Step" << std::endl; + projection_step ( (n == 2)); + verbose_cout << " Updating the Pressure" << std::endl; + update_pressure ( (n == 2)); + vel_exact.advance_time(dt); + } + output_results (n_steps); + } -template -void -NavierStokesProjection::interpolate_velocity() -{ - for (unsigned int d=0; d + void + NavierStokesProjection::interpolate_velocity() + { + for (unsigned int d=0; dNavierStokesProjection::diffusion_step} - - // The implementation of a diffusion - // step. Note that the expensive operation is - // the diffusion solve at the end of the - // function, which we have to do once for - // each velocity component. To accellerate - // things a bit, we allow to do this in - // %parallel, using the Threads::new_task - // function which makes sure that the - // dim solves are all taken care - // of and are scheduled to available - // processors: if your machine has more than - // one processor core and no other parts of - // this program are using resources - // currently, then the diffusion solves will - // run in %parallel. On the other hand, if - // your system has only one processor core - // then running things in %parallel would be - // inefficient (since it leads, for example, - // to cache congestion) and things will be - // executed sequentially. -template -void -NavierStokesProjection::diffusion_step (const bool reinit_prec) -{ - pres_tmp.equ (-1., pres_n, -4./3., phi_n, 1./3., phi_n_minus_1); + // @sect4{NavierStokesProjection::diffusion_step} + + // The implementation of a diffusion + // step. Note that the expensive operation is + // the diffusion solve at the end of the + // function, which we have to do once for + // each velocity component. To accellerate + // things a bit, we allow to do this in + // %parallel, using the Threads::new_task + // function which makes sure that the + // dim solves are all taken care + // of and are scheduled to available + // processors: if your machine has more than + // one processor core and no other parts of + // this program are using resources + // currently, then the diffusion solves will + // run in %parallel. On the other hand, if + // your system has only one processor core + // then running things in %parallel would be + // inefficient (since it leads, for example, + // to cache congestion) and things will be + // executed sequentially. + template + void + NavierStokesProjection::diffusion_step (const bool reinit_prec) + { + pres_tmp.equ (-1., pres_n, -4./3., phi_n, 1./3., phi_n_minus_1); - assemble_advection_term(); + assemble_advection_term(); - for (unsigned int d=0; d::const_iterator - boundaries = boundary_indicators.begin(); - boundaries != boundary_indicators.end(); - ++boundaries) - { - switch (*boundaries) - { - case 1: - VectorTools:: - interpolate_boundary_values (dof_handler_velocity, - *boundaries, - ZeroFunction(), - boundary_values); - break; - case 2: - VectorTools:: - interpolate_boundary_values (dof_handler_velocity, - *boundaries, - vel_exact, - boundary_values); - break; - case 3: - if (d != 0) + for (unsigned int d=0; d::const_iterator + boundaries = boundary_indicators.begin(); + boundaries != boundary_indicators.end(); + ++boundaries) + { + switch (*boundaries) + { + case 1: VectorTools:: interpolate_boundary_values (dof_handler_velocity, *boundaries, ZeroFunction(), boundary_values); - break; - case 4: - VectorTools:: - interpolate_boundary_values (dof_handler_velocity, - *boundaries, - ZeroFunction(), - boundary_values); - break; - default: - Assert (false, ExcNotImplemented()); - } - } - MatrixTools::apply_boundary_values (boundary_values, - vel_it_matrix[d], - u_n[d], - force[d]); - } + break; + case 2: + VectorTools:: + interpolate_boundary_values (dof_handler_velocity, + *boundaries, + vel_exact, + boundary_values); + break; + case 3: + if (d != 0) + VectorTools:: + interpolate_boundary_values (dof_handler_velocity, + *boundaries, + ZeroFunction(), + boundary_values); + break; + case 4: + VectorTools:: + interpolate_boundary_values (dof_handler_velocity, + *boundaries, + ZeroFunction(), + boundary_values); + break; + default: + Assert (false, ExcNotImplemented()); + } + } + MatrixTools::apply_boundary_values (boundary_values, + vel_it_matrix[d], + u_n[d], + force[d]); + } - Threads::TaskGroup tasks; - for(unsigned int d=0; d:: - AdditionalData (vel_diag_strength, - vel_off_diagonals)); - tasks += Threads::new_task (&NavierStokesProjection:: - diffusion_component_solve, - *this, d); - } - tasks.join_all(); -} + Threads::TaskGroup tasks; + for(unsigned int d=0; d:: + AdditionalData (vel_diag_strength, + vel_off_diagonals)); + tasks += Threads::new_task (&NavierStokesProjection:: + diffusion_component_solve, + *this, d); + } + tasks.join_all(); + } -template -void -NavierStokesProjection::diffusion_component_solve (const unsigned int d) -{ - SolverControl solver_control (vel_max_its, vel_eps*force[d].l2_norm()); - SolverGMRES<> gmres (solver_control, - SolverGMRES<>::AdditionalData (vel_Krylov_size)); - gmres.solve (vel_it_matrix[d], u_n[d], force[d], prec_velocity[d]); -} + template + void + NavierStokesProjection::diffusion_component_solve (const unsigned int d) + { + SolverControl solver_control (vel_max_its, vel_eps*force[d].l2_norm()); + SolverGMRES<> gmres (solver_control, + SolverGMRES<>::AdditionalData (vel_Krylov_size)); + gmres.solve (vel_it_matrix[d], u_n[d], force[d], prec_velocity[d]); + } - // @sect4{ The NavierStokesProjection::assemble_advection_term method and related} + // @sect4{ The NavierStokesProjection::assemble_advection_term method and related} - // The following few functions deal with - // assembling the advection terms, which is the part of the - // system matrix for the diffusion step that changes - // at every time step. As mentioned above, we - // will run the assembly loop over all cells - // in %parallel, using the WorkStream class - // and other facilities as described in the - // documentation module on @ref threads. -template -void -NavierStokesProjection::assemble_advection_term() -{ - vel_Advection = 0.; - AdvectionPerTaskData data (fe_velocity.dofs_per_cell); - AdvectionScratchData scratch (fe_velocity, quadrature_velocity, - update_values | - update_JxW_values | - update_gradients); - WorkStream::run (dof_handler_velocity.begin_active(), - dof_handler_velocity.end(), *this, - &NavierStokesProjection::assemble_one_cell_of_advection, - &NavierStokesProjection::copy_advection_local_to_global, - scratch, - data); -} + // The following few functions deal with + // assembling the advection terms, which is the part of the + // system matrix for the diffusion step that changes + // at every time step. As mentioned above, we + // will run the assembly loop over all cells + // in %parallel, using the WorkStream class + // and other facilities as described in the + // documentation module on @ref threads. + template + void + NavierStokesProjection::assemble_advection_term() + { + vel_Advection = 0.; + AdvectionPerTaskData data (fe_velocity.dofs_per_cell); + AdvectionScratchData scratch (fe_velocity, quadrature_velocity, + update_values | + update_JxW_values | + update_gradients); + WorkStream::run (dof_handler_velocity.begin_active(), + dof_handler_velocity.end(), *this, + &NavierStokesProjection::assemble_one_cell_of_advection, + &NavierStokesProjection::copy_advection_local_to_global, + scratch, + data); + } -template -void -NavierStokesProjection:: -assemble_one_cell_of_advection(const typename DoFHandler::active_cell_iterator &cell, - AdvectionScratchData &scratch, - AdvectionPerTaskData &data) -{ - scratch.fe_val.reinit(cell); - cell->get_dof_indices (data.local_dof_indices); - for (unsigned int d=0; d + void + NavierStokesProjection:: + assemble_one_cell_of_advection(const typename DoFHandler::active_cell_iterator &cell, + AdvectionScratchData &scratch, + AdvectionPerTaskData &data) + { + scratch.fe_val.reinit(cell); + cell->get_dof_indices (data.local_dof_indices); + for (unsigned int d=0; d -void -NavierStokesProjection:: -copy_advection_local_to_global(const AdvectionPerTaskData &data) -{ - for (unsigned int i=0; i + void + NavierStokesProjection:: + copy_advection_local_to_global(const AdvectionPerTaskData &data) + { + for (unsigned int i=0; iNavierStokesProjection::projection_step} + // @sect4{NavierStokesProjection::projection_step} - // This implements the projection step: -template -void -NavierStokesProjection::projection_step (const bool reinit_prec) -{ - pres_iterative.copy_from (pres_Laplace); + // This implements the projection step: + template + void + NavierStokesProjection::projection_step (const bool reinit_prec) + { + pres_iterative.copy_from (pres_Laplace); - pres_tmp = 0.; - for (unsigned d=0; d bval; - if (reinit_prec) - VectorTools::interpolate_boundary_values (dof_handler_pressure, 3, - ZeroFunction(), bval); + static std::map bval; + if (reinit_prec) + VectorTools::interpolate_boundary_values (dof_handler_pressure, 3, + ZeroFunction(), bval); - MatrixTools::apply_boundary_values (bval, pres_iterative, phi_n, pres_tmp); + MatrixTools::apply_boundary_values (bval, pres_iterative, phi_n, pres_tmp); - if (reinit_prec) - prec_pres_Laplace.initialize(pres_iterative, - SparseILU::AdditionalData (vel_diag_strength, - vel_off_diagonals) ); + if (reinit_prec) + prec_pres_Laplace.initialize(pres_iterative, + SparseILU::AdditionalData (vel_diag_strength, + vel_off_diagonals) ); - SolverControl solvercontrol (vel_max_its, vel_eps*pres_tmp.l2_norm()); - SolverCG<> cg (solvercontrol); - cg.solve (pres_iterative, phi_n, pres_tmp, prec_pres_Laplace); + SolverControl solvercontrol (vel_max_its, vel_eps*pres_tmp.l2_norm()); + SolverCG<> cg (solvercontrol); + cg.solve (pres_iterative, phi_n, pres_tmp, prec_pres_Laplace); - phi_n *= 1.5/dt; -} + phi_n *= 1.5/dt; + } - // @sect4{ NavierStokesProjection::update_pressure } - - // This is the pressure update step - // of the projection method. It - // implements the standard - // formulation of the method, that is - // @f[ - // p^{n+1} = p^n + \phi^{n+1}, - // @f] - // or the rotational form, which is - // @f[ - // p^{n+1} = p^n + \phi^{n+1} - \frac{1}{Re} \nabla\cdot u^{n+1}. - // @f] -template -void -NavierStokesProjection::update_pressure (const bool reinit_prec) -{ - pres_n_minus_1 = pres_n; - switch (type) - { - case RunTimeParameters::METHOD_STANDARD: - pres_n += phi_n; - break; - case RunTimeParameters::METHOD_ROTATIONAL: - if (reinit_prec) - prec_mass.initialize (pres_Mass); - pres_n = pres_tmp; - prec_mass.solve (pres_n); - pres_n.sadd(1./Re, 1., pres_n_minus_1, 1., phi_n); - break; - default: - Assert (false, ExcNotImplemented()); - }; -} + // @sect4{ NavierStokesProjection::update_pressure } + // This is the pressure update step + // of the projection method. It + // implements the standard + // formulation of the method, that is + // @f[ + // p^{n+1} = p^n + \phi^{n+1}, + // @f] + // or the rotational form, which is + // @f[ + // p^{n+1} = p^n + \phi^{n+1} - \frac{1}{Re} \nabla\cdot u^{n+1}. + // @f] + template + void + NavierStokesProjection::update_pressure (const bool reinit_prec) + { + pres_n_minus_1 = pres_n; + switch (type) + { + case RunTimeParameters::METHOD_STANDARD: + pres_n += phi_n; + break; + case RunTimeParameters::METHOD_ROTATIONAL: + if (reinit_prec) + prec_mass.initialize (pres_Mass); + pres_n = pres_tmp; + prec_mass.solve (pres_n); + pres_n.sadd(1./Re, 1., pres_n_minus_1, 1., phi_n); + break; + default: + Assert (false, ExcNotImplemented()); + }; + } - // @sect4{ NavierStokesProjection::output_results } - - // This method plots the current - // solution. The main difficulty is that we - // want to create a single output file that - // contains the data for all velocity - // components, the pressure, and also the - // vorticity of the flow. On the other hand, - // velocities and the pressure live on - // separate DoFHandler objects, and so can't - // be written to the same file using a single - // DataOut object. As a consequence, we have - // to work a bit harder to get the various - // pieces of data into a single DoFHandler - // object, and then use that to drive - // graphical output. - // - // We will not elaborate on this process - // here, but rather refer to step-31 and - // step-32, where a similar procedure is used - // (and is documented) to create a joint - // DoFHandler object for all variables. - // - // Let us also note that we here compute the - // vorticity as a scalar quantity in a - // separate function, using the $L^2$ - // projection of the quantity $\text{curl} u$ - // onto the finite element space used for the - // components of the velocity. In principle, - // however, we could also have computed as a - // pointwise quantity from the velocity, and - // do so through the DataPostprocessor - // mechanism discussed in step-29 and - // step-33. -template -void NavierStokesProjection::output_results (const unsigned int step) -{ - assemble_vorticity ( (step == 1)); - const FESystem joint_fe (fe_velocity, dim, - fe_pressure, 1, - fe_velocity, 1); - DoFHandler joint_dof_handler (triangulation); - joint_dof_handler.distribute_dofs (joint_fe); - Assert (joint_dof_handler.n_dofs() == - ((dim + 1)*dof_handler_velocity.n_dofs() + - dof_handler_pressure.n_dofs()), - ExcInternalError()); - static Vector joint_solution (joint_dof_handler.n_dofs()); - std::vector loc_joint_dof_indices (joint_fe.dofs_per_cell), - loc_vel_dof_indices (fe_velocity.dofs_per_cell), - loc_pres_dof_indices (fe_pressure.dofs_per_cell); - typename DoFHandler::active_cell_iterator - joint_cell = joint_dof_handler.begin_active(), - joint_endc = joint_dof_handler.end(), - vel_cell = dof_handler_velocity.begin_active(), - pres_cell = dof_handler_pressure.begin_active(); - for (; joint_cell != joint_endc; ++joint_cell, ++vel_cell, ++pres_cell) - { - joint_cell->get_dof_indices (loc_joint_dof_indices); - vel_cell->get_dof_indices (loc_vel_dof_indices), - pres_cell->get_dof_indices (loc_pres_dof_indices); - for (unsigned int i=0; i joint_solution_names (dim, "v"); - joint_solution_names.push_back ("p"); - joint_solution_names.push_back ("rot_u"); - DataOut data_out; - data_out.attach_dof_handler (joint_dof_handler); - std::vector< DataComponentInterpretation::DataComponentInterpretation > - component_interpretation (dim+2, - DataComponentInterpretation::component_is_part_of_vector); - component_interpretation[dim] - = DataComponentInterpretation::component_is_scalar; - component_interpretation[dim+1] - = DataComponentInterpretation::component_is_scalar; - data_out.add_data_vector (joint_solution, - joint_solution_names, - DataOut::type_dof_data, - component_interpretation); - data_out.build_patches (deg + 1); - std::ofstream output (("solution-" + - Utilities::int_to_string (step, 5) + - ".vtk").c_str()); - data_out.write_vtk (output); -} + // @sect4{ NavierStokesProjection::output_results } + + // This method plots the current + // solution. The main difficulty is that we + // want to create a single output file that + // contains the data for all velocity + // components, the pressure, and also the + // vorticity of the flow. On the other hand, + // velocities and the pressure live on + // separate DoFHandler objects, and so can't + // be written to the same file using a single + // DataOut object. As a consequence, we have + // to work a bit harder to get the various + // pieces of data into a single DoFHandler + // object, and then use that to drive + // graphical output. + // + // We will not elaborate on this process + // here, but rather refer to step-31 and + // step-32, where a similar procedure is used + // (and is documented) to create a joint + // DoFHandler object for all variables. + // + // Let us also note that we here compute the + // vorticity as a scalar quantity in a + // separate function, using the $L^2$ + // projection of the quantity $\text{curl} u$ + // onto the finite element space used for the + // components of the velocity. In principle, + // however, we could also have computed as a + // pointwise quantity from the velocity, and + // do so through the DataPostprocessor + // mechanism discussed in step-29 and + // step-33. + template + void NavierStokesProjection::output_results (const unsigned int step) + { + assemble_vorticity ( (step == 1)); + const FESystem joint_fe (fe_velocity, dim, + fe_pressure, 1, + fe_velocity, 1); + DoFHandler joint_dof_handler (triangulation); + joint_dof_handler.distribute_dofs (joint_fe); + Assert (joint_dof_handler.n_dofs() == + ((dim + 1)*dof_handler_velocity.n_dofs() + + dof_handler_pressure.n_dofs()), + ExcInternalError()); + static Vector joint_solution (joint_dof_handler.n_dofs()); + std::vector loc_joint_dof_indices (joint_fe.dofs_per_cell), + loc_vel_dof_indices (fe_velocity.dofs_per_cell), + loc_pres_dof_indices (fe_pressure.dofs_per_cell); + typename DoFHandler::active_cell_iterator + joint_cell = joint_dof_handler.begin_active(), + joint_endc = joint_dof_handler.end(), + vel_cell = dof_handler_velocity.begin_active(), + pres_cell = dof_handler_pressure.begin_active(); + for (; joint_cell != joint_endc; ++joint_cell, ++vel_cell, ++pres_cell) + { + joint_cell->get_dof_indices (loc_joint_dof_indices); + vel_cell->get_dof_indices (loc_vel_dof_indices), + pres_cell->get_dof_indices (loc_pres_dof_indices); + for (unsigned int i=0; i joint_solution_names (dim, "v"); + joint_solution_names.push_back ("p"); + joint_solution_names.push_back ("rot_u"); + DataOut data_out; + data_out.attach_dof_handler (joint_dof_handler); + std::vector< DataComponentInterpretation::DataComponentInterpretation > + component_interpretation (dim+2, + DataComponentInterpretation::component_is_part_of_vector); + component_interpretation[dim] + = DataComponentInterpretation::component_is_scalar; + component_interpretation[dim+1] + = DataComponentInterpretation::component_is_scalar; + data_out.add_data_vector (joint_solution, + joint_solution_names, + DataOut::type_dof_data, + component_interpretation); + data_out.build_patches (deg + 1); + std::ofstream output (("solution-" + + Utilities::int_to_string (step, 5) + + ".vtk").c_str()); + data_out.write_vtk (output); + } - // Following is the helper function that - // computes the vorticity by projecting the - // term $\text{curl} u$ onto the finite - // element space used for the components of - // the velocity. The function is only called - // whenever we generate graphical output, so - // not very often, and as a consequence we - // didn't bother parallelizing it using the - // WorkStream concept as we do for the other - // assembly functions. That should not be - // overly complicated, however, if - // needed. Moreover, the implementation that - // we have here only works for 2d, so we bail - // if that is not the case. -template -void NavierStokesProjection::assemble_vorticity (const bool reinit_prec) -{ - Assert (dim == 2, ExcNotImplemented()); - if (reinit_prec) - prec_vel_mass.initialize (vel_Mass); - - typename DoFHandler::active_cell_iterator - cell = dof_handler_velocity.begin_active(), - end = dof_handler_velocity.end(); - FEValues fe_val_vel (fe_velocity, quadrature_velocity, - update_gradients | - update_JxW_values | - update_values); - const unsigned int dpc = fe_velocity.dofs_per_cell, - nqp = quadrature_velocity.size(); - std::vector ldi (dpc); - Vector loc_rot (dpc); - - std::vector< Tensor<1,dim> > grad_u1 (nqp), grad_u2 (nqp); - rot_u = 0.; - for (; cell != end; ++cell) - { - fe_val_vel.reinit (cell); - cell->get_dof_indices (ldi); - fe_val_vel.get_function_gradients (u_n[0], grad_u1); - fe_val_vel.get_function_gradients (u_n[1], grad_u2); - loc_rot = 0.; - for (unsigned int q=0; q + void NavierStokesProjection::assemble_vorticity (const bool reinit_prec) + { + Assert (dim == 2, ExcNotImplemented()); + if (reinit_prec) + prec_vel_mass.initialize (vel_Mass); + + FEValues fe_val_vel (fe_velocity, quadrature_velocity, + update_gradients | + update_JxW_values | + update_values); + const unsigned int dpc = fe_velocity.dofs_per_cell, + nqp = quadrature_velocity.size(); + std::vector ldi (dpc); + Vector loc_rot (dpc); + + std::vector< Tensor<1,dim> > grad_u1 (nqp), grad_u2 (nqp); + rot_u = 0.; + + typename DoFHandler::active_cell_iterator + cell = dof_handler_velocity.begin_active(), + end = dof_handler_velocity.end(); + for (; cell != end; ++cell) + { + fe_val_vel.reinit (cell); + cell->get_dof_indices (ldi); + fe_val_vel.get_function_gradients (u_n[0], grad_u1); + fe_val_vel.get_function_gradients (u_n[1], grad_u2); + loc_rot = 0.; + for (unsigned int q=0; q test (data); test.run (data.verbose, data.output_interval); } diff --git a/deal.II/examples/step-36/step-36.cc b/deal.II/examples/step-36/step-36.cc index dbb614860b..989ec262de 100644 --- a/deal.II/examples/step-36/step-36.cc +++ b/deal.II/examples/step-36/step-36.cc @@ -3,7 +3,7 @@ /* Wolfgang Bangerth, Texas A&M University */ /* $Id$*/ /* */ -/* Copyright (C) 2009 by the deal.II authors */ +/* Copyright (C) 2009, 2011 by the deal.II authors */ /* */ /* This file is subject to QPL and may not be distributed */ /* without copyright and license information. Please refer */ @@ -53,503 +53,505 @@ #include #include - // Finally, as in previous programs, - // we import all the deal.II class - // and function names into the global - // namespace: -using namespace dealii; - - // @sect3{The EigenvalueProblem class template} - - // Following is the class declaration - // for the main class template. It - // looks pretty much exactly like - // what has already been shown in - // step-4: -template -class EigenvalueProblem + // Finally, as in previous programs, we + // import all the deal.II class and function + // names into the namespace into which + // everything in this program will go: +namespace Step36 { - public: - EigenvalueProblem (const std::string &prm_file); - void run (); - - private: - void make_grid_and_dofs (); - void assemble_system (); - void solve (); - void output_results () const; - - Triangulation triangulation; - FE_Q fe; - DoFHandler dof_handler; - - // With these exceptions: For our - // eigenvalue problem, we need - // both a stiffness matrix for - // the left hand side as well as - // a mass matrix for the right - // hand side. We also need not - // just one solution function, - // but a whole set of these for - // the eigenfunctions we want to - // compute, along with the - // corresponding eigenvalues: - PETScWrappers::SparseMatrix stiffness_matrix, mass_matrix; - std::vector eigenfunctions; - std::vector eigenvalues; - - // And then we need an object - // that will store several - // run-time parameters that we - // will specify in an input file: - ParameterHandler parameters; - - // Finally, we will have an - // object that contains - // "constraints" on our degrees - // of freedom. This could include - // hanging node constraints if we - // had adaptively refined meshes - // (which we don't have in the - // current program). Here, we - // will store the constraints for - // boundary nodes $U_i=0$. - ConstraintMatrix constraints; -}; - - // @sect3{Implementation of the EigenvalueProblem class} - - // @sect4{EigenvalueProblem::EigenvalueProblem} - - // First up, the constructor. The - // main new part is handling the - // run-time input parameters. We need - // to declare their existence first, - // and then read their values from - // the input file whose name is - // specified as an argument to this - // function: -template -EigenvalueProblem::EigenvalueProblem (const std::string &prm_file) - : - fe (1), - dof_handler (triangulation) -{ - parameters.declare_entry ("Global mesh refinement steps", "5", - Patterns::Integer (0, 20), - "The number of times the 1-cell coarse mesh should " - "be refined globally for our computations."); - parameters.declare_entry ("Number of eigenvalues/eigenfunctions", "5", - Patterns::Integer (0, 100), - "The number of eigenvalues/eigenfunctions " - "to be computed."); - parameters.declare_entry ("Potential", "0", - Patterns::Anything(), - "A functional description of the potential."); - - parameters.read_input (prm_file); -} + using namespace dealii; + // @sect3{The EigenvalueProblem class template} - // @sect4{EigenvalueProblem::make_grid_and_dofs} - - // The next function creates a mesh - // on the domain $[-1,1]^d$, refines - // it as many times as the input file - // calls for, and then attaches a - // DoFHandler to it and initializes - // the matrices and vectors to their - // correct sizes. We also build the - // constraints that correspond to the - // boundary values - // $u|_{\partial\Omega}=0$. - // - // For the matrices, we use the PETSc - // wrappers. These have the ability - // to allocate memory as necessary as - // non-zero entries are added. This - // seems inefficient: we could as - // well first compute the sparsity - // pattern, initialize the matrices - // with it, and as we then insert - // entries we can be sure that we do - // not need to re-allocate memory and - // free the one used previously. One - // way to do that would be to use - // code like this: - // @code - // CompressedSimpleSparsityPattern - // csp (dof_handler.n_dofs(), - // dof_handler.n_dofs()); - // DoFTools::make_sparsity_pattern (dof_handler, csp); - // csp.compress (); - // stiffness_matrix.reinit (csp); - // mass_matrix.reinit (csp); - // @endcode - // instead of the two - // reinit() calls for - // the stiffness and mass matrices - // below. - // - // This doesn't quite work, - // unfortunately. The code above may - // lead to a few entries in the - // non-zero pattern to which we only - // ever write zero entries; most - // notably, this holds true for - // off-diagonal entries for those - // rows and columns that belong to - // boundary nodes. This shouldn't be - // a problem, but for whatever - // reason, PETSc's ILU - // preconditioner, which we use to - // solve linear systems in the - // eigenvalue solver, doesn't like - // these extra entries and aborts - // with an error message. - // - // In the absense of any obvious way - // to avoid this, we simply settle - // for the second best option, which - // is have PETSc allocate memory as - // necessary. That said, since this - // is not a time critical part, this - // whole affair is of no further - // importance. -template -void EigenvalueProblem::make_grid_and_dofs () -{ - GridGenerator::hyper_cube (triangulation, -1, 1); - triangulation.refine_global (parameters.get_integer ("Global mesh refinement steps")); - dof_handler.distribute_dofs (fe); - - DoFTools::make_zero_boundary_constraints (dof_handler, constraints); - constraints.close (); - - stiffness_matrix.reinit (dof_handler.n_dofs(), - dof_handler.n_dofs(), - dof_handler.max_couplings_between_dofs()); - mass_matrix.reinit (dof_handler.n_dofs(), - dof_handler.n_dofs(), - dof_handler.max_couplings_between_dofs()); - - // The next step is to take care of - // the eigenspectrum. In this case, - // the outputs are eigenvalues and - // eigenfunctions, so we set the - // size of the list of - // eigenfunctions and eigenvalues - // to be as large as we asked for - // in the input file: - eigenfunctions - .resize (parameters.get_integer ("Number of eigenvalues/eigenfunctions")); - for (unsigned int i=0; i + class EigenvalueProblem + { + public: + EigenvalueProblem (const std::string &prm_file); + void run (); + + private: + void make_grid_and_dofs (); + void assemble_system (); + void solve (); + void output_results () const; + + Triangulation triangulation; + FE_Q fe; + DoFHandler dof_handler; + + // With these exceptions: For our + // eigenvalue problem, we need + // both a stiffness matrix for + // the left hand side as well as + // a mass matrix for the right + // hand side. We also need not + // just one solution function, + // but a whole set of these for + // the eigenfunctions we want to + // compute, along with the + // corresponding eigenvalues: + PETScWrappers::SparseMatrix stiffness_matrix, mass_matrix; + std::vector eigenfunctions; + std::vector eigenvalues; + + // And then we need an object + // that will store several + // run-time parameters that we + // will specify in an input file: + ParameterHandler parameters; + + // Finally, we will have an + // object that contains + // "constraints" on our degrees + // of freedom. This could include + // hanging node constraints if we + // had adaptively refined meshes + // (which we don't have in the + // current program). Here, we + // will store the constraints for + // boundary nodes $U_i=0$. + ConstraintMatrix constraints; + }; + + // @sect3{Implementation of the EigenvalueProblem class} + + // @sect4{EigenvalueProblem::EigenvalueProblem} + + // First up, the constructor. The + // main new part is handling the + // run-time input parameters. We need + // to declare their existence first, + // and then read their values from + // the input file whose name is + // specified as an argument to this + // function: + template + EigenvalueProblem::EigenvalueProblem (const std::string &prm_file) + : + fe (1), + dof_handler (triangulation) + { + parameters.declare_entry ("Global mesh refinement steps", "5", + Patterns::Integer (0, 20), + "The number of times the 1-cell coarse mesh should " + "be refined globally for our computations."); + parameters.declare_entry ("Number of eigenvalues/eigenfunctions", "5", + Patterns::Integer (0, 100), + "The number of eigenvalues/eigenfunctions " + "to be computed."); + parameters.declare_entry ("Potential", "0", + Patterns::Anything(), + "A functional description of the potential."); + + parameters.read_input (prm_file); + } - // @sect4{EigenvalueProblem::assemble_system} - - // Here, we assemble the global - // stiffness and mass matrices from - // local contributions $A^K_{ij} = - // \int_K \nabla\varphi_i(\mathbf x) - // \cdot \nabla\varphi_j(\mathbf x) + - // V(\mathbf x)\varphi_i(\mathbf - // x)\varphi_j(\mathbf x)$ and - // $M^K_{ij} = \int_K - // \varphi_i(\mathbf - // x)\varphi_j(\mathbf x)$ - // respectively. This function should - // be immediately familiar if you've - // seen previous tutorial - // programs. The only thing new would - // be setting up an object that - // described the potential $V(\mathbf - // x)$ using the expression that we - // got from the input file. We then - // need to evaluate this object at - // the quadrature points on each - // cell. If you've seen how to - // evaluate function objects (see, - // for example the coefficient in - // step-5), the code here will also - // look rather familiar. -template -void EigenvalueProblem::assemble_system () -{ - QGauss quadrature_formula(2); - - FEValues fe_values (fe, quadrature_formula, - update_values | update_gradients | - update_quadrature_points | update_JxW_values); - - const unsigned int dofs_per_cell = fe.dofs_per_cell; - const unsigned int n_q_points = quadrature_formula.size(); - - FullMatrix cell_stiffness_matrix (dofs_per_cell, dofs_per_cell); - FullMatrix cell_mass_matrix (dofs_per_cell, dofs_per_cell); - - std::vector local_dof_indices (dofs_per_cell); - - FunctionParser potential; - potential.initialize (FunctionParser::default_variable_names (), - parameters.get ("Potential"), - typename FunctionParser::ConstMap()); - - std::vector potential_values (n_q_points); - - - typename DoFHandler::active_cell_iterator - cell = dof_handler.begin_active (), - endc = dof_handler.end (); - for (; cell!=endc; ++cell) - { - fe_values.reinit (cell); - cell_stiffness_matrix = 0; - cell_mass_matrix = 0; - - potential.value_list (fe_values.get_quadrature_points(), - potential_values); - - for (unsigned int q_point=0; q_pointget_dof_indices (local_dof_indices); - - constraints - .distribute_local_to_global (cell_stiffness_matrix, - local_dof_indices, - stiffness_matrix); - constraints - .distribute_local_to_global (cell_mass_matrix, - local_dof_indices, - mass_matrix); - } + // @sect4{EigenvalueProblem::make_grid_and_dofs} - // At the end of the function, we - // tell PETSc that the matrices - // have now been fully assembled - // and that the sparse matrix - // representation can now be - // compressed as no more entries - // will be added: - stiffness_matrix.compress (); - mass_matrix.compress (); -} + // The next function creates a mesh + // on the domain $[-1,1]^d$, refines + // it as many times as the input file + // calls for, and then attaches a + // DoFHandler to it and initializes + // the matrices and vectors to their + // correct sizes. We also build the + // constraints that correspond to the + // boundary values + // $u|_{\partial\Omega}=0$. + // + // For the matrices, we use the PETSc + // wrappers. These have the ability + // to allocate memory as necessary as + // non-zero entries are added. This + // seems inefficient: we could as + // well first compute the sparsity + // pattern, initialize the matrices + // with it, and as we then insert + // entries we can be sure that we do + // not need to re-allocate memory and + // free the one used previously. One + // way to do that would be to use + // code like this: + // @code + // CompressedSimpleSparsityPattern + // csp (dof_handler.n_dofs(), + // dof_handler.n_dofs()); + // DoFTools::make_sparsity_pattern (dof_handler, csp); + // csp.compress (); + // stiffness_matrix.reinit (csp); + // mass_matrix.reinit (csp); + // @endcode + // instead of the two + // reinit() calls for + // the stiffness and mass matrices + // below. + // + // This doesn't quite work, + // unfortunately. The code above may + // lead to a few entries in the + // non-zero pattern to which we only + // ever write zero entries; most + // notably, this holds true for + // off-diagonal entries for those + // rows and columns that belong to + // boundary nodes. This shouldn't be + // a problem, but for whatever + // reason, PETSc's ILU + // preconditioner, which we use to + // solve linear systems in the + // eigenvalue solver, doesn't like + // these extra entries and aborts + // with an error message. + // + // In the absense of any obvious way + // to avoid this, we simply settle + // for the second best option, which + // is have PETSc allocate memory as + // necessary. That said, since this + // is not a time critical part, this + // whole affair is of no further + // importance. + template + void EigenvalueProblem::make_grid_and_dofs () + { + GridGenerator::hyper_cube (triangulation, -1, 1); + triangulation.refine_global (parameters.get_integer ("Global mesh refinement steps")); + dof_handler.distribute_dofs (fe); + + DoFTools::make_zero_boundary_constraints (dof_handler, constraints); + constraints.close (); + + stiffness_matrix.reinit (dof_handler.n_dofs(), + dof_handler.n_dofs(), + dof_handler.max_couplings_between_dofs()); + mass_matrix.reinit (dof_handler.n_dofs(), + dof_handler.n_dofs(), + dof_handler.max_couplings_between_dofs()); + + // The next step is to take care of + // the eigenspectrum. In this case, + // the outputs are eigenvalues and + // eigenfunctions, so we set the + // size of the list of + // eigenfunctions and eigenvalues + // to be as large as we asked for + // in the input file: + eigenfunctions + .resize (parameters.get_integer ("Number of eigenvalues/eigenfunctions")); + for (unsigned int i=0; i -void EigenvalueProblem::solve () -{ + // @sect4{EigenvalueProblem::assemble_system} + + // Here, we assemble the global + // stiffness and mass matrices from + // local contributions $A^K_{ij} = + // \int_K \nabla\varphi_i(\mathbf x) + // \cdot \nabla\varphi_j(\mathbf x) + + // V(\mathbf x)\varphi_i(\mathbf + // x)\varphi_j(\mathbf x)$ and + // $M^K_{ij} = \int_K + // \varphi_i(\mathbf + // x)\varphi_j(\mathbf x)$ + // respectively. This function should + // be immediately familiar if you've + // seen previous tutorial + // programs. The only thing new would + // be setting up an object that + // described the potential $V(\mathbf + // x)$ using the expression that we + // got from the input file. We then + // need to evaluate this object at + // the quadrature points on each + // cell. If you've seen how to + // evaluate function objects (see, + // for example the coefficient in + // step-5), the code here will also + // look rather familiar. + template + void EigenvalueProblem::assemble_system () + { + QGauss quadrature_formula(2); - // We start here, as we normally do, - // by assigning convergence control - // we want: - SolverControl solver_control (dof_handler.n_dofs(), 1e-9); - SLEPcWrappers::SolverKrylovSchur eigensolver (solver_control); - - // Before we actually solve for the - // eigenfunctions and -values, we - // have to also select which set of - // eigenvalues to solve for. Lets - // select those eigenvalues and - // corresponding eigenfunctions - // with the smallest real part (in - // fact, the problem we solve here - // is symmetric and so the - // eigenvalues are purely - // real). After that, we can - // actually let SLEPc do its work: - eigensolver.set_which_eigenpairs (EPS_SMALLEST_REAL); - - eigensolver.solve (stiffness_matrix, mass_matrix, - eigenvalues, eigenfunctions, - eigenfunctions.size()); - - // The output of the call above is - // a set of vectors and values. In - // eigenvalue problems, the - // eigenfunctions are only - // determined up to a constant that - // can be fixed pretty - // arbitrarily. Knowing nothing - // about the origin of the - // eigenvalue problem, SLEPc has no - // other choice than to normalize - // the eigenvectors to one in the - // $l_2$ (vector) - // norm. Unfortunately this norm - // has little to do with any norm - // we may be interested from a - // eigenfunction perspective: the - // $L_2(\Omega)$ norm, or maybe the - // $L_\infty(\Omega)$ norm. - // - // Let us choose the latter and - // rescale eigenfunctions so that - // they have $\|\phi_i(\mathbf - // x)\|_{L^\infty(\Omega)}=1$ - // instead of $\|\Phi\|_{l_2}=1$ - // (where $\phi_i$ is the $i$th - // eigenfunction and - // $\Phi_i$ the corresponding - // vector of nodal values). For the - // $Q_1$ elements chosen here, we - // know that the maximum of the - // function $\phi_i(\mathbf x)$ is - // attained at one of the nodes, so - // $\max_{\mathbf x}\phi_i(\mathbf - // x)=\max_j (\Phi_i)_j$, making - // the normalization in the - // $L_\infty$ norm trivial. Note - // that this doesn't work as easily - // if we had chosen $Q_k$ elements - // with $k>1$: there, the maximum - // of a function does not - // necessarily have to be attained - // at a node, and so $\max_{\mathbf - // x}\phi_i(\mathbf x)\ge\max_j - // (\Phi_i)_j$ (although the - // equality is usually nearly - // true). - for (unsigned int i=0; i fe_values (fe, quadrature_formula, + update_values | update_gradients | + update_quadrature_points | update_JxW_values); + const unsigned int dofs_per_cell = fe.dofs_per_cell; + const unsigned int n_q_points = quadrature_formula.size(); + + FullMatrix cell_stiffness_matrix (dofs_per_cell, dofs_per_cell); + FullMatrix cell_mass_matrix (dofs_per_cell, dofs_per_cell); + + std::vector local_dof_indices (dofs_per_cell); - // @sect4{EigenvalueProblem::output_results} - - // This is the last significant - // function of this program. It uses - // the DataOut class to generate - // graphical output from the - // eigenfunctions for later - // visualization. It works as in many - // of the other tutorial programs. - // - // The whole collection of functions - // is then output as a single VTK - // file. -template -void EigenvalueProblem::output_results () const -{ - DataOut data_out; - - data_out.attach_dof_handler (dof_handler); - - for (unsigned int i=0; i projected_potential (dof_handler.n_dofs()); - { FunctionParser potential; potential.initialize (FunctionParser::default_variable_names (), parameters.get ("Potential"), typename FunctionParser::ConstMap()); - VectorTools::interpolate (dof_handler, potential, projected_potential); + + std::vector potential_values (n_q_points); + + + typename DoFHandler::active_cell_iterator + cell = dof_handler.begin_active (), + endc = dof_handler.end (); + for (; cell!=endc; ++cell) + { + fe_values.reinit (cell); + cell_stiffness_matrix = 0; + cell_mass_matrix = 0; + + potential.value_list (fe_values.get_quadrature_points(), + potential_values); + + for (unsigned int q_point=0; q_pointget_dof_indices (local_dof_indices); + + constraints + .distribute_local_to_global (cell_stiffness_matrix, + local_dof_indices, + stiffness_matrix); + constraints + .distribute_local_to_global (cell_mass_matrix, + local_dof_indices, + mass_matrix); + } + + // At the end of the function, we + // tell PETSc that the matrices + // have now been fully assembled + // and that the sparse matrix + // representation can now be + // compressed as no more entries + // will be added: + stiffness_matrix.compress (); + mass_matrix.compress (); } - data_out.add_data_vector (projected_potential, "interpolated_potential"); - - data_out.build_patches (); - std::ofstream output ("eigenvectors.vtk"); - data_out.write_vtk (output); -} + // @sect4{EigenvalueProblem::solve} + + // This is the key new functionality + // of the program. Now that the + // system is set up, here is a good + // time to actually solve the + // problem: As with other examples + // this is done using a "solve" + // routine. Essentially, it works as + // in other programs: you set up a + // SolverControl object that + // describes the accuracy to which we + // want to solve the linear systems, + // and then we select the kind of + // solver we want. Here we choose the + // Krylov-Schur solver of SLEPc, a + // pretty fast and robust choice for + // this kind of problem: + template + void EigenvalueProblem::solve () + { - // @sect4{EigenvalueProblem::run} + // We start here, as we normally do, + // by assigning convergence control + // we want: + SolverControl solver_control (dof_handler.n_dofs(), 1e-9); + SLEPcWrappers::SolverKrylovSchur eigensolver (solver_control); + + // Before we actually solve for the + // eigenfunctions and -values, we + // have to also select which set of + // eigenvalues to solve for. Lets + // select those eigenvalues and + // corresponding eigenfunctions + // with the smallest real part (in + // fact, the problem we solve here + // is symmetric and so the + // eigenvalues are purely + // real). After that, we can + // actually let SLEPc do its work: + eigensolver.set_which_eigenpairs (EPS_SMALLEST_REAL); + + eigensolver.solve (stiffness_matrix, mass_matrix, + eigenvalues, eigenfunctions, + eigenfunctions.size()); + + // The output of the call above is + // a set of vectors and values. In + // eigenvalue problems, the + // eigenfunctions are only + // determined up to a constant that + // can be fixed pretty + // arbitrarily. Knowing nothing + // about the origin of the + // eigenvalue problem, SLEPc has no + // other choice than to normalize + // the eigenvectors to one in the + // $l_2$ (vector) + // norm. Unfortunately this norm + // has little to do with any norm + // we may be interested from a + // eigenfunction perspective: the + // $L_2(\Omega)$ norm, or maybe the + // $L_\infty(\Omega)$ norm. + // + // Let us choose the latter and + // rescale eigenfunctions so that + // they have $\|\phi_i(\mathbf + // x)\|_{L^\infty(\Omega)}=1$ + // instead of $\|\Phi\|_{l_2}=1$ + // (where $\phi_i$ is the $i$th + // eigenfunction and + // $\Phi_i$ the corresponding + // vector of nodal values). For the + // $Q_1$ elements chosen here, we + // know that the maximum of the + // function $\phi_i(\mathbf x)$ is + // attained at one of the nodes, so + // $\max_{\mathbf x}\phi_i(\mathbf + // x)=\max_j (\Phi_i)_j$, making + // the normalization in the + // $L_\infty$ norm trivial. Note + // that this doesn't work as easily + // if we had chosen $Q_k$ elements + // with $k>1$: there, the maximum + // of a function does not + // necessarily have to be attained + // at a node, and so $\max_{\mathbf + // x}\phi_i(\mathbf x)\ge\max_j + // (\Phi_i)_j$ (although the + // equality is usually nearly + // true). + for (unsigned int i=0; i -void EigenvalueProblem::run () -{ - make_grid_and_dofs (); - - std::cout << " Number of active cells: " - << triangulation.n_active_cells () - << std::endl - << " Number of degrees of freedom: " - << dof_handler.n_dofs () - << std::endl - << std::endl; - - assemble_system (); - solve (); - output_results (); - - for (unsigned int i=0; i + void EigenvalueProblem::output_results () const + { + DataOut data_out; + + data_out.attach_dof_handler (dof_handler); + + for (unsigned int i=0; i projected_potential (dof_handler.n_dofs()); + { + FunctionParser potential; + potential.initialize (FunctionParser::default_variable_names (), + parameters.get ("Potential"), + typename FunctionParser::ConstMap()); + VectorTools::interpolate (dof_handler, potential, projected_potential); + } + data_out.add_data_vector (projected_potential, "interpolated_potential"); + + data_out.build_patches (); + + std::ofstream output ("eigenvectors.vtk"); + data_out.write_vtk (output); + } + + + // @sect4{EigenvalueProblem::run} + + // This is the function which has the + // top-level control over + // everything. It is almost exactly + // the same as in step-4: + template + void EigenvalueProblem::run () + { + make_grid_and_dofs (); + + std::cout << " Number of active cells: " + << triangulation.n_active_cells () + << std::endl + << " Number of degrees of freedom: " + << dof_handler.n_dofs () + << std::endl << std::endl; -} + assemble_system (); + solve (); + output_results (); + + for (unsigned int i=0; imain function} -int main (int argc, char **argv) +int main (int argc, char **argv) { try { @@ -566,8 +568,11 @@ int main (int argc, char **argv) SlepcInitialize (&argc, &argv, 0, 0); { + using namespace dealii; + using namespace Step36; + deallog.depth_console (0); - + EigenvalueProblem<2> problem ("step-36.prm"); problem.run (); } @@ -592,7 +597,7 @@ int main (int argc, char **argv) return 1; } - catch (...) + catch (...) { std::cerr << std::endl << std::endl << "----------------------------------------------------" @@ -603,13 +608,13 @@ int main (int argc, char **argv) << std::endl; return 1; } - + // If no exceptions are thrown, // then we tell the program to stop // monkeying around and exit // nicely: - std::cout << std::endl - << "Job done." + std::cout << std::endl + << "Job done." << std::endl; return 0; diff --git a/deal.II/examples/step-38/step-38.cc b/deal.II/examples/step-38/step-38.cc index dfd540321a..61a32d7b4b 100644 --- a/deal.II/examples/step-38/step-38.cc +++ b/deal.II/examples/step-38/step-38.cc @@ -43,586 +43,590 @@ #include #include -using namespace dealii; - - // @sect3{The LaplaceBeltramiProblem class template} - - // This class is almost exactly similar to - // the LaplaceProblem class in - // step-4. - - // The essential differences are these: - // - // - The template parameter now denotes the - // dimensionality of the embedding space, - // which is no longer the same as the - // dimensionality of the domain and the - // triangulation on which we compute. We - // indicate this by calling the parameter - // @p spacedim , and introducing a constant - // @p dim equal to the dimensionality of - // the domain -- here equal to - // spacedim-1. - // - All member variables that have geometric - // aspects now need to know about both - // their own dimensionality as well as that - // of the embedding space. Consequently, we - // need to specify both of their template - // parameters one for the dimension of the - // mesh @p dim, and the other for the - // dimension of the embedding space, - // @p spacedim. This is exactly what we - // did in step-34, take a look there for - // a deeper explanation. - - // - We need an object that describes which - // kind of mapping to use from the - // reference cell to the cells that the - // triangulation is composed of. The - // classes derived from the Mapping base - // class do exactly this. Throughout most - // of deal.II, if you don't do anything at - // all, the library assumes that you want - // an object of kind MappingQ1 that uses a - // (bi-, tri-)linear mapping. In many - // cases, this is quite sufficient, which - // is why the use of these objects is - // mostly optional: for example, if you - // have a polygonal two-dimensional domain - // in two-dimensional space, a bilinear - // mapping of the reference cell to the - // cells of the triangulation yields an - // exact representation of the domain. If - // you have a curved domain, one may want - // to use a higher order mapping for those - // cells that lie at the boundary of the - // domain -- this is what we did in - // step-11, for example. However, here we - // have a curved domain, not just a curved - // boundary, and while we can approximate - // it with bilinearly mapped cells, it is - // really only prodent to use a higher - // order mapping for all - // cells. Consequently, this class has a - // member variable of type MappingQ; we - // will choose the polynomial degree of the - // mapping equal to the polynomial degree - // of the finite element used in the - // computations to ensure optimal approximation, though this - // iso-parametricity is not required. -template -class LaplaceBeltramiProblem -{ - public: - LaplaceBeltramiProblem (const unsigned degree = 2); - void run (); - - private: - static const unsigned int dim = spacedim-1; - - void make_grid_and_dofs (); - void assemble_system (); - void solve (); - void output_results () const; - void compute_error () const; - - - Triangulation triangulation; - FE_Q fe; - DoFHandler dof_handler; - MappingQ mapping; - - SparsityPattern sparsity_pattern; - SparseMatrix system_matrix; - - Vector solution; - Vector system_rhs; -}; - - - // @sect3{Equation data} - - // Next, let us define the classes that - // describe the exact solution and the right - // hand sides of the problem. This is in - // analogy to step-4 and step-7 where we also - // defined such objects. Given the discussion - // in the introduction, the actual formulas - // should be self-explanatory. A point of - // interest may be how we define the value - // and gradient functions for the 2d and 3d - // cases separately, using explicit - // specializations of the general - // template. An alternative to doing it this - // way might have been to define the general - // template and have a switch - // statement (or a sequence of - // ifs) for each possible value - // of the spatial dimension. -template -class Solution : public Function -{ - public: - Solution () : Function() {} - virtual double value (const Point &p, - const unsigned int component = 0) const; +namespace Step38 +{ + using namespace dealii; + + // @sect3{The LaplaceBeltramiProblem class template} + + // This class is almost exactly similar to + // the LaplaceProblem class in + // step-4. + + // The essential differences are these: + // + // - The template parameter now denotes the + // dimensionality of the embedding space, + // which is no longer the same as the + // dimensionality of the domain and the + // triangulation on which we compute. We + // indicate this by calling the parameter + // @p spacedim , and introducing a constant + // @p dim equal to the dimensionality of + // the domain -- here equal to + // spacedim-1. + // - All member variables that have geometric + // aspects now need to know about both + // their own dimensionality as well as that + // of the embedding space. Consequently, we + // need to specify both of their template + // parameters one for the dimension of the + // mesh @p dim, and the other for the + // dimension of the embedding space, + // @p spacedim. This is exactly what we + // did in step-34, take a look there for + // a deeper explanation. + + // - We need an object that describes which + // kind of mapping to use from the + // reference cell to the cells that the + // triangulation is composed of. The + // classes derived from the Mapping base + // class do exactly this. Throughout most + // of deal.II, if you don't do anything at + // all, the library assumes that you want + // an object of kind MappingQ1 that uses a + // (bi-, tri-)linear mapping. In many + // cases, this is quite sufficient, which + // is why the use of these objects is + // mostly optional: for example, if you + // have a polygonal two-dimensional domain + // in two-dimensional space, a bilinear + // mapping of the reference cell to the + // cells of the triangulation yields an + // exact representation of the domain. If + // you have a curved domain, one may want + // to use a higher order mapping for those + // cells that lie at the boundary of the + // domain -- this is what we did in + // step-11, for example. However, here we + // have a curved domain, not just a curved + // boundary, and while we can approximate + // it with bilinearly mapped cells, it is + // really only prodent to use a higher + // order mapping for all + // cells. Consequently, this class has a + // member variable of type MappingQ; we + // will choose the polynomial degree of the + // mapping equal to the polynomial degree + // of the finite element used in the + // computations to ensure optimal approximation, though this + // iso-parametricity is not required. + template + class LaplaceBeltramiProblem + { + public: + LaplaceBeltramiProblem (const unsigned degree = 2); + void run (); + + private: + static const unsigned int dim = spacedim-1; + + void make_grid_and_dofs (); + void assemble_system (); + void solve (); + void output_results () const; + void compute_error () const; + + + Triangulation triangulation; + FE_Q fe; + DoFHandler dof_handler; + MappingQ mapping; + + SparsityPattern sparsity_pattern; + SparseMatrix system_matrix; + + Vector solution; + Vector system_rhs; + }; + + + // @sect3{Equation data} + + // Next, let us define the classes that + // describe the exact solution and the right + // hand sides of the problem. This is in + // analogy to step-4 and step-7 where we also + // defined such objects. Given the discussion + // in the introduction, the actual formulas + // should be self-explanatory. A point of + // interest may be how we define the value + // and gradient functions for the 2d and 3d + // cases separately, using explicit + // specializations of the general + // template. An alternative to doing it this + // way might have been to define the general + // template and have a switch + // statement (or a sequence of + // ifs) for each possible value + // of the spatial dimension. + template + class Solution : public Function + { + public: + Solution () : Function() {} - virtual Tensor<1,dim> gradient (const Point &p, - const unsigned int component = 0) const; + virtual double value (const Point &p, + const unsigned int component = 0) const; -}; + virtual Tensor<1,dim> gradient (const Point &p, + const unsigned int component = 0) const; + }; -template <> -double -Solution<2>::value (const Point<2> &p, - const unsigned int) const -{ - return ( -2. * p(0) * p(1) ); -} + template <> + double + Solution<2>::value (const Point<2> &p, + const unsigned int) const + { + return ( -2. * p(0) * p(1) ); + } -template <> -Tensor<1,2> -Solution<2>::gradient (const Point<2> &p, - const unsigned int) const -{ - Tensor<1,2> return_value; - return_value[0] = -2. * p(1) * (1 - 2. * p(0) * p(0)); - return_value[1] = -2. * p(0) * (1 - 2. * p(1) * p(1)); - return return_value; -} + template <> + Tensor<1,2> + Solution<2>::gradient (const Point<2> &p, + const unsigned int) const + { + Tensor<1,2> return_value; + return_value[0] = -2. * p(1) * (1 - 2. * p(0) * p(0)); + return_value[1] = -2. * p(0) * (1 - 2. * p(1) * p(1)); + return return_value; + } -template <> -double -Solution<3>::value (const Point<3> &p, - const unsigned int) const -{ - return (std::sin(numbers::PI * p(0)) * - std::cos(numbers::PI * p(1))*exp(p(2))); -} + template <> + double + Solution<3>::value (const Point<3> &p, + const unsigned int) const + { + return (std::sin(numbers::PI * p(0)) * + std::cos(numbers::PI * p(1))*exp(p(2))); + } -template <> -Tensor<1,3> -Solution<3>::gradient (const Point<3> &p, - const unsigned int) const -{ - using numbers::PI; - Tensor<1,3> return_value; + template <> + Tensor<1,3> + Solution<3>::gradient (const Point<3> &p, + const unsigned int) const + { + using numbers::PI; - return_value[0] = PI *cos(PI * p(0))*cos(PI * p(1))*exp(p(2)); - return_value[1] = -PI *sin(PI * p(0))*sin(PI * p(1))*exp(p(2)); - return_value[2] = sin(PI * p(0))*cos(PI * p(1))*exp(p(2)); + Tensor<1,3> return_value; - return return_value; -} + return_value[0] = PI *cos(PI * p(0))*cos(PI * p(1))*exp(p(2)); + return_value[1] = -PI *sin(PI * p(0))*sin(PI * p(1))*exp(p(2)); + return_value[2] = sin(PI * p(0))*cos(PI * p(1))*exp(p(2)); + return return_value; + } -template -class RightHandSide : public Function -{ - public: - RightHandSide () : Function() {} - virtual double value (const Point &p, - const unsigned int component = 0) const; -}; + template + class RightHandSide : public Function + { + public: + RightHandSide () : Function() {} -template <> -double -RightHandSide<2>::value (const Point<2> &p, - const unsigned int /*component*/) const -{ - return ( -8. * p(0) * p(1) ); -} + virtual double value (const Point &p, + const unsigned int component = 0) const; + }; + template <> + double + RightHandSide<2>::value (const Point<2> &p, + const unsigned int /*component*/) const + { + return ( -8. * p(0) * p(1) ); + } -template <> -double -RightHandSide<3>::value (const Point<3> &p, - const unsigned int /*component*/) const -{ - using numbers::PI; - Tensor<2,3> hessian; + template <> + double + RightHandSide<3>::value (const Point<3> &p, + const unsigned int /*component*/) const + { + using numbers::PI; - hessian[0][0] = -PI*PI*sin(PI*p(0))*cos(PI*p(1))*exp(p(2)); - hessian[1][1] = -PI*PI*sin(PI*p(0))*cos(PI*p(1))*exp(p(2)); - hessian[2][2] = sin(PI*p(0))*cos(PI*p(1))*exp(p(2)); + Tensor<2,3> hessian; - hessian[0][1] = -PI*PI*cos(PI*p(0))*sin(PI*p(1))*exp(p(2)); - hessian[1][0] = -PI*PI*cos(PI*p(0))*sin(PI*p(1))*exp(p(2)); + hessian[0][0] = -PI*PI*sin(PI*p(0))*cos(PI*p(1))*exp(p(2)); + hessian[1][1] = -PI*PI*sin(PI*p(0))*cos(PI*p(1))*exp(p(2)); + hessian[2][2] = sin(PI*p(0))*cos(PI*p(1))*exp(p(2)); - hessian[0][2] = PI*cos(PI*p(0))*cos(PI*p(1))*exp(p(2)); - hessian[2][0] = PI*cos(PI*p(0))*cos(PI*p(1))*exp(p(2)); + hessian[0][1] = -PI*PI*cos(PI*p(0))*sin(PI*p(1))*exp(p(2)); + hessian[1][0] = -PI*PI*cos(PI*p(0))*sin(PI*p(1))*exp(p(2)); - hessian[1][2] = -PI*sin(PI*p(0))*sin(PI*p(1))*exp(p(2)); - hessian[2][1] = -PI*sin(PI*p(0))*sin(PI*p(1))*exp(p(2)); + hessian[0][2] = PI*cos(PI*p(0))*cos(PI*p(1))*exp(p(2)); + hessian[2][0] = PI*cos(PI*p(0))*cos(PI*p(1))*exp(p(2)); - Tensor<1,3> gradient; - gradient[0] = PI * cos(PI*p(0))*cos(PI*p(1))*exp(p(2)); - gradient[1] = - PI * sin(PI*p(0))*sin(PI*p(1))*exp(p(2)); - gradient[2] = sin(PI*p(0))*cos(PI*p(1))*exp(p(2)); + hessian[1][2] = -PI*sin(PI*p(0))*sin(PI*p(1))*exp(p(2)); + hessian[2][1] = -PI*sin(PI*p(0))*sin(PI*p(1))*exp(p(2)); - Point<3> normal = p; - normal /= p.norm(); + Tensor<1,3> gradient; + gradient[0] = PI * cos(PI*p(0))*cos(PI*p(1))*exp(p(2)); + gradient[1] = - PI * sin(PI*p(0))*sin(PI*p(1))*exp(p(2)); + gradient[2] = sin(PI*p(0))*cos(PI*p(1))*exp(p(2)); - return (- trace(hessian) - + 2 * (gradient * normal) - + (hessian * normal) * normal); -} + Point<3> normal = p; + normal /= p.norm(); + return (- trace(hessian) + + 2 * (gradient * normal) + + (hessian * normal) * normal); + } - // @sect3{Implementation of the LaplaceBeltramiProblem class} - - // The rest of the program is actually quite - // unspectacular if you know step-4. Our - // first step is to define the constructor, - // setting the polynomial degree of the - // finite element and mapping, and - // associating the DoF handler to the - // triangulation: -template -LaplaceBeltramiProblem:: -LaplaceBeltramiProblem (const unsigned degree) - : - fe (degree), - dof_handler(triangulation), - mapping (degree) -{} - - - // @sect4{LaplaceBeltramiProblem::make_grid_and_dofs} - - // The next step is to create the mesh, - // distribute degrees of freedom, and set up - // the various variables that describe the - // linear system. All of these steps are - // standard with the exception of how to - // create a mesh that describes a surface. We - // could generate a mesh for the domain we - // are interested in, generate a - // triangulation using a mesh generator, and - // read it in using the GridIn class. Or, as - // we do here, we generate the mesh using the - // facilities in the GridGenerator namespace. - // - // In particular, what we're going to do is - // this (enclosed between the set of braces - // below): we generate a - // spacedim dimensional mesh for - // the half disk (in 2d) or half ball (in - // 3d), using the - // GridGenerator::half_hyper_ball - // function. This function sets the boundary - // indicators of all faces on the outside of - // the boundary to zero for the ones located - // on the perimeter of the disk/ball, and one - // on the straight part that splits the full - // disk/ball into two halves. The next step - // is the main point: The - // GridTools::extract_boundary_mesh function - // creates a mesh that consists of those - // cells that are the faces of the previous - // mesh, i.e. it describes the surface - // cells of the original (volume) - // mesh. However, we do not want all faces: - // only those on the perimeter of the disk or - // ball which carry boundary indicator zero; - // we can select these cells using a set of - // boundary indicators that we pass to - // GridTools::extract_boundary_mesh. - // - // There is one point that needs to be - // mentioned. In order to refine a surface - // mesh appropriately if the manifold is - // curved (similarly to refining the faces of - // cells that are adjacent to a curved - // boundary), the triangulation has to have - // an object attached to it that described - // where new vertices should be located. If - // you don't attach such a boundary object, - // they will be located halfway between - // existing vertices; this is appropriate if - // you have a domain with straight boundaries - // (e.g. a polygon) but not when, as here, - // the manifold has curvature. So for things - // to work properly, we need to attach a - // manifold object to our (surface) - // triangulation. We create such an object - // (with indefinite, static, - // lifetime) at the top of the function and - // attach it to the triangulation for all - // cells with boundary indicator zero that - // will be created henceforth. - // - // The final step in creating the mesh is to - // refine it a number of times. The rest of - // the function is the same as in previous - // tutorial programs. -template -void LaplaceBeltramiProblem::make_grid_and_dofs () -{ - static HyperBallBoundary surface_description; - triangulation.set_boundary (0, surface_description); + // @sect3{Implementation of the LaplaceBeltramiProblem class} + + // The rest of the program is actually quite + // unspectacular if you know step-4. Our + // first step is to define the constructor, + // setting the polynomial degree of the + // finite element and mapping, and + // associating the DoF handler to the + // triangulation: + template + LaplaceBeltramiProblem:: + LaplaceBeltramiProblem (const unsigned degree) + : + fe (degree), + dof_handler(triangulation), + mapping (degree) + {} + + + // @sect4{LaplaceBeltramiProblem::make_grid_and_dofs} + + // The next step is to create the mesh, + // distribute degrees of freedom, and set up + // the various variables that describe the + // linear system. All of these steps are + // standard with the exception of how to + // create a mesh that describes a surface. We + // could generate a mesh for the domain we + // are interested in, generate a + // triangulation using a mesh generator, and + // read it in using the GridIn class. Or, as + // we do here, we generate the mesh using the + // facilities in the GridGenerator namespace. + // + // In particular, what we're going to do is + // this (enclosed between the set of braces + // below): we generate a + // spacedim dimensional mesh for + // the half disk (in 2d) or half ball (in + // 3d), using the + // GridGenerator::half_hyper_ball + // function. This function sets the boundary + // indicators of all faces on the outside of + // the boundary to zero for the ones located + // on the perimeter of the disk/ball, and one + // on the straight part that splits the full + // disk/ball into two halves. The next step + // is the main point: The + // GridTools::extract_boundary_mesh function + // creates a mesh that consists of those + // cells that are the faces of the previous + // mesh, i.e. it describes the surface + // cells of the original (volume) + // mesh. However, we do not want all faces: + // only those on the perimeter of the disk or + // ball which carry boundary indicator zero; + // we can select these cells using a set of + // boundary indicators that we pass to + // GridTools::extract_boundary_mesh. + // + // There is one point that needs to be + // mentioned. In order to refine a surface + // mesh appropriately if the manifold is + // curved (similarly to refining the faces of + // cells that are adjacent to a curved + // boundary), the triangulation has to have + // an object attached to it that described + // where new vertices should be located. If + // you don't attach such a boundary object, + // they will be located halfway between + // existing vertices; this is appropriate if + // you have a domain with straight boundaries + // (e.g. a polygon) but not when, as here, + // the manifold has curvature. So for things + // to work properly, we need to attach a + // manifold object to our (surface) + // triangulation. We create such an object + // (with indefinite, static, + // lifetime) at the top of the function and + // attach it to the triangulation for all + // cells with boundary indicator zero that + // will be created henceforth. + // + // The final step in creating the mesh is to + // refine it a number of times. The rest of + // the function is the same as in previous + // tutorial programs. + template + void LaplaceBeltramiProblem::make_grid_and_dofs () { - Triangulation volume_mesh; - GridGenerator::half_hyper_ball(volume_mesh); + static HyperBallBoundary surface_description; + triangulation.set_boundary (0, surface_description); - std::set boundary_ids; - boundary_ids.insert (0); + { + Triangulation volume_mesh; + GridGenerator::half_hyper_ball(volume_mesh); - GridTools::extract_boundary_mesh (volume_mesh, triangulation, - boundary_ids); - } - triangulation.refine_global(4); + std::set boundary_ids; + boundary_ids.insert (0); - std::cout << "Surface mesh has " << triangulation.n_active_cells() - << " cells." - << std::endl; + GridTools::extract_boundary_mesh (volume_mesh, triangulation, + boundary_ids); + } + triangulation.refine_global(4); - dof_handler.distribute_dofs (fe); + std::cout << "Surface mesh has " << triangulation.n_active_cells() + << " cells." + << std::endl; - std::cout << "Surface mesh has " << dof_handler.n_dofs() - << " degrees of freedom." - << std::endl; + dof_handler.distribute_dofs (fe); - CompressedSparsityPattern csp (dof_handler.n_dofs(), dof_handler.n_dofs()); - DoFTools::make_sparsity_pattern (dof_handler, csp); - sparsity_pattern.copy_from (csp); + std::cout << "Surface mesh has " << dof_handler.n_dofs() + << " degrees of freedom." + << std::endl; - system_matrix.reinit (sparsity_pattern); + CompressedSparsityPattern csp (dof_handler.n_dofs(), dof_handler.n_dofs()); + DoFTools::make_sparsity_pattern (dof_handler, csp); + sparsity_pattern.copy_from (csp); - solution.reinit (dof_handler.n_dofs()); - system_rhs.reinit (dof_handler.n_dofs()); -} + system_matrix.reinit (sparsity_pattern); + solution.reinit (dof_handler.n_dofs()); + system_rhs.reinit (dof_handler.n_dofs()); + } - // @sect4{LaplaceBeltramiProblem::assemble_system} - - // The following is the central function of - // this program, assembling the matrix that - // corresponds to the surface Laplacian - // (Laplace-Beltrami operator). Maybe - // surprisingly, it actually looks exactly - // the same as for the regular Laplace - // operator discussed in, for example, - // step-4. The key is that the - // FEValues::shape_gradient function does the - // magic: It returns the surface gradient - // $\nabla_K \phi_i(x_q)$ of the $i$th shape - // function at the $q$th quadrature - // point. The rest then does not need any - // changes either: -template -void LaplaceBeltramiProblem::assemble_system () -{ - system_matrix = 0; - system_rhs = 0; - const QGauss quadrature_formula(2*fe.degree); - FEValues fe_values (mapping, fe, quadrature_formula, - update_values | - update_gradients | - update_quadrature_points | - update_JxW_values); + // @sect4{LaplaceBeltramiProblem::assemble_system} + + // The following is the central function of + // this program, assembling the matrix that + // corresponds to the surface Laplacian + // (Laplace-Beltrami operator). Maybe + // surprisingly, it actually looks exactly + // the same as for the regular Laplace + // operator discussed in, for example, + // step-4. The key is that the + // FEValues::shape_gradient function does the + // magic: It returns the surface gradient + // $\nabla_K \phi_i(x_q)$ of the $i$th shape + // function at the $q$th quadrature + // point. The rest then does not need any + // changes either: + template + void LaplaceBeltramiProblem::assemble_system () + { + system_matrix = 0; + system_rhs = 0; - const unsigned int dofs_per_cell = fe.dofs_per_cell; - const unsigned int n_q_points = quadrature_formula.size(); + const QGauss quadrature_formula(2*fe.degree); + FEValues fe_values (mapping, fe, quadrature_formula, + update_values | + update_gradients | + update_quadrature_points | + update_JxW_values); - FullMatrix cell_matrix (dofs_per_cell, dofs_per_cell); - Vector cell_rhs (dofs_per_cell); + const unsigned int dofs_per_cell = fe.dofs_per_cell; + const unsigned int n_q_points = quadrature_formula.size(); - std::vector rhs_values(n_q_points); - std::vector local_dof_indices (dofs_per_cell); + FullMatrix cell_matrix (dofs_per_cell, dofs_per_cell); + Vector cell_rhs (dofs_per_cell); - const RightHandSide rhs; + std::vector rhs_values(n_q_points); + std::vector local_dof_indices (dofs_per_cell); - for (typename DoFHandler::active_cell_iterator - cell = dof_handler.begin_active(), - endc = dof_handler.end(); - cell!=endc; ++cell) - { - cell_matrix = 0; - cell_rhs = 0; + const RightHandSide rhs; - fe_values.reinit (cell); + for (typename DoFHandler::active_cell_iterator + cell = dof_handler.begin_active(), + endc = dof_handler.end(); + cell!=endc; ++cell) + { + cell_matrix = 0; + cell_rhs = 0; - rhs.value_list (fe_values.get_quadrature_points(), rhs_values); + fe_values.reinit (cell); - for (unsigned int i=0; iget_dof_indices (local_dof_indices); - for (unsigned int i=0; i boundary_values; - VectorTools::interpolate_boundary_values (mapping, - dof_handler, - 0, - Solution(), - boundary_values); - - MatrixTools::apply_boundary_values (boundary_values, - system_matrix, - solution, - system_rhs,false); -} + for (unsigned int i=0; iget_dof_indices (local_dof_indices); + for (unsigned int i=0; i boundary_values; + VectorTools::interpolate_boundary_values (mapping, + dof_handler, + 0, + Solution(), + boundary_values); + + MatrixTools::apply_boundary_values (boundary_values, + system_matrix, + solution, + system_rhs,false); + } - // @sect4{LaplaceBeltramiProblem::solve} + // @sect4{LaplaceBeltramiProblem::solve} - // The next function is the one that solves - // the linear system. Here, too, no changes - // are necessary: -template -void LaplaceBeltramiProblem::solve () -{ - SolverControl solver_control (solution.size(), - 1e-7 * system_rhs.l2_norm()); - SolverCG<> cg (solver_control); + // The next function is the one that solves + // the linear system. Here, too, no changes + // are necessary: + template + void LaplaceBeltramiProblem::solve () + { + SolverControl solver_control (solution.size(), + 1e-7 * system_rhs.l2_norm()); + SolverCG<> cg (solver_control); - PreconditionSSOR<> preconditioner; - preconditioner.initialize(system_matrix, 1.2); + PreconditionSSOR<> preconditioner; + preconditioner.initialize(system_matrix, 1.2); - cg.solve (system_matrix, solution, system_rhs, - preconditioner); -} + cg.solve (system_matrix, solution, system_rhs, + preconditioner); + } - // @sect4{LaplaceBeltramiProblem::output_result} - - // This is the function that generates - // graphical output from the solution. Most - // of it is boilerplate code, but there are - // two points worth pointing out: - // - // - The DataOut::add_data_vector function - // can take two kinds of vectors: Either - // vectors that have one value per degree - // of freedom defined by the DoFHandler - // object previously attached via - // DataOut::attach_dof_handler; and vectors - // that have one value for each cell of the - // triangulation, for example to output - // estimated errors for each - // cell. Typically, the DataOut class knows - // to tell these two kinds of vectors - // apart: there are almost always more - // degrees of freedom than cells, so we can - // differentiate by the two kinds looking - // at the length of a vector. We could do - // the same here, but only because we got - // lucky: we use a half sphere. If we had - // used the whole sphere as domain and - // $Q_1$ elements, we would have the same - // number of cells as vertices and - // consequently the two kinds of vectors - // would have the same number of - // elements. To avoid the resulting - // confusion, we have to tell the - // DataOut::add_data_vector function which - // kind of vector we have: DoF data. This - // is what the third argument to the - // function does. - // - The DataOut::build_patches function can - // generate output that subdivides each - // cell so that visualization programs can - // resolve curved manifolds or higher - // polynomial degree shape functions - // better. We here subdivide each element - // in each coordinate direction as many - // times as the polynomial degree of the - // finite element in use. -template -void LaplaceBeltramiProblem::output_results () const -{ - DataOut > data_out; - data_out.attach_dof_handler (dof_handler); - data_out.add_data_vector (solution, - "solution", - DataOut >::type_dof_data); - data_out.build_patches (mapping, - mapping.get_degree()); - - std::string filename ("solution-"); - filename += ('0'+spacedim);filename += "d.vtk"; - std::ofstream output (filename.c_str()); - data_out.write_vtk (output); -} + // @sect4{LaplaceBeltramiProblem::output_result} + + // This is the function that generates + // graphical output from the solution. Most + // of it is boilerplate code, but there are + // two points worth pointing out: + // + // - The DataOut::add_data_vector function + // can take two kinds of vectors: Either + // vectors that have one value per degree + // of freedom defined by the DoFHandler + // object previously attached via + // DataOut::attach_dof_handler; and vectors + // that have one value for each cell of the + // triangulation, for example to output + // estimated errors for each + // cell. Typically, the DataOut class knows + // to tell these two kinds of vectors + // apart: there are almost always more + // degrees of freedom than cells, so we can + // differentiate by the two kinds looking + // at the length of a vector. We could do + // the same here, but only because we got + // lucky: we use a half sphere. If we had + // used the whole sphere as domain and + // $Q_1$ elements, we would have the same + // number of cells as vertices and + // consequently the two kinds of vectors + // would have the same number of + // elements. To avoid the resulting + // confusion, we have to tell the + // DataOut::add_data_vector function which + // kind of vector we have: DoF data. This + // is what the third argument to the + // function does. + // - The DataOut::build_patches function can + // generate output that subdivides each + // cell so that visualization programs can + // resolve curved manifolds or higher + // polynomial degree shape functions + // better. We here subdivide each element + // in each coordinate direction as many + // times as the polynomial degree of the + // finite element in use. + template + void LaplaceBeltramiProblem::output_results () const + { + DataOut > data_out; + data_out.attach_dof_handler (dof_handler); + data_out.add_data_vector (solution, + "solution", + DataOut >::type_dof_data); + data_out.build_patches (mapping, + mapping.get_degree()); + + std::string filename ("solution-"); + filename += ('0'+spacedim);filename += "d.vtk"; + std::ofstream output (filename.c_str()); + data_out.write_vtk (output); + } - // @sect4{LaplaceBeltramiProblem::compute_error} + // @sect4{LaplaceBeltramiProblem::compute_error} - // This is the last piece of functionality: - // we want to compute the error in the - // numerical solution. It is a verbatim copy - // of the code previously shown and discussed - // in step-7. As mentioned in the - // introduction, the Solution - // class provides the (tangential) gradient - // of the solution. To avoid evaluating the - // error only a superconvergence points, we - // choose a quadrature rule of sufficiently - // high order. -template -void LaplaceBeltramiProblem::compute_error () const -{ - Vector difference_per_cell (triangulation.n_active_cells()); - VectorTools::integrate_difference (mapping, dof_handler, solution, - Solution(), - difference_per_cell, - QGauss(2*fe.degree+1), - VectorTools::H1_norm); - - std::cout << "H1 error = " - << difference_per_cell.l2_norm() - << std::endl; -} + // This is the last piece of functionality: + // we want to compute the error in the + // numerical solution. It is a verbatim copy + // of the code previously shown and discussed + // in step-7. As mentioned in the + // introduction, the Solution + // class provides the (tangential) gradient + // of the solution. To avoid evaluating the + // error only a superconvergence points, we + // choose a quadrature rule of sufficiently + // high order. + template + void LaplaceBeltramiProblem::compute_error () const + { + Vector difference_per_cell (triangulation.n_active_cells()); + VectorTools::integrate_difference (mapping, dof_handler, solution, + Solution(), + difference_per_cell, + QGauss(2*fe.degree+1), + VectorTools::H1_norm); + + std::cout << "H1 error = " + << difference_per_cell.l2_norm() + << std::endl; + } - // @sect4{LaplaceBeltramiProblem::run} + // @sect4{LaplaceBeltramiProblem::run} - // The last function provides the top-level - // logic. Its contents are self-explanatory: -template -void LaplaceBeltramiProblem::run () -{ - make_grid_and_dofs(); - assemble_system (); - solve (); - output_results (); - compute_error (); + // The last function provides the top-level + // logic. Its contents are self-explanatory: + template + void LaplaceBeltramiProblem::run () + { + make_grid_and_dofs(); + assemble_system (); + solve (); + output_results (); + compute_error (); + } } @@ -637,6 +641,9 @@ int main () { try { + using namespace dealii; + using namespace Step38; + deallog.depth_console (0); LaplaceBeltramiProblem<3> laplace_beltrami; diff --git a/deal.II/examples/step-39/step-39.cc b/deal.II/examples/step-39/step-39.cc index 330385f379..687e11fb83 100644 --- a/deal.II/examples/step-39/step-39.cc +++ b/deal.II/examples/step-39/step-39.cc @@ -71,843 +71,845 @@ // order to save typing, we tell the // compiler to search names in there // as well. -using namespace dealii; - - // This is the function we use to set - // the boundary values and also the - // exact solution we compare to. -Functions::SlitSingularityFunction<2> exact_solution; - - // @sect3{The local integrators} - - // MeshWorker separates local - // integration from the loops over - // cells and faces. Thus, we have to - // write local integration classes - // for generating matrices, the right - // hand side and the error - // estimator. - - // All these classes have the same - // three functions for integrating - // over cells, boundary faces and - // interior faces, respectively. All - // the information needed for the - // local integration is provided by - // MeshWorker::IntegrationInfo. Note - // that the signature of the functions cannot - // be changed, because it is expected - // by MeshWorker::integration_loop(). - - // The first class defining local - // integrators is responsible for - // computing cell and face - // matrices. It is used to assemble - // the global matrix as well as the - // level matrices. -template -class MatrixIntegrator : public Subscriptor -{ - public: - static void cell(MeshWorker::DoFInfo& dinfo, - typename MeshWorker::IntegrationInfo& info); - static void boundary(MeshWorker::DoFInfo& dinfo, - typename MeshWorker::IntegrationInfo& info); - static void face(MeshWorker::DoFInfo& dinfo1, - MeshWorker::DoFInfo& dinfo2, - typename MeshWorker::IntegrationInfo& info1, - typename MeshWorker::IntegrationInfo& info2); -}; - - - // On each cell, we integrate the - // Dirichlet form. We use the library - // of ready made integrals in - // LocalIntegrators to avoid writing - // these loops ourselves. Similarly, - // we implement Nitsche boundary - // conditions and the interior - // penalty fluxes between cells. - // - // The boundary und flux terms need a - // penalty parameter, which should be - // adjusted to the cell size and the - // polynomial degree. A safe choice - // of this parameter for constant - // coefficients can be found in - // LocalIntegrators::Laplace::compute_penalty() - // and we use this below. -template -void MatrixIntegrator::cell( - MeshWorker::DoFInfo& dinfo, - typename MeshWorker::IntegrationInfo& info) -{ - LocalIntegrators::Laplace::cell_matrix(dinfo.matrix(0,false).matrix, info.fe_values()); -} - - -template -void MatrixIntegrator::boundary( - MeshWorker::DoFInfo& dinfo, - typename MeshWorker::IntegrationInfo& info) -{ - const unsigned int deg = info.fe_values(0).get_fe().tensor_degree(); - LocalIntegrators::Laplace::nitsche_matrix( - dinfo.matrix(0,false).matrix, info.fe_values(0), - LocalIntegrators::Laplace::compute_penalty(dinfo, dinfo, deg, deg)); -} - - // Interior faces use the interior - // penalty method -template -void MatrixIntegrator::face( - MeshWorker::DoFInfo& dinfo1, - MeshWorker::DoFInfo& dinfo2, - typename MeshWorker::IntegrationInfo& info1, - typename MeshWorker::IntegrationInfo& info2) -{ - const unsigned int deg = info1.fe_values(0).get_fe().tensor_degree(); - LocalIntegrators::Laplace::ip_matrix( - dinfo1.matrix(0,false).matrix, dinfo1.matrix(0,true).matrix, - dinfo2.matrix(0,true).matrix, dinfo2.matrix(0,false).matrix, - info1.fe_values(0), info2.fe_values(0), - LocalIntegrators::Laplace::compute_penalty(dinfo1, dinfo2, deg, deg)); -} - - // The second local integrator builds - // the right hand side. In our - // example, the right hand side - // function is zero, such that only - // the boundary condition is set here - // in weak form. -template -class RHSIntegrator : public Subscriptor -{ - public: - static void cell(MeshWorker::DoFInfo& dinfo, typename MeshWorker::IntegrationInfo& info); - static void boundary(MeshWorker::DoFInfo& dinfo, typename MeshWorker::IntegrationInfo& info); - static void face(MeshWorker::DoFInfo& dinfo1, - MeshWorker::DoFInfo& dinfo2, - typename MeshWorker::IntegrationInfo& info1, - typename MeshWorker::IntegrationInfo& info2); -}; - - -template -void RHSIntegrator::cell(MeshWorker::DoFInfo&, typename MeshWorker::IntegrationInfo&) -{} - - -template -void RHSIntegrator::boundary(MeshWorker::DoFInfo& dinfo, typename MeshWorker::IntegrationInfo& info) -{ - const FEValuesBase& fe = info.fe_values(); - Vector& local_vector = dinfo.vector(0).block(0); - - std::vector boundary_values(fe.n_quadrature_points); - exact_solution.value_list(fe.get_quadrature_points(), boundary_values); - - const unsigned int deg = fe.get_fe().tensor_degree(); - const double penalty = 2. * deg * (deg+1) * dinfo.face->measure() / dinfo.cell->measure(); - - for (unsigned k=0;k -void RHSIntegrator::face(MeshWorker::DoFInfo&, - MeshWorker::DoFInfo&, - typename MeshWorker::IntegrationInfo&, - typename MeshWorker::IntegrationInfo&) -{} - - - // The third local integrator is - // responsible for the contributions - // to the error estimate. This is the - // standard energy estimator due to - // Karakashian and Pascal (2003). -template -class Estimator : public Subscriptor -{ - public: - static void cell(MeshWorker::DoFInfo& dinfo, typename MeshWorker::IntegrationInfo& info); - static void boundary(MeshWorker::DoFInfo& dinfo, typename MeshWorker::IntegrationInfo& info); - static void face(MeshWorker::DoFInfo& dinfo1, - MeshWorker::DoFInfo& dinfo2, - typename MeshWorker::IntegrationInfo& info1, - typename MeshWorker::IntegrationInfo& info2); -}; - - - // The cell contribution is the - // Laplacian of the discrete - // solution, since the right hand - // side is zero. -template -void Estimator::cell(MeshWorker::DoFInfo& dinfo, typename MeshWorker::IntegrationInfo& info) -{ - const FEValuesBase& fe = info.fe_values(); - - const std::vector >& DDuh = info.hessians[0][0]; - for (unsigned k=0;kdiameter() * trace(DDuh[k]); - dinfo.value(0) += t*t * fe.JxW(k); - } - dinfo.value(0) = std::sqrt(dinfo.value(0)); -} - - // At the boundary, we use simply a - // weighted form of the boundary - // residual, namely the norm of the - // difference between the finite - // element solution and the correct - // boundary condition. -template -void Estimator::boundary(MeshWorker::DoFInfo& dinfo, typename MeshWorker::IntegrationInfo& info) -{ - const FEValuesBase& fe = info.fe_values(); - - std::vector boundary_values(fe.n_quadrature_points); - exact_solution.value_list(fe.get_quadrature_points(), boundary_values); - - const std::vector& uh = info.values[0][0]; - - const unsigned int deg = fe.get_fe().tensor_degree(); - const double penalty = 2. * deg * (deg+1) * dinfo.face->measure() / dinfo.cell->measure(); - - for (unsigned k=0;k -void Estimator::face(MeshWorker::DoFInfo& dinfo1, - MeshWorker::DoFInfo& dinfo2, - typename MeshWorker::IntegrationInfo& info1, - typename MeshWorker::IntegrationInfo& info2) -{ - const FEValuesBase& fe = info1.fe_values(); - const std::vector& uh1 = info1.values[0][0]; - const std::vector& uh2 = info2.values[0][0]; - const std::vector >& Duh1 = info1.gradients[0][0]; - const std::vector >& Duh2 = info2.gradients[0][0]; - - const unsigned int deg = fe.get_fe().tensor_degree(); - const double penalty1 = deg * (deg+1) * dinfo1.face->measure() / dinfo1.cell->measure(); - const double penalty2 = deg * (deg+1) * dinfo2.face->measure() / dinfo2.cell->measure(); - const double penalty = penalty1 + penalty2; - const double h = dinfo1.face->measure(); - - for (unsigned k=0;k -class ErrorIntegrator : public Subscriptor -{ - public: - static void cell(MeshWorker::DoFInfo& dinfo, typename MeshWorker::IntegrationInfo& info); - static void boundary(MeshWorker::DoFInfo& dinfo, typename MeshWorker::IntegrationInfo& info); - static void face(MeshWorker::DoFInfo& dinfo1, - MeshWorker::DoFInfo& dinfo2, - typename MeshWorker::IntegrationInfo& info1, - typename MeshWorker::IntegrationInfo& info2); -}; - - // Here we have the integration on - // cells. There is currently no good - // interfce in MeshWorker that would - // allow us to access values of - // regular functions in the - // quadrature points. Thus, we have - // to create the vectors for the - // exact function's values and - // gradients inside the cell - // integrator. After that, everything - // is as before and we just add up - // the squares of the differences. - - // Additionally to computing the error - // in the energy norm, we use the - // capability of the mesh worker to - // compute two functionals at the - // same time and compute the - // L2-error in the - // same loop. Obviously, this one - // does not have any jump terms and - // only appears in the integration on - // cells. -template -void ErrorIntegrator::cell( - MeshWorker::DoFInfo& dinfo, - typename MeshWorker::IntegrationInfo& info) -{ - const FEValuesBase& fe = info.fe_values(); - std::vector > exact_gradients(fe.n_quadrature_points); - std::vector exact_values(fe.n_quadrature_points); - - exact_solution.gradient_list(fe.get_quadrature_points(), exact_gradients); - exact_solution.value_list(fe.get_quadrature_points(), exact_values); - - const std::vector >& Duh = info.gradients[0][0]; - const std::vector& uh = info.values[0][0]; - - for (unsigned k=0;k -void ErrorIntegrator::boundary( - MeshWorker::DoFInfo& dinfo, - typename MeshWorker::IntegrationInfo& info) -{ - const FEValuesBase& fe = info.fe_values(); - - std::vector exact_values(fe.n_quadrature_points); - exact_solution.value_list(fe.get_quadrature_points(), exact_values); - - const std::vector& uh = info.values[0][0]; - - const unsigned int deg = fe.get_fe().tensor_degree(); - const double penalty = 2. * deg * (deg+1) * dinfo.face->measure() / dinfo.cell->measure(); - - for (unsigned k=0;k -void ErrorIntegrator::face( - MeshWorker::DoFInfo& dinfo1, - MeshWorker::DoFInfo& dinfo2, - typename MeshWorker::IntegrationInfo& info1, - typename MeshWorker::IntegrationInfo& info2) -{ - const FEValuesBase& fe = info1.fe_values(); - const std::vector& uh1 = info1.values[0][0]; - const std::vector& uh2 = info2.values[0][0]; - - const unsigned int deg = fe.get_fe().tensor_degree(); - const double penalty1 = deg * (deg+1) * dinfo1.face->measure() / dinfo1.cell->measure(); - const double penalty2 = deg * (deg+1) * dinfo2.face->measure() / dinfo2.cell->measure(); - const double penalty = penalty1 + penalty2; - - for (unsigned k=0;k -class Step39 -{ - public: - typedef MeshWorker::IntegrationInfo CellInfo; - - Step39(const FiniteElement& fe); - - void run(unsigned int n_steps); - - private: - void setup_system (); - void assemble_matrix (); - void assemble_mg_matrix (); - void assemble_right_hand_side (); - void error (); - double estimate (); - void solve (); - void output_results (const unsigned int cycle) const; - - // The member objects related to - // the discretization are here. - Triangulation triangulation; - const MappingQ1 mapping; - const FiniteElement& fe; - MGDoFHandler mg_dof_handler; - DoFHandler& dof_handler; - - // Then, we have the matrices and - // vectors related to the global - // discrete system. - SparsityPattern sparsity; - SparseMatrix matrix; - Vector solution; - Vector right_hand_side; - BlockVector estimates; - - // Finally, we have a group of - // sparsity patterns and sparse - // matrices related to the - // multilevel preconditioner. - // First, we have a level matrix - // and its sparsity pattern. - MGLevelObject mg_sparsity; - MGLevelObject > mg_matrix; - - // When we perform multigrid with - // local smoothing on locally - // refined meshes, additional - // matrices are required; see - // Kanschat (2004). Here is the - // sparsity pattern for these - // edge matrices. We only need - // one, because the pattern of - // the up matrix is the - // transpose of that of the down - // matrix. Actually, we do not - // care too much about these - // details, since the MeshWorker - // is filling these matrices. - MGLevelObject mg_sparsity_dg_interface; - // The flux matrix at the - // refinement edge, coupling fine - // level degrees of freedom to - // coarse level. - MGLevelObject > mg_matrix_dg_down; - // The transpose of the flux - // matrix at the refinement edge, - // coupling coarse level degrees - // of freedom to fine level. - MGLevelObject > mg_matrix_dg_up; -}; - - - // The constructor simply sets up the - // coarse grid and the - // DoFHandler. The FiniteElement is - // provided as a parameter to allow - // flexibility. -template -Step39::Step39(const FiniteElement& fe) - : - mapping(), - fe(fe), - mg_dof_handler(triangulation), - dof_handler(mg_dof_handler), - estimates(1) +namespace Step39 { - GridGenerator::hyper_cube_slit(triangulation, -1, 1); -} - - - // In this function, we set up the - // dimension of the linear system and - // the sparsity patterns for the - // global matrix as well as the level - // matrices. -template -void -Step39::setup_system() -{ - // First, we use the finite element - // to distribute degrees of - // freedom over the mesh and number - // them. - dof_handler.distribute_dofs(fe); - unsigned int n_dofs = dof_handler.n_dofs(); - // Then, we already know the size - // of the vectors representing - // finite element functions. - solution.reinit(n_dofs); - right_hand_side.reinit(n_dofs); - - // Next, we set up the sparsity - // pattern for the global - // matrix. Since we do not know the - // row sizes in advance, we first - // fill a temporary - // CompressedSparsityPattern object - // and copy it to the regular - // SparsityPattern once it is - // complete. - CompressedSparsityPattern c_sparsity(n_dofs); - DoFTools::make_flux_sparsity_pattern(dof_handler, c_sparsity); - sparsity.copy_from(c_sparsity); - matrix.reinit(sparsity); - - const unsigned int n_levels = triangulation.n_levels(); - // The global system is set up, now - // we attend to the level - // matrices. We resize all matrix - // objects to hold one matrix per level. - mg_matrix.resize(0, n_levels-1); - mg_matrix.clear(); - mg_matrix_dg_up.resize(0, n_levels-1); - mg_matrix_dg_up.clear(); - mg_matrix_dg_down.resize(0, n_levels-1); - mg_matrix_dg_down.clear(); - // It is important to update the - // sparsity patterns after - // clear() was called for - // the level matrices, since the - // matrices lock the sparsity - // pattern through the Smartpointer - // ans Subscriptor mechanism. - mg_sparsity.resize(0, n_levels-1); - mg_sparsity_dg_interface.resize(0, n_levels-1); - - // Now all objects are prepared to - // hold one sparsity pattern or - // matrix per level. What's left is - // setting up the sparsity patterns - // on each level. - for (unsigned int level=mg_sparsity.get_minlevel(); - level<=mg_sparsity.get_maxlevel();++level) - { - // These are roughly the same - // lines as above for the - // global matrix, now for each - // level. - CompressedSparsityPattern c_sparsity(mg_dof_handler.n_dofs(level)); - MGTools::make_flux_sparsity_pattern(mg_dof_handler, c_sparsity, level); - mg_sparsity[level].copy_from(c_sparsity); - mg_matrix[level].reinit(mg_sparsity[level]); - - // Additionally, we need to - // initialize the transfer - // matrices at the refinement - // edge between levels. They - // are stored at the index - // referring to the finer of - // the two indices, thus there - // is no such object on level - // 0. - if (level>0) - { - CompressedSparsityPattern ci_sparsity; - ci_sparsity.reinit(mg_dof_handler.n_dofs(level-1), mg_dof_handler.n_dofs(level)); - MGTools::make_flux_sparsity_pattern_edge(mg_dof_handler, ci_sparsity, level); - mg_sparsity_dg_interface[level].copy_from(ci_sparsity); - mg_matrix_dg_up[level].reinit(mg_sparsity_dg_interface[level]); - mg_matrix_dg_down[level].reinit(mg_sparsity_dg_interface[level]); - } - } -} - - - // In this function, we assemble the - // global system matrix, where by - // global we indicate that this is - // the matrix of the discrete system - // we solve and it is covering the - // whole mesh. -template -void -Step39::assemble_matrix() -{ - // First, we need t set up the - // object providing the values we - // integrate. This object contains - // all FEValues and FEFaceValues - // objects needed and also - // maintains them automatically - // such that they always point to - // the current cell. To this end, - // we need to tell it first, where - // and what to compute. Since we - // are not doing anything fancy, we - // can rely on their standard - // choice for quadrature rules. + using namespace dealii; + + // This is the function we use to set + // the boundary values and also the + // exact solution we compare to. + Functions::SlitSingularityFunction<2> exact_solution; + + // @sect3{The local integrators} + + // MeshWorker separates local + // integration from the loops over + // cells and faces. Thus, we have to + // write local integration classes + // for generating matrices, the right + // hand side and the error + // estimator. + + // All these classes have the same + // three functions for integrating + // over cells, boundary faces and + // interior faces, respectively. All + // the information needed for the + // local integration is provided by + // MeshWorker::IntegrationInfo. Note + // that the signature of the functions cannot + // be changed, because it is expected + // by MeshWorker::integration_loop(). + + // The first class defining local + // integrators is responsible for + // computing cell and face + // matrices. It is used to assemble + // the global matrix as well as the + // level matrices. + template + class MatrixIntegrator : public Subscriptor + { + public: + static void cell(MeshWorker::DoFInfo& dinfo, + typename MeshWorker::IntegrationInfo& info); + static void boundary(MeshWorker::DoFInfo& dinfo, + typename MeshWorker::IntegrationInfo& info); + static void face(MeshWorker::DoFInfo& dinfo1, + MeshWorker::DoFInfo& dinfo2, + typename MeshWorker::IntegrationInfo& info1, + typename MeshWorker::IntegrationInfo& info2); + }; + + + // On each cell, we integrate the + // Dirichlet form. We use the library + // of ready made integrals in + // LocalIntegrators to avoid writing + // these loops ourselves. Similarly, + // we implement Nitsche boundary + // conditions and the interior + // penalty fluxes between cells. // - // Since their default update flags - // are minimal, we add what we need - // additionally, namely the values - // and gradients of shape functions - // on all objects (cells, boundary - // and interior faces). Afterwards, - // we are ready to initialize the - // container, which will create all - // necessary FEValuesBase objects - // for integration. - MeshWorker::IntegrationInfoBox info_box; - UpdateFlags update_flags = update_values | update_gradients; - info_box.add_update_flags_all(update_flags); - info_box.initialize(fe, mapping); - - // This is the object into which we - // integrate local data. It is - // filled by the local integration - // routines in MatrixIntegrator and - // then used by the assembler to - // distribute the information into - // the global matrix. - MeshWorker::DoFInfo dof_info(dof_handler); - - // Finally, we need an object that - // assembles the local matrix into - // the global matrix. - MeshWorker::Assembler::MatrixSimple > assembler; - assembler.initialize(matrix); - - // Now, we throw everything into a - // MeshWorker::loop(), which here - // traverses all active cells of - // the mesh, computes cell and face - // matrices and assembles them into - // the global matrix. We use the - // variable dof_handler - // here in order to use the global - // numbering of degrees of freedom. - MeshWorker::integration_loop( - dof_handler.begin_active(), dof_handler.end(), - dof_info, info_box, - &MatrixIntegrator::cell, - &MatrixIntegrator::boundary, - &MatrixIntegrator::face, - assembler); -} - - - // Now, we do the same for the level - // matrices. Not too surprisingly, - // this function looks like a twin of - // the previous one. Indeed, there - // are only two minor differences. -template -void -Step39::assemble_mg_matrix() -{ - MeshWorker::IntegrationInfoBox info_box; - UpdateFlags update_flags = update_values | update_gradients; - info_box.add_update_flags_all(update_flags); - info_box.initialize(fe, mapping); - - MeshWorker::DoFInfo dof_info(mg_dof_handler); - - // Obviously, the assembler needs - // to be replaced by one filling - // level matrices. Note that it - // automatically fills the edge - // matrices as well. - MeshWorker::Assembler::MGMatrixSimple > assembler; - assembler.initialize(mg_matrix); - assembler.initialize_fluxes(mg_matrix_dg_up, mg_matrix_dg_down); - - // Here is the other difference to - // the previous function: we run - // over all cells, not only the - // active ones. And we use - // mg_dof_handler, since - // we need the degrees of freedom - // on each level, not the global - // numbering. - MeshWorker::integration_loop ( - mg_dof_handler.begin(), mg_dof_handler.end(), - dof_info, info_box, - &MatrixIntegrator::cell, - &MatrixIntegrator::boundary, - &MatrixIntegrator::face, - assembler); -} - - - // Here we have another clone of the - // assemble function. The difference - // to assembling the system matrix - // consists in that we assemble a - // vector here. -template -void -Step39::assemble_right_hand_side() -{ - MeshWorker::IntegrationInfoBox info_box; - UpdateFlags update_flags = update_quadrature_points | update_values | update_gradients; - info_box.add_update_flags_all(update_flags); - info_box.initialize(fe, mapping); - - MeshWorker::DoFInfo dof_info(dof_handler); - - // Since this assembler alows us to - // fill several vectors, the - // interface is a little more - // complicated as above. The - // pointers to the vectors have to - // be stored in a NamedData - // object. While this seems to - // cause two extra lines of code - // here, it actually comes handy in - // more complex applications. - MeshWorker::Assembler::ResidualSimple > assembler; - NamedData* > data; - Vector* rhs = &right_hand_side; - data.add(rhs, "RHS"); - assembler.initialize(data); - - MeshWorker::integration_loop( - dof_handler.begin_active(), dof_handler.end(), - dof_info, info_box, - &RHSIntegrator::cell, - &RHSIntegrator::boundary, - &RHSIntegrator::face, - assembler); - - right_hand_side *= -1.; -} - - - // Now that we have coded all - // functions building the discrete - // linear system, it is about time - // that we actually solve it. -template -void -Step39::solve() -{ - // The solver of choice is - // conjugate gradient. - SolverControl control(1000, 1.e-12); - SolverCG > solver(control); - - // Now we are setting up the - // components of the multilevel - // preconditioner. First, we need - // transfer between grid - // levels. The object we are using - // here generates sparse matrices - // for these transfers. - MGTransferPrebuilt > mg_transfer; - mg_transfer.build_matrices(mg_dof_handler); - - // Then, we need an exact solver - // for the matrix on the coarsest - // level. - FullMatrix coarse_matrix; - coarse_matrix.copy_from (mg_matrix[0]); - MGCoarseGridHouseholder > mg_coarse; - mg_coarse.initialize(coarse_matrix); - - // While transfer and coarse grid - // solver are pretty much generic, - // more flexibility is offered for - // the smoother. First, we choose - // Gauss-Seidel as our smoothing - // method. - GrowingVectorMemory > mem; - typedef PreconditionSOR > RELAXATION; - MGSmootherRelaxation, RELAXATION, Vector > - mg_smoother(mem); - RELAXATION::AdditionalData smoother_data(1.); - mg_smoother.initialize(mg_matrix, smoother_data); - - // Do two smoothing steps on each - // level. - mg_smoother.set_steps(2); - // Since the SOR method is not - // symmetric, but we use conjugate - // gradient iteration below, here - // is a trick to make the - // multilevel preconditioner a - // symmetric operator even for - // nonsymmetric smoothers. - mg_smoother.set_symmetric(true); - // The smoother class optionally - // implements the variable V-cycle, - // which we do not want here. - mg_smoother.set_variable(false); - - // Finally, we must wrap our - // matrices in an object having the - // required multiplication - // functions. - MGMatrix, Vector > mgmatrix(&mg_matrix); - MGMatrix, Vector > mgdown(&mg_matrix_dg_down); - MGMatrix, Vector > mgup(&mg_matrix_dg_up); - - // Now, we are ready to set up the - // V-cycle operator and the - // multilevel preconditioner. - Multigrid > mg(mg_dof_handler, mgmatrix, - mg_coarse, mg_transfer, - mg_smoother, mg_smoother); - // Let us not forget the edge - // matrices needed because of the - // adaptive refinement. - mg.set_edge_flux_matrices(mgdown, mgup); - - // After all preparations, wrap the - // Multigrid object into another - // object, which can be used as a - // regular preconditioner, - PreconditionMG, - MGTransferPrebuilt > > + // The boundary und flux terms need a + // penalty parameter, which should be + // adjusted to the cell size and the + // polynomial degree. A safe choice + // of this parameter for constant + // coefficients can be found in + // LocalIntegrators::Laplace::compute_penalty() + // and we use this below. + template + void MatrixIntegrator::cell( + MeshWorker::DoFInfo& dinfo, + typename MeshWorker::IntegrationInfo& info) + { + LocalIntegrators::Laplace::cell_matrix(dinfo.matrix(0,false).matrix, info.fe_values()); + } + + + template + void MatrixIntegrator::boundary( + MeshWorker::DoFInfo& dinfo, + typename MeshWorker::IntegrationInfo& info) + { + const unsigned int deg = info.fe_values(0).get_fe().tensor_degree(); + LocalIntegrators::Laplace::nitsche_matrix( + dinfo.matrix(0,false).matrix, info.fe_values(0), + LocalIntegrators::Laplace::compute_penalty(dinfo, dinfo, deg, deg)); + } + + // Interior faces use the interior + // penalty method + template + void MatrixIntegrator::face( + MeshWorker::DoFInfo& dinfo1, + MeshWorker::DoFInfo& dinfo2, + typename MeshWorker::IntegrationInfo& info1, + typename MeshWorker::IntegrationInfo& info2) + { + const unsigned int deg = info1.fe_values(0).get_fe().tensor_degree(); + LocalIntegrators::Laplace::ip_matrix( + dinfo1.matrix(0,false).matrix, dinfo1.matrix(0,true).matrix, + dinfo2.matrix(0,true).matrix, dinfo2.matrix(0,false).matrix, + info1.fe_values(0), info2.fe_values(0), + LocalIntegrators::Laplace::compute_penalty(dinfo1, dinfo2, deg, deg)); + } + + // The second local integrator builds + // the right hand side. In our + // example, the right hand side + // function is zero, such that only + // the boundary condition is set here + // in weak form. + template + class RHSIntegrator : public Subscriptor + { + public: + static void cell(MeshWorker::DoFInfo& dinfo, typename MeshWorker::IntegrationInfo& info); + static void boundary(MeshWorker::DoFInfo& dinfo, typename MeshWorker::IntegrationInfo& info); + static void face(MeshWorker::DoFInfo& dinfo1, + MeshWorker::DoFInfo& dinfo2, + typename MeshWorker::IntegrationInfo& info1, + typename MeshWorker::IntegrationInfo& info2); + }; + + + template + void RHSIntegrator::cell(MeshWorker::DoFInfo&, typename MeshWorker::IntegrationInfo&) + {} + + + template + void RHSIntegrator::boundary(MeshWorker::DoFInfo& dinfo, typename MeshWorker::IntegrationInfo& info) + { + const FEValuesBase& fe = info.fe_values(); + Vector& local_vector = dinfo.vector(0).block(0); + + std::vector boundary_values(fe.n_quadrature_points); + exact_solution.value_list(fe.get_quadrature_points(), boundary_values); + + const unsigned int deg = fe.get_fe().tensor_degree(); + const double penalty = 2. * deg * (deg+1) * dinfo.face->measure() / dinfo.cell->measure(); + + for (unsigned k=0;k + void RHSIntegrator::face(MeshWorker::DoFInfo&, + MeshWorker::DoFInfo&, + typename MeshWorker::IntegrationInfo&, + typename MeshWorker::IntegrationInfo&) + {} + + + // The third local integrator is + // responsible for the contributions + // to the error estimate. This is the + // standard energy estimator due to + // Karakashian and Pascal (2003). + template + class Estimator : public Subscriptor + { + public: + static void cell(MeshWorker::DoFInfo& dinfo, typename MeshWorker::IntegrationInfo& info); + static void boundary(MeshWorker::DoFInfo& dinfo, typename MeshWorker::IntegrationInfo& info); + static void face(MeshWorker::DoFInfo& dinfo1, + MeshWorker::DoFInfo& dinfo2, + typename MeshWorker::IntegrationInfo& info1, + typename MeshWorker::IntegrationInfo& info2); + }; + + + // The cell contribution is the + // Laplacian of the discrete + // solution, since the right hand + // side is zero. + template + void Estimator::cell(MeshWorker::DoFInfo& dinfo, typename MeshWorker::IntegrationInfo& info) + { + const FEValuesBase& fe = info.fe_values(); + + const std::vector >& DDuh = info.hessians[0][0]; + for (unsigned k=0;kdiameter() * trace(DDuh[k]); + dinfo.value(0) += t*t * fe.JxW(k); + } + dinfo.value(0) = std::sqrt(dinfo.value(0)); + } + + // At the boundary, we use simply a + // weighted form of the boundary + // residual, namely the norm of the + // difference between the finite + // element solution and the correct + // boundary condition. + template + void Estimator::boundary(MeshWorker::DoFInfo& dinfo, typename MeshWorker::IntegrationInfo& info) + { + const FEValuesBase& fe = info.fe_values(); + + std::vector boundary_values(fe.n_quadrature_points); + exact_solution.value_list(fe.get_quadrature_points(), boundary_values); + + const std::vector& uh = info.values[0][0]; + + const unsigned int deg = fe.get_fe().tensor_degree(); + const double penalty = 2. * deg * (deg+1) * dinfo.face->measure() / dinfo.cell->measure(); + + for (unsigned k=0;k + void Estimator::face(MeshWorker::DoFInfo& dinfo1, + MeshWorker::DoFInfo& dinfo2, + typename MeshWorker::IntegrationInfo& info1, + typename MeshWorker::IntegrationInfo& info2) + { + const FEValuesBase& fe = info1.fe_values(); + const std::vector& uh1 = info1.values[0][0]; + const std::vector& uh2 = info2.values[0][0]; + const std::vector >& Duh1 = info1.gradients[0][0]; + const std::vector >& Duh2 = info2.gradients[0][0]; + + const unsigned int deg = fe.get_fe().tensor_degree(); + const double penalty1 = deg * (deg+1) * dinfo1.face->measure() / dinfo1.cell->measure(); + const double penalty2 = deg * (deg+1) * dinfo2.face->measure() / dinfo2.cell->measure(); + const double penalty = penalty1 + penalty2; + const double h = dinfo1.face->measure(); + + for (unsigned k=0;k + class ErrorIntegrator : public Subscriptor + { + public: + static void cell(MeshWorker::DoFInfo& dinfo, typename MeshWorker::IntegrationInfo& info); + static void boundary(MeshWorker::DoFInfo& dinfo, typename MeshWorker::IntegrationInfo& info); + static void face(MeshWorker::DoFInfo& dinfo1, + MeshWorker::DoFInfo& dinfo2, + typename MeshWorker::IntegrationInfo& info1, + typename MeshWorker::IntegrationInfo& info2); + }; + + // Here we have the integration on + // cells. There is currently no good + // interfce in MeshWorker that would + // allow us to access values of + // regular functions in the + // quadrature points. Thus, we have + // to create the vectors for the + // exact function's values and + // gradients inside the cell + // integrator. After that, everything + // is as before and we just add up + // the squares of the differences. + + // Additionally to computing the error + // in the energy norm, we use the + // capability of the mesh worker to + // compute two functionals at the + // same time and compute the + // L2-error in the + // same loop. Obviously, this one + // does not have any jump terms and + // only appears in the integration on + // cells. + template + void ErrorIntegrator::cell( + MeshWorker::DoFInfo& dinfo, + typename MeshWorker::IntegrationInfo& info) + { + const FEValuesBase& fe = info.fe_values(); + std::vector > exact_gradients(fe.n_quadrature_points); + std::vector exact_values(fe.n_quadrature_points); + + exact_solution.gradient_list(fe.get_quadrature_points(), exact_gradients); + exact_solution.value_list(fe.get_quadrature_points(), exact_values); + + const std::vector >& Duh = info.gradients[0][0]; + const std::vector& uh = info.values[0][0]; + + for (unsigned k=0;k + void ErrorIntegrator::boundary( + MeshWorker::DoFInfo& dinfo, + typename MeshWorker::IntegrationInfo& info) + { + const FEValuesBase& fe = info.fe_values(); + + std::vector exact_values(fe.n_quadrature_points); + exact_solution.value_list(fe.get_quadrature_points(), exact_values); + + const std::vector& uh = info.values[0][0]; + + const unsigned int deg = fe.get_fe().tensor_degree(); + const double penalty = 2. * deg * (deg+1) * dinfo.face->measure() / dinfo.cell->measure(); + + for (unsigned k=0;k + void ErrorIntegrator::face( + MeshWorker::DoFInfo& dinfo1, + MeshWorker::DoFInfo& dinfo2, + typename MeshWorker::IntegrationInfo& info1, + typename MeshWorker::IntegrationInfo& info2) + { + const FEValuesBase& fe = info1.fe_values(); + const std::vector& uh1 = info1.values[0][0]; + const std::vector& uh2 = info2.values[0][0]; + + const unsigned int deg = fe.get_fe().tensor_degree(); + const double penalty1 = deg * (deg+1) * dinfo1.face->measure() / dinfo1.cell->measure(); + const double penalty2 = deg * (deg+1) * dinfo2.face->measure() / dinfo2.cell->measure(); + const double penalty = penalty1 + penalty2; + + for (unsigned k=0;k + class InteriorPenaltyProblem + { + public: + typedef MeshWorker::IntegrationInfo CellInfo; + + InteriorPenaltyProblem(const FiniteElement& fe); + + void run(unsigned int n_steps); + + private: + void setup_system (); + void assemble_matrix (); + void assemble_mg_matrix (); + void assemble_right_hand_side (); + void error (); + double estimate (); + void solve (); + void output_results (const unsigned int cycle) const; + + // The member objects related to + // the discretization are here. + Triangulation triangulation; + const MappingQ1 mapping; + const FiniteElement& fe; + MGDoFHandler mg_dof_handler; + DoFHandler& dof_handler; + + // Then, we have the matrices and + // vectors related to the global + // discrete system. + SparsityPattern sparsity; + SparseMatrix matrix; + Vector solution; + Vector right_hand_side; + BlockVector estimates; + + // Finally, we have a group of + // sparsity patterns and sparse + // matrices related to the + // multilevel preconditioner. + // First, we have a level matrix + // and its sparsity pattern. + MGLevelObject mg_sparsity; + MGLevelObject > mg_matrix; + + // When we perform multigrid with + // local smoothing on locally + // refined meshes, additional + // matrices are required; see + // Kanschat (2004). Here is the + // sparsity pattern for these + // edge matrices. We only need + // one, because the pattern of + // the up matrix is the + // transpose of that of the down + // matrix. Actually, we do not + // care too much about these + // details, since the MeshWorker + // is filling these matrices. + MGLevelObject mg_sparsity_dg_interface; + // The flux matrix at the + // refinement edge, coupling fine + // level degrees of freedom to + // coarse level. + MGLevelObject > mg_matrix_dg_down; + // The transpose of the flux + // matrix at the refinement edge, + // coupling coarse level degrees + // of freedom to fine level. + MGLevelObject > mg_matrix_dg_up; + }; + + + // The constructor simply sets up the + // coarse grid and the + // DoFHandler. The FiniteElement is + // provided as a parameter to allow + // flexibility. + template + InteriorPenaltyProblem::InteriorPenaltyProblem(const FiniteElement& fe) + : + mapping(), + fe(fe), + mg_dof_handler(triangulation), + dof_handler(mg_dof_handler), + estimates(1) + { + GridGenerator::hyper_cube_slit(triangulation, -1, 1); + } + + + // In this function, we set up the + // dimension of the linear system and + // the sparsity patterns for the + // global matrix as well as the level + // matrices. + template + void + InteriorPenaltyProblem::setup_system() + { + // First, we use the finite element + // to distribute degrees of + // freedom over the mesh and number + // them. + dof_handler.distribute_dofs(fe); + unsigned int n_dofs = dof_handler.n_dofs(); + // Then, we already know the size + // of the vectors representing + // finite element functions. + solution.reinit(n_dofs); + right_hand_side.reinit(n_dofs); + + // Next, we set up the sparsity + // pattern for the global + // matrix. Since we do not know the + // row sizes in advance, we first + // fill a temporary + // CompressedSparsityPattern object + // and copy it to the regular + // SparsityPattern once it is + // complete. + CompressedSparsityPattern c_sparsity(n_dofs); + DoFTools::make_flux_sparsity_pattern(dof_handler, c_sparsity); + sparsity.copy_from(c_sparsity); + matrix.reinit(sparsity); + + const unsigned int n_levels = triangulation.n_levels(); + // The global system is set up, now + // we attend to the level + // matrices. We resize all matrix + // objects to hold one matrix per level. + mg_matrix.resize(0, n_levels-1); + mg_matrix.clear(); + mg_matrix_dg_up.resize(0, n_levels-1); + mg_matrix_dg_up.clear(); + mg_matrix_dg_down.resize(0, n_levels-1); + mg_matrix_dg_down.clear(); + // It is important to update the + // sparsity patterns after + // clear() was called for + // the level matrices, since the + // matrices lock the sparsity + // pattern through the Smartpointer + // ans Subscriptor mechanism. + mg_sparsity.resize(0, n_levels-1); + mg_sparsity_dg_interface.resize(0, n_levels-1); + + // Now all objects are prepared to + // hold one sparsity pattern or + // matrix per level. What's left is + // setting up the sparsity patterns + // on each level. + for (unsigned int level=mg_sparsity.get_minlevel(); + level<=mg_sparsity.get_maxlevel();++level) + { + // These are roughly the same + // lines as above for the + // global matrix, now for each + // level. + CompressedSparsityPattern c_sparsity(mg_dof_handler.n_dofs(level)); + MGTools::make_flux_sparsity_pattern(mg_dof_handler, c_sparsity, level); + mg_sparsity[level].copy_from(c_sparsity); + mg_matrix[level].reinit(mg_sparsity[level]); + + // Additionally, we need to + // initialize the transfer + // matrices at the refinement + // edge between levels. They + // are stored at the index + // referring to the finer of + // the two indices, thus there + // is no such object on level + // 0. + if (level>0) + { + CompressedSparsityPattern ci_sparsity; + ci_sparsity.reinit(mg_dof_handler.n_dofs(level-1), mg_dof_handler.n_dofs(level)); + MGTools::make_flux_sparsity_pattern_edge(mg_dof_handler, ci_sparsity, level); + mg_sparsity_dg_interface[level].copy_from(ci_sparsity); + mg_matrix_dg_up[level].reinit(mg_sparsity_dg_interface[level]); + mg_matrix_dg_down[level].reinit(mg_sparsity_dg_interface[level]); + } + } + } + + + // In this function, we assemble the + // global system matrix, where by + // global we indicate that this is + // the matrix of the discrete system + // we solve and it is covering the + // whole mesh. + template + void + InteriorPenaltyProblem::assemble_matrix() + { + // First, we need t set up the + // object providing the values we + // integrate. This object contains + // all FEValues and FEFaceValues + // objects needed and also + // maintains them automatically + // such that they always point to + // the current cell. To this end, + // we need to tell it first, where + // and what to compute. Since we + // are not doing anything fancy, we + // can rely on their standard + // choice for quadrature rules. + // + // Since their default update flags + // are minimal, we add what we need + // additionally, namely the values + // and gradients of shape functions + // on all objects (cells, boundary + // and interior faces). Afterwards, + // we are ready to initialize the + // container, which will create all + // necessary FEValuesBase objects + // for integration. + MeshWorker::IntegrationInfoBox info_box; + UpdateFlags update_flags = update_values | update_gradients; + info_box.add_update_flags_all(update_flags); + info_box.initialize(fe, mapping); + + // This is the object into which we + // integrate local data. It is + // filled by the local integration + // routines in MatrixIntegrator and + // then used by the assembler to + // distribute the information into + // the global matrix. + MeshWorker::DoFInfo dof_info(dof_handler); + + // Finally, we need an object that + // assembles the local matrix into + // the global matrix. + MeshWorker::Assembler::MatrixSimple > assembler; + assembler.initialize(matrix); + + // Now, we throw everything into a + // MeshWorker::loop(), which here + // traverses all active cells of + // the mesh, computes cell and face + // matrices and assembles them into + // the global matrix. We use the + // variable dof_handler + // here in order to use the global + // numbering of degrees of freedom. + MeshWorker::integration_loop( + dof_handler.begin_active(), dof_handler.end(), + dof_info, info_box, + &MatrixIntegrator::cell, + &MatrixIntegrator::boundary, + &MatrixIntegrator::face, + assembler); + } + + + // Now, we do the same for the level + // matrices. Not too surprisingly, + // this function looks like a twin of + // the previous one. Indeed, there + // are only two minor differences. + template + void + InteriorPenaltyProblem::assemble_mg_matrix() + { + MeshWorker::IntegrationInfoBox info_box; + UpdateFlags update_flags = update_values | update_gradients; + info_box.add_update_flags_all(update_flags); + info_box.initialize(fe, mapping); + + MeshWorker::DoFInfo dof_info(mg_dof_handler); + + // Obviously, the assembler needs + // to be replaced by one filling + // level matrices. Note that it + // automatically fills the edge + // matrices as well. + MeshWorker::Assembler::MGMatrixSimple > assembler; + assembler.initialize(mg_matrix); + assembler.initialize_fluxes(mg_matrix_dg_up, mg_matrix_dg_down); + + // Here is the other difference to + // the previous function: we run + // over all cells, not only the + // active ones. And we use + // mg_dof_handler, since + // we need the degrees of freedom + // on each level, not the global + // numbering. + MeshWorker::integration_loop ( + mg_dof_handler.begin(), mg_dof_handler.end(), + dof_info, info_box, + &MatrixIntegrator::cell, + &MatrixIntegrator::boundary, + &MatrixIntegrator::face, + assembler); + } + + + // Here we have another clone of the + // assemble function. The difference + // to assembling the system matrix + // consists in that we assemble a + // vector here. + template + void + InteriorPenaltyProblem::assemble_right_hand_side() + { + MeshWorker::IntegrationInfoBox info_box; + UpdateFlags update_flags = update_quadrature_points | update_values | update_gradients; + info_box.add_update_flags_all(update_flags); + info_box.initialize(fe, mapping); + + MeshWorker::DoFInfo dof_info(dof_handler); + + // Since this assembler alows us to + // fill several vectors, the + // interface is a little more + // complicated as above. The + // pointers to the vectors have to + // be stored in a NamedData + // object. While this seems to + // cause two extra lines of code + // here, it actually comes handy in + // more complex applications. + MeshWorker::Assembler::ResidualSimple > assembler; + NamedData* > data; + Vector* rhs = &right_hand_side; + data.add(rhs, "RHS"); + assembler.initialize(data); + + MeshWorker::integration_loop( + dof_handler.begin_active(), dof_handler.end(), + dof_info, info_box, + &RHSIntegrator::cell, + &RHSIntegrator::boundary, + &RHSIntegrator::face, + assembler); + + right_hand_side *= -1.; + } + + + // Now that we have coded all + // functions building the discrete + // linear system, it is about time + // that we actually solve it. + template + void + InteriorPenaltyProblem::solve() + { + // The solver of choice is + // conjugate gradient. + SolverControl control(1000, 1.e-12); + SolverCG > solver(control); + + // Now we are setting up the + // components of the multilevel + // preconditioner. First, we need + // transfer between grid + // levels. The object we are using + // here generates sparse matrices + // for these transfers. + MGTransferPrebuilt > mg_transfer; + mg_transfer.build_matrices(mg_dof_handler); + + // Then, we need an exact solver + // for the matrix on the coarsest + // level. + FullMatrix coarse_matrix; + coarse_matrix.copy_from (mg_matrix[0]); + MGCoarseGridHouseholder > mg_coarse; + mg_coarse.initialize(coarse_matrix); + + // While transfer and coarse grid + // solver are pretty much generic, + // more flexibility is offered for + // the smoother. First, we choose + // Gauss-Seidel as our smoothing + // method. + GrowingVectorMemory > mem; + typedef PreconditionSOR > RELAXATION; + MGSmootherRelaxation, RELAXATION, Vector > + mg_smoother(mem); + RELAXATION::AdditionalData smoother_data(1.); + mg_smoother.initialize(mg_matrix, smoother_data); + + // Do two smoothing steps on each + // level. + mg_smoother.set_steps(2); + // Since the SOR method is not + // symmetric, but we use conjugate + // gradient iteration below, here + // is a trick to make the + // multilevel preconditioner a + // symmetric operator even for + // nonsymmetric smoothers. + mg_smoother.set_symmetric(true); + // The smoother class optionally + // implements the variable V-cycle, + // which we do not want here. + mg_smoother.set_variable(false); + + // Finally, we must wrap our + // matrices in an object having the + // required multiplication + // functions. + MGMatrix, Vector > mgmatrix(&mg_matrix); + MGMatrix, Vector > mgdown(&mg_matrix_dg_down); + MGMatrix, Vector > mgup(&mg_matrix_dg_up); + + // Now, we are ready to set up the + // V-cycle operator and the + // multilevel preconditioner. + Multigrid > mg(mg_dof_handler, mgmatrix, + mg_coarse, mg_transfer, + mg_smoother, mg_smoother); + // Let us not forget the edge + // matrices needed because of the + // adaptive refinement. + mg.set_edge_flux_matrices(mgdown, mgup); + + // After all preparations, wrap the + // Multigrid object into another + // object, which can be used as a + // regular preconditioner, + PreconditionMG, + MGTransferPrebuilt > > preconditioner(mg_dof_handler, mg, mg_transfer); // and use it to solve the system. solver.solve(matrix, solution, right_hand_side, preconditioner); @@ -920,7 +922,7 @@ Step39::solve() // also have an input vector. template double -Step39::estimate() +InteriorPenaltyProblem::estimate() { // The results of the estimator are // stored in a vector with one @@ -936,7 +938,7 @@ Step39::estimate() // tampering with them. std::vector old_user_indices; triangulation.save_user_indices(old_user_indices); - + estimates.block(0).reinit(triangulation.n_active_cells()); unsigned int i=0; for (typename Triangulation::active_cell_iterator cell = triangulation.begin_active(); @@ -957,7 +959,7 @@ Step39::estimate() // solution we just computed. NamedData* > solution_data; solution_data.add(&solution, "solution"); - + // Then, we tell the Meshworker::VectorSelector // for cells, that we need the // second derivatives of this @@ -983,19 +985,19 @@ Step39::estimate() // derivatives we requested above. info_box.add_update_flags_boundary(update_quadrature_points); info_box.initialize(fe, mapping, solution_data); - + MeshWorker::DoFInfo dof_info(dof_handler); // The assembler stores one number // per cell, but else this is the // same as in the computation of // the right hand side. - MeshWorker::Assembler::CellsAndFaces assembler; + MeshWorker::Assembler::CellsAndFaces assembler; NamedData* > out_data; BlockVector* est = &estimates; out_data.add(est, "cells"); assembler.initialize(out_data, false); - + MeshWorker::integration_loop ( dof_handler.begin_active(), dof_handler.end(), dof_info, info_box, @@ -1026,7 +1028,7 @@ Step39::estimate() // needs two blocks here. template void -Step39::error() +InteriorPenaltyProblem::error() { BlockVector errors(2); errors.block(0).reinit(triangulation.n_active_cells()); @@ -1042,23 +1044,23 @@ Step39::error() NamedData* > solution_data; solution_data.add(&solution, "solution"); - + info_box.cell_selector.add("solution", true, true, false); info_box.boundary_selector.add("solution", true, false, false); info_box.face_selector.add("solution", true, false, false); - + info_box.add_update_flags_cell(update_quadrature_points); info_box.add_update_flags_boundary(update_quadrature_points); info_box.initialize(fe, mapping, solution_data); - + MeshWorker::DoFInfo dof_info(dof_handler); - - MeshWorker::Assembler::CellsAndFaces assembler; + + MeshWorker::Assembler::CellsAndFaces assembler; NamedData* > out_data; BlockVector* est = &errors; out_data.add(est, "cells"); assembler.initialize(out_data, false); - + MeshWorker::integration_loop ( dof_handler.begin_active(), dof_handler.end(), dof_info, info_box, @@ -1074,7 +1076,7 @@ Step39::error() // Some graphical output template -void Step39::output_results (const unsigned int cycle) const +void InteriorPenaltyProblem::output_results (const unsigned int cycle) const { // Output of the solution in // gnuplot format. @@ -1086,14 +1088,14 @@ void Step39::output_results (const unsigned int cycle) const deallog << "Writing solution to <" << filename << ">..." << std::endl << std::endl; std::ofstream gnuplot_output (filename.c_str()); - + DataOut data_out; data_out.attach_dof_handler (dof_handler); data_out.add_data_vector (solution, "u"); data_out.add_data_vector (estimates.block(0), "est"); data_out.build_patches (); - + data_out.write_gnuplot(gnuplot_output); } @@ -1102,7 +1104,7 @@ void Step39::output_results (const unsigned int cycle) const // examples. template void -Step39::run(unsigned int n_steps) +InteriorPenaltyProblem::run(unsigned int n_steps) { deallog << "Element: " << fe.get_name() << std::endl; for (unsigned int s=0;s::run(unsigned int n_steps) 0.5, 0.0); triangulation.execute_coarsening_and_refinement (); } - + deallog << "Triangulation " << triangulation.n_active_cells() << " cells, " << triangulation.n_levels() << " levels" << std::endl; - + setup_system(); deallog << "DoFHandler " << dof_handler.n_dofs() << " dofs, level dofs"; for (unsigned int l=0;l::run(unsigned int n_steps) output_results(s); } } +} + int main() { + using namespace dealii; + using namespace Step39; + std::ofstream logfile("deallog"); deallog.attach(logfile); FE_DGQ<2> fe1(3); - Step39<2> test1(fe1); + InteriorPenaltyProblem<2> test1(fe1); test1.run(12); } -- 2.39.5