/* $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 */
// 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 <int dim>
-class RHS: public Function<dim>
+namespace Step30
{
- public:
- virtual void value_list (const std::vector<Point<dim> > &points,
- std::vector<double> &values,
- const unsigned int component=0) const;
-};
-
-
-template <int dim>
-class BoundaryValues: public Function<dim>
-{
- public:
- virtual void value_list (const std::vector<Point<dim> > &points,
- std::vector<double> &values,
- const unsigned int component=0) const;
-};
-
-
-template <int dim>
-class Beta
-{
- public:
- Beta () {}
- void value_list (const std::vector<Point<dim> > &points,
- std::vector<Point<dim> > &values) const;
-};
-
-
-template <int dim>
-void RHS<dim>::value_list(const std::vector<Point<dim> > &points,
- std::vector<double> &values,
- const unsigned int) const
-{
- Assert(values.size()==points.size(),
- ExcDimensionMismatch(values.size(),points.size()));
-
- for (unsigned int i=0; i<values.size(); ++i)
- values[i]=0;
-}
-
-
- // The flow field is chosen to be a
- // quarter circle with
- // counterclockwise flow direction
- // and with the origin as midpoint
- // for the right half of the domain
- // with positive $x$ values, whereas
- // the flow simply goes to the left
- // in the left part of the domain at
- // a velocity that matches the one
- // coming in from the right. In the
- // circular part the magnitude of the
- // flow velocity is proportional to
- // the distance from the origin. This
- // is a difference to step-12, where
- // the magnitude was 1
- // evereywhere. the new definition
- // leads to a linear variation of
- // $\beta$ along each given face of a
- // cell. On the other hand, the
- // solution $u(x,y)$ is exactly the
- // same as before.
-template <int dim>
-void Beta<dim>::value_list(const std::vector<Point<dim> > &points,
- std::vector<Point<dim> > &values) const
-{
- Assert(values.size()==points.size(),
- ExcDimensionMismatch(values.size(),points.size()));
-
- for (unsigned int i=0; i<points.size(); ++i)
- {
- if (points[i](0) > 0)
- {
- values[i](0) = -points[i](1);
- values[i](1) = points[i](0);
- }
- else
- {
- values[i] = Point<dim>();
- 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 <int dim>
+ class RHS: public Function<dim>
+ {
+ public:
+ virtual void value_list (const std::vector<Point<dim> > &points,
+ std::vector<double> &values,
+ const unsigned int component=0) const;
+ };
+
+
+ template <int dim>
+ class BoundaryValues: public Function<dim>
+ {
+ public:
+ virtual void value_list (const std::vector<Point<dim> > &points,
+ std::vector<double> &values,
+ const unsigned int component=0) const;
+ };
+
+
+ template <int dim>
+ class Beta
+ {
+ public:
+ Beta () {}
+ void value_list (const std::vector<Point<dim> > &points,
+ std::vector<Point<dim> > &values) const;
+ };
+
+
+ template <int dim>
+ void RHS<dim>::value_list(const std::vector<Point<dim> > &points,
+ std::vector<double> &values,
+ const unsigned int) const
+ {
+ Assert(values.size()==points.size(),
+ ExcDimensionMismatch(values.size(),points.size()));
+
+ for (unsigned int i=0; i<values.size(); ++i)
+ values[i]=0;
+ }
+
+
+ // The flow field is chosen to be a
+ // quarter circle with
+ // counterclockwise flow direction
+ // and with the origin as midpoint
+ // for the right half of the domain
+ // with positive $x$ values, whereas
+ // the flow simply goes to the left
+ // in the left part of the domain at
+ // a velocity that matches the one
+ // coming in from the right. In the
+ // circular part the magnitude of the
+ // flow velocity is proportional to
+ // the distance from the origin. This
+ // is a difference to step-12, where
+ // the magnitude was 1
+ // evereywhere. the new definition
+ // leads to a linear variation of
+ // $\beta$ along each given face of a
+ // cell. On the other hand, the
+ // solution $u(x,y)$ is exactly the
+ // same as before.
+ template <int dim>
+ void Beta<dim>::value_list(const std::vector<Point<dim> > &points,
+ std::vector<Point<dim> > &values) const
+ {
+ Assert(values.size()==points.size(),
+ ExcDimensionMismatch(values.size(),points.size()));
+
+ for (unsigned int i=0; i<points.size(); ++i)
+ {
+ if (points[i](0) > 0)
+ {
+ values[i](0) = -points[i](1);
+ values[i](1) = points[i](0);
+ }
+ else
+ {
+ values[i] = Point<dim>();
+ values[i](0) = -points[i](1);
+ }
+ }
+ }
-template <int dim>
-void BoundaryValues<dim>::value_list(const std::vector<Point<dim> > &points,
+ template <int dim>
+ void BoundaryValues<dim>::value_list(const std::vector<Point<dim> > &points,
std::vector<double> &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<values.size(); ++i)
- {
- if (points[i](0)<0.5)
- values[i]=1.;
- else
- values[i]=0.;
- }
-}
-
-
- // @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 <int dim>
-class DGTransportEquation
-{
- public:
- DGTransportEquation();
-
- void assemble_cell_term(const FEValues<dim>& fe_v,
- FullMatrix<double> &ui_vi_matrix,
- Vector<double> &cell_vector) const;
-
- void assemble_boundary_term(const FEFaceValues<dim>& fe_v,
- FullMatrix<double> &ui_vi_matrix,
- Vector<double> &cell_vector) const;
-
- void assemble_face_term2(const FEFaceValuesBase<dim>& fe_v,
- const FEFaceValuesBase<dim>& fe_v_neighbor,
- FullMatrix<double> &ui_vi_matrix,
- FullMatrix<double> &ue_vi_matrix,
- FullMatrix<double> &ui_ve_matrix,
- FullMatrix<double> &ue_ve_matrix) const;
- private:
- const Beta<dim> beta_function;
- const RHS<dim> rhs_function;
- const BoundaryValues<dim> 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 <int dim>
-DGTransportEquation<dim>::DGTransportEquation ()
- :
- beta_function (),
- rhs_function (),
- boundary_function ()
-{}
-
-
-template <int dim>
-void DGTransportEquation<dim>::assemble_cell_term(
- const FEValues<dim> &fe_v,
- FullMatrix<double> &ui_vi_matrix,
- Vector<double> &cell_vector) const
-{
- const std::vector<double> &JxW = fe_v.get_JxW_values ();
-
- std::vector<Point<dim> > beta (fe_v.n_quadrature_points);
- std::vector<double> 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<fe_v.n_quadrature_points; ++point)
- for (unsigned int i=0; i<fe_v.dofs_per_cell; ++i)
+ for (unsigned int i=0; i<values.size(); ++i)
{
- for (unsigned int j=0; j<fe_v.dofs_per_cell; ++j)
- ui_vi_matrix(i,j) -= beta[point]*fe_v.shape_grad(i,point)*
- fe_v.shape_value(j,point) *
- JxW[point];
-
- cell_vector(i) += rhs[point] * fe_v.shape_value(i,point) * JxW[point];
+ if (points[i](0)<0.5)
+ values[i]=1.;
+ else
+ values[i]=0.;
}
-}
-
-
-template <int dim>
-void DGTransportEquation<dim>::assemble_boundary_term(
- const FEFaceValues<dim>& fe_v,
- FullMatrix<double> &ui_vi_matrix,
- Vector<double> &cell_vector) const
-{
- const std::vector<double> &JxW = fe_v.get_JxW_values ();
- const std::vector<Point<dim> > &normals = fe_v.get_normal_vectors ();
-
- std::vector<Point<dim> > beta (fe_v.n_quadrature_points);
- std::vector<double> 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; point<fe_v.n_quadrature_points; ++point)
- {
- const double beta_n=beta[point] * normals[point];
- if (beta_n>0)
- for (unsigned int i=0; i<fe_v.dofs_per_cell; ++i)
- for (unsigned int j=0; j<fe_v.dofs_per_cell; ++j)
- ui_vi_matrix(i,j) += beta_n *
- fe_v.shape_value(j,point) *
- fe_v.shape_value(i,point) *
- JxW[point];
- else
- for (unsigned int i=0; i<fe_v.dofs_per_cell; ++i)
- cell_vector(i) -= beta_n *
- g[point] *
- fe_v.shape_value(i,point) *
- JxW[point];
- }
-}
-
-
-template <int dim>
-void DGTransportEquation<dim>::assemble_face_term2(
- const FEFaceValuesBase<dim>& fe_v,
- const FEFaceValuesBase<dim>& fe_v_neighbor,
- FullMatrix<double> &ui_vi_matrix,
- FullMatrix<double> &ue_vi_matrix,
- FullMatrix<double> &ui_ve_matrix,
- FullMatrix<double> &ue_ve_matrix) const
-{
- const std::vector<double> &JxW = fe_v.get_JxW_values ();
- const std::vector<Point<dim> > &normals = fe_v.get_normal_vectors ();
-
- std::vector<Point<dim> > beta (fe_v.n_quadrature_points);
-
- beta_function.value_list (fe_v.get_quadrature_points(), beta);
-
- for (unsigned int point=0; point<fe_v.n_quadrature_points; ++point)
- {
- const double beta_n=beta[point] * normals[point];
- if (beta_n>0)
+ }
+
+
+ // @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 <int dim>
+ class DGTransportEquation
+ {
+ public:
+ DGTransportEquation();
+
+ void assemble_cell_term(const FEValues<dim>& fe_v,
+ FullMatrix<double> &ui_vi_matrix,
+ Vector<double> &cell_vector) const;
+
+ void assemble_boundary_term(const FEFaceValues<dim>& fe_v,
+ FullMatrix<double> &ui_vi_matrix,
+ Vector<double> &cell_vector) const;
+
+ void assemble_face_term2(const FEFaceValuesBase<dim>& fe_v,
+ const FEFaceValuesBase<dim>& fe_v_neighbor,
+ FullMatrix<double> &ui_vi_matrix,
+ FullMatrix<double> &ue_vi_matrix,
+ FullMatrix<double> &ui_ve_matrix,
+ FullMatrix<double> &ue_ve_matrix) const;
+ private:
+ const Beta<dim> beta_function;
+ const RHS<dim> rhs_function;
+ const BoundaryValues<dim> 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 <int dim>
+ DGTransportEquation<dim>::DGTransportEquation ()
+ :
+ beta_function (),
+ rhs_function (),
+ boundary_function ()
+ {}
+
+
+ template <int dim>
+ void DGTransportEquation<dim>::assemble_cell_term(
+ const FEValues<dim> &fe_v,
+ FullMatrix<double> &ui_vi_matrix,
+ Vector<double> &cell_vector) const
+ {
+ const std::vector<double> &JxW = fe_v.get_JxW_values ();
+
+ std::vector<Point<dim> > beta (fe_v.n_quadrature_points);
+ std::vector<double> 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<fe_v.n_quadrature_points; ++point)
+ for (unsigned int i=0; i<fe_v.dofs_per_cell; ++i)
{
- for (unsigned int i=0; i<fe_v.dofs_per_cell; ++i)
- for (unsigned int j=0; j<fe_v.dofs_per_cell; ++j)
- ui_vi_matrix(i,j) += beta_n *
+ for (unsigned int j=0; j<fe_v.dofs_per_cell; ++j)
+ ui_vi_matrix(i,j) -= beta[point]*fe_v.shape_grad(i,point)*
fe_v.shape_value(j,point) *
- fe_v.shape_value(i,point) *
JxW[point];
- for (unsigned int k=0; k<fe_v_neighbor.dofs_per_cell; ++k)
- for (unsigned int j=0; j<fe_v.dofs_per_cell; ++j)
- ui_ve_matrix(k,j) -= beta_n *
- fe_v.shape_value(j,point) *
- fe_v_neighbor.shape_value(k,point) *
- JxW[point];
+ cell_vector(i) += rhs[point] * fe_v.shape_value(i,point) * JxW[point];
}
- else
- {
- for (unsigned int i=0; i<fe_v.dofs_per_cell; ++i)
- for (unsigned int l=0; l<fe_v_neighbor.dofs_per_cell; ++l)
- ue_vi_matrix(i,l) += beta_n *
- fe_v_neighbor.shape_value(l,point) *
- fe_v.shape_value(i,point) *
- JxW[point];
-
- for (unsigned int k=0; k<fe_v_neighbor.dofs_per_cell; ++k)
- for (unsigned int l=0; l<fe_v_neighbor.dofs_per_cell; ++l)
- ue_ve_matrix(k,l) -= beta_n *
- fe_v_neighbor.shape_value(l,point) *
- fe_v_neighbor.shape_value(k,point) *
- JxW[point];
- }
- }
-}
-
-
- // @sect3{Class: DGMethod}
- //
- // Even the main class of this
- // program stays more or less the
- // same. We omit one of the assembly
- // routines and use only the second,
- // more effective one of the two
- // presented in step-12. However, we
- // introduce a new routine
- // (set_anisotropic_flags) and modify
- // another one (refine_grid).
-template <int dim>
-class DGMethod
-{
- public:
- DGMethod (const bool anisotropic);
- ~DGMethod ();
-
- void run ();
-
- private:
- void setup_system ();
- void assemble_system1 ();
- void assemble_system2 ();
- void solve (Vector<double> &solution);
- void refine_grid ();
- void set_anisotropic_flags ();
- void output_results (const unsigned int cycle) const;
-
- Triangulation<dim> triangulation;
- const MappingQ1<dim> 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<dim> fe;
- DoFHandler<dim> dof_handler;
-
- SparsityPattern sparsity_pattern;
- SparseMatrix<double> 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<dim> quadrature;
- const QGauss<dim-1> face_quadrature;
-
- Vector<double> solution2;
- Vector<double> right_hand_side;
-
- const DGTransportEquation<dim> dg;
-};
-
-
-template <int dim>
-DGMethod<dim>::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
- // <code>degree+1</code>
- // gauss points,
- // which enables us
- // to integrate
- // exactly
- // polynomials of
- // degree
- // <code>2*degree+1</code>,
- // enough for all the
- // integrals we will
- // perform in this
- // program.
- quadrature (degree+1),
- face_quadrature (degree+1),
- dg ()
-{}
-
-
-template <int dim>
-DGMethod<dim>::~DGMethod ()
-{
- dof_handler.clear ();
-}
-
-
-template <int dim>
-void DGMethod<dim>::setup_system ()
-{
- dof_handler.distribute_dofs (fe);
- sparsity_pattern.reinit (dof_handler.n_dofs(),
- dof_handler.n_dofs(),
- (GeometryInfo<dim>::faces_per_cell
- *GeometryInfo<dim>::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
- // <code>assemble_system2</code> function that
- // implements the DG discretization in its
- // second version. This function is very
- // similar to the <code>assemble_system2</code>
- // 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 <int dim>
-void DGMethod<dim>::assemble_system2 ()
-{
- const unsigned int dofs_per_cell = dof_handler.get_fe().dofs_per_cell;
- std::vector<unsigned int> dofs (dofs_per_cell);
- std::vector<unsigned int> 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<dim> fe_v (
- mapping, fe, quadrature, update_flags);
- FEFaceValues<dim> fe_v_face (
- mapping, fe, face_quadrature, face_update_flags);
- FESubfaceValues<dim> fe_v_subface (
- mapping, fe, face_quadrature, face_update_flags);
- FEFaceValues<dim> fe_v_face_neighbor (
- mapping, fe, face_quadrature, neighbor_face_update_flags);
-
-
- FullMatrix<double> ui_vi_matrix (dofs_per_cell, dofs_per_cell);
- FullMatrix<double> ue_vi_matrix (dofs_per_cell, dofs_per_cell);
-
- FullMatrix<double> ui_ve_matrix (dofs_per_cell, dofs_per_cell);
- FullMatrix<double> ue_ve_matrix (dofs_per_cell, dofs_per_cell);
-
- Vector<double> cell_vector (dofs_per_cell);
-
- typename DoFHandler<dim>::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 <int dim>
+ void DGTransportEquation<dim>::assemble_boundary_term(
+ const FEFaceValues<dim>& fe_v,
+ FullMatrix<double> &ui_vi_matrix,
+ Vector<double> &cell_vector) const
+ {
+ const std::vector<double> &JxW = fe_v.get_JxW_values ();
+ const std::vector<Point<dim> > &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<GeometryInfo<dim>::faces_per_cell; ++face_no)
- {
- typename DoFHandler<dim>::face_iterator face=
- cell->face(face_no);
+ std::vector<Point<dim> > beta (fe_v.n_quadrature_points);
+ std::vector<double> 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<dim>::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_no<face->number_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<dim>::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; i<dofs_per_cell; ++i)
- for (unsigned int j=0; j<dofs_per_cell; ++j)
- {
- system_matrix.add(dofs[i], dofs_neighbor[j],
- ue_vi_matrix(i,j));
- system_matrix.add(dofs_neighbor[i], dofs[j],
- ui_ve_matrix(i,j));
- system_matrix.add(dofs_neighbor[i], dofs_neighbor[j],
- ue_ve_matrix(i,j));
- }
- }
- }
- else
- {
- // 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())))
- {
- // 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; i<dofs_per_cell; ++i)
- for (unsigned int j=0; j<dofs_per_cell; ++j)
- {
- system_matrix.add(dofs[i], dofs_neighbor[j],
- ue_vi_matrix(i,j));
- system_matrix.add(dofs_neighbor[i], dofs[j],
- ui_ve_matrix(i,j));
- system_matrix.add(dofs_neighbor[i], dofs_neighbor[j],
- ue_ve_matrix(i,j));
- }
- }
+ for (unsigned int point=0; point<fe_v.n_quadrature_points; ++point)
+ {
+ const double beta_n=beta[point] * normals[point];
+ if (beta_n>0)
+ for (unsigned int i=0; i<fe_v.dofs_per_cell; ++i)
+ for (unsigned int j=0; j<fe_v.dofs_per_cell; ++j)
+ ui_vi_matrix(i,j) += beta_n *
+ fe_v.shape_value(j,point) *
+ fe_v.shape_value(i,point) *
+ JxW[point];
+ else
+ for (unsigned int i=0; i<fe_v.dofs_per_cell; ++i)
+ cell_vector(i) -= beta_n *
+ g[point] *
+ fe_v.shape_value(i,point) *
+ JxW[point];
+ }
+ }
- // We do not need to consider
- // case d), as those faces
- // are treated 'from the
- // other side within case b).
- }
- }
- }
-
- for (unsigned int i=0; i<dofs_per_cell; ++i)
- for (unsigned int j=0; j<dofs_per_cell; ++j)
- system_matrix.add(dofs[i], dofs[j], ui_vi_matrix(i,j));
-
- for (unsigned int i=0; i<dofs_per_cell; ++i)
- right_hand_side(dofs[i]) += cell_vector(i);
- }
-}
+ template <int dim>
+ void DGTransportEquation<dim>::assemble_face_term2(
+ const FEFaceValuesBase<dim>& fe_v,
+ const FEFaceValuesBase<dim>& fe_v_neighbor,
+ FullMatrix<double> &ui_vi_matrix,
+ FullMatrix<double> &ue_vi_matrix,
+ FullMatrix<double> &ui_ve_matrix,
+ FullMatrix<double> &ue_ve_matrix) const
+ {
+ const std::vector<double> &JxW = fe_v.get_JxW_values ();
+ const std::vector<Point<dim> > &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 <int dim>
-void DGMethod<dim>::solve (Vector<double> &solution)
-{
- SolverControl solver_control (1000, 1e-12, false, false);
- SolverRichardson<> solver (solver_control);
+ std::vector<Point<dim> > beta (fe_v.n_quadrature_points);
- PreconditionBlockSSOR<SparseMatrix<double> > preconditioner;
+ beta_function.value_list (fe_v.get_quadrature_points(), beta);
- preconditioner.initialize(system_matrix, fe.dofs_per_cell);
+ for (unsigned int point=0; point<fe_v.n_quadrature_points; ++point)
+ {
+ const double beta_n=beta[point] * normals[point];
+ if (beta_n>0)
+ {
+ for (unsigned int i=0; i<fe_v.dofs_per_cell; ++i)
+ for (unsigned int j=0; j<fe_v.dofs_per_cell; ++j)
+ ui_vi_matrix(i,j) += beta_n *
+ fe_v.shape_value(j,point) *
+ fe_v.shape_value(i,point) *
+ JxW[point];
+
+ for (unsigned int k=0; k<fe_v_neighbor.dofs_per_cell; ++k)
+ for (unsigned int j=0; j<fe_v.dofs_per_cell; ++j)
+ ui_ve_matrix(k,j) -= beta_n *
+ fe_v.shape_value(j,point) *
+ fe_v_neighbor.shape_value(k,point) *
+ JxW[point];
+ }
+ else
+ {
+ for (unsigned int i=0; i<fe_v.dofs_per_cell; ++i)
+ for (unsigned int l=0; l<fe_v_neighbor.dofs_per_cell; ++l)
+ ue_vi_matrix(i,l) += beta_n *
+ fe_v_neighbor.shape_value(l,point) *
+ fe_v.shape_value(i,point) *
+ JxW[point];
+
+ for (unsigned int k=0; k<fe_v_neighbor.dofs_per_cell; ++k)
+ for (unsigned int l=0; l<fe_v_neighbor.dofs_per_cell; ++l)
+ ue_ve_matrix(k,l) -= beta_n *
+ fe_v_neighbor.shape_value(l,point) *
+ fe_v_neighbor.shape_value(k,point) *
+ JxW[point];
+ }
+ }
+ }
+
+
+ // @sect3{Class: DGMethod}
+ //
+ // Even the main class of this
+ // program stays more or less the
+ // same. We omit one of the assembly
+ // routines and use only the second,
+ // more effective one of the two
+ // presented in step-12. However, we
+ // introduce a new routine
+ // (set_anisotropic_flags) and modify
+ // another one (refine_grid).
+ template <int dim>
+ class DGMethod
+ {
+ public:
+ DGMethod (const bool anisotropic);
+ ~DGMethod ();
+
+ void run ();
+
+ private:
+ void setup_system ();
+ void assemble_system1 ();
+ void assemble_system2 ();
+ void solve (Vector<double> &solution);
+ void refine_grid ();
+ void set_anisotropic_flags ();
+ void output_results (const unsigned int cycle) const;
+
+ Triangulation<dim> triangulation;
+ const MappingQ1<dim> 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<dim> fe;
+ DoFHandler<dim> dof_handler;
+
+ SparsityPattern sparsity_pattern;
+ SparseMatrix<double> 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<dim> quadrature;
+ const QGauss<dim-1> face_quadrature;
+
+ Vector<double> solution2;
+ Vector<double> right_hand_side;
+
+ const DGTransportEquation<dim> dg;
+ };
+
+
+ template <int dim>
+ DGMethod<dim>::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
+ // <code>degree+1</code>
+ // gauss points,
+ // which enables us
+ // to integrate
+ // exactly
+ // polynomials of
+ // degree
+ // <code>2*degree+1</code>,
+ // enough for all the
+ // integrals we will
+ // perform in this
+ // program.
+ quadrature (degree+1),
+ face_quadrature (degree+1),
+ dg ()
+ {}
+
+
+ template <int dim>
+ DGMethod<dim>::~DGMethod ()
+ {
+ dof_handler.clear ();
+ }
+
+
+ template <int dim>
+ void DGMethod<dim>::setup_system ()
+ {
+ dof_handler.distribute_dofs (fe);
+ sparsity_pattern.reinit (dof_handler.n_dofs(),
+ dof_handler.n_dofs(),
+ (GeometryInfo<dim>::faces_per_cell
+ *GeometryInfo<dim>::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
+ // <code>assemble_system2</code> function that
+ // implements the DG discretization in its
+ // second version. This function is very
+ // similar to the <code>assemble_system2</code>
+ // 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 <int dim>
+ void DGMethod<dim>::assemble_system2 ()
+ {
+ const unsigned int dofs_per_cell = dof_handler.get_fe().dofs_per_cell;
+ std::vector<unsigned int> dofs (dofs_per_cell);
+ std::vector<unsigned int> 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<dim> fe_v (
+ mapping, fe, quadrature, update_flags);
+ FEFaceValues<dim> fe_v_face (
+ mapping, fe, face_quadrature, face_update_flags);
+ FESubfaceValues<dim> fe_v_subface (
+ mapping, fe, face_quadrature, face_update_flags);
+ FEFaceValues<dim> fe_v_face_neighbor (
+ mapping, fe, face_quadrature, neighbor_face_update_flags);
+
+
+ FullMatrix<double> ui_vi_matrix (dofs_per_cell, dofs_per_cell);
+ FullMatrix<double> ue_vi_matrix (dofs_per_cell, dofs_per_cell);
+
+ FullMatrix<double> ui_ve_matrix (dofs_per_cell, dofs_per_cell);
+ FullMatrix<double> ue_ve_matrix (dofs_per_cell, dofs_per_cell);
+
+ Vector<double> cell_vector (dofs_per_cell);
+
+ typename DoFHandler<dim>::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 <int dim>
-void DGMethod<dim>::refine_grid ()
-{
- Vector<float> 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<dim>::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 <int dim>
-void DGMethod<dim>::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<dim> fe_v_face (mapping, fe, face_quadrature, face_update_flags);
- FESubfaceValues<dim> fe_v_subface (mapping, fe, face_quadrature, face_update_flags);
- FEFaceValues<dim> fe_v_face_neighbor (mapping, fe, face_quadrature, update_values);
-
- // Now we need to loop over all active cells.
- typename DoFHandler<dim>::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<dim> jump;
- Point<dim> area;
-
for (unsigned int face_no=0; face_no<GeometryInfo<dim>::faces_per_cell; ++face_no)
{
- typename DoFHandler<dim>::face_iterator face = cell->face(face_no);
-
- if (!face->at_boundary())
+ typename DoFHandler<dim>::face_iterator face=
+ cell->face(face_no);
+
+ // Case a)
+ if (face->at_boundary())
{
- Assert (cell->neighbor(face_no).state() == IteratorState::valid, ExcInternalError());
- typename DoFHandler<dim>::cell_iterator neighbor = cell->neighbor(face_no);
-
- std::vector<double> u (fe_v_face.n_quadrature_points);
- std::vector<double> 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<dim>::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_no<face->number_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_no<face->number_of_children(); ++subface_no)
{
- // get an iterator
- // pointing to the cell
- // behind the present
- // subface...
- typename DoFHandler<dim>::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<dim>::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<double> &JxW = fe_v_subface.get_JxW_values ();
- // Now we loop over all
- // quadrature points
- for (unsigned int x=0; x<fe_v_subface.n_quadrature_points; ++x)
- {
- // and integrate
- // the absolute
- // value of the
- // jump of the
- // solution,
- // i.e. the
- // absolute value
- // of the
- // difference
- // between the
- // function value
- // seen from the
- // current cell and
- // the neighboring
- // cell,
- // respectively. We
- // know, that the
- // first two faces
- // are orthogonal
- // to the first
- // coordinate
- // direction on the
- // unit cell, the
- // second two faces
- // are orthogonal
- // to the second
- // coordinate
- // direction and so
- // on, so we
- // accumulate these
- // values ito
- // vectors with
- // <code>dim</code>
- // 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; i<dofs_per_cell; ++i)
+ for (unsigned int j=0; j<dofs_per_cell; ++j)
+ {
+ system_matrix.add(dofs[i], dofs_neighbor[j],
+ ue_vi_matrix(i,j));
+ system_matrix.add(dofs_neighbor[i], dofs[j],
+ ui_ve_matrix(i,j));
+ system_matrix.add(dofs_neighbor[i], dofs_neighbor[j],
+ ue_ve_matrix(i,j));
+ }
}
}
- else
+ else
{
- if (!cell->neighbor_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<double> &JxW = fe_v_face.get_JxW_values ();
-
- for (unsigned int x=0; x<fe_v_face.n_quadrature_points; ++x)
- {
- jump[face_no/2]+=std::fabs(u[x]-u_neighbor[x])*JxW[x];
- area[face_no/2]+=JxW[x];
- }
- }
- else //i.e. neighbor is coarser than cell
- {
- // Now the neighbor is
- // actually
- // coarser. This case
- // is new, in that it
- // did not occur in the
- // assembly
- // routine. Here, we
- // have to consider it,
- // but this is not
- // overly
- // complicated. We
- // simply use the @p
- // neighbor_of_coarser_neighbor
- // function, which
- // again takes care of
- // anisotropic
- // refinement and
- // non-standard face
- // orientation by
- // itself.
- std::pair<unsigned int,unsigned int> neighbor_face_subface
- = cell->neighbor_of_coarser_neighbor(face_no);
- Assert (neighbor_face_subface.first<GeometryInfo<dim>::faces_per_cell, ExcInternalError());
- Assert (neighbor_face_subface.second<neighbor->face(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<double> &JxW = fe_v_face.get_JxW_values ();
-
- for (unsigned int x=0; x<fe_v_face.n_quadrature_points; ++x)
- {
- jump[face_no/2]+=std::fabs(u[x]-u_neighbor[x])*JxW[x];
- area[face_no/2]+=JxW[x];
- }
+
+ 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; i<dofs_per_cell; ++i)
+ for (unsigned int j=0; j<dofs_per_cell; ++j)
+ {
+ system_matrix.add(dofs[i], dofs_neighbor[j],
+ ue_vi_matrix(i,j));
+ system_matrix.add(dofs_neighbor[i], dofs[j],
+ ui_ve_matrix(i,j));
+ system_matrix.add(dofs_neighbor[i], dofs_neighbor[j],
+ ue_ve_matrix(i,j));
+ }
}
+
+ // We do not need to consider
+ // case d), as those faces
+ // are treated 'from the
+ // other side within case b).
}
}
}
- // Now we analyze the size of the mean
- // jumps, which we get dividing the
- // jumps by the measure of the
- // respective faces.
- double average_jumps[dim];
- double sum_of_average_jumps=0.;
- for (unsigned int i=0; i<dim; ++i)
- {
- average_jumps[i] = jump(i)/area(i);
- sum_of_average_jumps += average_jumps[i];
- }
- // Now we loop over the <code>dim</code>
- // 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<dim; ++i)
- if (average_jumps[i] > anisotropic_threshold_ratio*(sum_of_average_jumps-average_jumps[i]))
- cell->set_refine_flag(RefinementCase<dim>::cut_axis(i));
+ for (unsigned int i=0; i<dofs_per_cell; ++i)
+ for (unsigned int j=0; j<dofs_per_cell; ++j)
+ system_matrix.add(dofs[i], dofs[j], ui_vi_matrix(i,j));
+
+ for (unsigned int i=0; i<dofs_per_cell; ++i)
+ right_hand_side(dofs[i]) += cell_vector(i);
}
-}
-
- // @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 <int dim>
-void DGMethod<dim>::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<dim> 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 <int dim>
+ void DGMethod<dim>::solve (Vector<double> &solution)
+ {
+ SolverControl solver_control (1000, 1e-12, false, false);
+ SolverRichardson<> solver (solver_control);
+
+ PreconditionBlockSSOR<SparseMatrix<double> > 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 <int dim>
+ void DGMethod<dim>::refine_grid ()
+ {
+ Vector<float> 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<dim>::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 <int dim>
+ void DGMethod<dim>::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<dim> fe_v_face (mapping, fe, face_quadrature, face_update_flags);
+ FESubfaceValues<dim> fe_v_subface (mapping, fe, face_quadrature, face_update_flags);
+ FEFaceValues<dim> fe_v_face_neighbor (mapping, fe, face_quadrature, update_values);
+
+ // Now we need to loop over all active cells.
+ typename DoFHandler<dim>::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<dim> jump;
+ Point<dim> area;
+ for (unsigned int face_no=0; face_no<GeometryInfo<dim>::faces_per_cell; ++face_no)
+ {
+ typename DoFHandler<dim>::face_iterator face = cell->face(face_no);
-template <int dim>
-void DGMethod<dim>::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<dim>::cell_iterator neighbor = cell->neighbor(face_no);
+
+ std::vector<double> u (fe_v_face.n_quadrature_points);
+ std::vector<double> 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_no<face->number_of_children(); ++subface_no)
+ {
+ // get an iterator
+ // pointing to the cell
+ // behind the present
+ // subface...
+ typename DoFHandler<dim>::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<double> &JxW = fe_v_subface.get_JxW_values ();
+ // Now we loop over all
+ // quadrature points
+ for (unsigned int x=0; x<fe_v_subface.n_quadrature_points; ++x)
+ {
+ // and integrate
+ // the absolute
+ // value of the
+ // jump of the
+ // solution,
+ // i.e. the
+ // absolute value
+ // of the
+ // difference
+ // between the
+ // function value
+ // seen from the
+ // current cell and
+ // the neighboring
+ // cell,
+ // respectively. We
+ // know, that the
+ // first two faces
+ // are orthogonal
+ // to the first
+ // coordinate
+ // direction on the
+ // unit cell, the
+ // second two faces
+ // are orthogonal
+ // to the second
+ // coordinate
+ // direction and so
+ // on, so we
+ // accumulate these
+ // values ito
+ // vectors with
+ // <code>dim</code>
+ // 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<double> &JxW = fe_v_face.get_JxW_values ();
+
+ for (unsigned int x=0; x<fe_v_face.n_quadrature_points; ++x)
+ {
+ jump[face_no/2]+=std::fabs(u[x]-u_neighbor[x])*JxW[x];
+ area[face_no/2]+=JxW[x];
+ }
+ }
+ else //i.e. neighbor is coarser than cell
+ {
+ // Now the neighbor is
+ // actually
+ // coarser. This case
+ // is new, in that it
+ // did not occur in the
+ // assembly
+ // routine. Here, we
+ // have to consider it,
+ // but this is not
+ // overly
+ // complicated. We
+ // simply use the @p
+ // neighbor_of_coarser_neighbor
+ // function, which
+ // again takes care of
+ // anisotropic
+ // refinement and
+ // non-standard face
+ // orientation by
+ // itself.
+ std::pair<unsigned int,unsigned int> neighbor_face_subface
+ = cell->neighbor_of_coarser_neighbor(face_no);
+ Assert (neighbor_face_subface.first<GeometryInfo<dim>::faces_per_cell, ExcInternalError());
+ Assert (neighbor_face_subface.second<neighbor->face(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<double> &JxW = fe_v_face.get_JxW_values ();
+
+ for (unsigned int x=0; x<fe_v_face.n_quadrature_points; ++x)
+ {
+ jump[face_no/2]+=std::fabs(u[x]-u_neighbor[x])*JxW[x];
+ area[face_no/2]+=JxW[x];
+ }
+ }
+ }
+ }
+ }
+ // Now we analyze the size of the mean
+ // jumps, which we get dividing the
+ // jumps by the measure of the
+ // respective faces.
+ double average_jumps[dim];
+ double sum_of_average_jumps=0.;
+ for (unsigned int i=0; i<dim; ++i)
+ {
+ average_jumps[i] = jump(i)/area(i);
+ sum_of_average_jumps += average_jumps[i];
+ }
- if (cycle == 0)
- {
- // Create the rectangular domain.
- Point<dim> p1,p2;
- p1(0)=0;
- p1(0)=-1;
+ // Now we loop over the <code>dim</code>
+ // 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<dim; ++i)
- p2(i)=1.;
- // Adjust the number of cells in
- // different directions to obtain
- // completely isotropic cells for the
- // original mesh.
- std::vector<unsigned int> 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<dim>::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 <int dim>
+ void DGMethod<dim>::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<dim> 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 <int dim>
+ void DGMethod<dim>::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<dim> p1,p2;
+ p1(0)=0;
+ p1(0)=-1;
+ for (unsigned int i=0; i<dim; ++i)
+ p2(i)=1.;
+ // Adjust the number of cells in
+ // different directions to obtain
+ // completely isotropic cells for the
+ // original mesh.
+ std::vector<unsigned int> 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
// <code>const unsigned int dim = 3;</code>.
const unsigned int dim = 2;
-
+
{
// First, we perform a run with
// isotropic refinement.
DGMethod<dim> dgmethod_iso(false);
dgmethod_iso.run ();
}
-
+
{
// Now we do a second run, this time
// with anisotropic refinement.
<< std::endl;
return 1;
}
- catch (...)
+ catch (...)
{
std::cerr << std::endl << std::endl
<< "----------------------------------------------------"
<< std::endl;
return 1;
};
-
+
return 0;
}
/* $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 */
// 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 <code>dim-1</code> 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 <i>T</i>, 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 <code>dim-1</code> 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 <i>T</i>, 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 <int dim>
- class TemperatureInitialValues : public Function<dim>
- {
- public:
- TemperatureInitialValues () : Function<dim>(1) {}
+ template <int dim>
+ class TemperatureInitialValues : public Function<dim>
+ {
+ public:
+ TemperatureInitialValues () : Function<dim>(1) {}
- virtual double value (const Point<dim> &p,
- const unsigned int component = 0) const;
+ virtual double value (const Point<dim> &p,
+ const unsigned int component = 0) const;
- virtual void vector_value (const Point<dim> &p,
- Vector<double> &value) const;
- };
+ virtual void vector_value (const Point<dim> &p,
+ Vector<double> &value) const;
+ };
- template <int dim>
- double
- TemperatureInitialValues<dim>::value (const Point<dim> &,
- const unsigned int) const
- {
- return 0;
- }
+ template <int dim>
+ double
+ TemperatureInitialValues<dim>::value (const Point<dim> &,
+ const unsigned int) const
+ {
+ return 0;
+ }
- template <int dim>
- void
- TemperatureInitialValues<dim>::vector_value (const Point<dim> &p,
- Vector<double> &values) const
- {
- for (unsigned int c=0; c<this->n_components; ++c)
- values(c) = TemperatureInitialValues<dim>::value (p, c);
- }
+ template <int dim>
+ void
+ TemperatureInitialValues<dim>::vector_value (const Point<dim> &p,
+ Vector<double> &values) const
+ {
+ for (unsigned int c=0; c<this->n_components; ++c)
+ values(c) = TemperatureInitialValues<dim>::value (p, c);
+ }
- template <int dim>
- class TemperatureRightHandSide : public Function<dim>
- {
- public:
- TemperatureRightHandSide () : Function<dim>(1) {}
+ template <int dim>
+ class TemperatureRightHandSide : public Function<dim>
+ {
+ public:
+ TemperatureRightHandSide () : Function<dim>(1) {}
- virtual double value (const Point<dim> &p,
- const unsigned int component = 0) const;
+ virtual double value (const Point<dim> &p,
+ const unsigned int component = 0) const;
- virtual void vector_value (const Point<dim> &p,
- Vector<double> &value) const;
- };
+ virtual void vector_value (const Point<dim> &p,
+ Vector<double> &value) const;
+ };
- template <int dim>
- double
- TemperatureRightHandSide<dim>::value (const Point<dim> &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<dim> source_centers[3]
- = { (dim == 2 ? Point<dim>(.3,.1) : Point<dim>(.3,.5,.1)),
- (dim == 2 ? Point<dim>(.45,.1) : Point<dim>(.45,.5,.1)),
- (dim == 2 ? Point<dim>(.75,.1) : Point<dim>(.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 <int dim>
+ double
+ TemperatureRightHandSide<dim>::value (const Point<dim> &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<dim> source_centers[3]
+ = { (dim == 2 ? Point<dim>(.3,.1) : Point<dim>(.3,.5,.1)),
+ (dim == 2 ? Point<dim>(.45,.1) : Point<dim>(.45,.5,.1)),
+ (dim == 2 ? Point<dim>(.75,.1) : Point<dim>(.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 <int dim>
- void
- TemperatureRightHandSide<dim>::vector_value (const Point<dim> &p,
- Vector<double> &values) const
- {
- for (unsigned int c=0; c<this->n_components; ++c)
- values(c) = TemperatureRightHandSide<dim>::value (p, c);
+ template <int dim>
+ void
+ TemperatureRightHandSide<dim>::vector_value (const Point<dim> &p,
+ Vector<double> &values) const
+ {
+ for (unsigned int c=0; c<this->n_components; ++c)
+ values(c) = TemperatureRightHandSide<dim>::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 <code>InverseMatrix</code> class template}
-
- // This class is an interface to
- // calculate the action of an
- // "inverted" matrix on a vector
- // (using the <code>vmult</code>
- // 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
- // <code>Preconditioner</code>.
- //
- // In a minor deviation from the
- // implementation of the same class in
- // step-22 (and step-20), we make the
- // <code>vmult</code> 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 <code>main()</code> 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
- // <code>main()</code> 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 <code>abort()</code>,
- // allowing us to trace back in a debugger
- // how we got to the current place.
- template <class Matrix, class Preconditioner>
- 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 <code>InverseMatrix</code> class template}
+
+ // This class is an interface to
+ // calculate the action of an
+ // "inverted" matrix on a vector
+ // (using the <code>vmult</code>
+ // 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
+ // <code>Preconditioner</code>.
+ //
+ // In a minor deviation from the
+ // implementation of the same class in
+ // step-22 (and step-20), we make the
+ // <code>vmult</code> 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 <code>main()</code> 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
+ // <code>main()</code> 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 <code>abort()</code>,
+ // allowing us to trace back in a debugger
+ // how we got to the current place.
+ template <class Matrix, class Preconditioner>
+ class InverseMatrix : public Subscriptor
+ {
+ public:
+ InverseMatrix (const Matrix &m,
+ const Preconditioner &preconditioner);
- template <typename VectorType>
- void vmult (VectorType &dst,
- const VectorType &src) const;
- private:
- const SmartPointer<const Matrix> matrix;
- const Preconditioner &preconditioner;
- };
+ template <typename VectorType>
+ void vmult (VectorType &dst,
+ const VectorType &src) const;
+ private:
+ const SmartPointer<const Matrix> matrix;
+ const Preconditioner &preconditioner;
+ };
- template <class Matrix, class Preconditioner>
- InverseMatrix<Matrix,Preconditioner>::
- InverseMatrix (const Matrix &m,
- const Preconditioner &preconditioner)
- :
- matrix (&m),
- preconditioner (preconditioner)
- {}
+ template <class Matrix, class Preconditioner>
+ InverseMatrix<Matrix,Preconditioner>::
+ InverseMatrix (const Matrix &m,
+ const Preconditioner &preconditioner)
+ :
+ matrix (&m),
+ preconditioner (preconditioner)
+ {}
- template <class Matrix, class Preconditioner>
- template <typename VectorType>
- void
- InverseMatrix<Matrix,Preconditioner>::
- vmult (VectorType &dst,
- const VectorType &src) const
- {
- SolverControl solver_control (src.size(), 1e-7*src.l2_norm());
- SolverCG<VectorType> cg (solver_control);
- dst = 0;
+ template <class Matrix, class Preconditioner>
+ template <typename VectorType>
+ void
+ InverseMatrix<Matrix,Preconditioner>::
+ vmult (VectorType &dst,
+ const VectorType &src) const
+ {
+ SolverControl solver_control (src.size(), 1e-7*src.l2_norm());
+ SolverCG<VectorType> 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 <i>P</i> 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 <i>A</i> 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 <em>each</em> outer
+ // solver step (using the AMG
+ // preconditioner).
+ //
+ // Having the above explanations in mind,
+ // we define a preconditioner class with
+ // a <code>vmult</code> 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 PreconditionerA, class PreconditionerMp>
+ class BlockSchurPreconditioner : public Subscriptor
+ {
+ public:
+ BlockSchurPreconditioner (
+ const TrilinosWrappers::BlockSparseMatrix &S,
+ const InverseMatrix<TrilinosWrappers::SparseMatrix,
+ PreconditionerMp> &Mpinv,
+ const PreconditionerA &Apreconditioner);
+
+ void vmult (TrilinosWrappers::BlockVector &dst,
+ const TrilinosWrappers::BlockVector &src) const;
+
+ private:
+ const SmartPointer<const TrilinosWrappers::BlockSparseMatrix> stokes_matrix;
+ const SmartPointer<const InverseMatrix<TrilinosWrappers::SparseMatrix,
+ PreconditionerMp > > m_inverse;
+ const PreconditionerA &a_preconditioner;
+
+ mutable TrilinosWrappers::Vector tmp;
+ };
+
+
+
+ template <class PreconditionerA, class PreconditionerMp>
+ BlockSchurPreconditioner<PreconditionerA, PreconditionerMp>::
+ BlockSchurPreconditioner(const TrilinosWrappers::BlockSparseMatrix &S,
+ const InverseMatrix<TrilinosWrappers::SparseMatrix,
+ PreconditionerMp> &Mpinv,
+ const PreconditionerA &Apreconditioner)
+ :
+ stokes_matrix (&S),
+ m_inverse (&Mpinv),
+ a_preconditioner (Apreconditioner),
+ tmp (stokes_matrix->block(1,1).m())
+ {}
+
+
+ // Next is the <code>vmult</code>
+ // 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 <i>A</i>, 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 <class PreconditionerA, class PreconditionerMp>
+ void BlockSchurPreconditioner<PreconditionerA, PreconditionerMp>::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 <i>P</i> 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 <i>A</i> 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 <em>each</em> outer
- // solver step (using the AMG
- // preconditioner).
+
+
+ // @sect3{The <code>BoussinesqFlowProblem</code> 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 <code>vmult</code> 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 PreconditionerA, class PreconditionerMp>
- 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 <code>if</code> into the
+ // respective function and thereby keeping
+ // our main <code>run()</code> function clean
+ // and easy to read.
+ template <int dim>
+ class BoussinesqFlowProblem
{
public:
- BlockSchurPreconditioner (
- const TrilinosWrappers::BlockSparseMatrix &S,
- const InverseMatrix<TrilinosWrappers::SparseMatrix,
- PreconditionerMp> &Mpinv,
- const PreconditionerA &Apreconditioner);
-
- void vmult (TrilinosWrappers::BlockVector &dst,
- const TrilinosWrappers::BlockVector &src) const;
+ BoussinesqFlowProblem ();
+ void run ();
private:
- const SmartPointer<const TrilinosWrappers::BlockSparseMatrix> stokes_matrix;
- const SmartPointer<const InverseMatrix<TrilinosWrappers::SparseMatrix,
- PreconditionerMp > > 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<double,double> 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<double> &old_temperature,
+ const std::vector<double> &old_old_temperature,
+ const std::vector<Tensor<1,dim> > &old_temperature_grads,
+ const std::vector<Tensor<1,dim> > &old_old_temperature_grads,
+ const std::vector<double> &old_temperature_laplacians,
+ const std::vector<double> &old_old_temperature_laplacians,
+ const std::vector<Tensor<1,dim> > &old_velocity_values,
+ const std::vector<Tensor<1,dim> > &old_old_velocity_values,
+ const std::vector<double> &gamma_values,
+ const double global_u_infty,
+ const double global_T_variation,
+ const double cell_diameter) const;
+
+
+ Triangulation<dim> triangulation;
+ double global_Omega_diameter;
+
+ const unsigned int stokes_degree;
+ FESystem<dim> stokes_fe;
+ DoFHandler<dim> stokes_dof_handler;
+ ConstraintMatrix stokes_constraints;
+
+ std::vector<unsigned int> 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<dim> temperature_fe;
+ DoFHandler<dim> 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<TrilinosWrappers::PreconditionAMG> Amg_preconditioner;
+ std_cxx1x::shared_ptr<TrilinosWrappers::PreconditionIC> Mp_preconditioner;
+
+ bool rebuild_stokes_matrix;
+ bool rebuild_temperature_matrices;
+ bool rebuild_stokes_preconditioner;
};
+ // @sect3{BoussinesqFlowProblem class implementation}
- template <class PreconditionerA, class PreconditionerMp>
- BlockSchurPreconditioner<PreconditionerA, PreconditionerMp>::
- BlockSchurPreconditioner(const TrilinosWrappers::BlockSparseMatrix &S,
- const InverseMatrix<TrilinosWrappers::SparseMatrix,
- PreconditionerMp> &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 <int dim>
+ BoussinesqFlowProblem<dim>::BoussinesqFlowProblem ()
:
- stokes_matrix (&S),
- m_inverse (&Mpinv),
- a_preconditioner (Apreconditioner),
- tmp (stokes_matrix->block(1,1).m())
+ triangulation (Triangulation<dim>::maximum_smoothing),
+
+ stokes_degree (1),
+ stokes_fe (FE_Q<dim>(stokes_degree+1), dim,
+ FE_Q<dim>(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 <code>vmult</code>
- // 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
+ // <code>stokes_fe_values.get_function_values</code>
+ // to get the values of the entire Stokes
+ // solution (velocities and pressures) we use
+ // <code>stokes_fe_values[velocities].get_function_values</code>
+ // 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<double>, allowing
+ // us to process it right away using the
+ // <code>norm()</code> 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 <i>A</i>, 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 <class PreconditionerA, class PreconditionerMp>
- void BlockSchurPreconditioner<PreconditionerA, PreconditionerMp>::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
+ // <code>stokes_degree+1</code> times since
+ // this is the polynomial degree of the
+ // Stokes element in use:
+ template <int dim>
+ double BoussinesqFlowProblem<dim>::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<dim> quadrature_formula (QTrapez<1>(),
+ stokes_degree+1);
+ const unsigned int n_q_points = quadrature_formula.size();
+ FEValues<dim> fe_values (stokes_fe, quadrature_formula, update_values);
+ std::vector<Tensor<1,dim> > velocity_values(n_q_points);
+ double max_velocity = 0;
+ const FEValuesExtractors::Vector velocities (0);
- // @sect3{The <code>BoussinesqFlowProblem</code> 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 <code>if</code> into the
- // respective function and thereby keeping
- // our main <code>run()</code> function clean
- // and easy to read.
-template <int dim>
-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<double,double> get_extrapolated_temperature_range () const;
- void solve ();
- void output_results () const;
- void refine_mesh (const unsigned int max_grid_level);
+ typename DoFHandler<dim>::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<double> &old_temperature,
- const std::vector<double> &old_old_temperature,
- const std::vector<Tensor<1,dim> > &old_temperature_grads,
- const std::vector<Tensor<1,dim> > &old_old_temperature_grads,
- const std::vector<double> &old_temperature_laplacians,
- const std::vector<double> &old_old_temperature_laplacians,
- const std::vector<Tensor<1,dim> > &old_velocity_values,
- const std::vector<Tensor<1,dim> > &old_old_velocity_values,
- const std::vector<double> &gamma_values,
- const double global_u_infty,
- const double global_T_variation,
- const double cell_diameter) const;
+ for (unsigned int q=0; q<n_q_points; ++q)
+ max_velocity = std::max (max_velocity, velocity_values[q].norm());
+ }
+ return max_velocity;
+ }
- Triangulation<dim> triangulation;
- double global_Omega_diameter;
- const unsigned int stokes_degree;
- FESystem<dim> stokes_fe;
- DoFHandler<dim> stokes_dof_handler;
- ConstraintMatrix stokes_constraints;
- std::vector<unsigned int> 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 <int dim>
+ std::pair<double,double>
+ BoussinesqFlowProblem<dim>::get_extrapolated_temperature_range () const
+ {
+ const QIterated<dim> quadrature_formula (QTrapez<1>(),
+ temperature_degree);
+ const unsigned int n_q_points = quadrature_formula.size();
- const unsigned int temperature_degree;
- FE_Q<dim> temperature_fe;
- DoFHandler<dim> temperature_dof_handler;
- ConstraintMatrix temperature_constraints;
+ FEValues<dim> fe_values (temperature_fe, quadrature_formula,
+ update_values);
+ std::vector<double> old_temperature_values(n_q_points);
+ std::vector<double> 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<double>::max(),
+ max_temperature = -std::numeric_limits<double>::max();
+
+ typename DoFHandler<dim>::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<n_q_points; ++q)
+ {
+ const double temperature =
+ (1. + time_step/old_time_step) * old_temperature_values[q]-
+ time_step/old_time_step * old_old_temperature_values[q];
- TrilinosWrappers::Vector temperature_solution;
- TrilinosWrappers::Vector old_temperature_solution;
- TrilinosWrappers::Vector old_old_temperature_solution;
- TrilinosWrappers::Vector temperature_rhs;
+ min_temperature = std::min (min_temperature, temperature);
+ max_temperature = std::max (max_temperature, temperature);
+ }
+ }
+ return std::make_pair(min_temperature, max_temperature);
+ }
+ else
+ {
+ double min_temperature = std::numeric_limits<double>::max(),
+ max_temperature = -std::numeric_limits<double>::max();
+
+ typename DoFHandler<dim>::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<n_q_points; ++q)
+ {
+ const double temperature = old_temperature_values[q];
- double time_step;
- double old_time_step;
- unsigned int timestep_number;
+ min_temperature = std::min (min_temperature, temperature);
+ max_temperature = std::max (max_temperature, temperature);
+ }
+ }
- std_cxx1x::shared_ptr<TrilinosWrappers::PreconditionAMG> Amg_preconditioner;
- std_cxx1x::shared_ptr<TrilinosWrappers::PreconditionIC> 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 <int dim>
-BoussinesqFlowProblem<dim>::BoussinesqFlowProblem ()
- :
- triangulation (Triangulation<dim>::maximum_smoothing),
-
- stokes_degree (1),
- stokes_fe (FE_Q<dim>(stokes_degree+1), dim,
- FE_Q<dim>(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
- // <code>stokes_fe_values.get_function_values</code>
- // to get the values of the entire Stokes
- // solution (velocities and pressures) we use
- // <code>stokes_fe_values[velocities].get_function_values</code>
- // 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<double>, allowing
- // us to process it right away using the
- // <code>norm()</code> 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
- // <code>stokes_degree+1</code> times since
- // this is the polynomial degree of the
- // Stokes element in use:
-template <int dim>
-double BoussinesqFlowProblem<dim>::get_maximal_velocity () const
-{
- const QIterated<dim> 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 <int dim>
+ double
+ BoussinesqFlowProblem<dim>::
+ compute_viscosity (const std::vector<double> &old_temperature,
+ const std::vector<double> &old_old_temperature,
+ const std::vector<Tensor<1,dim> > &old_temperature_grads,
+ const std::vector<Tensor<1,dim> > &old_old_temperature_grads,
+ const std::vector<double> &old_temperature_laplacians,
+ const std::vector<double> &old_old_temperature_laplacians,
+ const std::vector<Tensor<1,dim> > &old_velocity_values,
+ const std::vector<Tensor<1,dim> > &old_old_velocity_values,
+ const std::vector<double> &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<dim> fe_values (stokes_fe, quadrature_formula, update_values);
- std::vector<Tensor<1,dim> > 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<dim>::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<n_q_points; ++q)
- max_velocity = std::max (max_velocity, velocity_values[q].norm());
- }
+ for (unsigned int q=0; q < n_q_points; ++q)
+ {
+ const Tensor<1,dim> 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 <int dim>
-std::pair<double,double>
-BoussinesqFlowProblem<dim>::get_extrapolated_temperature_range () const
-{
- const QIterated<dim> 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<dim> fe_values (temperature_fe, quadrature_formula,
- update_values);
- std::vector<double> old_temperature_values(n_q_points);
- std::vector<double> old_old_temperature_values(n_q_points);
- if (timestep_number != 0)
- {
- double min_temperature = std::numeric_limits<double>::max(),
- max_temperature = -std::numeric_limits<double>::max();
- typename DoFHandler<dim>::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<n_q_points; ++q)
- {
- const double temperature =
- (1. + time_step/old_time_step) * old_temperature_values[q]-
- time_step/old_time_step * old_old_temperature_values[q];
-
- min_temperature = std::min (min_temperature, temperature);
- max_temperature = std::max (max_temperature, temperature);
- }
- }
+ // @sect4{BoussinesqFlowProblem::setup_dofs}
+ //
+ // This is the function that sets up the
+ // DoFHandler objects we have here (one for
+ // the Stokes part and one for the
+ // temperature part) as well as set to the
+ // right sizes the various objects required
+ // for the linear algebra in this
+ // program. Its basic operations are similar
+ // to what we do in step-22.
+ //
+ // The body of the function first
+ // enumerates all degrees of freedom for
+ // the Stokes and temperature systems. For
+ // the Stokes part, degrees of freedom are
+ // then sorted to ensure that velocities
+ // precede pressure DoFs so that we can
+ // partition the Stokes matrix into a
+ // $2\times 2$ matrix. As a difference to
+ // step-22, we do not perform any
+ // additional DoF renumbering. In that
+ // program, it paid off since our solver
+ // was heavily dependent on ILU's, whereas
+ // we use AMG here which is not sensitive
+ // to the DoF numbering. The IC
+ // preconditioner for the inversion of the
+ // pressure mass matrix would of course
+ // take advantage of a Cuthill-McKee like
+ // renumbering, but its costs are low
+ // compared to the velocity portion, so the
+ // additional work does not pay off.
+ //
+ // We then proceed with the generation of the
+ // hanging node constraints that arise from
+ // adaptive grid refinement for both
+ // DoFHandler objects. For the velocity, we
+ // impose no-flux boundary conditions
+ // $\mathbf{u}\cdot \mathbf{n}=0$ by adding
+ // constraints to the object that already
+ // stores the hanging node constraints
+ // matrix. The second parameter in the
+ // function describes the first of the
+ // velocity components in the total dof
+ // vector, which is zero here. The variable
+ // <code>no_normal_flux_boundaries</code>
+ // 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 <int dim>
+ void BoussinesqFlowProblem<dim>::setup_dofs ()
+ {
+ std::vector<unsigned int> 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<unsigned char> 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<double>::max(),
- max_temperature = -std::numeric_limits<double>::max();
-
- typename DoFHandler<dim>::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<n_q_points; ++q)
- {
- const double temperature = old_temperature_values[q];
+ temperature_constraints.clear ();
+ DoFTools::make_hanging_node_constraints (temperature_dof_handler,
+ temperature_constraints);
+ temperature_constraints.close ();
+ }
- min_temperature = std::min (min_temperature, temperature);
- max_temperature = std::max (max_temperature, temperature);
- }
- }
+ std::vector<unsigned int> 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
+ // <code>false</code>. 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 <int dim>
-double
-BoussinesqFlowProblem<dim>::
-compute_viscosity (const std::vector<double> &old_temperature,
- const std::vector<double> &old_old_temperature,
- const std::vector<Tensor<1,dim> > &old_temperature_grads,
- const std::vector<Tensor<1,dim> > &old_old_temperature_grads,
- const std::vector<double> &old_temperature_laplacians,
- const std::vector<double> &old_old_temperature_laplacians,
- const std::vector<Tensor<1,dim> > &old_velocity_values,
- const std::vector<Tensor<1,dim> > &old_old_velocity_values,
- const std::vector<double> &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<dim+1; ++c)
+ for (unsigned int d=0; d<dim+1; ++d)
+ if (! ((c==dim) && (d==dim)))
+ coupling[c][d] = DoFTools::always;
+ else
+ coupling[c][d] = DoFTools::none;
- const unsigned int n_q_points = old_temperature.size();
+ DoFTools::make_sparsity_pattern (stokes_dof_handler, coupling, csp,
+ stokes_constraints, false);
- double max_residual = 0;
- double max_velocity = 0;
+ stokes_matrix.reinit (csp);
+ }
- for (unsigned int q=0; q < n_q_points; ++q)
{
- const Tensor<1,dim> 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; c<dim+1; ++c)
+ for (unsigned int d=0; d<dim+1; ++d)
+ if (c == d)
+ coupling[c][d] = DoFTools::always;
+ else
+ coupling[c][d] = DoFTools::none;
- 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.);
+ DoFTools::make_sparsity_pattern (stokes_dof_handler, coupling, csp,
+ stokes_constraints, false);
- return (beta *
- max_velocity *
- std::min (cell_diameter,
- std::pow(cell_diameter,alpha) *
- max_residual / global_scaling));
-}
+ stokes_preconditioner_matrix.reinit (csp);
+ }
+ // 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);
- // @sect4{BoussinesqFlowProblem::setup_dofs}
- //
- // This is the function that sets up the
- // DoFHandler objects we have here (one for
- // the Stokes part and one for the
- // temperature part) as well as set to the
- // right sizes the various objects required
- // for the linear algebra in this
- // program. Its basic operations are similar
- // to what we do in step-22.
- //
- // The body of the function first
- // enumerates all degrees of freedom for
- // the Stokes and temperature systems. For
- // the Stokes part, degrees of freedom are
- // then sorted to ensure that velocities
- // precede pressure DoFs so that we can
- // partition the Stokes matrix into a
- // $2\times 2$ matrix. As a difference to
- // step-22, we do not perform any
- // additional DoF renumbering. In that
- // program, it paid off since our solver
- // was heavily dependent on ILU's, whereas
- // we use AMG here which is not sensitive
- // to the DoF numbering. The IC
- // preconditioner for the inversion of the
- // pressure mass matrix would of course
- // take advantage of a Cuthill-McKee like
- // renumbering, but its costs are low
- // compared to the velocity portion, so the
- // additional work does not pay off.
- //
- // We then proceed with the generation of the
- // hanging node constraints that arise from
- // adaptive grid refinement for both
- // DoFHandler objects. For the velocity, we
- // impose no-flux boundary conditions
- // $\mathbf{u}\cdot \mathbf{n}=0$ by adding
- // constraints to the object that already
- // stores the hanging node constraints
- // matrix. The second parameter in the
- // function describes the first of the
- // velocity components in the total dof
- // vector, which is zero here. The variable
- // <code>no_normal_flux_boundaries</code>
- // 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 <int dim>
-void BoussinesqFlowProblem<dim>::setup_dofs ()
-{
- std::vector<unsigned int> 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<unsigned char> 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<unsigned int> 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
- // <code>false</code>. 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
+ // <code>phi_grad_u</code> and
+ // <code>phi_p</code> 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 <int dim>
+ void
+ BoussinesqFlowProblem<dim>::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<dim> quadrature_formula(stokes_degree+2);
+ FEValues<dim> 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<dim+1; ++c)
- for (unsigned int d=0; d<dim+1; ++d)
- if (! ((c==dim) && (d==dim)))
- coupling[c][d] = DoFTools::always;
- else
- coupling[c][d] = DoFTools::none;
+ FullMatrix<double> local_matrix (dofs_per_cell, dofs_per_cell);
+ std::vector<unsigned int> local_dof_indices (dofs_per_cell);
- DoFTools::make_sparsity_pattern (stokes_dof_handler, coupling, csp,
- stokes_constraints, false);
+ std::vector<Tensor<2,dim> > phi_grad_u (dofs_per_cell);
+ std::vector<double> 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<dim+1; ++c)
- for (unsigned int d=0; d<dim+1; ++d)
- if (c == d)
- coupling[c][d] = DoFTools::always;
- else
- coupling[c][d] = DoFTools::none;
+ typename DoFHandler<dim>::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; q<n_q_points; ++q)
+ {
+ for (unsigned int k=0; k<dofs_per_cell; ++k)
+ {
+ phi_grad_u[k] = stokes_fe_values[velocities].gradient(k,q);
+ phi_p[k] = stokes_fe_values[pressure].value (k, q);
+ }
- DoFTools::make_sparsity_pattern (stokes_dof_handler, coupling, csp,
- stokes_constraints, false);
+ for (unsigned int i=0; i<dofs_per_cell; ++i)
+ for (unsigned int j=0; j<dofs_per_cell; ++j)
+ local_matrix(i,j) += (EquationData::eta *
+ scalar_product (phi_grad_u[i], phi_grad_u[j])
+ +
+ (1./EquationData::eta) *
+ phi_p[i] * phi_p[j])
+ * stokes_fe_values.JxW(q);
+ }
- stokes_preconditioner_matrix.reinit (csp);
+ cell->get_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
+ // <code>rebuild_stokes_preconditioner</code>
+ // has the value
+ // <code>false</code>). Otherwise its first
+ // task is to call
+ // <code>assemble_stokes_preconditioner</code>
+ // to generate the preconditioner matrices.
+ //
+ // Next, we set up the preconditioner for
+ // the velocity-velocity matrix
+ // <i>A</i>. 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
+ // <i>A</i>). 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
+ // <code>dim</code> 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 <int dim>
+ void
+ BoussinesqFlowProblem<dim>::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<TrilinosWrappers::PreconditionAMG>
+ (new TrilinosWrappers::PreconditionAMG());
+
+ std::vector<std::vector<bool> > constant_modes;
+ std::vector<bool> 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
+ // <code>aggregation_threshold</code> 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
+ // <code>aggregation_threshold</code>
+ // 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 <code>std_cxx1x::shared_ptr</code>
+ // 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<TrilinosWrappers::PreconditionIC>
+ (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
- // <code>phi_grad_u</code> and
- // <code>phi_p</code> 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 <int dim>
-void
-BoussinesqFlowProblem<dim>::assemble_stokes_preconditioner ()
-{
- stokes_preconditioner_matrix = 0;
-
- const QGauss<dim> quadrature_formula(stokes_degree+2);
- FEValues<dim> 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<double> local_matrix (dofs_per_cell, dofs_per_cell);
- std::vector<unsigned int> local_dof_indices (dofs_per_cell);
-
- std::vector<Tensor<2,dim> > phi_grad_u (dofs_per_cell);
- std::vector<double> 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 <code>rebuild_stokes_matrix</code>, 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 <int dim>
+ void BoussinesqFlowProblem<dim>::assemble_stokes_system ()
+ {
+ std::cout << " Assembling..." << std::flush;
+
+ if (rebuild_stokes_matrix == true)
+ stokes_matrix=0;
+
+ stokes_rhs=0;
+
+ const QGauss<dim> quadrature_formula (stokes_degree+2);
+ FEValues<dim> stokes_fe_values (stokes_fe, quadrature_formula,
+ update_values |
+ update_quadrature_points |
+ update_JxW_values |
+ (rebuild_stokes_matrix == true
+ ?
+ update_gradients
+ :
+ UpdateFlags(0)));
+
+ FEValues<dim> 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<double> local_matrix (dofs_per_cell, dofs_per_cell);
+ Vector<double> local_rhs (dofs_per_cell);
+
+ std::vector<unsigned int> 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
+ // <code>old_solution_values</code>.
+ //
+ // 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<double> old_temperature_values(n_q_points);
+
+ std::vector<Tensor<1,dim> > phi_u (dofs_per_cell);
+ std::vector<SymmetricTensor<2,dim> > grads_phi_u (dofs_per_cell);
+ std::vector<double> div_phi_u (dofs_per_cell);
+ std::vector<double> 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<dim>::active_cell_iterator
+ cell = stokes_dof_handler.begin_active(),
+ endc = stokes_dof_handler.end();
+ typename DoFHandler<dim>::active_cell_iterator
+ temperature_cell = temperature_dof_handler.begin_active();
- typename DoFHandler<dim>::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; q<n_q_points; ++q)
- {
- for (unsigned int k=0; k<dofs_per_cell; ++k)
- {
- phi_grad_u[k] = stokes_fe_values[velocities].gradient(k,q);
- phi_p[k] = stokes_fe_values[pressure].value (k, q);
- }
-
- for (unsigned int i=0; i<dofs_per_cell; ++i)
- for (unsigned int j=0; j<dofs_per_cell; ++j)
- local_matrix(i,j) += (EquationData::eta *
- scalar_product (phi_grad_u[i], phi_grad_u[j])
- +
- (1./EquationData::eta) *
- phi_p[i] * phi_p[j])
- * stokes_fe_values.JxW(q);
- }
+ for (; cell!=endc; ++cell, ++temperature_cell)
+ {
+ stokes_fe_values.reinit (cell);
+ temperature_fe_values.reinit (temperature_cell);
+
+ local_matrix = 0;
+ local_rhs = 0;
+
+ temperature_fe_values.get_function_values (old_temperature_solution,
+ old_temperature_values);
+
+ for (unsigned int q=0; q<n_q_points; ++q)
+ {
+ const double old_temperature = old_temperature_values[q];
+
+ // Next we extract the values and
+ // gradients of basis functions
+ // relevant to the terms in the
+ // inner products. As shown in
+ // step-22 this helps accelerate
+ // assembly.
+ //
+ // Once this is done, we start the
+ // loop over the rows and columns
+ // of the local matrix and feed the
+ // matrix with the relevant
+ // products. The right hand side is
+ // filled with the forcing term
+ // driven by temperature in
+ // direction of gravity (which is
+ // vertical in our example). Note
+ // that the right hand side term is
+ // always generated, whereas the
+ // matrix contributions are only
+ // updated when it is requested by
+ // the
+ // <code>rebuild_matrices</code>
+ // flag.
+ for (unsigned int k=0; k<dofs_per_cell; ++k)
+ {
+ phi_u[k] = stokes_fe_values[velocities].value (k,q);
+ if (rebuild_stokes_matrix)
+ {
+ grads_phi_u[k] = stokes_fe_values[velocities].symmetric_gradient(k,q);
+ div_phi_u[k] = stokes_fe_values[velocities].divergence (k, q);
+ phi_p[k] = stokes_fe_values[pressure].value (k, q);
+ }
+ }
- cell->get_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<dofs_per_cell; ++i)
+ for (unsigned int j=0; j<dofs_per_cell; ++j)
+ local_matrix(i,j) += (EquationData::eta * 2 *
+ (grads_phi_u[i] * grads_phi_u[j])
+ - div_phi_u[i] * phi_p[j]
+ - phi_p[i] * div_phi_u[j])
+ * stokes_fe_values.JxW(q);
+
+ const Point<dim> gravity = -( (dim == 2) ? (Point<dim> (0,1)) :
+ (Point<dim> (0,0,1)) );
+ for (unsigned int i=0; i<dofs_per_cell; ++i)
+ local_rhs(i) += (-EquationData::density *
+ EquationData::beta *
+ gravity * phi_u[i] * old_temperature)*
+ stokes_fe_values.JxW(q);
+ }
+
+ // The last step in the loop over all
+ // cells is to enter the local
+ // contributions into the global matrix
+ // and vector structures to the
+ // positions specified in
+ // <code>local_dof_indices</code>.
+ // 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
- // <code>rebuild_stokes_preconditioner</code>
- // has the value
- // <code>false</code>). Otherwise its first
- // task is to call
- // <code>assemble_stokes_preconditioner</code>
- // to generate the preconditioner matrices.
- //
- // Next, we set up the preconditioner for
- // the velocity-velocity matrix
- // <i>A</i>. 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
- // <i>A</i>). 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
- // <code>dim</code> 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 <int dim>
-void
-BoussinesqFlowProblem<dim>::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<TrilinosWrappers::PreconditionAMG>
- (new TrilinosWrappers::PreconditionAMG());
-
- std::vector<std::vector<bool> > constant_modes;
- std::vector<bool> 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
- // <code>aggregation_threshold</code> 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
- // <code>aggregation_threshold</code>
- // 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 <code>std_cxx1x::shared_ptr</code>
- // 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<TrilinosWrappers::PreconditionIC>
- (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 <code>rebuild_stokes_matrix</code>, 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 <int dim>
-void BoussinesqFlowProblem<dim>::assemble_stokes_system ()
-{
- std::cout << " Assembling..." << std::flush;
-
- if (rebuild_stokes_matrix == true)
- stokes_matrix=0;
-
- stokes_rhs=0;
-
- const QGauss<dim> quadrature_formula (stokes_degree+2);
- FEValues<dim> stokes_fe_values (stokes_fe, quadrature_formula,
- update_values |
- update_quadrature_points |
- update_JxW_values |
- (rebuild_stokes_matrix == true
- ?
- update_gradients
- :
- UpdateFlags(0)));
-
- FEValues<dim> 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<double> local_matrix (dofs_per_cell, dofs_per_cell);
- Vector<double> local_rhs (dofs_per_cell);
-
- std::vector<unsigned int> 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
- // <code>old_solution_values</code>.
+ // @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<double> old_temperature_values(n_q_points);
-
- std::vector<Tensor<1,dim> > phi_u (dofs_per_cell);
- std::vector<SymmetricTensor<2,dim> > grads_phi_u (dofs_per_cell);
- std::vector<double> div_phi_u (dofs_per_cell);
- std::vector<double> 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<dim>::active_cell_iterator
- cell = stokes_dof_handler.begin_active(),
- endc = stokes_dof_handler.end();
- typename DoFHandler<dim>::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 <int dim>
+ void BoussinesqFlowProblem<dim>::assemble_temperature_matrix ()
+ {
+ if (rebuild_temperature_matrices == false)
+ return;
+
+ temperature_mass_matrix = 0;
+ temperature_stiffness_matrix = 0;
+
+ QGauss<dim> quadrature_formula (temperature_degree+2);
+ FEValues<dim> 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<double> local_mass_matrix (dofs_per_cell, dofs_per_cell);
+ FullMatrix<double> local_stiffness_matrix (dofs_per_cell, dofs_per_cell);
+
+ std::vector<unsigned int> local_dof_indices (dofs_per_cell);
+
+ std::vector<double> phi_T (dofs_per_cell);
+ std::vector<Tensor<1,dim> > 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
+ // <code>EquationData::kappa</code>. Finally,
+ // we let the constraints object insert
+ // these values into the global matrix, and
+ // directly condense the constraints into
+ // the matrix.
+ typename DoFHandler<dim>::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; q<n_q_points; ++q)
- {
- const double old_temperature = old_temperature_values[q];
-
- // Next we extract the values and
- // gradients of basis functions
- // relevant to the terms in the
- // inner products. As shown in
- // step-22 this helps accelerate
- // assembly.
- //
- // Once this is done, we start the
- // loop over the rows and columns
- // of the local matrix and feed the
- // matrix with the relevant
- // products. The right hand side is
- // filled with the forcing term
- // driven by temperature in
- // direction of gravity (which is
- // vertical in our example). Note
- // that the right hand side term is
- // always generated, whereas the
- // matrix contributions are only
- // updated when it is requested by
- // the
- // <code>rebuild_matrices</code>
- // flag.
- for (unsigned int k=0; k<dofs_per_cell; ++k)
- {
- phi_u[k] = stokes_fe_values[velocities].value (k,q);
- if (rebuild_stokes_matrix)
- {
- grads_phi_u[k] = stokes_fe_values[velocities].symmetric_gradient(k,q);
- div_phi_u[k] = stokes_fe_values[velocities].divergence (k, q);
- phi_p[k] = stokes_fe_values[pressure].value (k, q);
- }
- }
+ for (unsigned int q=0; q<n_q_points; ++q)
+ {
+ for (unsigned int k=0; k<dofs_per_cell; ++k)
+ {
+ grad_phi_T[k] = temperature_fe_values.shape_grad (k,q);
+ phi_T[k] = temperature_fe_values.shape_value (k, q);
+ }
- if (rebuild_stokes_matrix)
for (unsigned int i=0; i<dofs_per_cell; ++i)
for (unsigned int j=0; j<dofs_per_cell; ++j)
- local_matrix(i,j) += (EquationData::eta * 2 *
- (grads_phi_u[i] * grads_phi_u[j])
- - div_phi_u[i] * phi_p[j]
- - phi_p[i] * div_phi_u[j])
- * stokes_fe_values.JxW(q);
-
- const Point<dim> gravity = -( (dim == 2) ? (Point<dim> (0,1)) :
- (Point<dim> (0,0,1)) );
- for (unsigned int i=0; i<dofs_per_cell; ++i)
- local_rhs(i) += (-EquationData::density *
- EquationData::beta *
- gravity * phi_u[i] * old_temperature)*
- stokes_fe_values.JxW(q);
- }
+ {
+ local_mass_matrix(i,j)
+ += (phi_T[i] * phi_T[j]
+ *
+ temperature_fe_values.JxW(q));
+ local_stiffness_matrix(i,j)
+ += (EquationData::kappa * grad_phi_T[i] * grad_phi_T[j]
+ *
+ temperature_fe_values.JxW(q));
+ }
+ }
- // The last step in the loop over all
- // cells is to enter the local
- // contributions into the global matrix
- // and vector structures to the
- // positions specified in
- // <code>local_dof_indices</code>.
- // 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
+ // <code>update_hessians</code>.
+ //
+ // 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 <int dim>
+ void BoussinesqFlowProblem<dim>::
+ 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 <int dim>
-void BoussinesqFlowProblem<dim>::assemble_temperature_matrix ()
-{
- if (rebuild_temperature_matrices == false)
- return;
-
- temperature_mass_matrix = 0;
- temperature_stiffness_matrix = 0;
-
- QGauss<dim> quadrature_formula (temperature_degree+2);
- FEValues<dim> 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<double> local_mass_matrix (dofs_per_cell, dofs_per_cell);
- FullMatrix<double> local_stiffness_matrix (dofs_per_cell, dofs_per_cell);
-
- std::vector<unsigned int> local_dof_indices (dofs_per_cell);
-
- std::vector<double> phi_T (dofs_per_cell);
- std::vector<Tensor<1,dim> > 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
- // <code>EquationData::kappa</code>. Finally,
- // we let the constraints object insert
- // these values into the global matrix, and
- // directly condense the constraints into
- // the matrix.
- typename DoFHandler<dim>::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<dim> quadrature_formula(temperature_degree+2);
+ FEValues<dim> temperature_fe_values (temperature_fe, quadrature_formula,
+ update_values |
+ update_gradients |
+ update_hessians |
+ update_quadrature_points |
+ update_JxW_values);
+ FEValues<dim> 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<double> local_rhs (dofs_per_cell);
+ FullMatrix<double> local_matrix (dofs_per_cell, dofs_per_cell);
+
+ std::vector<unsigned int> 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
+ // <i>n-1</i> and <i>n-2</i>,
+ // respectively) and gradients at
+ // quadrature points of the current
+ // cell. We also declarate an object to
+ // hold the temperature right hande side
+ // values (<code>gamma_values</code>),
+ // 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<Tensor<1,dim> > old_velocity_values (n_q_points);
+ std::vector<Tensor<1,dim> > old_old_velocity_values (n_q_points);
+ std::vector<double> old_temperature_values (n_q_points);
+ std::vector<double> old_old_temperature_values(n_q_points);
+ std::vector<Tensor<1,dim> > old_temperature_grads(n_q_points);
+ std::vector<Tensor<1,dim> > old_old_temperature_grads(n_q_points);
+ std::vector<double> old_temperature_laplacians(n_q_points);
+ std::vector<double> old_old_temperature_laplacians(n_q_points);
+
+ EquationData::TemperatureRightHandSide<dim> temperature_right_hand_side;
+ std::vector<double> gamma_values (n_q_points);
+
+ std::vector<double> phi_T (dofs_per_cell);
+ std::vector<Tensor<1,dim> > grad_phi_T (dofs_per_cell);
+
+ const std::pair<double,double>
+ 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
+ // <code>stokes_fe_values[velocities].get_function_values</code>.
+ typename DoFHandler<dim>::active_cell_iterator
+ cell = temperature_dof_handler.begin_active(),
+ endc = temperature_dof_handler.end();
+ typename DoFHandler<dim>::active_cell_iterator
+ stokes_cell = stokes_dof_handler.begin_active();
- for (unsigned int q=0; q<n_q_points; ++q)
- {
- for (unsigned int k=0; k<dofs_per_cell; ++k)
- {
- grad_phi_T[k] = temperature_fe_values.shape_grad (k,q);
- phi_T[k] = temperature_fe_values.shape_value (k, q);
- }
-
- for (unsigned int i=0; i<dofs_per_cell; ++i)
- for (unsigned int j=0; j<dofs_per_cell; ++j)
+ 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<n_q_points; ++q)
+ {
+ for (unsigned int k=0; k<dofs_per_cell; ++k)
{
- local_mass_matrix(i,j)
- += (phi_T[i] * phi_T[j]
- *
- temperature_fe_values.JxW(q));
- local_stiffness_matrix(i,j)
- += (EquationData::kappa * grad_phi_T[i] * grad_phi_T[j]
- *
- temperature_fe_values.JxW(q));
+ grad_phi_T[k] = temperature_fe_values.shape_grad (k,q);
+ phi_T[k] = temperature_fe_values.shape_value (k, q);
}
- }
- cell->get_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; i<dofs_per_cell; ++i)
+ local_rhs(i) += (old_Ts * phi_T[i]
+ -
+ time_step *
+ extrapolated_u * ext_grad_T * phi_T[i]
+ -
+ time_step *
+ nu * ext_grad_T * grad_phi_T[i]
+ +
+ time_step *
+ gamma_values[q] * phi_T[i])
+ *
+ temperature_fe_values.JxW(q);
+ }
+
+ cell->get_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
- // <code>update_hessians</code>.
- //
- // 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 <int dim>
-void BoussinesqFlowProblem<dim>::
- 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
+ // <code>BlockSchurPreconditioner</code>,
+ // all we have to do is to initialize the
+ // class appropriately. What we need to
+ // pass down is an
+ // <code>InverseMatrix</code> 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 <code>Mp_preconditioner</code>
+ // and <code>Amg_preconditioner</code> are
+ // only pointers, so we use <code>*</code>
+ // 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 <int dim>
+ void BoussinesqFlowProblem<dim>::solve ()
+ {
+ std::cout << " Solving..." << std::endl;
- temperature_rhs = 0;
-
- const QGauss<dim> quadrature_formula(temperature_degree+2);
- FEValues<dim> temperature_fe_values (temperature_fe, quadrature_formula,
- update_values |
- update_gradients |
- update_hessians |
- update_quadrature_points |
- update_JxW_values);
- FEValues<dim> 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<double> local_rhs (dofs_per_cell);
- FullMatrix<double> local_matrix (dofs_per_cell, dofs_per_cell);
-
- std::vector<unsigned int> 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
- // <i>n-1</i> and <i>n-2</i>,
- // respectively) and gradients at
- // quadrature points of the current
- // cell. We also declarate an object to
- // hold the temperature right hande side
- // values (<code>gamma_values</code>),
- // 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<Tensor<1,dim> > old_velocity_values (n_q_points);
- std::vector<Tensor<1,dim> > old_old_velocity_values (n_q_points);
- std::vector<double> old_temperature_values (n_q_points);
- std::vector<double> old_old_temperature_values(n_q_points);
- std::vector<Tensor<1,dim> > old_temperature_grads(n_q_points);
- std::vector<Tensor<1,dim> > old_old_temperature_grads(n_q_points);
- std::vector<double> old_temperature_laplacians(n_q_points);
- std::vector<double> old_old_temperature_laplacians(n_q_points);
-
- EquationData::TemperatureRightHandSide<dim> temperature_right_hand_side;
- std::vector<double> gamma_values (n_q_points);
-
- std::vector<double> phi_T (dofs_per_cell);
- std::vector<Tensor<1,dim> > grad_phi_T (dofs_per_cell);
-
- const std::pair<double,double>
- 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
- // <code>stokes_fe_values[velocities].get_function_values</code>.
- typename DoFHandler<dim>::active_cell_iterator
- cell = temperature_dof_handler.begin_active(),
- endc = temperature_dof_handler.end();
- typename DoFHandler<dim>::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<n_q_points; ++q)
- {
- for (unsigned int k=0; k<dofs_per_cell; ++k)
- {
- grad_phi_T[k] = temperature_fe_values.shape_grad (k,q);
- phi_T[k] = temperature_fe_values.shape_value (k, q);
- }
-
- 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]);
-
- for (unsigned int i=0; i<dofs_per_cell; ++i)
- local_rhs(i) += (old_Ts * phi_T[i]
- -
- time_step *
- extrapolated_u * ext_grad_T * phi_T[i]
- -
- time_step *
- nu * ext_grad_T * grad_phi_T[i]
- +
- time_step *
- gamma_values[q] * phi_T[i])
- *
- temperature_fe_values.JxW(q);
- }
+ const LinearSolvers::InverseMatrix<TrilinosWrappers::SparseMatrix,
+ TrilinosWrappers::PreconditionIC>
+ 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<TrilinosWrappers::PreconditionAMG,
+ TrilinosWrappers::PreconditionIC>
+ preconditioner (stokes_matrix, mp_inverse, *Amg_preconditioner);
+ SolverControl solver_control (stokes_matrix.m(),
+ 1e-6*stokes_rhs.l2_norm());
+ SolverGMRES<TrilinosWrappers::BlockVector>
+ gmres (solver_control,
+ SolverGMRES<TrilinosWrappers::BlockVector >::AdditionalData(100));
+ for (unsigned int i=0; i<stokes_solution.size(); ++i)
+ if (stokes_constraints.is_constrained(i))
+ stokes_solution(i) = 0;
- // @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
- // <code>BlockSchurPreconditioner</code>,
- // all we have to do is to initialize the
- // class appropriately. What we need to
- // pass down is an
- // <code>InverseMatrix</code> 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 <code>Mp_preconditioner</code>
- // and <code>Amg_preconditioner</code> are
- // only pointers, so we use <code>*</code>
- // 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 <int dim>
-void BoussinesqFlowProblem<dim>::solve ()
-{
- std::cout << " Solving..." << std::endl;
+ gmres.solve(stokes_matrix, stokes_solution, stokes_rhs, preconditioner);
- {
- const LinearSolvers::InverseMatrix<TrilinosWrappers::SparseMatrix,
- TrilinosWrappers::PreconditionIC>
- mp_inverse (stokes_preconditioner_matrix.block(1,1), *Mp_preconditioner);
+ stokes_constraints.distribute (stokes_solution);
- const LinearSolvers::BlockSchurPreconditioner<TrilinosWrappers::PreconditionAMG,
- TrilinosWrappers::PreconditionIC>
- 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<TrilinosWrappers::BlockVector>
- gmres (solver_control,
- SolverGMRES<TrilinosWrappers::BlockVector >::AdditionalData(100));
+ temperature_solution = old_temperature_solution;
+
+ // Next we set up the temperature system
+ // and the right hand side using the
+ // function
+ // <code>assemble_temperature_system()</code>.
+ // 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
+ // <code>TrilinosWrappers::Vector</code>.
+ // 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<stokes_solution.size(); ++i)
- if (stokes_constraints.is_constrained(i))
- stokes_solution(i) = 0;
+ SolverControl solver_control (temperature_matrix.m(),
+ 1e-8*temperature_rhs.l2_norm());
+ SolverCG<TrilinosWrappers::Vector> 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<temperature_solution.size(); ++i)
+ {
+ min_temperature = std::min<double> (min_temperature,
+ temperature_solution(i));
+ max_temperature = std::max<double> (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
- // <code>assemble_temperature_system()</code>.
- // 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
- // <code>TrilinosWrappers::Vector</code>.
- // 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 <int dim>
+ void BoussinesqFlowProblem<dim>::output_results () const
{
+ if (timestep_number % 10 != 0)
+ return;
+
+ const FESystem<dim> joint_fe (stokes_fe, 1,
+ temperature_fe, 1);
+ DoFHandler<dim> 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<double> 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
+ // <code>joint_fe.system_to_base_index(i).first.first</code>.
+ // 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 <i>n-1</i>
+ // in each time step and then for the
+ // temperature at time level <i>n</i>
+ // 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<unsigned int> local_joint_dof_indices (joint_fe.dofs_per_cell);
+ std::vector<unsigned int> local_stokes_dof_indices (stokes_fe.dofs_per_cell);
+ std::vector<unsigned int> local_temperature_dof_indices (temperature_fe.dofs_per_cell);
- SolverControl solver_control (temperature_matrix.m(),
- 1e-8*temperature_rhs.l2_norm());
- SolverCG<TrilinosWrappers::Vector> 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<dim>::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<temperature_solution.size(); ++i)
- {
- min_temperature = std::min<double> (min_temperature,
- temperature_solution(i));
- max_temperature = std::max<double> (max_temperature,
- temperature_solution(i));
- }
+ for (unsigned int i=0; i<joint_fe.dofs_per_cell; ++i)
+ if (joint_fe.system_to_base_index(i).first.first == 0)
+ {
+ Assert (joint_fe.system_to_base_index(i).second
+ <
+ local_stokes_dof_indices.size(),
+ ExcInternalError());
+ joint_solution(local_joint_dof_indices[i])
+ = stokes_solution(local_stokes_dof_indices[joint_fe.system_to_base_index(i).second]);
+ }
+ else
+ {
+ Assert (joint_fe.system_to_base_index(i).first.first == 1,
+ ExcInternalError());
+ Assert (joint_fe.system_to_base_index(i).second
+ <
+ local_temperature_dof_indices.size(),
+ ExcInternalError());
+ joint_solution(local_joint_dof_indices[i])
+ = temperature_solution(local_temperature_dof_indices[joint_fe.system_to_base_index(i).second]);
+ }
+ }
+ }
- std::cout << " Temperature range: "
- << min_temperature << ' ' << max_temperature
- << std::endl;
+ // 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 <code>dim</code>
+ // 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<std::string> joint_solution_names (dim, "velocity");
+ joint_solution_names.push_back ("p");
+ joint_solution_names.push_back ("T");
+
+ DataOut<dim> data_out;
+
+ data_out.attach_dof_handler (joint_dof_handler);
+
+ std::vector<DataComponentInterpretation::DataComponentInterpretation>
+ data_component_interpretation
+ (dim+2, DataComponentInterpretation::component_is_scalar);
+ for (unsigned int i=0; i<dim; ++i)
+ data_component_interpretation[i]
+ = DataComponentInterpretation::component_is_part_of_vector;
+
+ data_out.add_data_vector (joint_solution, joint_solution_names,
+ DataOut<dim>::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 <int dim>
-void BoussinesqFlowProblem<dim>::output_results () const
-{
- if (timestep_number % 10 != 0)
- return;
-
- const FESystem<dim> joint_fe (stokes_fe, 1,
- temperature_fe, 1);
- DoFHandler<dim> 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<double> 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
- // <code>joint_fe.system_to_base_index(i).first.first</code>.
- // 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
+ // <code>refine_and_coarsen_fixed_fraction</code>
+ // 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 <i>n-1</i>
- // in each time step and then for the
- // temperature at time level <i>n</i>
- // 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 <int dim>
+ void BoussinesqFlowProblem<dim>::refine_mesh (const unsigned int max_grid_level)
{
- std::vector<unsigned int> local_joint_dof_indices (joint_fe.dofs_per_cell);
- std::vector<unsigned int> local_stokes_dof_indices (stokes_fe.dofs_per_cell);
- std::vector<unsigned int> local_temperature_dof_indices (temperature_fe.dofs_per_cell);
-
- typename DoFHandler<dim>::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<joint_fe.dofs_per_cell; ++i)
- if (joint_fe.system_to_base_index(i).first.first == 0)
- {
- Assert (joint_fe.system_to_base_index(i).second
- <
- local_stokes_dof_indices.size(),
- ExcInternalError());
- joint_solution(local_joint_dof_indices[i])
- = stokes_solution(local_stokes_dof_indices[joint_fe.system_to_base_index(i).second]);
- }
- else
- {
- Assert (joint_fe.system_to_base_index(i).first.first == 1,
- ExcInternalError());
- Assert (joint_fe.system_to_base_index(i).second
- <
- local_temperature_dof_indices.size(),
- ExcInternalError());
- joint_solution(local_joint_dof_indices[i])
- = temperature_solution(local_temperature_dof_indices[joint_fe.system_to_base_index(i).second]);
- }
- }
+ Vector<float> estimated_error_per_cell (triangulation.n_active_cells());
+
+ KellyErrorEstimator<dim>::estimate (temperature_dof_handler,
+ QGauss<dim-1>(temperature_degree+1),
+ typename FunctionMap<dim>::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<dim>::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<TrilinosWrappers::Vector> x_temperature (2);
+ x_temperature[0] = temperature_solution;
+ x_temperature[1] = old_temperature_solution;
+ TrilinosWrappers::BlockVector x_stokes = stokes_solution;
+
+ SolutionTransfer<dim,TrilinosWrappers::Vector>
+ temperature_trans(temperature_dof_handler);
+ SolutionTransfer<dim,TrilinosWrappers::BlockVector>
+ 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
+ // <code>setup_dofs</code>
+ // 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<TrilinosWrappers::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 <code>dim</code>
- // 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<std::string> joint_solution_names (dim, "velocity");
- joint_solution_names.push_back ("p");
- joint_solution_names.push_back ("T");
-
- DataOut<dim> data_out;
-
- data_out.attach_dof_handler (joint_dof_handler);
-
- std::vector<DataComponentInterpretation::DataComponentInterpretation>
- data_component_interpretation
- (dim+2, DataComponentInterpretation::component_is_scalar);
- for (unsigned int i=0; i<dim; ++i)
- data_component_interpretation[i]
- = DataComponentInterpretation::component_is_part_of_vector;
-
- data_out.add_data_vector (joint_solution, joint_solution_names,
- DataOut<dim>::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
- // <code>refine_and_coarsen_fixed_fraction</code>
- // 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 <int dim>
-void BoussinesqFlowProblem<dim>::refine_mesh (const unsigned int max_grid_level)
-{
- Vector<float> estimated_error_per_cell (triangulation.n_active_cells());
-
- KellyErrorEstimator<dim>::estimate (temperature_dof_handler,
- QGauss<dim-1>(temperature_degree+1),
- typename FunctionMap<dim>::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<dim>::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<TrilinosWrappers::Vector> x_temperature (2);
- x_temperature[0] = temperature_solution;
- x_temperature[1] = old_temperature_solution;
- TrilinosWrappers::BlockVector x_stokes = stokes_solution;
-
- SolutionTransfer<dim,TrilinosWrappers::Vector>
- temperature_trans(temperature_dof_handler);
- SolutionTransfer<dim,TrilinosWrappers::BlockVector>
- 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
- // <code>setup_dofs</code>
- // 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<TrilinosWrappers::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 <code>dim</code>
- // 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
- // <code>goto</code> statement to
- // jump out of the time loop right
- // after mesh refinement to start all
- // over again on the new mesh
- // beginning at the
- // <code>start_time_iteration</code>
- // label.
- //
- // Before we start, we project the
- // initial values to the grid and
- // obtain the first data for the
- // <code>old_temperature_solution</code>
- // vector. Then, we initialize time
- // step number and time step and
- // start the time loop.
-template <int dim>
-void BoussinesqFlowProblem<dim>::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 <code>dim</code>
+ // 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
+ // <code>goto</code> statement to
+ // jump out of the time loop right
+ // after mesh refinement to start all
+ // over again on the new mesh
+ // beginning at the
+ // <code>start_time_iteration</code>
+ // label.
+ //
+ // Before we start, we project the
+ // initial values to the grid and
+ // obtain the first data for the
+ // <code>old_temperature_solution</code>
+ // vector. Then, we initialize time
+ // step number and time step and
+ // start the time loop.
+ template <int dim>
+ void BoussinesqFlowProblem<dim>::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<dim>(temperature_degree+2),
- EquationData::TemperatureInitialValues<dim>(),
- old_temperature_solution);
+ start_time_iteration:
- timestep_number = 0;
- time_step = old_time_step = 0;
+ VectorTools::project (temperature_dof_handler,
+ temperature_constraints,
+ QGauss<dim>(temperature_degree+2),
+ EquationData::TemperatureInitialValues<dim>(),
+ 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);
+ }
}
{
try
{
+ using namespace dealii;
+ using namespace Step31;
+
deallog.depth_console (0);
Utilities::System::MPI_InitFinalize mpi_initialization (argc, argv);
/* $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 */
#include <vector>
#include <memory>
- // 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 <int dim>
-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 <code>dim+2</code>
- // 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<std::string>
- component_names ()
- {
- std::vector<std::string> 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 <int dim>
+ 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 <code>dim+2</code>
+ // 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<std::string>
+ component_names ()
+ {
+ std::vector<std::string> names (dim, "momentum");
+ names.push_back ("density");
+ names.push_back ("energy_density");
- return names;
- }
+ return names;
+ }
- static
- std::vector<DataComponentInterpretation::DataComponentInterpretation>
- component_interpretation ()
- {
- std::vector<DataComponentInterpretation::DataComponentInterpretation>
+ static
+ std::vector<DataComponentInterpretation::DataComponentInterpretation>
+ component_interpretation ()
+ {
+ std::vector<DataComponentInterpretation::DataComponentInterpretation>
+ 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
- // <code>std::vector@<number@></code> and
- // <code>Vector@<number@></code>. The
- // problem is that the former has an
- // access operator
- // <code>operator[]</code> whereas the
- // latter, for historical reasons, has
- // <code>operator()</code>. 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 <code>v[i]</code> or
- // <code>v(i)</code>, we can use
- // <code>*(v.begin() + i)</code>, i.e. we
- // generate an iterator that points to
- // the <code>i</code>th element, and then
- // dereference it. This works for both
- // kinds of vectors -- not the prettiest
- // solution, but one that works.
- template <typename number, typename InputVector>
- static
- number
- compute_kinetic_energy (const InputVector &W)
- {
- number kinetic_energy = 0;
- for (unsigned int d=0; d<dim; ++d)
- kinetic_energy += *(W.begin()+first_momentum_component+d) *
- *(W.begin()+first_momentum_component+d);
- kinetic_energy *= 1./(2 * *(W.begin() + density_component));
+ // @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
+ // <code>std::vector@<number@></code> and
+ // <code>Vector@<number@></code>. The
+ // problem is that the former has an
+ // access operator
+ // <code>operator[]</code> whereas the
+ // latter, for historical reasons, has
+ // <code>operator()</code>. 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 <code>v[i]</code> or
+ // <code>v(i)</code>, we can use
+ // <code>*(v.begin() + i)</code>, i.e. we
+ // generate an iterator that points to
+ // the <code>i</code>th element, and then
+ // dereference it. This works for both
+ // kinds of vectors -- not the prettiest
+ // solution, but one that works.
+ template <typename number, typename InputVector>
+ static
+ number
+ compute_kinetic_energy (const InputVector &W)
+ {
+ number kinetic_energy = 0;
+ for (unsigned int d=0; d<dim; ++d)
+ kinetic_energy += *(W.begin()+first_momentum_component+d) *
+ *(W.begin()+first_momentum_component+d);
+ kinetic_energy *= 1./(2 * *(W.begin() + density_component));
- return kinetic_energy;
- }
+ return kinetic_energy;
+ }
- template <typename number, typename InputVector>
- static
- number
- compute_pressure (const InputVector &W)
- {
- return ((gas_gamma-1.0) *
- (*(W.begin() + energy_component) -
- compute_kinetic_energy<number>(W)));
- }
+ template <typename number, typename InputVector>
+ static
+ number
+ compute_pressure (const InputVector &W)
+ {
+ return ((gas_gamma-1.0) *
+ (*(W.begin() + energy_component) -
+ compute_kinetic_energy<number>(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
- // <code>dim</code> 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 <typename InputVector, typename number>
- 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
- // <code>dim</code> columns of the
- // matrix that correspond to the
- // momentum terms:
- const number pressure = compute_pressure<number> (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
+ // <code>dim</code> 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 <typename InputVector, typename number>
+ 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
+ // <code>dim</code> columns of the
+ // matrix that correspond to the
+ // momentum terms:
+ const number pressure = compute_pressure<number> (W);
+
+ for (unsigned int d=0; d<dim; ++d)
+ {
+ for (unsigned int e=0; e<dim; ++e)
+ flux[first_momentum_component+d][e]
+ = W[first_momentum_component+d] *
+ W[first_momentum_component+e] /
+ W[density_component];
- for (unsigned int d=0; d<dim; ++d)
- {
- for (unsigned int e=0; e<dim; ++e)
- flux[first_momentum_component+d][e]
- = W[first_momentum_component+d] *
- W[first_momentum_component+e] /
- W[density_component];
+ flux[first_momentum_component+d][d] += pressure;
+ }
- flux[first_momentum_component+d][d] += pressure;
- }
+ // Then the terms for the
+ // density (i.e. mass
+ // conservation), and,
+ // lastly, conservation of
+ // energy:
+ for (unsigned int d=0; d<dim; ++d)
+ flux[density_component][d] = W[first_momentum_component+d];
+
+ for (unsigned int d=0; d<dim; ++d)
+ flux[energy_component][d] = W[first_momentum_component+d] /
+ W[density_component] *
+ (W[energy_component] + pressure);
+ }
- // Then the terms for the
- // density (i.e. mass
- // conservation), and,
- // lastly, conservation of
- // energy:
- for (unsigned int d=0; d<dim; ++d)
- flux[density_component][d] = W[first_momentum_component+d];
- for (unsigned int d=0; d<dim; ++d)
- flux[energy_component][d] = W[first_momentum_component+d] /
- W[density_component] *
- (W[energy_component] + pressure);
- }
+ // @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 <typename InputVector>
+ static
+ void numerical_normal_flux (const Point<dim> &normal,
+ const InputVector &Wplus,
+ const InputVector &Wminus,
+ const double alpha,
+ Sacado::Fad::DFad<double> (&normal_flux)[n_components])
+ {
+ Sacado::Fad::DFad<double> iflux[n_components][dim];
+ Sacado::Fad::DFad<double> 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 <typename InputVector>
- static
- void numerical_normal_flux (const Point<dim> &normal,
- const InputVector &Wplus,
- const InputVector &Wminus,
- const double alpha,
- Sacado::Fad::DFad<double> (&normal_flux)[n_components])
- {
- Sacado::Fad::DFad<double> iflux[n_components][dim];
- Sacado::Fad::DFad<double> oflux[n_components][dim];
+ for (unsigned int di=0; di<n_components; ++di)
+ {
+ normal_flux[di] = 0;
+ for (unsigned int d=0; d<dim; ++d)
+ normal_flux[di] += 0.5*(iflux[di][d] + oflux[di][d]) * normal[d];
- compute_flux_matrix (Wplus, iflux);
- compute_flux_matrix (Wminus, oflux);
+ normal_flux[di] += 0.5*alpha*(Wplus[di] - Wminus[di]);
+ }
+ }
- for (unsigned int di=0; di<n_components; ++di)
- {
- normal_flux[di] = 0;
- for (unsigned int d=0; d<dim; ++d)
- normal_flux[di] += 0.5*(iflux[di][d] + oflux[di][d]) * normal[d];
+ // @sect4{EulerEquations::compute_forcing_vector}
+
+ // In the same way as describing the flux
+ // function $\mathbf F(\mathbf w)$, we
+ // also need to have a way to describe
+ // the right hand side forcing term. As
+ // mentioned in the introduction, we
+ // consider only gravity here, which
+ // leads to the specific form $\mathbf
+ // G(\mathbf w) = \left(
+ // g_1\rho, g_2\rho, g_3\rho, 0,
+ // \rho \mathbf g \cdot \mathbf v
+ // \right)^T$, shown here for
+ // the 3d case. More specifically, we
+ // will consider only $\mathbf
+ // g=(0,0,-1)^T$ in 3d, or $\mathbf
+ // g=(0,-1)^T$ in 2d. This naturally
+ // leads to the following function:
+ template <typename InputVector, typename number>
+ 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<n_components; ++c)
+ switch (c)
+ {
+ case first_momentum_component+dim-1:
+ forcing[c] = gravity * W[density_component];
+ break;
+ case energy_component:
+ forcing[c] = gravity *
+ W[density_component] *
+ W[first_momentum_component+dim-1];
+ break;
+ default:
+ forcing[c] = 0;
+ }
+ }
- // @sect4{EulerEquations::compute_forcing_vector}
-
- // In the same way as describing the flux
- // function $\mathbf F(\mathbf w)$, we
- // also need to have a way to describe
- // the right hand side forcing term. As
- // mentioned in the introduction, we
- // consider only gravity here, which
- // leads to the specific form $\mathbf
- // G(\mathbf w) = \left(
- // g_1\rho, g_2\rho, g_3\rho, 0,
- // \rho \mathbf g \cdot \mathbf v
- // \right)^T$, shown here for
- // the 3d case. More specifically, we
- // will consider only $\mathbf
- // g=(0,0,-1)^T$ in 3d, or $\mathbf
- // g=(0,-1)^T$ in 2d. This naturally
- // leads to the following function:
- template <typename InputVector, typename number>
- static
- void compute_forcing_vector (const InputVector &W,
- number (&forcing)[n_components])
- {
- const double gravity = -1.0;
- for (unsigned int c=0; c<n_components; ++c)
- switch (c)
- {
- case first_momentum_component+dim-1:
- forcing[c] = gravity * W[density_component];
- break;
- case energy_component:
- forcing[c] = gravity *
- W[density_component] *
- W[first_momentum_component+dim-1];
- break;
- default:
- forcing[c] = 0;
- }
- }
+ // @sect4{Dealing with boundary conditions}
+ // Another thing we have to deal with is
+ // boundary conditions. To this end, let
+ // us first define the kinds of boundary
+ // conditions we currently know how to
+ // deal with:
+ enum BoundaryKind
+ {
+ inflow_boundary,
+ outflow_boundary,
+ no_penetration_boundary,
+ pressure_boundary
+ };
- // @sect4{Dealing with boundary conditions}
- // Another thing we have to deal with is
- // boundary conditions. To this end, let
- // us first define the kinds of boundary
- // conditions we currently know how to
- // deal with:
- enum BoundaryKind
- {
- inflow_boundary,
- outflow_boundary,
- no_penetration_boundary,
- pressure_boundary
- };
+ // 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
+ // <code>Wminus</code> will of course be
+ // modified, so it shouldn't be a
+ // <code>const</code> 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
+ // <code>Wminus</code> is of type
+ // <code>Table@<2,Sacado::Fad::DFad@<double@>
+ // @></code>, this being 2d table with
+ // indices representing the quadrature
+ // point and the vector component,
+ // respectively. We call this function
+ // with <code>Wminus[q]</code> 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 <i>accessor</i> 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
+ // <code>const</code>. With no good
+ // solution around at the moment, we'll
+ // go with the pragmatic, even if not
+ // pretty, solution shown here:
+ template <typename DataVector>
+ static
+ void
+ compute_Wminus (const BoundaryKind (&boundary_kind)[n_components],
+ const Point<dim> &normal_vector,
+ const DataVector &Wplus,
+ const Vector<double> &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
- // <code>Wminus</code> will of course be
- // modified, so it shouldn't be a
- // <code>const</code> 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
- // <code>Wminus</code> is of type
- // <code>Table@<2,Sacado::Fad::DFad@<double@>
- // @></code>, this being 2d table with
- // indices representing the quadrature
- // point and the vector component,
- // respectively. We call this function
- // with <code>Wminus[q]</code> 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 <i>accessor</i> 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
- // <code>const</code>. With no good
- // solution around at the moment, we'll
- // go with the pragmatic, even if not
- // pretty, solution shown here:
- template <typename DataVector>
- static
- void
- compute_Wminus (const BoundaryKind (&boundary_kind)[n_components],
- const Point<dim> &normal_vector,
- const DataVector &Wplus,
- const Vector<double> &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<dim; ++d)
+ if (boundary_kind[d] == inflow_boundary)
+ kinetic_energy += boundary_values(d)*boundary_values(d);
+ else
+ kinetic_energy += Wplus[d]*Wplus[d];
+ kinetic_energy *= 1./2./density;
- // 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]);
-
- typename DataVector::value_type kinetic_energy = 0;
- for (unsigned int d=0; d<dim; ++d)
- if (boundary_kind[d] == inflow_boundary)
- kinetic_energy += boundary_values(d)*boundary_values(d);
- else
- kinetic_energy += Wplus[d]*Wplus[d];
- kinetic_energy *= 1./2./density;
-
- Wminus[c] = boundary_values(c) / (gas_gamma-1.0) +
- kinetic_energy;
+ Wminus[c] = boundary_values(c) / (gas_gamma-1.0) +
+ kinetic_energy;
- break;
- }
+ break;
+ }
- 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<double> 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<double> 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
+ // <code>ConservationLaw</code> that will
+ // use all the information we provide
+ // here in the <code>EulerEquation</code>
+ // 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<dim> &dof_handler,
+ const Mapping<dim> &mapping,
+ const Vector<double> &solution,
+ Vector<double> &refinement_indicators)
+ {
+ const unsigned int dofs_per_cell = dof_handler.get_fe().dofs_per_cell;
+ std::vector<unsigned int> dofs (dofs_per_cell);
- default:
- Assert (false, ExcNotImplemented());
+ const QMidpoint<dim> quadrature_formula;
+ const UpdateFlags update_flags = update_gradients;
+ FEValues<dim> fe_v (mapping, dof_handler.get_fe(),
+ quadrature_formula, update_flags);
+
+ std::vector<std::vector<Tensor<1,dim> > >
+ dU (1, std::vector<Tensor<1,dim> >(n_components));
+
+ typename DoFHandler<dim>::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
- // <code>ConservationLaw</code> that will
- // use all the information we provide
- // here in the <code>EulerEquation</code>
- // 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<dim> &dof_handler,
- const Mapping<dim> &mapping,
- const Vector<double> &solution,
- Vector<double> &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<dim>
{
- const unsigned int dofs_per_cell = dof_handler.get_fe().dofs_per_cell;
- std::vector<unsigned int> dofs (dofs_per_cell);
+ public:
+ Postprocessor (const bool do_schlieren_plot);
- const QMidpoint<dim> quadrature_formula;
- const UpdateFlags update_flags = update_gradients;
- FEValues<dim> fe_v (mapping, dof_handler.get_fe(),
- quadrature_formula, update_flags);
+ virtual
+ void
+ compute_derived_quantities_vector (const std::vector<Vector<double> > &uh,
+ const std::vector<std::vector<Tensor<1,dim> > > &duh,
+ const std::vector<std::vector<Tensor<2,dim> > > &dduh,
+ const std::vector<Point<dim> > &normals,
+ const std::vector<Point<dim> > &evaluation_points,
+ std::vector<Vector<double> > &computed_quantities) const;
- std::vector<std::vector<Tensor<1,dim> > >
- dU (1, std::vector<Tensor<1,dim> >(n_components));
+ virtual std::vector<std::string> get_names () const;
- typename DoFHandler<dim>::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<DataComponentInterpretation::DataComponentInterpretation>
+ 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<dim>
- {
- public:
- Postprocessor (const bool do_schlieren_plot);
+ template <int dim>
+ const double EulerEquations<dim>::gas_gamma = 1.4;
- virtual
- void
- compute_derived_quantities_vector (const std::vector<Vector<double> > &uh,
- const std::vector<std::vector<Tensor<1,dim> > > &duh,
- const std::vector<std::vector<Tensor<2,dim> > > &dduh,
- const std::vector<Point<dim> > &normals,
- const std::vector<Point<dim> > &evaluation_points,
- std::vector<Vector<double> > &computed_quantities) const;
- virtual std::vector<std::string> get_names () const;
- virtual
- std::vector<DataComponentInterpretation::DataComponentInterpretation>
- get_data_component_interpretation () const;
+ template <int dim>
+ EulerEquations<dim>::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 <int dim>
+ void
+ EulerEquations<dim>::Postprocessor::
+ compute_derived_quantities_vector (const std::vector<Vector<double> > &uh,
+ const std::vector<std::vector<Tensor<1,dim> > > &duh,
+ const std::vector<std::vector<Tensor<2,dim> > > &/*dduh*/,
+ const std::vector<Point<dim> > &/*normals*/,
+ const std::vector<Point<dim> > &/*evaluation_points*/,
+ std::vector<Vector<double> > &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 <code>duh</code> vector only
+ // contains data if we really need it (the
+ // system knows about this because we say
+ // so in the
+ // <code>get_needed_update_flags()</code>
+ // 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 <int dim>
-const double EulerEquations<dim>::gas_gamma = 1.4;
-
-
-
-template <int dim>
-EulerEquations<dim>::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 <int dim>
-void
-EulerEquations<dim>::Postprocessor::
-compute_derived_quantities_vector (const std::vector<Vector<double> > &uh,
- const std::vector<std::vector<Tensor<1,dim> > > &duh,
- const std::vector<std::vector<Tensor<2,dim> > > &/*dduh*/,
- const std::vector<Point<dim> > &/*normals*/,
- const std::vector<Point<dim> > &/*evaluation_points*/,
- std::vector<Vector<double> > &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 <code>duh</code> vector only
- // contains data if we really need it (the
- // system knows about this because we say
- // so in the
- // <code>get_needed_update_flags()</code>
- // 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
- // <code>dim</code> 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
- // <code>first_momentum_component</code>
- // and <code>density_component</code>
- // information:
- for (unsigned int q=0; q<n_quadrature_points; ++q)
- {
- const double density = uh[q](density_component);
+ Assert (computed_quantities.size() == n_quadrature_points,
+ ExcInternalError());
- for (unsigned int d=0; d<dim; ++d)
- computed_quantities[q](d)
- = uh[q](first_momentum_component+d) / density;
+ Assert (uh[0].size() == n_components,
+ ExcInternalError());
- computed_quantities[q](dim) = compute_pressure<double> (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
+ // <code>dim</code> 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
+ // <code>first_momentum_component</code>
+ // and <code>density_component</code>
+ // information:
+ for (unsigned int q=0; q<n_quadrature_points; ++q)
+ {
+ const double density = uh[q](density_component);
- if (do_schlieren_plot == true)
- computed_quantities[q](dim+1) = duh[q][density_component] *
- duh[q][density_component];
- }
-}
+ for (unsigned int d=0; d<dim; ++d)
+ computed_quantities[q](d)
+ = uh[q](first_momentum_component+d) / density;
+ computed_quantities[q](dim) = compute_pressure<double> (uh[q]);
-template <int dim>
-std::vector<std::string>
-EulerEquations<dim>::Postprocessor::
-get_names () const
-{
- std::vector<std::string> names;
- for (unsigned int d=0; d<dim; ++d)
- names.push_back ("velocity");
- names.push_back ("pressure");
+ if (do_schlieren_plot == true)
+ computed_quantities[q](dim+1) = duh[q][density_component] *
+ duh[q][density_component];
+ }
+ }
- if (do_schlieren_plot == true)
- names.push_back ("schlieren_plot");
- return names;
-}
+ template <int dim>
+ std::vector<std::string>
+ EulerEquations<dim>::Postprocessor::
+ get_names () const
+ {
+ std::vector<std::string> names;
+ for (unsigned int d=0; d<dim; ++d)
+ names.push_back ("velocity");
+ names.push_back ("pressure");
+ if (do_schlieren_plot == true)
+ names.push_back ("schlieren_plot");
+
+ return names;
+ }
-template <int dim>
-std::vector<DataComponentInterpretation::DataComponentInterpretation>
-EulerEquations<dim>::Postprocessor::
-get_data_component_interpretation () const
-{
- std::vector<DataComponentInterpretation::DataComponentInterpretation>
- interpretation (dim,
- DataComponentInterpretation::component_is_part_of_vector);
- interpretation.push_back (DataComponentInterpretation::
- component_is_scalar);
+ template <int dim>
+ std::vector<DataComponentInterpretation::DataComponentInterpretation>
+ EulerEquations<dim>::Postprocessor::
+ get_data_component_interpretation () const
+ {
+ std::vector<DataComponentInterpretation::DataComponentInterpretation>
+ 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 <int dim>
-UpdateFlags
-EulerEquations<dim>::Postprocessor::
-get_needed_update_flags () const
-{
- if (do_schlieren_plot == true)
- return update_values | update_gradients;
- else
- return update_values;
-}
+ template <int dim>
+ UpdateFlags
+ EulerEquations<dim>::Postprocessor::
+ get_needed_update_flags () const
+ {
+ if (do_schlieren_plot == true)
+ return update_values | update_gradients;
+ else
+ return update_values;
+ }
-template <int dim>
-unsigned int
-EulerEquations<dim>::Postprocessor::
-n_output_variables () const
-{
- if (do_schlieren_plot == true)
- return dim+2;
- else
- return dim+1;
-}
+ template <int dim>
+ unsigned int
+ EulerEquations<dim>::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
- // <code>Parameters</code>. 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
- // <code>declare_parameters()</code>
- // and
- // <code>parse_parameters()</code>
- // 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
- // <code>declare_parameters()</code>
- // 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
+ // <code>Parameters</code>. 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
+ // <code>declare_parameters()</code>
+ // and
+ // <code>parse_parameters()</code>
+ // 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
- // <code>declare_parameters()</code>.
- 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
+ // <code>declare_parameters()</code>
+ // 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
+ // <code>declare_parameters()</code>.
+ 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 <quiet|verbose>.");
- prm.declare_entry("method", "gmres",
- Patterns::Selection("gmres|direct"),
- "The kind of solver for the linear system. "
- "Choices are <gmres|direct>.");
- 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 <quiet|verbose>.");
+ prm.declare_entry("method", "gmres",
+ Patterns::Selection("gmres|direct"),
+ "The kind of solver for the linear system. "
+ "Choices are <gmres|direct>.");
+ 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
- // <code>declare_parameters()</code> and
- // <code>parse_parameters()</code>
- // 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 <code>dim+2</code>
- // conserved variables of the Euler
- // system. Similarly, we allow up to
- // <code>max_n_boundaries</code> 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
- // <code>max_n_boundaries</code> 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
- // <code>BoundaryCondition</code>
- // structure: first, an array of size
- // <code>n_components</code> 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 <code>BoundaryCondition</code>
- // 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
- // <code>AllParameters::parse_parameters()</code>
- //
- // For the same reason of having to tell
- // Function objects their vector size at
- // construction time, we have to have a
- // constructor of the
- // <code>AllParameters</code> class that
- // at least initializes the other
- // FunctionParser object, i.e. the one
- // describing initial conditions.
- template <int dim>
- 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
+ // <code>declare_parameters()</code> and
+ // <code>parse_parameters()</code>
+ // 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 <code>dim+2</code>
+ // conserved variables of the Euler
+ // system. Similarly, we allow up to
+ // <code>max_n_boundaries</code> 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
+ // <code>max_n_boundaries</code> 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
+ // <code>BoundaryCondition</code>
+ // structure: first, an array of size
+ // <code>n_components</code> 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 <code>BoundaryCondition</code>
+ // 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
+ // <code>AllParameters::parse_parameters()</code>
+ //
+ // For the same reason of having to tell
+ // Function objects their vector size at
+ // construction time, we have to have a
+ // constructor of the
+ // <code>AllParameters</code> class that
+ // at least initializes the other
+ // FunctionParser object, i.e. the one
+ // describing initial conditions.
+ template <int dim>
+ struct AllParameters : public Solver,
+ public Refinement,
+ public Flux,
+ public Output
+ {
+ static const unsigned int max_n_boundaries = 10;
- struct BoundaryConditions
- {
- typename EulerEquations<dim>::BoundaryKind
- kind[EulerEquations<dim>::n_components];
+ struct BoundaryConditions
+ {
+ typename EulerEquations<dim>::BoundaryKind
+ kind[EulerEquations<dim>::n_components];
- FunctionParser<dim> values;
+ FunctionParser<dim> 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<dim> initial_conditions;
- BoundaryConditions boundary_conditions[max_n_boundaries];
+ FunctionParser<dim> 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 <int dim>
- AllParameters<dim>::BoundaryConditions::BoundaryConditions ()
- :
- values (EulerEquations<dim>::n_components)
- {}
+ template <int dim>
+ AllParameters<dim>::BoundaryConditions::BoundaryConditions ()
+ :
+ values (EulerEquations<dim>::n_components)
+ {}
- template <int dim>
- AllParameters<dim>::AllParameters ()
- :
- initial_conditions (EulerEquations<dim>::n_components)
- {}
+ template <int dim>
+ AllParameters<dim>::AllParameters ()
+ :
+ initial_conditions (EulerEquations<dim>::n_components)
+ {}
- template <int dim>
- void
- AllParameters<dim>::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 <int dim>
+ void
+ AllParameters<dim>::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<max_n_boundaries; ++b)
+ prm.enter_subsection("time stepping");
{
- prm.enter_subsection("boundary_" +
- Utilities::int_to_string(b));
+ 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();
+
+
+ for (unsigned int b=0; b<max_n_boundaries; ++b)
{
- prm.declare_entry("no penetration", "false",
- Patterns::Bool(),
- "whether the named boundary allows gas to "
- "penetrate or is a rigid wall");
+ prm.enter_subsection("boundary_" +
+ Utilities::int_to_string(b));
+ {
+ prm.declare_entry("no penetration", "false",
+ Patterns::Bool(),
+ "whether the named boundary allows gas to "
+ "penetrate or is a rigid wall");
- for (unsigned int di=0; di<EulerEquations<dim>::n_components; ++di)
- {
- prm.declare_entry("w_" + Utilities::int_to_string(di),
- "outflow",
- Patterns::Selection("inflow|outflow|pressure"),
- "<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<EulerEquations<dim>::n_components; ++di)
+ {
+ prm.declare_entry("w_" + Utilities::int_to_string(di),
+ "outflow",
+ Patterns::Selection("inflow|outflow|pressure"),
+ "<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<EulerEquations<dim>::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<EulerEquations<dim>::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 <int dim>
- void
- AllParameters<dim>::parse_parameters (ParameterHandler &prm)
- {
- mesh_filename = prm.get("mesh");
- diffusion_power = prm.get_double("diffusion power");
-
- prm.enter_subsection("time stepping");
+ template <int dim>
+ void
+ AllParameters<dim>::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<max_n_boundaries;
- ++boundary_id)
+ prm.enter_subsection("time stepping");
{
- prm.enter_subsection("boundary_" +
- Utilities::int_to_string(boundary_id));
+ 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();
+
+ for (unsigned int boundary_id=0; boundary_id<max_n_boundaries;
+ ++boundary_id)
{
- std::vector<std::string>
- expressions(EulerEquations<dim>::n_components, "0.0");
+ prm.enter_subsection("boundary_" +
+ Utilities::int_to_string(boundary_id));
+ {
+ std::vector<std::string>
+ expressions(EulerEquations<dim>::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<EulerEquations<dim>::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<dim>::no_penetration_boundary;
- else if (boundary_type == "inflow")
- boundary_conditions[boundary_id].kind[di]
- = EulerEquations<dim>::inflow_boundary;
- else if (boundary_type == "pressure")
- boundary_conditions[boundary_id].kind[di]
- = EulerEquations<dim>::pressure_boundary;
- else if (boundary_type == "outflow")
- boundary_conditions[boundary_id].kind[di]
- = EulerEquations<dim>::outflow_boundary;
- else
- AssertThrow (false, ExcNotImplemented());
-
- expressions[di] = prm.get("w_" + Utilities::int_to_string(di) +
- " value");
- }
+ for (unsigned int di=0; di<EulerEquations<dim>::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<dim>::no_penetration_boundary;
+ else if (boundary_type == "inflow")
+ boundary_conditions[boundary_id].kind[di]
+ = EulerEquations<dim>::inflow_boundary;
+ else if (boundary_type == "pressure")
+ boundary_conditions[boundary_id].kind[di]
+ = EulerEquations<dim>::pressure_boundary;
+ else if (boundary_type == "outflow")
+ boundary_conditions[boundary_id].kind[di]
+ = EulerEquations<dim>::outflow_boundary;
+ else
+ AssertThrow (false, ExcNotImplemented());
+
+ expressions[di] = prm.get("w_" + Utilities::int_to_string(di) +
+ " value");
+ }
- boundary_conditions[boundary_id].values
- .initialize (FunctionParser<dim>::default_variable_names(),
- expressions,
- std::map<std::string, double>());
+ boundary_conditions[boundary_id].values
+ .initialize (FunctionParser<dim>::default_variable_names(),
+ expressions,
+ std::map<std::string, double>());
+ }
+ prm.leave_subsection();
}
- prm.leave_subsection();
+
+ prm.enter_subsection("initial condition");
+ {
+ std::vector<std::string> expressions (EulerEquations<dim>::n_components,
+ "0.0");
+ for (unsigned int di = 0; di < EulerEquations<dim>::n_components; di++)
+ expressions[di] = prm.get("w_" + Utilities::int_to_string(di) +
+ " value");
+ initial_conditions.initialize (FunctionParser<dim>::default_variable_names(),
+ expressions,
+ std::map<std::string, double>());
}
+ prm.leave_subsection();
- prm.enter_subsection("initial condition");
- {
- std::vector<std::string> expressions (EulerEquations<dim>::n_components,
- "0.0");
- for (unsigned int di = 0; di < EulerEquations<dim>::n_components; di++)
- expressions[di] = prm.get("w_" + Utilities::int_to_string(di) +
- " value");
- initial_conditions.initialize (FunctionParser<dim>::default_variable_names(),
- expressions,
- std::map<std::string, double>());
+ 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
- // <code>assemble_system</code>
- // 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 <int dim>
-class ConservationLaw
-{
- public:
- ConservationLaw (const char *input_filename);
- void run ();
-
- private:
- void setup_system ();
-
- void assemble_system ();
- void assemble_cell_term (const FEValues<dim> &fe_v,
- const std::vector<unsigned int> &dofs);
- void assemble_face_term (const unsigned int face_no,
- const FEFaceValuesBase<dim> &fe_v,
- const FEFaceValuesBase<dim> &fe_v_neighbor,
- const std::vector<unsigned int> &dofs,
- const std::vector<unsigned int> &dofs_neighbor,
- const bool external_face,
- const unsigned int boundary_id,
- const double face_diameter);
-
- std::pair<unsigned int, double> solve (Vector<double> &solution);
-
- void compute_refinement_indicators (Vector<double> &indicator) const;
- void refine_grid (const Vector<double> &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<dim> triangulation;
- const MappingQ1<dim> mapping;
-
- const FESystem<dim> fe;
- DoFHandler<dim> dof_handler;
-
- const QGauss<dim> quadrature;
- const QGauss<dim-1> face_quadrature;
-
- // Next come a number of data
- // vectors that correspond to the
- // solution of the previous time
- // step
- // (<code>old_solution</code>),
- // the best guess of the current
- // solution
- // (<code>current_solution</code>;
- // we say <i>guess</i> because
- // the Newton iteration to
- // compute it may not have
- // converged yet, whereas
- // <code>old_solution</code>
- // 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<double> old_solution;
- Vector<double> current_solution;
- Vector<double> predictor;
-
- Vector<double> 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<dim> 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 <int dim>
-ConservationLaw<dim>::ConservationLaw (const char *input_filename)
- :
- mapping (),
- fe (FE_Q<dim>(1), EulerEquations<dim>::n_components),
- dof_handler (triangulation),
- quadrature (2),
- face_quadrature (2),
- verbose_cout (std::cout, false)
-{
- ParameterHandler prm;
- Parameters::AllParameters<dim>::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
+ // <code>assemble_system</code>
+ // 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 <int dim>
+ class ConservationLaw
+ {
+ public:
+ ConservationLaw (const char *input_filename);
+ void run ();
+
+ private:
+ void setup_system ();
+
+ void assemble_system ();
+ void assemble_cell_term (const FEValues<dim> &fe_v,
+ const std::vector<unsigned int> &dofs);
+ void assemble_face_term (const unsigned int face_no,
+ const FEFaceValuesBase<dim> &fe_v,
+ const FEFaceValuesBase<dim> &fe_v_neighbor,
+ const std::vector<unsigned int> &dofs,
+ const std::vector<unsigned int> &dofs_neighbor,
+ const bool external_face,
+ const unsigned int boundary_id,
+ const double face_diameter);
+
+ std::pair<unsigned int, double> solve (Vector<double> &solution);
+
+ void compute_refinement_indicators (Vector<double> &indicator) const;
+ void refine_grid (const Vector<double> &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<dim> triangulation;
+ const MappingQ1<dim> mapping;
+
+ const FESystem<dim> fe;
+ DoFHandler<dim> dof_handler;
+
+ const QGauss<dim> quadrature;
+ const QGauss<dim-1> face_quadrature;
+
+ // Next come a number of data
+ // vectors that correspond to the
+ // solution of the previous time
+ // step
+ // (<code>old_solution</code>),
+ // the best guess of the current
+ // solution
+ // (<code>current_solution</code>;
+ // we say <i>guess</i> because
+ // the Newton iteration to
+ // compute it may not have
+ // converged yet, whereas
+ // <code>old_solution</code>
+ // 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<double> old_solution;
+ Vector<double> current_solution;
+ Vector<double> predictor;
+
+ Vector<double> 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<dim> 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 <int dim>
-void ConservationLaw<dim>::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 <int dim>
+ ConservationLaw<dim>::ConservationLaw (const char *input_filename)
+ :
+ mapping (),
+ fe (FE_Q<dim>(1), EulerEquations<dim>::n_components),
+ dof_handler (triangulation),
+ quadrature (2),
+ face_quadrature (2),
+ verbose_cout (std::cout, false)
+ {
+ ParameterHandler prm;
+ Parameters::AllParameters<dim>::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 <code>JxW</code> 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 <int dim>
-void ConservationLaw<dim>::assemble_system ()
-{
- const unsigned int dofs_per_cell = dof_handler.get_fe().dofs_per_cell;
-
- std::vector<unsigned int> dof_indices (dofs_per_cell);
- std::vector<unsigned int> 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<dim> fe_v (mapping, fe, quadrature,
- update_flags);
- FEFaceValues<dim> fe_v_face (mapping, fe, face_quadrature,
- face_update_flags);
- FESubfaceValues<dim> fe_v_subface (mapping, fe, face_quadrature,
- face_update_flags);
- FEFaceValues<dim> fe_v_face_neighbor (mapping, fe, face_quadrature,
- neighbor_face_update_flags);
- FESubfaceValues<dim> 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<dim>::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
- // <code>assemble_face_terms</code>
- // 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<GeometryInfo<dim>::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<unsigned int>(),
- 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<dim>::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<dim>::cell_iterator
- neighbor = cell->neighbor(face_no);
- Assert(neighbor->level() == cell->level()-1,
- ExcInternalError());
-
- neighbor->get_dof_indices (dof_indices_neighbor);
-
- const std::pair<unsigned int, unsigned int>
- 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 <int dim>
+ void ConservationLaw<dim>::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
- // <code>W_theta</code>, $\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
- // <code>W</code>), the previous time step's
- // solution $W_{n}$ (variable
- // <code>W_old</code>), as well as the linear
- // combination $\theta W_{n+1}^k +
- // (1-\theta)W_n$ that results from choosing
- // different time stepping schemes (variable
- // <code>W_theta</code>).
- //
- // 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
- // <code>EulerEquations::n_components</code>
- // 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
- // <code>std::vector@<std::vector@<T@>
- // @></code> 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 <int dim>
-void
-ConservationLaw<dim>::
-assemble_cell_term (const FEValues<dim> &fe_v,
- const std::vector<unsigned int> &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<double> >
- W (n_q_points, EulerEquations<dim>::n_components);
-
- Table<2,double>
- W_old (n_q_points, EulerEquations<dim>::n_components);
-
- Table<2,Sacado::Fad::DFad<double> >
- W_theta (n_q_points, EulerEquations<dim>::n_components);
-
- Table<3,Sacado::Fad::DFad<double> >
- grad_W (n_q_points, EulerEquations<dim>::n_components, dim);
-
- std::vector<double> 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<Sacado::Fad::DFad<double> > independent_local_dof_values(dofs_per_cell);
- for (unsigned int i=0; i<dofs_per_cell; ++i)
- independent_local_dof_values[i] = current_solution(dof_indices[i]);
-
- // 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.
+ // @sect4{ConservationLaw::assemble_system}
//
- // In order to mark the variables as
- // independent, the following does the
- // trick, marking
- // <code>independent_local_dof_values[i]</code>
- // as the $i$th independent variable out of
- // a total of <code>dofs_per_cell</code>:
- for (unsigned int i=0; i<dofs_per_cell; ++i)
- independent_local_dof_values[i].diff (i, dofs_per_cell);
-
- // After all these declarations, let us
- // actually compute something. First, the
- // values of <code>W</code>,
- // <code>W_old</code>,
- // <code>W_theta</code>, and
- // <code>grad_W</code>, 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
- // <code>old_solution</code> 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_q_points; ++q)
- for (unsigned int c=0; c<EulerEquations<dim>::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 <code>JxW</code> 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 <int dim>
+ void ConservationLaw<dim>::assemble_system ()
+ {
+ const unsigned int dofs_per_cell = dof_handler.get_fe().dofs_per_cell;
+
+ std::vector<unsigned int> dof_indices (dofs_per_cell);
+ std::vector<unsigned int> 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<dim> fe_v (mapping, fe, quadrature,
+ update_flags);
+ FEFaceValues<dim> fe_v_face (mapping, fe, face_quadrature,
+ face_update_flags);
+ FESubfaceValues<dim> fe_v_subface (mapping, fe, face_quadrature,
+ face_update_flags);
+ FEFaceValues<dim> fe_v_face_neighbor (mapping, fe, face_quadrature,
+ neighbor_face_update_flags);
+ FESubfaceValues<dim> 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<dim>::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; d<dim; ++d)
- grad_W[q][c][d] = 0;
- }
+ 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
+ // <code>assemble_face_terms</code>
+ // 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<GeometryInfo<dim>::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<unsigned int>(),
+ true,
+ cell->face(face_no)->boundary_indicator(),
+ cell->face(face_no)->diameter());
+ }
- for (unsigned int q=0; q<n_q_points; ++q)
- for (unsigned int i=0; i<dofs_per_cell; ++i)
- {
- const unsigned int c = fe_v.get_fe().system_to_component_index(i).first;
-
- W[q][c] += independent_local_dof_values[i] *
- fe_v.shape_value_component(i, q, c);
- W_old[q][c] += old_solution(dof_indices[i]) *
- fe_v.shape_value_component(i, q, c);
- W_theta[q][c] += (parameters.theta *
- independent_local_dof_values[i]
- +
- (1-parameters.theta) *
- old_solution(dof_indices[i])) *
- fe_v.shape_value_component(i, q, c);
+ // 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<dim>::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<dim>::cell_iterator
+ neighbor = cell->neighbor(face_no);
+ Assert(neighbor->level() == cell->level()-1,
+ ExcInternalError());
+
+ neighbor->get_dof_indices (dof_indices_neighbor);
+
+ const std::pair<unsigned int, unsigned int>
+ 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<double> FluxMatrix[EulerEquations<dim>::n_components][dim];
- FluxMatrix *flux = new FluxMatrix[n_q_points];
-
- typedef Sacado::Fad::DFad<double> ForcingVector[EulerEquations<dim>::n_components];
- ForcingVector *forcing = new ForcingVector[n_q_points];
-
- for (unsigned int q=0; q<n_q_points; ++q)
- {
- EulerEquations<dim>::compute_flux_matrix (W_theta[q], flux[q]);
- EulerEquations<dim>::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
- // <code>component_i</code> 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
- // <code>right_hand_side</code> vector,
- // we negate this residual.
- for (unsigned int i=0; i<fe_v.dofs_per_cell; ++i)
- {
- Sacado::Fad::DFad<double> 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; point<fe_v.n_quadrature_points; ++point)
- {
- if (parameters.is_stationary == false)
- F_i += 1.0 / parameters.time_step *
- (W[point][component_i] - W_old[point][component_i]) *
- fe_v.shape_value_component(i, point, component_i) *
- fe_v.JxW(point);
+ // 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
+ // <code>W_theta</code>, $\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
+ // <code>W</code>), the previous time step's
+ // solution $W_{n}$ (variable
+ // <code>W_old</code>), as well as the linear
+ // combination $\theta W_{n+1}^k +
+ // (1-\theta)W_n$ that results from choosing
+ // different time stepping schemes (variable
+ // <code>W_theta</code>).
+ //
+ // 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
+ // <code>EulerEquations::n_components</code>
+ // 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
+ // <code>std::vector@<std::vector@<T@>
+ // @></code> 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 <int dim>
+ void
+ ConservationLaw<dim>::
+ assemble_cell_term (const FEValues<dim> &fe_v,
+ const std::vector<unsigned int> &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<dim; d++)
- F_i -= flux[point][component_i][d] *
- fe_v.shape_grad_component(i, point, component_i)[d] *
- fe_v.JxW(point);
+ Table<2,Sacado::Fad::DFad<double> >
+ W (n_q_points, EulerEquations<dim>::n_components);
- for (unsigned int d=0; d<dim; d++)
- F_i += 1.0*std::pow(fe_v.get_cell()->diameter(),
- 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<dim>::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<double> >
+ W_theta (n_q_points, EulerEquations<dim>::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
- // <code>F_i.fastAccessDx(k)</code>,
- // 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<dofs_per_cell; ++k)
- residual_derivatives[k] = F_i.fastAccessDx(k);
- system_matrix.add(dof_indices[i], dof_indices, residual_derivatives);
- right_hand_side(dof_indices[i]) -= F_i.val();
- }
+ Table<3,Sacado::Fad::DFad<double> >
+ grad_W (n_q_points, EulerEquations<dim>::n_components, dim);
- delete[] forcing;
- delete[] flux;
-}
+ std::vector<double> 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<Sacado::Fad::DFad<double> > independent_local_dof_values(dofs_per_cell);
+ for (unsigned int i=0; i<dofs_per_cell; ++i)
+ independent_local_dof_values[i] = current_solution(dof_indices[i]);
- // @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 <int dim>
-void
-ConservationLaw<dim>::assemble_face_term(const unsigned int face_no,
- const FEFaceValuesBase<dim> &fe_v,
- const FEFaceValuesBase<dim> &fe_v_neighbor,
- const std::vector<unsigned int> &dof_indices,
- const std::vector<unsigned int> &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
+ // <code>independent_local_dof_values[i]</code>
+ // as the $i$th independent variable out of
+ // a total of <code>dofs_per_cell</code>:
+ for (unsigned int i=0; i<dofs_per_cell; ++i)
+ independent_local_dof_values[i].diff (i, dofs_per_cell);
+
+ // After all these declarations, let us
+ // actually compute something. First, the
+ // values of <code>W</code>,
+ // <code>W_old</code>,
+ // <code>W_theta</code>, and
+ // <code>grad_W</code>, 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
+ // <code>old_solution</code> 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_q_points; ++q)
+ for (unsigned int c=0; c<EulerEquations<dim>::n_components; ++c)
+ {
+ W[q][c] = 0;
+ W_old[q][c] = 0;
+ W_theta[q][c] = 0;
+ for (unsigned int d=0; d<dim; ++d)
+ grad_W[q][c][d] = 0;
+ }
- std::vector<Sacado::Fad::DFad<double> >
- independent_local_dof_values (dofs_per_cell),
- independent_neighbor_dof_values (external_face == false ?
- dofs_per_cell :
- 0);
+ for (unsigned int q=0; q<n_q_points; ++q)
+ for (unsigned int i=0; i<dofs_per_cell; ++i)
+ {
+ const unsigned int c = fe_v.get_fe().system_to_component_index(i).first;
- const unsigned int n_independent_variables = (external_face == false ?
- 2 * dofs_per_cell :
- dofs_per_cell);
+ W[q][c] += independent_local_dof_values[i] *
+ fe_v.shape_value_component(i, q, c);
+ W_old[q][c] += old_solution(dof_indices[i]) *
+ fe_v.shape_value_component(i, q, c);
+ W_theta[q][c] += (parameters.theta *
+ independent_local_dof_values[i]
+ +
+ (1-parameters.theta) *
+ old_solution(dof_indices[i])) *
+ fe_v.shape_value_component(i, q, c);
+
+ 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];
+ }
- for (unsigned int i = 0; i < dofs_per_cell; i++)
- {
- independent_local_dof_values[i] = current_solution(dof_indices[i]);
- independent_local_dof_values[i].diff(i, n_independent_variables);
- }
- if (external_face == false)
- for (unsigned int i = 0; i < dofs_per_cell; i++)
+ // 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<double> FluxMatrix[EulerEquations<dim>::n_components][dim];
+ FluxMatrix *flux = new FluxMatrix[n_q_points];
+
+ typedef Sacado::Fad::DFad<double> ForcingVector[EulerEquations<dim>::n_components];
+ ForcingVector *forcing = new ForcingVector[n_q_points];
+
+ for (unsigned int q=0; q<n_q_points; ++q)
{
- independent_neighbor_dof_values[i]
- = current_solution(dof_indices_neighbor[i]);
- independent_neighbor_dof_values[i]
- .diff(i+dofs_per_cell, n_independent_variables);
+ EulerEquations<dim>::compute_flux_matrix (W_theta[q], flux[q]);
+ EulerEquations<dim>::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
- // <code>fe_v</code> variable now is of
- // type FEFaceValues or FESubfaceValues:
- Table<2,Sacado::Fad::DFad<double> >
- Wplus (n_q_points, EulerEquations<dim>::n_components),
- Wminus (n_q_points, EulerEquations<dim>::n_components);
-
- for (unsigned int q=0; q<n_q_points; ++q)
- for (unsigned int i=0; i<dofs_per_cell; ++i)
+ // 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
+ // <code>component_i</code> 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
+ // <code>right_hand_side</code> vector,
+ // we negate this residual.
+ for (unsigned int i=0; i<fe_v.dofs_per_cell; ++i)
{
- const unsigned int component_i = fe_v.get_fe().system_to_component_index(i).first;
- Wplus[q][component_i] += (parameters.theta *
- independent_local_dof_values[i]
- +
- (1.0-parameters.theta) *
- old_solution(dof_indices[i])) *
- fe_v.shape_value_component(i, q, component_i);
- }
-
- // 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<n_q_points; ++q)
- for (unsigned int i=0; i<dofs_per_cell; ++i)
- {
- const unsigned int component_i = fe_v_neighbor.get_fe().
- system_to_component_index(i).first;
- Wminus[q][component_i] += (parameters.theta *
- independent_neighbor_dof_values[i]
- +
- (1.0-parameters.theta) *
- old_solution(dof_indices_neighbor[i]))*
- fe_v_neighbor.shape_value_component(i, q, component_i);
- }
- }
- // On the other hand, if this is an
- // external boundary face, then the values
- // of $W^-$ will be either functions of
- // $W^+$, or they will be prescribed,
- // depending on the kind of boundary
- // condition imposed here.
- //
- // To start the evaluation, let us ensure
- // that the boundary id specified for this
- // boundary is one for which we actually
- // have data in the parameters
- // object. Next, we evaluate the function
- // object for the inhomogeneity. This is a
- // bit tricky: a given boundary might have
- // both prescribed and implicit values. If
- // a particular component is not
- // prescribed, the values evaluate to zero
- // and are ignored below.
- //
- // The rest is done by a function that
- // actually knows the specifics of Euler
- // equation boundary conditions. Note that
- // since we are using fad variables here,
- // sensitivities will be updated
- // appropriately, a process that would
- // otherwise be tremendously complicated.
- else
- {
- Assert (boundary_id < Parameters::AllParameters<dim>::max_n_boundaries,
- ExcIndexRange (boundary_id, 0,
- Parameters::AllParameters<dim>::max_n_boundaries));
-
- std::vector<Vector<double> >
- boundary_values(n_q_points, Vector<double>(EulerEquations<dim>::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<dim>::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<double> NormalFlux[EulerEquations<dim>::n_components];
- NormalFlux *normal_fluxes = new NormalFlux[n_q_points];
-
- double alpha;
+ Sacado::Fad::DFad<double> 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<n_q_points; ++q)
- EulerEquations<dim>::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<double> residual_derivatives (dofs_per_cell);
- for (unsigned int i=0; i<fe_v.dofs_per_cell; ++i)
- if (fe_v.get_fe().has_support_on_face(i, face_no) == true)
- {
- Sacado::Fad::DFad<double> 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; point<n_q_points; ++point)
+ for (unsigned int point=0; point<fe_v.n_quadrature_points; ++point)
{
- const unsigned int
- component_i = fe_v.get_fe().system_to_component_index(i).first;
-
- F_i += normal_fluxes[point][component_i] *
+ if (parameters.is_stationary == false)
+ F_i += 1.0 / parameters.time_step *
+ (W[point][component_i] - W_old[point][component_i]) *
+ fe_v.shape_value_component(i, point, component_i) *
+ fe_v.JxW(point);
+
+ for (unsigned int d=0; d<dim; d++)
+ F_i -= flux[point][component_i][d] *
+ fe_v.shape_grad_component(i, point, component_i)[d] *
+ fe_v.JxW(point);
+
+ for (unsigned int d=0; d<dim; d++)
+ F_i += 1.0*std::pow(fe_v.get_cell()->diameter(),
+ 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
+ // <code>F_i.fastAccessDx(k)</code>,
+ // 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<dofs_per_cell; ++k)
residual_derivatives[k] = F_i.fastAccessDx(k);
system_matrix.add(dof_indices[i], dof_indices, residual_derivatives);
-
- if (external_face == false)
- {
- for (unsigned int k=0; k<dofs_per_cell; ++k)
- residual_derivatives[k] = F_i.fastAccessDx(dofs_per_cell+k);
- system_matrix.add (dof_indices[i], dof_indices_neighbor,
- residual_derivatives);
- }
-
right_hand_side(dof_indices[i]) -= F_i.val();
}
- delete[] normal_fluxes;
-}
+ delete[] forcing;
+ delete[] flux;
+ }
- // @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.
-
-template <int dim>
-std::pair<unsigned int, double>
-ConservationLaw<dim>::solve (Vector<double> &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 <int dim>
+ void
+ ConservationLaw<dim>::assemble_face_term(const unsigned int face_no,
+ const FEFaceValuesBase<dim> &fe_v,
+ const FEFaceValuesBase<dim> &fe_v_neighbor,
+ const std::vector<unsigned int> &dof_indices,
+ const std::vector<unsigned int> &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<Sacado::Fad::DFad<double> >
+ independent_local_dof_values (dofs_per_cell),
+ independent_neighbor_dof_values (external_face == false ?
+ dofs_per_cell :
+ 0);
- return std::pair<unsigned int, double> (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<Epetra_CrsMatrix*>
- (&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
+ // <code>fe_v</code> variable now is of
+ // type FEFaceValues or FESubfaceValues:
+ Table<2,Sacado::Fad::DFad<double> >
+ Wplus (n_q_points, EulerEquations<dim>::n_components),
+ Wminus (n_q_points, EulerEquations<dim>::n_components);
+
+ for (unsigned int q=0; q<n_q_points; ++q)
+ for (unsigned int i=0; i<dofs_per_cell; ++i)
+ {
+ const unsigned int component_i = fe_v.get_fe().system_to_component_index(i).first;
+ Wplus[q][component_i] += (parameters.theta *
+ independent_local_dof_values[i]
+ +
+ (1.0-parameters.theta) *
+ old_solution(dof_indices[i])) *
+ fe_v.shape_value_component(i, q, component_i);
+ }
- return std::pair<unsigned int, double> (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<n_q_points; ++q)
+ for (unsigned int i=0; i<dofs_per_cell; ++i)
+ {
+ const unsigned int component_i = fe_v_neighbor.get_fe().
+ system_to_component_index(i).first;
+ Wminus[q][component_i] += (parameters.theta *
+ independent_neighbor_dof_values[i]
+ +
+ (1.0-parameters.theta) *
+ old_solution(dof_indices_neighbor[i]))*
+ fe_v_neighbor.shape_value_component(i, q, component_i);
+ }
+ }
+ // On the other hand, if this is an
+ // external boundary face, then the values
+ // of $W^-$ will be either functions of
+ // $W^+$, or they will be prescribed,
+ // depending on the kind of boundary
+ // condition imposed here.
+ //
+ // To start the evaluation, let us ensure
+ // that the boundary id specified for this
+ // boundary is one for which we actually
+ // have data in the parameters
+ // object. Next, we evaluate the function
+ // object for the inhomogeneity. This is a
+ // bit tricky: a given boundary might have
+ // both prescribed and implicit values. If
+ // a particular component is not
+ // prescribed, the values evaluate to zero
+ // and are ignored below.
+ //
+ // The rest is done by a function that
+ // actually knows the specifics of Euler
+ // equation boundary conditions. Note that
+ // since we are using fad variables here,
+ // sensitivities will be updated
+ // appropriately, a process that would
+ // otherwise be tremendously complicated.
+ else
+ {
+ Assert (boundary_id < Parameters::AllParameters<dim>::max_n_boundaries,
+ ExcIndexRange (boundary_id, 0,
+ Parameters::AllParameters<dim>::max_n_boundaries));
+
+ std::vector<Vector<double> >
+ boundary_values(n_q_points, Vector<double>(EulerEquations<dim>::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<dim>::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<unsigned int, double> (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<double> NormalFlux[EulerEquations<dim>::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 <code>EulerEquation</code>
- // class would know about this, and so we
- // simply defer to the respective function
- // we've implemented there:
-template <int dim>
-void
-ConservationLaw<dim>::
-compute_refinement_indicators (Vector<double> &refinement_indicators) const
-{
- EulerEquations<dim>::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<n_q_points; ++q)
+ EulerEquations<dim>::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<double> residual_derivatives (dofs_per_cell);
+ for (unsigned int i=0; i<fe_v.dofs_per_cell; ++i)
+ if (fe_v.get_fe().has_support_on_face(i, face_no) == true)
+ {
+ Sacado::Fad::DFad<double> F_i = 0;
+ for (unsigned int point=0; point<n_q_points; ++point)
+ {
+ const unsigned int
+ component_i = fe_v.get_fe().system_to_component_index(i).first;
- // @sect4{ConservationLaw::refine_grid}
+ F_i += normal_fluxes[point][component_i] *
+ fe_v.shape_value_component(i, point, component_i) *
+ fe_v.JxW(point);
+ }
- // 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 <int dim>
-void
-ConservationLaw<dim>::refine_grid (const Vector<double> &refinement_indicators)
-{
- typename DoFHandler<dim>::active_cell_iterator
- cell = dof_handler.begin_active(),
- endc = dof_handler.end();
+ for (unsigned int k=0; k<dofs_per_cell; ++k)
+ residual_derivatives[k] = F_i.fastAccessDx(k);
+ system_matrix.add(dof_indices[i], dof_indices, residual_derivatives);
- for (unsigned int cell_no=0; cell!=endc; ++cell, ++cell_no)
- {
- cell->clear_coarsen_flag();
- cell->clear_refine_flag();
+ if (external_face == false)
+ {
+ for (unsigned int k=0; k<dofs_per_cell; ++k)
+ residual_derivatives[k] = F_i.fastAccessDx(dofs_per_cell+k);
+ system_matrix.add (dof_indices[i], dof_indices_neighbor,
+ residual_derivatives);
+ }
- 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();
- }
+ 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<Vector<double> > transfer_in;
- std::vector<Vector<double> > 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<dim> soltrans(dof_handler);
- soltrans.prepare_for_coarsening_and_refinement(transfer_in);
+ template <int dim>
+ std::pair<unsigned int, double>
+ ConservationLaw<dim>::solve (Vector<double> &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<unsigned int, double> (solver_control.last_step(),
+ solver_control.last_value());
+ }
- {
- Vector<double> new_old_solution(1);
- Vector<double> 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<Epetra_CrsMatrix*>
+ (&system_matrix.trilinos_matrix()));
+
+ solver.Iterate(parameters.max_iterations, parameters.linear_residual);
+
+ return std::pair<unsigned int, double> (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<unsigned int, double> (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 <code>EulerEquation</code>
+ // class would know about this, and so we
+ // simply defer to the respective function
+ // we've implemented there:
+ template <int dim>
+ void
+ ConservationLaw<dim>::
+ compute_refinement_indicators (Vector<double> &refinement_indicators) const
+ {
+ EulerEquations<dim>::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 <int dim>
-void ConservationLaw<dim>::output_results () const
-{
- typename EulerEquations<dim>::Postprocessor
- postprocessor (parameters.schlieren_plot);
+ // @sect4{ConservationLaw::refine_grid}
- DataOut<dim> 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 <int dim>
+ void
+ ConservationLaw<dim>::refine_grid (const Vector<double> &refinement_indicators)
+ {
+ typename DoFHandler<dim>::active_cell_iterator
+ cell = dof_handler.begin_active(),
+ endc = dof_handler.end();
- data_out.add_data_vector (current_solution,
- EulerEquations<dim>::component_names (),
- DataOut<dim>::type_dof_data,
- EulerEquations<dim>::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<Vector<double> > transfer_in;
+ std::vector<Vector<double> > 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<dim> 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<double> new_old_solution(1);
+ Vector<double> 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 <int dim>
-void ConservationLaw<dim>::run ()
-{
- {
- GridIn<dim> 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<parameters.shock_levels; ++i)
- {
- Vector<double> 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 <int dim>
+ void ConservationLaw<dim>::output_results () const
+ {
+ typename EulerEquations<dim>::Postprocessor
+ postprocessor (parameters.schlieren_plot);
- compute_refinement_indicators(refinement_indicators);
- refine_grid(refinement_indicators);
+ DataOut<dim> data_out;
+ data_out.attach_dof_handler (dof_handler);
- setup_system();
+ data_out.add_data_vector (current_solution,
+ EulerEquations<dim>::component_names (),
+ DataOut<dim>::type_dof_data,
+ EulerEquations<dim>::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<double> 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
- // <code>main()</code> 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 <code>if
- // (!(nonlin_iter @<= 10)) throw
- // ExcMessage ("No convergence in
- // nonlinear solver");</code>. 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<unsigned int, double> 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 <int dim>
+ void ConservationLaw<dim>::run ()
+ {
+ {
+ GridIn<dim> 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<parameters.shock_levels; ++i)
{
Vector<double> 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<double> 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
+ // <code>main()</code> 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 <code>if
+ // (!(nonlin_iter @<= 10)) throw
+ // ExcMessage ("No convergence in
+ // nonlinear solver");</code>. 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<unsigned int, double> 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<double> 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()}
// 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 ();
}
// $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
// 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 <int dim>
- double single_layer(const Point<dim> &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 <int dim>
- Point<dim> double_layer(const Point<dim> &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<dim>();
- }
- }
-}
+ template <int dim>
+ double single_layer(const Point<dim> &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 <int dim>
-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 <int dim>
+ Point<dim> double_layer(const Point<dim> &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<dim>();
+ }
+ }
+ }
- 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<dim-1> & get_singular_quadrature(
- const typename DoFHandler<dim-1, dim>::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<dim-1, dim> tria;
- FE_Q<dim-1,dim> fe;
- DoFHandler<dim-1,dim> dh;
- MappingQ<dim-1, dim> 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<double> system_matrix;
- Vector<double> 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<double> phi;
- Vector<double> 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<dim> wind;
- Functions::ParsedFunction<dim> exact_solution;
-
- unsigned int singular_quadrature_order;
- std_cxx1x::shared_ptr<Quadrature<dim-1> > 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 <int dim>
-BEMProblem<dim>::BEMProblem(const unsigned int fe_degree,
- const unsigned int mapping_degree)
- :
- fe(fe_degree),
- dh(tria),
- mapping(mapping_degree, true),
- wind(dim)
-{}
-
-
-template <int dim>
-void BEMProblem<dim>::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 <int dim>
+ 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<dim-1> & get_singular_quadrature(
+ const typename DoFHandler<dim-1, dim>::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<dim-1, dim> tria;
+ FE_Q<dim-1,dim> fe;
+ DoFHandler<dim-1,dim> dh;
+ MappingQ<dim-1, dim> 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<double> system_matrix;
+ Vector<double> 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<double> phi;
+ Vector<double> 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<dim> wind;
+ Functions::ParsedFunction<dim> exact_solution;
+
+ unsigned int singular_quadrature_order;
+ std_cxx1x::shared_ptr<Quadrature<dim-1> > 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 <int dim>
+ BEMProblem<dim>::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 <int dim>
+ void BEMProblem<dim>::read_parameters (const std::string &filename)
{
- quadrature =
- std_cxx1x::shared_ptr<Quadrature<dim-1> >
- (new QuadratureSelector<dim-1> (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<Quadrature<dim-1> >
+ (new QuadratureSelector<dim-1> (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 <int dim>
-void BEMProblem<dim>::read_domain()
-{
- static const Point<dim> center = Point<dim>();
- static const HyperBallBoundary<dim-1, dim> 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<dim-1, dim> 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 <int dim>
+ void BEMProblem<dim>::read_domain()
+ {
+ static const Point<dim> center = Point<dim>();
+ static const HyperBallBoundary<dim-1, dim> 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 <int dim>
-void BEMProblem<dim>::refine_and_resize()
-{
- tria.refine_global(1);
+ GridIn<dim-1, dim> 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 <int dim>
+ void BEMProblem<dim>::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 <int dim>
-void BEMProblem<dim>::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<dim-1,dim> 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<unsigned int> local_dof_indices(fe.dofs_per_cell);
-
- std::vector<Vector<double> > cell_wind(n_q_points, Vector<double>(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<double> 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<Point<dim> > support_points(dh.n_dofs());
- DoFTools::map_dofs_to_support_points<dim-1, dim>( 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<dim-1,dim>::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<Point<dim> > &q_points = fe_v.get_quadrature_points();
- const std::vector<Point<dim> > &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<dh.n_dofs() ; ++i)
- {
+ system_rhs.reinit(n_dofs);
+ phi.reinit(n_dofs);
+ alpha.reinit(n_dofs);
+ }
- local_matrix_row_i = 0;
- bool is_singular = false;
- unsigned int singular_index = numbers::invalid_unsigned_int;
+ // @sect4{BEMProblem::assemble_system}
- for(unsigned int j=0; j<fe.dofs_per_cell; ++j)
- if(local_dof_indices[j] == i)
- {
- singular_index = j;
- is_singular = true;
- break;
- }
-
- // We then perform the
- // integral. If the index $i$
- // is not one of the local
- // degrees of freedom, we
- // simply have to add the
- // single layer terms to the
- // right hand side, and the
- // double layer terms to the
- // matrix:
- if(is_singular == false)
- {
- for(unsigned int q=0; q<n_q_points; ++q)
- {
- normal_wind = 0;
- for(unsigned int d=0; d<dim; ++d)
- normal_wind += normals[q][d]*cell_wind[q](d);
+ // The following is the main function
+ // of this program, assembling the
+ // matrix that corresponds to the
+ // boundary integral equation.
+ template <int dim>
+ void BEMProblem<dim>::assemble_system()
+ {
- const Point<dim> 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<dim-1,dim> 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<unsigned int> local_dof_indices(fe.dofs_per_cell);
+
+ std::vector<Vector<double> > cell_wind(n_q_points, Vector<double>(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<double> 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<Point<dim> > support_points(dh.n_dofs());
+ DoFTools::map_dofs_to_support_points<dim-1, dim>( 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<dim-1,dim>::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<Point<dim> > &q_points = fe_v.get_quadrature_points();
+ const std::vector<Point<dim> > &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<dh.n_dofs() ; ++i)
+ {
- system_rhs(i) += ( LaplaceKernel::single_layer(R) *
- normal_wind *
- fe_v.JxW(q) );
+ local_matrix_row_i = 0;
- for(unsigned int j=0; j<fe.dofs_per_cell; ++j)
+ bool is_singular = false;
+ unsigned int singular_index = numbers::invalid_unsigned_int;
- local_matrix_row_i(j) -= ( ( LaplaceKernel::double_layer(R) *
- normals[q] ) *
- fe_v.shape_value(j,q) *
- fe_v.JxW(q) );
+ for (unsigned int j=0; j<fe.dofs_per_cell; ++j)
+ if (local_dof_indices[j] == i)
+ {
+ singular_index = j;
+ is_singular = true;
+ break;
}
- } else {
- // Now we treat the more
- // delicate case. If we
- // are here, this means
- // that the cell that
- // runs on the $j$ index
- // contains
- // support_point[i]. In
- // this case both the
- // single and the double
- // layer potential are
- // singular, and they
- // require special
- // treatment.
- //
- // Whenever the
- // integration is
- // performed with the
- // singularity inside the
- // given cell, then a
- // special quadrature
- // formula is used that
- // allows one to
- // integrate arbitrary
- // functions against a
- // singular weight on the
- // reference cell.
- //
- // The correct quadrature
- // formula is selected by
- // the
- // get_singular_quadrature
- // function, which is
- // explained in detail below.
- Assert(singular_index != numbers::invalid_unsigned_int,
- ExcInternalError());
-
- const Quadrature<dim-1> & singular_quadrature =
- get_singular_quadrature(cell, singular_index);
-
- FEValues<dim-1,dim> 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<Vector<double> > singular_cell_wind( singular_quadrature.size(),
- Vector<double>(dim) );
-
- const std::vector<Point<dim> > &singular_normals = fe_v_singular.get_cell_normal_vectors();
- const std::vector<Point<dim> > &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<singular_quadrature.size(); ++q)
+
+ // We then perform the
+ // integral. If the index $i$
+ // is not one of the local
+ // degrees of freedom, we
+ // simply have to add the
+ // single layer terms to the
+ // right hand side, and the
+ // double layer terms to the
+ // matrix:
+ if (is_singular == false)
{
- const Point<dim> R = singular_q_points[q] - support_points[i];
- double normal_wind = 0;
- for(unsigned int d=0; d<dim; ++d)
- normal_wind += (singular_cell_wind[q](d)*
- singular_normals[q][d]);
+ for (unsigned int q=0; q<n_q_points; ++q)
+ {
+ normal_wind = 0;
+ for (unsigned int d=0; d<dim; ++d)
+ normal_wind += normals[q][d]*cell_wind[q](d);
+
+ const Point<dim> 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<fe.dofs_per_cell; ++j)
+
+ local_matrix_row_i(j) -= ( ( LaplaceKernel::double_layer(R) *
+ normals[q] ) *
+ fe_v.shape_value(j,q) *
+ fe_v.JxW(q) );
+ }
+ } else {
+ // Now we treat the more
+ // delicate case. If we
+ // are here, this means
+ // that the cell that
+ // runs on the $j$ index
+ // contains
+ // support_point[i]. In
+ // this case both the
+ // single and the double
+ // layer potential are
+ // singular, and they
+ // require special
+ // treatment.
+ //
+ // Whenever the
+ // integration is
+ // performed with the
+ // singularity inside the
+ // given cell, then a
+ // special quadrature
+ // formula is used that
+ // allows one to
+ // integrate arbitrary
+ // functions against a
+ // singular weight on the
+ // reference cell.
+ //
+ // The correct quadrature
+ // formula is selected by
+ // the
+ // get_singular_quadrature
+ // function, which is
+ // explained in detail below.
+ Assert(singular_index != numbers::invalid_unsigned_int,
+ ExcInternalError());
+
+ const Quadrature<dim-1> & singular_quadrature =
+ get_singular_quadrature(cell, singular_index);
+
+ FEValues<dim-1,dim> 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<Vector<double> > singular_cell_wind( singular_quadrature.size(),
+ Vector<double>(dim) );
+
+ const std::vector<Point<dim> > &singular_normals = fe_v_singular.get_cell_normal_vectors();
+ const std::vector<Point<dim> > &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<singular_quadrature.size(); ++q)
+ {
+ const Point<dim> R = singular_q_points[q] - support_points[i];
+ double normal_wind = 0;
+ for (unsigned int d=0; d<dim; ++d)
+ normal_wind += (singular_cell_wind[q](d)*
+ singular_normals[q][d]);
- system_rhs(i) += ( LaplaceKernel::single_layer(R) *
- normal_wind *
- fe_v_singular.JxW(q) );
+ system_rhs(i) += ( LaplaceKernel::single_layer(R) *
+ normal_wind *
+ fe_v_singular.JxW(q) );
- for(unsigned int j=0; j<fe.dofs_per_cell; ++j) {
+ for (unsigned int j=0; j<fe.dofs_per_cell; ++j) {
local_matrix_row_i(j) -= (( LaplaceKernel::double_layer(R) *
singular_normals[q]) *
fe_v_singular.shape_value(j,q) *
fe_v_singular.JxW(q) );
+ }
}
- }
- }
+ }
- // Finally, we need to add
- // the contributions of the
- // current cell to the
- // global matrix.
- for(unsigned int j=0; j<fe.dofs_per_cell; ++j)
+ // Finally, we need to add
+ // the contributions of the
+ // current cell to the
+ // global matrix.
+ for (unsigned int j=0; j<fe.dofs_per_cell; ++j)
system_matrix(i,local_dof_indices[j])
- += local_matrix_row_i(j);
- }
- }
+ += local_matrix_row_i(j);
+ }
+ }
- // The second part of the integral
- // operator is the term
- // $\alpha(\mathbf{x}_i)
- // \phi_j(\mathbf{x}_i)$. Since we
- // use a collocation scheme,
- // $\phi_j(\mathbf{x}_i)=\delta_{ij}$
- // and the corresponding matrix is
- // a diagonal one with entries
- // equal to $\alpha(\mathbf{x}_i)$.
-
- // One quick way to compute this
- // diagonal matrix of the solid
- // angles, is to use the Neumann
- // matrix itself. It is enough to
- // multiply the matrix with a
- // vector of elements all equal to
- // -1, to get the diagonal matrix
- // of the alpha angles, or solid
- // angles (see the formula in the
- // introduction for this). The
- // result is then added back onto
- // the system matrix object to
- // yield the final form of the
- // matrix:
- Vector<double> ones(dh.n_dofs());
- ones.add(-1.);
-
- system_matrix.vmult(alpha, ones);
- alpha.add(1);
- for(unsigned int i = 0; i<dh.n_dofs(); ++i)
+ // The second part of the integral
+ // operator is the term
+ // $\alpha(\mathbf{x}_i)
+ // \phi_j(\mathbf{x}_i)$. Since we
+ // use a collocation scheme,
+ // $\phi_j(\mathbf{x}_i)=\delta_{ij}$
+ // and the corresponding matrix is
+ // a diagonal one with entries
+ // equal to $\alpha(\mathbf{x}_i)$.
+
+ // One quick way to compute this
+ // diagonal matrix of the solid
+ // angles, is to use the Neumann
+ // matrix itself. It is enough to
+ // multiply the matrix with a
+ // vector of elements all equal to
+ // -1, to get the diagonal matrix
+ // of the alpha angles, or solid
+ // angles (see the formula in the
+ // introduction for this). The
+ // result is then added back onto
+ // the system matrix object to
+ // yield the final form of the
+ // matrix:
+ Vector<double> ones(dh.n_dofs());
+ ones.add(-1.);
+
+ system_matrix.vmult(alpha, ones);
+ alpha.add(1);
+ for (unsigned int i = 0; i<dh.n_dofs(); ++i)
system_matrix(i,i) += alpha(i);
-}
+ }
- // @sect4{BEMProblem::solve_system}
+ // @sect4{BEMProblem::solve_system}
- // The next function simply solves
- // the linear system.
-template <int dim>
-void BEMProblem<dim>::solve_system()
-{
+ // The next function simply solves
+ // the linear system.
+ template <int dim>
+ void BEMProblem<dim>::solve_system()
+ {
SolverGMRES<Vector<double> > 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 <int dim>
-void BEMProblem<dim>::compute_errors(const unsigned int cycle)
-{
- Vector<float> 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<double> 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 <int dim>
+ void BEMProblem<dim>::compute_errors(const unsigned int cycle)
+ {
+ Vector<float> 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<double> 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<QGaussOneOverR<2> > quadratures;
- if(quadratures.size() == 0)
- for(unsigned int i=0; i<fe.dofs_per_cell; ++i)
- quadratures.push_back(QGaussOneOverR<2>(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<QGaussOneOverR<2> > quadratures;
+ if (quadratures.size() == 0)
+ for (unsigned int i=0; i<fe.dofs_per_cell; ++i)
+ quadratures.push_back(QGaussOneOverR<2>(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 <int dim>
-void BEMProblem<dim>::compute_exterior_solution()
-{
- Triangulation<dim> external_tria;
- GridGenerator::hyper_cube(external_tria, -2, 2);
+ // @sect4{BEMProblem::compute_exterior_solution}
- FE_Q<dim> external_fe(1);
- DoFHandler<dim> external_dh (external_tria);
- Vector<double> 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 <int dim>
+ void BEMProblem<dim>::compute_exterior_solution()
+ {
+ Triangulation<dim> 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<dim> external_fe(1);
+ DoFHandler<dim> external_dh (external_tria);
+ Vector<double> external_phi;
- typename DoFHandler<dim-1,dim>::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<dim-1,dim>::active_cell_iterator
+ cell = dh.begin_active(),
+ endc = dh.end();
- FEValues<dim-1,dim> 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<dim-1,dim> fe_v(mapping, fe, *quadrature,
+ update_values |
+ update_cell_normal_vectors |
+ update_quadrature_points |
+ update_JxW_values);
- std::vector<unsigned int> dofs(fe.dofs_per_cell);
+ const unsigned int n_q_points = fe_v.n_quadrature_points;
- std::vector<double> local_phi(n_q_points);
- std::vector<double> normal_wind(n_q_points);
- std::vector<Vector<double> > local_wind(n_q_points, Vector<double>(dim) );
+ std::vector<unsigned int> dofs(fe.dofs_per_cell);
- std::vector<Point<dim> > external_support_points(external_dh.n_dofs());
- DoFTools::map_dofs_to_support_points<dim>(StaticMappingQ1<dim>::mapping,
- external_dh, external_support_points);
+ std::vector<double> local_phi(n_q_points);
+ std::vector<double> normal_wind(n_q_points);
+ std::vector<Vector<double> > local_wind(n_q_points, Vector<double>(dim) );
- for(cell = dh.begin_active(); cell != endc; ++cell)
- {
- fe_v.reinit(cell);
+ std::vector<Point<dim> > external_support_points(external_dh.n_dofs());
+ DoFTools::map_dofs_to_support_points<dim>(StaticMappingQ1<dim>::mapping,
+ external_dh, external_support_points);
- const std::vector<Point<dim> > &q_points = fe_v.get_quadrature_points();
- const std::vector<Point<dim> > &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<Point<dim> > &q_points = fe_v.get_quadrature_points();
+ const std::vector<Point<dim> > &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<n_q_points; ++q){
- normal_wind[q] = 0;
- for(unsigned int d=0; d<dim; ++d)
- normal_wind[q] += normals[q][d]*local_wind[q](d);
- }
+ wind.vector_value_list(q_points, local_wind);
- for(unsigned int i=0; i<external_dh.n_dofs(); ++i)
- for(unsigned int q=0; q<n_q_points; ++q)
- {
+ for (unsigned int q=0; q<n_q_points; ++q){
+ normal_wind[q] = 0;
+ for (unsigned int d=0; d<dim; ++d)
+ normal_wind[q] += normals[q][d]*local_wind[q](d);
+ }
- const Point<dim> R = q_points[q] - external_support_points[i];
+ for (unsigned int i=0; i<external_dh.n_dofs(); ++i)
+ for (unsigned int q=0; q<n_q_points; ++q)
+ {
- external_phi(i) += ( ( LaplaceKernel::single_layer(R) *
- normal_wind[q]
- +
- (LaplaceKernel::double_layer(R) *
- normals[q] ) *
- local_phi[q] ) *
- fe_v.JxW(q) );
- }
- }
+ const Point<dim> R = q_points[q] - external_support_points[i];
- DataOut<dim> 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<dim> 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 <int dim>
-void BEMProblem<dim>::output_results(const unsigned int cycle)
-{
- DataOut<dim-1, DoFHandler<dim-1, dim> > dataout;
+ // Outputting the results of our
+ // computations is a rather
+ // mechanical tasks. All the
+ // components of this function have
+ // been discussed before.
+ template <int dim>
+ void BEMProblem<dim>::output_results(const unsigned int cycle)
+ {
+ DataOut<dim-1, DoFHandler<dim-1, dim> > 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<dim-1, DoFHandler<dim-1, dim> >::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<dim-1, DoFHandler<dim-1, dim> >::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 <int dim>
-void BEMProblem<dim>::run()
-{
+ // This is the main function. It
+ // should be self explanatory in its
+ // briefness:
+ template <int dim>
+ void BEMProblem<dim>::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<n_cycles; ++cycle)
- {
- refine_and_resize();
- assemble_system();
- solve_system();
- compute_errors(cycle);
- output_results(cycle);
- }
+ for (unsigned int cycle=0; cycle<n_cycles; ++cycle)
+ {
+ refine_and_resize();
+ assemble_system();
+ solve_system();
+ compute_errors(cycle);
+ output_results(cycle);
+ }
- if(extend_solution == true)
- compute_exterior_solution();
+ if (extend_solution == true)
+ compute_exterior_solution();
+ }
}
{
try
{
- unsigned int degree = 1;
- unsigned int mapping_degree = 1;
+ using namespace dealii;
+ using namespace Step34;
+
+ const unsigned int degree = 1;
+ const unsigned int mapping_degree = 1;
deallog.depth_console (3);
BEMProblem<2> laplace_problem_2d(degree, mapping_degree);
/* $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 */
#include <cmath>
#include <iostream>
- // 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
- // <code>enum</code>. 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
+ // <code>enum</code>. 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 <int dim>
+ class MultiComponentFunction: public Function<dim>
{
- 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 <int dim>
+ MultiComponentFunction<dim>::
+ MultiComponentFunction (const double initial_time)
+ :
+ Function<dim> (1, initial_time), comp(0)
+ {}
+
+
+ template <int dim>
+ void MultiComponentFunction<dim>::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<dim, ExcIndexRange (d, 0, dim));
+ comp = d;
}
- prm.leave_subsection();
- verbose = prm.get_bool ("verbose");
- output_interval = prm.get_integer ("output_interval");
- }
-}
+ // With this class defined, we
+ // declare classes that describe
+ // the boundary conditions for
+ // velocity and pressure:
+ template <int dim>
+ class Velocity : public MultiComponentFunction<dim>
+ {
+ public:
+ Velocity (const double initial_time = 0.0);
+ virtual double value (const Point<dim> &p,
+ const unsigned int component = 0) const;
+ virtual void value_list (const std::vector< Point<dim> > &points,
+ std::vector<double> &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 <int dim>
- class MultiComponentFunction: public Function<dim>
- {
- public:
- MultiComponentFunction (const double initial_time = 0.);
- void set_component (const unsigned int d);
- protected:
- unsigned int comp;
- };
+ template <int dim>
+ Velocity<dim>::Velocity (const double initial_time)
+ :
+ MultiComponentFunction<dim> (initial_time)
+ {}
- template <int dim>
- MultiComponentFunction<dim>::
- MultiComponentFunction (const double initial_time)
- :
- Function<dim> (1, initial_time), comp(0)
- {}
+ template <int dim>
+ void Velocity<dim>::value_list (const std::vector<Point<dim> > &points,
+ std::vector<double> &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<n_points; ++i)
+ values[i] = Velocity<dim>::value (points[i]);
+ }
- template <int dim>
- void MultiComponentFunction<dim>::set_component(const unsigned int d)
- {
- Assert (d<dim, ExcIndexRange (d, 0, dim));
- comp = d;
- }
+ template <int dim>
+ double Velocity<dim>::value (const Point<dim> &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 <int dim>
- class Velocity : public MultiComponentFunction<dim>
- {
- public:
- Velocity (const double initial_time = 0.0);
- virtual double value (const Point<dim> &p,
- const unsigned int component = 0) const;
- virtual void value_list (const std::vector< Point<dim> > &points,
- std::vector<double> &values,
- const unsigned int component = 0) const;
- };
+ template <int dim>
+ class Pressure: public Function<dim>
+ {
+ public:
+ Pressure (const double initial_time = 0.0);
+ virtual double value (const Point<dim> &p,
+ const unsigned int component = 0) const;
- template <int dim>
- Velocity<dim>::Velocity (const double initial_time)
- :
- MultiComponentFunction<dim> (initial_time)
- {}
+ virtual void value_list (const std::vector< Point<dim> > &points,
+ std::vector<double> &values,
+ const unsigned int component = 0) const;
+ };
+ template <int dim>
+ Pressure<dim>::Pressure (const double initial_time)
+ :
+ Function<dim> (1, initial_time)
+ {}
- template <int dim>
- void Velocity<dim>::value_list (const std::vector<Point<dim> > &points,
- std::vector<double> &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<n_points; ++i)
- values[i] = Velocity<dim>::value (points[i]);
- }
+ template <int dim>
+ double Pressure<dim>::value (const Point<dim> &p,
+ const unsigned int) const
+ {
+ return 25.-p(0);
+ }
- template <int dim>
- double Velocity<dim>::value (const Point<dim> &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 <int dim>
+ void Pressure<dim>::value_list (const std::vector<Point<dim> > &points,
+ std::vector<double> &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<n_points; ++i)
+ values[i] = Pressure<dim>::value (points[i]);
+ }
}
+ // @sect3{The <code>NavierStokesProjection</code> 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 <int dim>
- class Pressure: public Function<dim>
+ class NavierStokesProjection
{
public:
- Pressure (const double initial_time = 0.0);
-
- virtual double value (const Point<dim> &p,
- const unsigned int component = 0) const;
+ NavierStokesProjection (const RunTimeParameters::Data_Storage &data);
- virtual void value_list (const std::vector< Point<dim> > &points,
- std::vector<double> &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<dim> vel_exact;
+ std::map<unsigned int, double> boundary_values;
+ std::vector<unsigned char> boundary_indicators;
+
+ Triangulation<dim> triangulation;
+
+ FE_Q<dim> fe_velocity;
+ FE_Q<dim> fe_pressure;
+
+ DoFHandler<dim> dof_handler_velocity;
+ DoFHandler<dim> dof_handler_pressure;
+
+ QGauss<dim> quadrature_pressure;
+ QGauss<dim> quadrature_velocity;
+
+ SparsityPattern sparsity_pattern_velocity;
+ SparsityPattern sparsity_pattern_pressure;
+ SparsityPattern sparsity_pattern_pres_vel;
+
+ SparseMatrix<double> vel_Laplace_plus_Mass;
+ SparseMatrix<double> vel_it_matrix[dim];
+ SparseMatrix<double> vel_Mass;
+ SparseMatrix<double> vel_Laplace;
+ SparseMatrix<double> vel_Advection;
+ SparseMatrix<double> pres_Laplace;
+ SparseMatrix<double> pres_Mass;
+ SparseMatrix<double> pres_Diff[dim];
+ SparseMatrix<double> pres_iterative;
+
+ Vector<double> pres_n;
+ Vector<double> pres_n_minus_1;
+ Vector<double> phi_n;
+ Vector<double> phi_n_minus_1;
+ Vector<double> u_n[dim];
+ Vector<double> u_n_minus_1[dim];
+ Vector<double> u_star[dim];
+ Vector<double> force[dim];
+ Vector<double> v_tmp;
+ Vector<double> pres_tmp;
+ Vector<double> rot_u;
+
+ SparseILU<double> prec_velocity[dim];
+ SparseILU<double> 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<dim>::active_cell_iterator,
+ typename DoFHandler<dim>::active_cell_iterator
+ > IteratorTuple;
+
+ typedef SynchronousIterators<IteratorTuple> IteratorPair;
+
+ void initialize_gradient_operator();
+
+ struct InitGradPerTaskData
+ {
+ unsigned int d;
+ unsigned int vel_dpc;
+ unsigned int pres_dpc;
+ FullMatrix<double> local_grad;
+ std::vector<unsigned int> vel_local_dof_indices;
+ std::vector<unsigned int> 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<dim> fe_val_vel;
+ FEValues<dim> fe_val_pres;
+ InitGradScratchData (const FE_Q<dim> &fe_v,
+ const FE_Q<dim> &fe_p,
+ const QGauss<dim> &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<double> local_advection;
+ std::vector<unsigned int> 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<dim> > u_star_local;
+ std::vector< Tensor<1,dim> > grad_u_star;
+ std::vector<double> u_star_tmp;
+ FEValues<dim> fe_val;
+ AdvectionScratchData (const FE_Q<dim> &fe,
+ const QGauss<dim> &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<dim>::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 <int dim>
- Pressure<dim>::Pressure (const double initial_time)
- :
- Function<dim> (1, initial_time)
- {}
- template <int dim>
- double Pressure<dim>::value (const Point<dim> &p,
- const unsigned int) const
- {
- return 25.-p(0);
- }
+ // @sect4{ <code>NavierStokesProjection::NavierStokesProjection</code> }
+ // In the constructor, we just read
+ // all the data from the
+ // <code>Data_Storage</code> 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 <int dim>
- void Pressure<dim>::value_list (const std::vector<Point<dim> > &points,
- std::vector<double> &values,
- const unsigned int) const
+ NavierStokesProjection<dim>::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<n_points; ++i)
- values[i] = Pressure<dim>::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{ <code>NavierStokesProjection::create_triangulation_and_dofs</code> }
- // @sect3{The <code>NavierStokesProjection</code> 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 <int dim>
+ void
+ NavierStokesProjection<dim>::
+ create_triangulation_and_dofs (const unsigned int n_refines)
+ {
+ GridIn<dim> 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 <int dim>
-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<dim> vel_exact;
- std::map<unsigned int, double> boundary_values;
- std::vector<unsigned char> boundary_indicators;
-
- Triangulation<dim> triangulation;
-
- FE_Q<dim> fe_velocity;
- FE_Q<dim> fe_pressure;
-
- DoFHandler<dim> dof_handler_velocity;
- DoFHandler<dim> dof_handler_pressure;
-
- QGauss<dim> quadrature_pressure;
- QGauss<dim> quadrature_velocity;
-
- SparsityPattern sparsity_pattern_velocity;
- SparsityPattern sparsity_pattern_pressure;
- SparsityPattern sparsity_pattern_pres_vel;
-
- SparseMatrix<double> vel_Laplace_plus_Mass;
- SparseMatrix<double> vel_it_matrix[dim];
- SparseMatrix<double> vel_Mass;
- SparseMatrix<double> vel_Laplace;
- SparseMatrix<double> vel_Advection;
- SparseMatrix<double> pres_Laplace;
- SparseMatrix<double> pres_Mass;
- SparseMatrix<double> pres_Diff[dim];
- SparseMatrix<double> pres_iterative;
-
- Vector<double> pres_n;
- Vector<double> pres_n_minus_1;
- Vector<double> phi_n;
- Vector<double> phi_n_minus_1;
- Vector<double> u_n[dim];
- Vector<double> u_n_minus_1[dim];
- Vector<double> u_star[dim];
- Vector<double> force[dim];
- Vector<double> v_tmp;
- Vector<double> pres_tmp;
- Vector<double> rot_u;
-
- SparseILU<double> prec_velocity[dim];
- SparseILU<double> 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<dim>::active_cell_iterator,
- typename DoFHandler<dim>::active_cell_iterator
- > IteratorTuple;
-
- typedef SynchronousIterators<IteratorTuple> IteratorPair;
-
- void initialize_gradient_operator();
-
- struct InitGradPerTaskData
{
- unsigned int d;
- unsigned int vel_dpc;
- unsigned int pres_dpc;
- FullMatrix<double> local_grad;
- std::vector<unsigned int> vel_local_dof_indices;
- std::vector<unsigned int> 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<dim> fe_val_vel;
- FEValues<dim> fe_val_pres;
- InitGradScratchData (const FE_Q<dim> &fe_v,
- const FE_Q<dim> &fe_p,
- const QGauss<dim> &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<double> local_advection;
- std::vector<unsigned int> 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<dim> > u_star_local;
- std::vector< Tensor<1,dim> > grad_u_star;
- std::vector<double> u_star_tmp;
- FEValues<dim> fe_val;
- AdvectionScratchData (const FE_Q<dim> &fe,
- const QGauss<dim> &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<dim; ++d)
+ {
+ u_n[d].reinit (dof_handler_velocity.n_dofs());
+ u_n_minus_1[d].reinit (dof_handler_velocity.n_dofs());
+ u_star[d].reinit (dof_handler_velocity.n_dofs());
+ force[d].reinit (dof_handler_velocity.n_dofs());
+ }
+ v_tmp.reinit (dof_handler_velocity.n_dofs());
+ rot_u.reinit (dof_handler_velocity.n_dofs());
- void assemble_one_cell_of_advection (const typename DoFHandler<dim>::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{ <code>NavierStokesProjection::NavierStokesProjection</code> }
-
- // In the constructor, we just read
- // all the data from the
- // <code>Data_Storage</code> 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 <int dim>
-NavierStokesProjection<dim>::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{ <code>NavierStokesProjection::initialize</code> }
- // @sect4{ <code>NavierStokesProjection::create_triangulation_and_dofs</code> }
-
- // 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 <int dim>
-void
-NavierStokesProjection<dim>::
-create_triangulation_and_dofs (const unsigned int n_refines)
-{
- GridIn<dim> grid_in;
- grid_in.attach_triangulation (triangulation);
-
+ // This method creates the constant
+ // matrices and loads the initial
+ // data
+ template <int dim>
+ void
+ NavierStokesProjection<dim>::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<dim> 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<dim; ++d)
+ {
+ vel_exact.set_time (t_0);
+ vel_exact.set_component(d);
+ VectorTools::interpolate (dof_handler_velocity, ZeroFunction<dim>(), u_n_minus_1[d]);
+ vel_exact.advance_time (dt);
+ VectorTools::interpolate (dof_handler_velocity, ZeroFunction<dim>(), 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<dim; ++d)
- {
- u_n[d].reinit (dof_handler_velocity.n_dofs());
- u_n_minus_1[d].reinit (dof_handler_velocity.n_dofs());
- u_star[d].reinit (dof_handler_velocity.n_dofs());
- force[d].reinit (dof_handler_velocity.n_dofs());
- }
- v_tmp.reinit (dof_handler_velocity.n_dofs());
- rot_u.reinit (dof_handler_velocity.n_dofs());
- 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
- << std::endl;
-}
-
-
- // @sect4{ <code>NavierStokesProjection::initialize</code> }
-
- // This method creates the constant
- // matrices and loads the initial
- // data
-template <int dim>
-void
-NavierStokesProjection<dim>::initialize()
-{
- 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<dim> 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<dim; ++d)
- {
- vel_exact.set_time (t_0);
- vel_exact.set_component(d);
- VectorTools::interpolate (dof_handler_velocity, ZeroFunction<dim>(), u_n_minus_1[d]);
- vel_exact.advance_time (dt);
- VectorTools::interpolate (dof_handler_velocity, ZeroFunction<dim>(), u_n[d]);
- }
-}
-
-
- // @sect4{ The <code>NavierStokesProjection::initialize_*_matrices</code> methods }
-
- // In this set of methods we initialize the
- // sparsity patterns, the constraints (if
- // any) and assemble the matrices that do not
- // depend on the timestep
- // <code>dt</code>. Note that for the Laplace
- // and mass matrices, we can use functions in
- // the library that do this. Because the
- // expensive operations of this function --
- // creating the two matrices -- are entirely
- // independent, we could in principle mark
- // them as tasks that can be worked on in
- // %parallel using the Threads::new_task
- // functions. We won't do that here since
- // these functions internally already are
- // parallelized, and in particular because
- // the current function is only called once
- // per program run and so does not incur a
- // cost in each time step. The necessary
- // modifications would be quite
- // straightforward, however.
-template <int dim>
-void
-NavierStokesProjection<dim>::initialize_velocity_matrices()
-{
- sparsity_pattern_velocity.reinit (dof_handler_velocity.n_dofs(),
- dof_handler_velocity.n_dofs(),
- dof_handler_velocity.max_couplings_between_dofs());
- DoFTools::make_sparsity_pattern (dof_handler_velocity,
- sparsity_pattern_velocity);
- sparsity_pattern_velocity.compress();
-
- vel_Laplace_plus_Mass.reinit (sparsity_pattern_velocity);
- for (unsigned int d=0; d<dim; ++d)
- vel_it_matrix[d].reinit (sparsity_pattern_velocity);
- vel_Mass.reinit (sparsity_pattern_velocity);
- vel_Laplace.reinit (sparsity_pattern_velocity);
- vel_Advection.reinit (sparsity_pattern_velocity);
-
- MatrixCreator::create_mass_matrix (dof_handler_velocity,
- quadrature_velocity,
- vel_Mass);
- MatrixCreator::create_laplace_matrix (dof_handler_velocity,
- quadrature_velocity,
- vel_Laplace);
-}
+ // @sect4{ The <code>NavierStokesProjection::initialize_*_matrices</code> methods }
+
+ // In this set of methods we initialize the
+ // sparsity patterns, the constraints (if
+ // any) and assemble the matrices that do not
+ // depend on the timestep
+ // <code>dt</code>. Note that for the Laplace
+ // and mass matrices, we can use functions in
+ // the library that do this. Because the
+ // expensive operations of this function --
+ // creating the two matrices -- are entirely
+ // independent, we could in principle mark
+ // them as tasks that can be worked on in
+ // %parallel using the Threads::new_task
+ // functions. We won't do that here since
+ // these functions internally already are
+ // parallelized, and in particular because
+ // the current function is only called once
+ // per program run and so does not incur a
+ // cost in each time step. The necessary
+ // modifications would be quite
+ // straightforward, however.
+ template <int dim>
+ void
+ NavierStokesProjection<dim>::initialize_velocity_matrices()
+ {
+ sparsity_pattern_velocity.reinit (dof_handler_velocity.n_dofs(),
+ dof_handler_velocity.n_dofs(),
+ dof_handler_velocity.max_couplings_between_dofs());
+ DoFTools::make_sparsity_pattern (dof_handler_velocity,
+ sparsity_pattern_velocity);
+ sparsity_pattern_velocity.compress();
+
+ vel_Laplace_plus_Mass.reinit (sparsity_pattern_velocity);
+ for (unsigned int d=0; d<dim; ++d)
+ vel_it_matrix[d].reinit (sparsity_pattern_velocity);
+ vel_Mass.reinit (sparsity_pattern_velocity);
+ vel_Laplace.reinit (sparsity_pattern_velocity);
+ vel_Advection.reinit (sparsity_pattern_velocity);
+
+ MatrixCreator::create_mass_matrix (dof_handler_velocity,
+ quadrature_velocity,
+ vel_Mass);
+ MatrixCreator::create_laplace_matrix (dof_handler_velocity,
+ quadrature_velocity,
+ vel_Laplace);
+ }
- // The initialization of the matrices
- // that act on the pressure space is similar
- // to the ones that act on the velocity space.
-template <int dim>
-void
-NavierStokesProjection<dim>::initialize_pressure_matrices()
-{
- sparsity_pattern_pressure.reinit (dof_handler_pressure.n_dofs(), dof_handler_pressure.n_dofs(),
- dof_handler_pressure.max_couplings_between_dofs());
- DoFTools::make_sparsity_pattern (dof_handler_pressure, sparsity_pattern_pressure);
-
- sparsity_pattern_pressure.compress();
-
- pres_Laplace.reinit (sparsity_pattern_pressure);
- pres_iterative.reinit (sparsity_pattern_pressure);
- pres_Mass.reinit (sparsity_pattern_pressure);
-
- MatrixCreator::create_laplace_matrix (dof_handler_pressure,
- quadrature_pressure,
- pres_Laplace);
- MatrixCreator::create_mass_matrix (dof_handler_pressure,
- quadrature_pressure,
- pres_Mass);
-}
+ // The initialization of the matrices
+ // that act on the pressure space is similar
+ // to the ones that act on the velocity space.
+ template <int dim>
+ void
+ NavierStokesProjection<dim>::initialize_pressure_matrices()
+ {
+ sparsity_pattern_pressure.reinit (dof_handler_pressure.n_dofs(), dof_handler_pressure.n_dofs(),
+ dof_handler_pressure.max_couplings_between_dofs());
+ DoFTools::make_sparsity_pattern (dof_handler_pressure, sparsity_pattern_pressure);
+
+ sparsity_pattern_pressure.compress();
+
+ pres_Laplace.reinit (sparsity_pattern_pressure);
+ pres_iterative.reinit (sparsity_pattern_pressure);
+ pres_Mass.reinit (sparsity_pattern_pressure);
+
+ MatrixCreator::create_laplace_matrix (dof_handler_pressure,
+ quadrature_pressure,
+ pres_Laplace);
+ MatrixCreator::create_mass_matrix (dof_handler_pressure,
+ quadrature_pressure,
+ pres_Mass);
+ }
- // For the gradient operator, we
- // start by initializing the sparsity
- // pattern and compressing it. It is
- // important to notice here that the
- // gradient operator acts from the
- // pressure space into the velocity
- // space, so we have to deal with two
- // different finite element
- // spaces. To keep the loops
- // synchronized, we use the
- // <code>typedef</code>'s that we
- // have defined before, namely
- // <code>PairedIterators</code> and
- // <code>IteratorPair</code>.
-template <int dim>
-void
-NavierStokesProjection<dim>::initialize_gradient_operator()
-{
- sparsity_pattern_pres_vel.reinit (dof_handler_velocity.n_dofs(),
- dof_handler_pressure.n_dofs(),
- dof_handler_velocity.max_couplings_between_dofs());
- DoFTools::make_sparsity_pattern (dof_handler_velocity,
- dof_handler_pressure,
- sparsity_pattern_pres_vel);
- sparsity_pattern_pres_vel.compress();
-
- InitGradPerTaskData per_task_data (0, fe_velocity.dofs_per_cell,
- fe_pressure.dofs_per_cell);
- InitGradScratchData scratch_data (fe_velocity,
- fe_pressure,
- quadrature_velocity,
- update_gradients | update_JxW_values,
- update_values);
-
- for (unsigned int d=0; d<dim; ++d)
- {
- pres_Diff[d].reinit (sparsity_pattern_pres_vel);
- per_task_data.d = d;
- WorkStream::run (IteratorPair (IteratorTuple (dof_handler_velocity.begin_active(),
- dof_handler_pressure.begin_active()
- )
- ),
- IteratorPair (IteratorTuple (dof_handler_velocity.end(),
- dof_handler_pressure.end()
- )
- ),
- *this,
- &NavierStokesProjection<dim>::assemble_one_cell_of_gradient,
- &NavierStokesProjection<dim>::copy_gradient_local_to_global,
- scratch_data,
- per_task_data
- );
- }
-}
+ // For the gradient operator, we
+ // start by initializing the sparsity
+ // pattern and compressing it. It is
+ // important to notice here that the
+ // gradient operator acts from the
+ // pressure space into the velocity
+ // space, so we have to deal with two
+ // different finite element
+ // spaces. To keep the loops
+ // synchronized, we use the
+ // <code>typedef</code>'s that we
+ // have defined before, namely
+ // <code>PairedIterators</code> and
+ // <code>IteratorPair</code>.
+ template <int dim>
+ void
+ NavierStokesProjection<dim>::initialize_gradient_operator()
+ {
+ sparsity_pattern_pres_vel.reinit (dof_handler_velocity.n_dofs(),
+ dof_handler_pressure.n_dofs(),
+ dof_handler_velocity.max_couplings_between_dofs());
+ DoFTools::make_sparsity_pattern (dof_handler_velocity,
+ dof_handler_pressure,
+ sparsity_pattern_pres_vel);
+ sparsity_pattern_pres_vel.compress();
+
+ InitGradPerTaskData per_task_data (0, fe_velocity.dofs_per_cell,
+ fe_pressure.dofs_per_cell);
+ InitGradScratchData scratch_data (fe_velocity,
+ fe_pressure,
+ quadrature_velocity,
+ update_gradients | update_JxW_values,
+ update_values);
+
+ for (unsigned int d=0; d<dim; ++d)
+ {
+ pres_Diff[d].reinit (sparsity_pattern_pres_vel);
+ per_task_data.d = d;
+ WorkStream::run (IteratorPair (IteratorTuple (dof_handler_velocity.begin_active(),
+ dof_handler_pressure.begin_active()
+ )
+ ),
+ IteratorPair (IteratorTuple (dof_handler_velocity.end(),
+ dof_handler_pressure.end()
+ )
+ ),
+ *this,
+ &NavierStokesProjection<dim>::assemble_one_cell_of_gradient,
+ &NavierStokesProjection<dim>::copy_gradient_local_to_global,
+ scratch_data,
+ per_task_data
+ );
+ }
+ }
-template <int dim>
-void
-NavierStokesProjection<dim>::
-assemble_one_cell_of_gradient (const IteratorPair &SI,
- InitGradScratchData &scratch,
- InitGradPerTaskData &data)
-{
- scratch.fe_val_vel.reinit (std_cxx1x::get<0> (SI.iterators));
- scratch.fe_val_pres.reinit (std_cxx1x::get<1> (SI.iterators));
+ template <int dim>
+ void
+ NavierStokesProjection<dim>::
+ assemble_one_cell_of_gradient (const IteratorPair &SI,
+ InitGradScratchData &scratch,
+ InitGradPerTaskData &data)
+ {
+ scratch.fe_val_vel.reinit (std_cxx1x::get<0> (SI.iterators));
+ scratch.fe_val_pres.reinit (std_cxx1x::get<1> (SI.iterators));
- std_cxx1x::get<0> (SI.iterators)->get_dof_indices (data.vel_local_dof_indices);
- std_cxx1x::get<1> (SI.iterators)->get_dof_indices (data.pres_local_dof_indices);
+ std_cxx1x::get<0> (SI.iterators)->get_dof_indices (data.vel_local_dof_indices);
+ std_cxx1x::get<1> (SI.iterators)->get_dof_indices (data.pres_local_dof_indices);
- data.local_grad = 0.;
- for (unsigned int q=0; q<scratch.nqp; ++q)
- {
- for (unsigned int i=0; i<data.vel_dpc; ++i)
- for (unsigned int j=0; j<data.pres_dpc; ++j)
- data.local_grad (i, j) += -scratch.fe_val_vel.JxW(q) *
- scratch.fe_val_vel.shape_grad (i, q)[data.d] *
- scratch.fe_val_pres.shape_value (j, q);
- }
-}
+ data.local_grad = 0.;
+ for (unsigned int q=0; q<scratch.nqp; ++q)
+ {
+ for (unsigned int i=0; i<data.vel_dpc; ++i)
+ for (unsigned int j=0; j<data.pres_dpc; ++j)
+ data.local_grad (i, j) += -scratch.fe_val_vel.JxW(q) *
+ scratch.fe_val_vel.shape_grad (i, q)[data.d] *
+ scratch.fe_val_pres.shape_value (j, q);
+ }
+ }
-template <int dim>
-void
-NavierStokesProjection<dim>::
-copy_gradient_local_to_global(const InitGradPerTaskData &data)
-{
- for (unsigned int i=0; i<data.vel_dpc; ++i)
- for (unsigned int j=0; j<data.pres_dpc; ++j)
- pres_Diff[data.d].add (data.vel_local_dof_indices[i], data.pres_local_dof_indices[j],
- data.local_grad (i, j) );
-}
+ template <int dim>
+ void
+ NavierStokesProjection<dim>::
+ copy_gradient_local_to_global(const InitGradPerTaskData &data)
+ {
+ for (unsigned int i=0; i<data.vel_dpc; ++i)
+ for (unsigned int j=0; j<data.pres_dpc; ++j)
+ pres_Diff[data.d].add (data.vel_local_dof_indices[i], data.pres_local_dof_indices[j],
+ data.local_grad (i, j) );
+ }
- // @sect4{ <code>NavierStokesProjection::run</code> }
-
- // This is the time marching
- // function, which starting at
- // <code>t_0</code> advances in time
- // using the projection method with
- // time step <code>dt</code> until
- // <code>T</code>.
- //
- // Its second parameter, <code>verbose</code>
- // 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 <int dim>
-void
-NavierStokesProjection<dim>::run (const bool verbose,
- const unsigned int output_interval)
-{
- ConditionalOStream verbose_cout (std::cout, verbose);
+ // @sect4{ <code>NavierStokesProjection::run</code> }
+
+ // This is the time marching
+ // function, which starting at
+ // <code>t_0</code> advances in time
+ // using the projection method with
+ // time step <code>dt</code> until
+ // <code>T</code>.
+ //
+ // Its second parameter, <code>verbose</code>
+ // 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 <int dim>
+ void
+ NavierStokesProjection<dim>::run (const bool verbose,
+ const unsigned int output_interval)
+ {
+ ConditionalOStream verbose_cout (std::cout, verbose);
- const unsigned int n_steps = static_cast<unsigned int>((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<unsigned int>((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 <int dim>
-void
-NavierStokesProjection<dim>::interpolate_velocity()
-{
- for (unsigned int d=0; d<dim; ++d)
- u_star[d].equ (2., u_n[d], -1, u_n_minus_1[d]);
-}
+ template <int dim>
+ void
+ NavierStokesProjection<dim>::interpolate_velocity()
+ {
+ for (unsigned int d=0; d<dim; ++d)
+ u_star[d].equ (2., u_n[d], -1, u_n_minus_1[d]);
+ }
- // @sect4{<code>NavierStokesProjection::diffusion_step</code>}
-
- // 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
- // <code>dim</code> 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 <int dim>
-void
-NavierStokesProjection<dim>::diffusion_step (const bool reinit_prec)
-{
- pres_tmp.equ (-1., pres_n, -4./3., phi_n, 1./3., phi_n_minus_1);
+ // @sect4{<code>NavierStokesProjection::diffusion_step</code>}
+
+ // 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
+ // <code>dim</code> 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 <int dim>
+ void
+ NavierStokesProjection<dim>::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<dim; ++d)
- {
- force[d] = 0.;
- v_tmp.equ (2./dt,u_n[d],-.5/dt,u_n_minus_1[d]);
- vel_Mass.vmult_add (force[d], v_tmp);
-
- pres_Diff[d].vmult_add (force[d], pres_tmp);
- u_n_minus_1[d] = u_n[d];
-
- vel_it_matrix[d].copy_from (vel_Laplace_plus_Mass);
- vel_it_matrix[d].add (1., vel_Advection);
-
- vel_exact.set_component(d);
- boundary_values.clear();
- for (std::vector<unsigned char>::const_iterator
- boundaries = boundary_indicators.begin();
- boundaries != boundary_indicators.end();
- ++boundaries)
- {
- switch (*boundaries)
- {
- case 1:
- VectorTools::
- interpolate_boundary_values (dof_handler_velocity,
- *boundaries,
- ZeroFunction<dim>(),
- 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<dim; ++d)
+ {
+ force[d] = 0.;
+ v_tmp.equ (2./dt,u_n[d],-.5/dt,u_n_minus_1[d]);
+ vel_Mass.vmult_add (force[d], v_tmp);
+
+ pres_Diff[d].vmult_add (force[d], pres_tmp);
+ u_n_minus_1[d] = u_n[d];
+
+ vel_it_matrix[d].copy_from (vel_Laplace_plus_Mass);
+ vel_it_matrix[d].add (1., vel_Advection);
+
+ vel_exact.set_component(d);
+ boundary_values.clear();
+ for (std::vector<unsigned char>::const_iterator
+ boundaries = boundary_indicators.begin();
+ boundaries != boundary_indicators.end();
+ ++boundaries)
+ {
+ switch (*boundaries)
+ {
+ case 1:
VectorTools::
interpolate_boundary_values (dof_handler_velocity,
*boundaries,
ZeroFunction<dim>(),
boundary_values);
- break;
- case 4:
- VectorTools::
- interpolate_boundary_values (dof_handler_velocity,
- *boundaries,
- ZeroFunction<dim>(),
- 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<dim>(),
+ boundary_values);
+ break;
+ case 4:
+ VectorTools::
+ interpolate_boundary_values (dof_handler_velocity,
+ *boundaries,
+ ZeroFunction<dim>(),
+ boundary_values);
+ break;
+ default:
+ Assert (false, ExcNotImplemented());
+ }
+ }
+ MatrixTools::apply_boundary_values (boundary_values,
+ vel_it_matrix[d],
+ u_n[d],
+ force[d]);
+ }
- Threads::TaskGroup<void> tasks;
- for(unsigned int d=0; d<dim; ++d)
- {
- if (reinit_prec)
- prec_velocity[d].initialize (vel_it_matrix[d],
- SparseILU<double>::
- AdditionalData (vel_diag_strength,
- vel_off_diagonals));
- tasks += Threads::new_task (&NavierStokesProjection<dim>::
- diffusion_component_solve,
- *this, d);
- }
- tasks.join_all();
-}
+ Threads::TaskGroup<void> tasks;
+ for(unsigned int d=0; d<dim; ++d)
+ {
+ if (reinit_prec)
+ prec_velocity[d].initialize (vel_it_matrix[d],
+ SparseILU<double>::
+ AdditionalData (vel_diag_strength,
+ vel_off_diagonals));
+ tasks += Threads::new_task (&NavierStokesProjection<dim>::
+ diffusion_component_solve,
+ *this, d);
+ }
+ tasks.join_all();
+ }
-template <int dim>
-void
-NavierStokesProjection<dim>::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 <int dim>
+ void
+ NavierStokesProjection<dim>::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 <code>NavierStokesProjection::assemble_advection_term</code> method and related}
+ // @sect4{ The <code>NavierStokesProjection::assemble_advection_term</code> 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 <int dim>
-void
-NavierStokesProjection<dim>::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<dim>::assemble_one_cell_of_advection,
- &NavierStokesProjection<dim>::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 <int dim>
+ void
+ NavierStokesProjection<dim>::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<dim>::assemble_one_cell_of_advection,
+ &NavierStokesProjection<dim>::copy_advection_local_to_global,
+ scratch,
+ data);
+ }
-template <int dim>
-void
-NavierStokesProjection<dim>::
-assemble_one_cell_of_advection(const typename DoFHandler<dim>::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<dim; ++d)
- {
- scratch.fe_val.get_function_values (u_star[d], scratch.u_star_tmp);
- for (unsigned int q=0; q<scratch.nqp; ++q)
- scratch.u_star_local[q](d) = scratch.u_star_tmp[q];
- }
+ template <int dim>
+ void
+ NavierStokesProjection<dim>::
+ assemble_one_cell_of_advection(const typename DoFHandler<dim>::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<dim; ++d)
+ {
+ scratch.fe_val.get_function_values (u_star[d], scratch.u_star_tmp);
+ for (unsigned int q=0; q<scratch.nqp; ++q)
+ scratch.u_star_local[q](d) = scratch.u_star_tmp[q];
+ }
- for (unsigned int d=0; d<dim; ++d)
- {
- scratch.fe_val.get_function_gradients (u_star[d], scratch.grad_u_star);
- for (unsigned int q=0; q<scratch.nqp; ++q)
- {
- if (d==0)
- scratch.u_star_tmp[q] = 0.;
- scratch.u_star_tmp[q] += scratch.grad_u_star[q][d];
- }
- }
+ for (unsigned int d=0; d<dim; ++d)
+ {
+ scratch.fe_val.get_function_gradients (u_star[d], scratch.grad_u_star);
+ for (unsigned int q=0; q<scratch.nqp; ++q)
+ {
+ if (d==0)
+ scratch.u_star_tmp[q] = 0.;
+ scratch.u_star_tmp[q] += scratch.grad_u_star[q][d];
+ }
+ }
- data.local_advection = 0.;
- for (unsigned int q=0; q<scratch.nqp; ++q)
- for (unsigned int i=0; i<scratch.dpc; ++i)
- for (unsigned int j=0; j<scratch.dpc; ++j)
- data.local_advection(i,j) += (scratch.u_star_local[q] *
- scratch.fe_val.shape_grad (j, q) *
- scratch.fe_val.shape_value (i, q)
- +
- 0.5 *
- scratch.u_star_tmp[q] *
- scratch.fe_val.shape_value (i, q) *
- scratch.fe_val.shape_value (j, q))
- *
- scratch.fe_val.JxW(q) ;
-}
+ data.local_advection = 0.;
+ for (unsigned int q=0; q<scratch.nqp; ++q)
+ for (unsigned int i=0; i<scratch.dpc; ++i)
+ for (unsigned int j=0; j<scratch.dpc; ++j)
+ data.local_advection(i,j) += (scratch.u_star_local[q] *
+ scratch.fe_val.shape_grad (j, q) *
+ scratch.fe_val.shape_value (i, q)
+ +
+ 0.5 *
+ scratch.u_star_tmp[q] *
+ scratch.fe_val.shape_value (i, q) *
+ scratch.fe_val.shape_value (j, q))
+ *
+ scratch.fe_val.JxW(q) ;
+ }
-template <int dim>
-void
-NavierStokesProjection<dim>::
-copy_advection_local_to_global(const AdvectionPerTaskData &data)
-{
- for (unsigned int i=0; i<fe_velocity.dofs_per_cell; ++i)
- for (unsigned int j=0; j<fe_velocity.dofs_per_cell; ++j)
- vel_Advection.add (data.local_dof_indices[i],
- data.local_dof_indices[j],
- data.local_advection(i,j));
-}
+ template <int dim>
+ void
+ NavierStokesProjection<dim>::
+ copy_advection_local_to_global(const AdvectionPerTaskData &data)
+ {
+ for (unsigned int i=0; i<fe_velocity.dofs_per_cell; ++i)
+ for (unsigned int j=0; j<fe_velocity.dofs_per_cell; ++j)
+ vel_Advection.add (data.local_dof_indices[i],
+ data.local_dof_indices[j],
+ data.local_advection(i,j));
+ }
- // @sect4{<code>NavierStokesProjection::projection_step</code>}
+ // @sect4{<code>NavierStokesProjection::projection_step</code>}
- // This implements the projection step:
-template <int dim>
-void
-NavierStokesProjection<dim>::projection_step (const bool reinit_prec)
-{
- pres_iterative.copy_from (pres_Laplace);
+ // This implements the projection step:
+ template <int dim>
+ void
+ NavierStokesProjection<dim>::projection_step (const bool reinit_prec)
+ {
+ pres_iterative.copy_from (pres_Laplace);
- pres_tmp = 0.;
- for (unsigned d=0; d<dim; ++d)
- pres_Diff[d].Tvmult_add (pres_tmp, u_n[d]);
+ pres_tmp = 0.;
+ for (unsigned d=0; d<dim; ++d)
+ pres_Diff[d].Tvmult_add (pres_tmp, u_n[d]);
- phi_n_minus_1 = phi_n;
+ phi_n_minus_1 = phi_n;
- static std::map<unsigned int, double> bval;
- if (reinit_prec)
- VectorTools::interpolate_boundary_values (dof_handler_pressure, 3,
- ZeroFunction<dim>(), bval);
+ static std::map<unsigned int, double> bval;
+ if (reinit_prec)
+ VectorTools::interpolate_boundary_values (dof_handler_pressure, 3,
+ ZeroFunction<dim>(), 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<double>::AdditionalData (vel_diag_strength,
- vel_off_diagonals) );
+ if (reinit_prec)
+ prec_pres_Laplace.initialize(pres_iterative,
+ SparseILU<double>::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{ <code>NavierStokesProjection::update_pressure</code> }
-
- // 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 <int dim>
-void
-NavierStokesProjection<dim>::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{ <code>NavierStokesProjection::update_pressure</code> }
+ // 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 <int dim>
+ void
+ NavierStokesProjection<dim>::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{ <code>NavierStokesProjection::output_results</code> }
-
- // 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 <int dim>
-void NavierStokesProjection<dim>::output_results (const unsigned int step)
-{
- assemble_vorticity ( (step == 1));
- const FESystem<dim> joint_fe (fe_velocity, dim,
- fe_pressure, 1,
- fe_velocity, 1);
- DoFHandler<dim> 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<double> joint_solution (joint_dof_handler.n_dofs());
- std::vector<unsigned int> 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<dim>::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_fe.dofs_per_cell; ++i)
- switch (joint_fe.system_to_base_index(i).first.first)
- {
- case 0:
- Assert (joint_fe.system_to_base_index(i).first.second < dim,
- ExcInternalError());
- joint_solution (loc_joint_dof_indices[i]) =
- u_n[ joint_fe.system_to_base_index(i).first.second ]
- (loc_vel_dof_indices[ joint_fe.system_to_base_index(i).second ]);
- break;
- case 1:
- Assert (joint_fe.system_to_base_index(i).first.second == 0,
- ExcInternalError());
- joint_solution (loc_joint_dof_indices[i]) =
- pres_n (loc_pres_dof_indices[ joint_fe.system_to_base_index(i).second ]);
- break;
- case 2:
- Assert (joint_fe.system_to_base_index(i).first.second == 0,
- ExcInternalError());
- joint_solution (loc_joint_dof_indices[i]) =
- rot_u (loc_vel_dof_indices[ joint_fe.system_to_base_index(i).second ]);
- break;
- default:
- Assert (false, ExcInternalError());
- }
- }
- std::vector<std::string> joint_solution_names (dim, "v");
- joint_solution_names.push_back ("p");
- joint_solution_names.push_back ("rot_u");
- DataOut<dim> 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<dim>::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{ <code>NavierStokesProjection::output_results</code> }
+
+ // 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 <int dim>
+ void NavierStokesProjection<dim>::output_results (const unsigned int step)
+ {
+ assemble_vorticity ( (step == 1));
+ const FESystem<dim> joint_fe (fe_velocity, dim,
+ fe_pressure, 1,
+ fe_velocity, 1);
+ DoFHandler<dim> 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<double> joint_solution (joint_dof_handler.n_dofs());
+ std::vector<unsigned int> 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<dim>::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_fe.dofs_per_cell; ++i)
+ switch (joint_fe.system_to_base_index(i).first.first)
+ {
+ case 0:
+ Assert (joint_fe.system_to_base_index(i).first.second < dim,
+ ExcInternalError());
+ joint_solution (loc_joint_dof_indices[i]) =
+ u_n[ joint_fe.system_to_base_index(i).first.second ]
+ (loc_vel_dof_indices[ joint_fe.system_to_base_index(i).second ]);
+ break;
+ case 1:
+ Assert (joint_fe.system_to_base_index(i).first.second == 0,
+ ExcInternalError());
+ joint_solution (loc_joint_dof_indices[i]) =
+ pres_n (loc_pres_dof_indices[ joint_fe.system_to_base_index(i).second ]);
+ break;
+ case 2:
+ Assert (joint_fe.system_to_base_index(i).first.second == 0,
+ ExcInternalError());
+ joint_solution (loc_joint_dof_indices[i]) =
+ rot_u (loc_vel_dof_indices[ joint_fe.system_to_base_index(i).second ]);
+ break;
+ default:
+ Assert (false, ExcInternalError());
+ }
+ }
+ std::vector<std::string> joint_solution_names (dim, "v");
+ joint_solution_names.push_back ("p");
+ joint_solution_names.push_back ("rot_u");
+ DataOut<dim> 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<dim>::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 <int dim>
-void NavierStokesProjection<dim>::assemble_vorticity (const bool reinit_prec)
-{
- Assert (dim == 2, ExcNotImplemented());
- if (reinit_prec)
- prec_vel_mass.initialize (vel_Mass);
-
- typename DoFHandler<dim>::active_cell_iterator
- cell = dof_handler_velocity.begin_active(),
- end = dof_handler_velocity.end();
- FEValues<dim> 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<unsigned int> ldi (dpc);
- Vector<double> 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<nqp; ++q)
- for (unsigned int i=0; i<dpc; ++i)
- loc_rot(i) += (grad_u2[q][0] - grad_u1[q][1]) *
- fe_val_vel.shape_value (i, q) *
- fe_val_vel.JxW(q);
- for (unsigned int i=0; i<dpc; ++i)
- rot_u (ldi[i]) += loc_rot(i);
- }
+ // 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 <int dim>
+ void NavierStokesProjection<dim>::assemble_vorticity (const bool reinit_prec)
+ {
+ Assert (dim == 2, ExcNotImplemented());
+ if (reinit_prec)
+ prec_vel_mass.initialize (vel_Mass);
+
+ FEValues<dim> 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<unsigned int> ldi (dpc);
+ Vector<double> loc_rot (dpc);
+
+ std::vector< Tensor<1,dim> > grad_u1 (nqp), grad_u2 (nqp);
+ rot_u = 0.;
+
+ typename DoFHandler<dim>::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<nqp; ++q)
+ for (unsigned int i=0; i<dpc; ++i)
+ loc_rot(i) += (grad_u2[q][0] - grad_u1[q][1]) *
+ fe_val_vel.shape_value (i, q) *
+ fe_val_vel.JxW(q);
+
+ for (unsigned int i=0; i<dpc; ++i)
+ rot_u (ldi[i]) += loc_rot(i);
+ }
- prec_vel_mass.solve (rot_u);
+ prec_vel_mass.solve (rot_u);
+ }
}
{
try
{
+ using namespace dealii;
+ using namespace Step35;
+
RunTimeParameters::Data_Storage data;
data.read_data ("parameter-file.prm");
+
deallog.depth_console (data.verbose ? 2 : 0);
+
NavierStokesProjection<2> test (data);
test.run (data.verbose, data.output_interval);
}
/* 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 */
#include <fstream>
#include <iostream>
- // Finally, as in previous programs,
- // we import all the deal.II class
- // and function names into the global
- // namespace:
-using namespace dealii;
-
- // @sect3{The <code>EigenvalueProblem</code> 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 <int dim>
-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<dim> triangulation;
- FE_Q<dim> fe;
- DoFHandler<dim> 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<PETScWrappers::Vector> eigenfunctions;
- std::vector<double> 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 <code>EigenvalueProblem</code> 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 <int dim>
-EigenvalueProblem<dim>::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 <code>EigenvalueProblem</code> 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
- // <code>reinit()</code> 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 <int dim>
-void EigenvalueProblem<dim>::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<eigenfunctions.size (); ++i)
- eigenfunctions[i].reinit (dof_handler.n_dofs ());
-
- eigenvalues.resize (eigenfunctions.size ());
-}
+ // 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 <int dim>
+ 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<dim> triangulation;
+ FE_Q<dim> fe;
+ DoFHandler<dim> 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<PETScWrappers::Vector> eigenfunctions;
+ std::vector<double> 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 <code>EigenvalueProblem</code> 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 <int dim>
+ EigenvalueProblem<dim>::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 <int dim>
-void EigenvalueProblem<dim>::assemble_system ()
-{
- QGauss<dim> quadrature_formula(2);
-
- FEValues<dim> 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<double> cell_stiffness_matrix (dofs_per_cell, dofs_per_cell);
- FullMatrix<double> cell_mass_matrix (dofs_per_cell, dofs_per_cell);
-
- std::vector<unsigned int> local_dof_indices (dofs_per_cell);
-
- FunctionParser<dim> potential;
- potential.initialize (FunctionParser<dim>::default_variable_names (),
- parameters.get ("Potential"),
- typename FunctionParser<dim>::ConstMap());
-
- std::vector<double> potential_values (n_q_points);
-
-
- typename DoFHandler<dim>::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_point<n_q_points; ++q_point)
- for (unsigned int i=0; i<dofs_per_cell; ++i)
- for (unsigned int j=0; j<dofs_per_cell; ++j)
- {
- cell_stiffness_matrix (i, j)
- += (fe_values.shape_grad (i, q_point) *
- fe_values.shape_grad (j, q_point)
- +
- potential_values[q_point] *
- fe_values.shape_value (i, q_point) *
- fe_values.shape_value (j, q_point)
- ) * fe_values.JxW (q_point);
-
- cell_mass_matrix (i, j)
- += (fe_values.shape_value (i, q_point) *
- fe_values.shape_value (j, q_point)
- ) * fe_values.JxW (q_point);
- }
-
- // Now that we have the local
- // matrix contributions, we
- // transfer them into the
- // global objects and take care
- // of zero boundary
- // constraints:
- cell->get_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
+ // <code>reinit()</code> 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 <int dim>
+ void EigenvalueProblem<dim>::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<eigenfunctions.size (); ++i)
+ eigenfunctions[i].reinit (dof_handler.n_dofs ());
+
+ eigenvalues.resize (eigenfunctions.size ());
+ }
- // @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 <int dim>
-void EigenvalueProblem<dim>::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 <int dim>
+ void EigenvalueProblem<dim>::assemble_system ()
+ {
+ QGauss<dim> 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
- // eigen<i>function</i> 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<eigenfunctions.size(); ++i)
- eigenfunctions[i] /= eigenfunctions[i].linfty_norm ();
-}
+ FEValues<dim> 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<double> cell_stiffness_matrix (dofs_per_cell, dofs_per_cell);
+ FullMatrix<double> cell_mass_matrix (dofs_per_cell, dofs_per_cell);
+
+ std::vector<unsigned int> 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 <int dim>
-void EigenvalueProblem<dim>::output_results () const
-{
- DataOut<dim> data_out;
-
- data_out.attach_dof_handler (dof_handler);
-
- for (unsigned int i=0; i<eigenfunctions.size(); ++i)
- data_out.add_data_vector (eigenfunctions[i],
- std::string("eigenfunction_") +
- Utilities::int_to_string(i));
-
- // The only thing worth discussing
- // may be that because the potential
- // is specified as a function
- // expression in the input file, it
- // would be nice to also have it as a
- // graphical representation along
- // with the eigenfunctions. The
- // process to achieve this is
- // relatively straightforward: we
- // build an object that represents
- // $V(\mathbf x)$ and then we
- // interpolate this continuous
- // function onto the finite element
- // space. The result we also attach
- // to the DataOut object for
- // visualization.
- Vector<double> projected_potential (dof_handler.n_dofs());
- {
FunctionParser<dim> potential;
potential.initialize (FunctionParser<dim>::default_variable_names (),
parameters.get ("Potential"),
typename FunctionParser<dim>::ConstMap());
- VectorTools::interpolate (dof_handler, potential, projected_potential);
+
+ std::vector<double> potential_values (n_q_points);
+
+
+ typename DoFHandler<dim>::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_point<n_q_points; ++q_point)
+ for (unsigned int i=0; i<dofs_per_cell; ++i)
+ for (unsigned int j=0; j<dofs_per_cell; ++j)
+ {
+ cell_stiffness_matrix (i, j)
+ += (fe_values.shape_grad (i, q_point) *
+ fe_values.shape_grad (j, q_point)
+ +
+ potential_values[q_point] *
+ fe_values.shape_value (i, q_point) *
+ fe_values.shape_value (j, q_point)
+ ) * fe_values.JxW (q_point);
+
+ cell_mass_matrix (i, j)
+ += (fe_values.shape_value (i, q_point) *
+ fe_values.shape_value (j, q_point)
+ ) * fe_values.JxW (q_point);
+ }
+
+ // Now that we have the local
+ // matrix contributions, we
+ // transfer them into the
+ // global objects and take care
+ // of zero boundary
+ // constraints:
+ cell->get_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 <int dim>
+ void EigenvalueProblem<dim>::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
+ // eigen<i>function</i> 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<eigenfunctions.size(); ++i)
+ eigenfunctions[i] /= eigenfunctions[i].linfty_norm ();
+ }
- // This is the function which has the
- // top-level control over
- // everything. It is almost exactly
- // the same as in step-4:
-template <int dim>
-void EigenvalueProblem<dim>::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<eigenvalues.size(); ++i)
- std::cout << " Eigenvalue " << i
- << " : " << eigenvalues[i]
+
+ // @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 <int dim>
+ void EigenvalueProblem<dim>::output_results () const
+ {
+ DataOut<dim> data_out;
+
+ data_out.attach_dof_handler (dof_handler);
+
+ for (unsigned int i=0; i<eigenfunctions.size(); ++i)
+ data_out.add_data_vector (eigenfunctions[i],
+ std::string("eigenfunction_") +
+ Utilities::int_to_string(i));
+
+ // The only thing worth discussing
+ // may be that because the potential
+ // is specified as a function
+ // expression in the input file, it
+ // would be nice to also have it as a
+ // graphical representation along
+ // with the eigenfunctions. The
+ // process to achieve this is
+ // relatively straightforward: we
+ // build an object that represents
+ // $V(\mathbf x)$ and then we
+ // interpolate this continuous
+ // function onto the finite element
+ // space. The result we also attach
+ // to the DataOut object for
+ // visualization.
+ Vector<double> projected_potential (dof_handler.n_dofs());
+ {
+ FunctionParser<dim> potential;
+ potential.initialize (FunctionParser<dim>::default_variable_names (),
+ parameters.get ("Potential"),
+ typename FunctionParser<dim>::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 <int dim>
+ void EigenvalueProblem<dim>::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<eigenvalues.size(); ++i)
+ std::cout << " Eigenvalue " << i
+ << " : " << eigenvalues[i]
+ << std::endl;
+ }
+}
// @sect3{The <code>main</code> function}
-int main (int argc, char **argv)
+int main (int argc, char **argv)
{
try
{
SlepcInitialize (&argc, &argv, 0, 0);
{
+ using namespace dealii;
+ using namespace Step36;
+
deallog.depth_console (0);
-
+
EigenvalueProblem<2> problem ("step-36.prm");
problem.run ();
}
return 1;
}
- catch (...)
+ catch (...)
{
std::cerr << std::endl << std::endl
<< "----------------------------------------------------"
<< 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;
#include <fstream>
#include <iostream>
-using namespace dealii;
-
- // @sect3{The <code>LaplaceBeltramiProblem</code> class template}
-
- // This class is almost exactly similar to
- // the <code>LaplaceProblem</code> 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
- // <code>spacedim-1</code>.
- // - 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 <int spacedim>
-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<dim,spacedim> triangulation;
- FE_Q<dim,spacedim> fe;
- DoFHandler<dim,spacedim> dof_handler;
- MappingQ<dim, spacedim> mapping;
-
- SparsityPattern sparsity_pattern;
- SparseMatrix<double> system_matrix;
-
- Vector<double> solution;
- Vector<double> 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 <code>switch</code>
- // statement (or a sequence of
- // <code>if</code>s) for each possible value
- // of the spatial dimension.
-template <int dim>
-class Solution : public Function<dim>
-{
- public:
- Solution () : Function<dim>() {}
- virtual double value (const Point<dim> &p,
- const unsigned int component = 0) const;
+namespace Step38
+{
+ using namespace dealii;
+
+ // @sect3{The <code>LaplaceBeltramiProblem</code> class template}
+
+ // This class is almost exactly similar to
+ // the <code>LaplaceProblem</code> 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
+ // <code>spacedim-1</code>.
+ // - 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 <int spacedim>
+ 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<dim,spacedim> triangulation;
+ FE_Q<dim,spacedim> fe;
+ DoFHandler<dim,spacedim> dof_handler;
+ MappingQ<dim, spacedim> mapping;
+
+ SparsityPattern sparsity_pattern;
+ SparseMatrix<double> system_matrix;
+
+ Vector<double> solution;
+ Vector<double> 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 <code>switch</code>
+ // statement (or a sequence of
+ // <code>if</code>s) for each possible value
+ // of the spatial dimension.
+ template <int dim>
+ class Solution : public Function<dim>
+ {
+ public:
+ Solution () : Function<dim>() {}
- virtual Tensor<1,dim> gradient (const Point<dim> &p,
- const unsigned int component = 0) const;
+ virtual double value (const Point<dim> &p,
+ const unsigned int component = 0) const;
-};
+ virtual Tensor<1,dim> gradient (const Point<dim> &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 <int dim>
-class RightHandSide : public Function<dim>
-{
- public:
- RightHandSide () : Function<dim>() {}
- virtual double value (const Point<dim> &p,
- const unsigned int component = 0) const;
-};
+ template <int dim>
+ class RightHandSide : public Function<dim>
+ {
+ public:
+ RightHandSide () : Function<dim>() {}
-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<dim> &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 <code>LaplaceBeltramiProblem</code> 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 <int spacedim>
-LaplaceBeltramiProblem<spacedim>::
-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
- // <code>spacedim</code> 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 <i>surface</i>
- // 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, <code>static</code>,
- // 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 <int spacedim>
-void LaplaceBeltramiProblem<spacedim>::make_grid_and_dofs ()
-{
- static HyperBallBoundary<dim,spacedim> surface_description;
- triangulation.set_boundary (0, surface_description);
+ // @sect3{Implementation of the <code>LaplaceBeltramiProblem</code> 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 <int spacedim>
+ LaplaceBeltramiProblem<spacedim>::
+ 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
+ // <code>spacedim</code> 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 <i>surface</i>
+ // 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, <code>static</code>,
+ // 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 <int spacedim>
+ void LaplaceBeltramiProblem<spacedim>::make_grid_and_dofs ()
{
- Triangulation<spacedim> volume_mesh;
- GridGenerator::half_hyper_ball(volume_mesh);
+ static HyperBallBoundary<dim,spacedim> surface_description;
+ triangulation.set_boundary (0, surface_description);
- std::set<unsigned char> boundary_ids;
- boundary_ids.insert (0);
+ {
+ Triangulation<spacedim> volume_mesh;
+ GridGenerator::half_hyper_ball(volume_mesh);
- GridTools::extract_boundary_mesh (volume_mesh, triangulation,
- boundary_ids);
- }
- triangulation.refine_global(4);
+ std::set<unsigned char> 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 <int spacedim>
-void LaplaceBeltramiProblem<spacedim>::assemble_system ()
-{
- system_matrix = 0;
- system_rhs = 0;
- const QGauss<dim> quadrature_formula(2*fe.degree);
- FEValues<dim,spacedim> 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 <int spacedim>
+ void LaplaceBeltramiProblem<spacedim>::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<dim> quadrature_formula(2*fe.degree);
+ FEValues<dim,spacedim> fe_values (mapping, fe, quadrature_formula,
+ update_values |
+ update_gradients |
+ update_quadrature_points |
+ update_JxW_values);
- FullMatrix<double> cell_matrix (dofs_per_cell, dofs_per_cell);
- Vector<double> 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<double> rhs_values(n_q_points);
- std::vector<unsigned int> local_dof_indices (dofs_per_cell);
+ FullMatrix<double> cell_matrix (dofs_per_cell, dofs_per_cell);
+ Vector<double> cell_rhs (dofs_per_cell);
- const RightHandSide<spacedim> rhs;
+ std::vector<double> rhs_values(n_q_points);
+ std::vector<unsigned int> local_dof_indices (dofs_per_cell);
- for (typename DoFHandler<dim,spacedim>::active_cell_iterator
- cell = dof_handler.begin_active(),
- endc = dof_handler.end();
- cell!=endc; ++cell)
- {
- cell_matrix = 0;
- cell_rhs = 0;
+ const RightHandSide<spacedim> rhs;
- fe_values.reinit (cell);
+ for (typename DoFHandler<dim,spacedim>::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; i<dofs_per_cell; ++i)
- for (unsigned int j=0; j<dofs_per_cell; ++j)
- for (unsigned int q_point=0; q_point<n_q_points; ++q_point)
- cell_matrix(i,j) += fe_values.shape_grad(i,q_point) *
- fe_values.shape_grad(j,q_point) *
- fe_values.JxW(q_point);
-
- for (unsigned int i=0; i<dofs_per_cell; ++i)
- for (unsigned int q_point=0; q_point<n_q_points; ++q_point)
- cell_rhs(i) += fe_values.shape_value(i,q_point) *
- rhs_values[q_point]*
- fe_values.JxW(q_point);
-
- cell->get_dof_indices (local_dof_indices);
- for (unsigned int i=0; i<dofs_per_cell; ++i)
- {
- for (unsigned int j=0; j<dofs_per_cell; ++j)
- system_matrix.add (local_dof_indices[i],
- local_dof_indices[j],
- cell_matrix(i,j));
+ rhs.value_list (fe_values.get_quadrature_points(), rhs_values);
- system_rhs(local_dof_indices[i]) += cell_rhs(i);
- }
- }
+ for (unsigned int i=0; i<dofs_per_cell; ++i)
+ for (unsigned int j=0; j<dofs_per_cell; ++j)
+ for (unsigned int q_point=0; q_point<n_q_points; ++q_point)
+ cell_matrix(i,j) += fe_values.shape_grad(i,q_point) *
+ fe_values.shape_grad(j,q_point) *
+ fe_values.JxW(q_point);
- std::map<unsigned int,double> boundary_values;
- VectorTools::interpolate_boundary_values (mapping,
- dof_handler,
- 0,
- Solution<spacedim>(),
- boundary_values);
-
- MatrixTools::apply_boundary_values (boundary_values,
- system_matrix,
- solution,
- system_rhs,false);
-}
+ for (unsigned int i=0; i<dofs_per_cell; ++i)
+ for (unsigned int q_point=0; q_point<n_q_points; ++q_point)
+ cell_rhs(i) += fe_values.shape_value(i,q_point) *
+ rhs_values[q_point]*
+ fe_values.JxW(q_point);
+
+ cell->get_dof_indices (local_dof_indices);
+ for (unsigned int i=0; i<dofs_per_cell; ++i)
+ {
+ for (unsigned int j=0; j<dofs_per_cell; ++j)
+ system_matrix.add (local_dof_indices[i],
+ local_dof_indices[j],
+ cell_matrix(i,j));
+
+ system_rhs(local_dof_indices[i]) += cell_rhs(i);
+ }
+ }
+
+ std::map<unsigned int,double> boundary_values;
+ VectorTools::interpolate_boundary_values (mapping,
+ dof_handler,
+ 0,
+ Solution<spacedim>(),
+ 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 <int spacedim>
-void LaplaceBeltramiProblem<spacedim>::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 <int spacedim>
+ void LaplaceBeltramiProblem<spacedim>::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 <int spacedim>
-void LaplaceBeltramiProblem<spacedim>::output_results () const
-{
- DataOut<dim,DoFHandler<dim,spacedim> > data_out;
- data_out.attach_dof_handler (dof_handler);
- data_out.add_data_vector (solution,
- "solution",
- DataOut<dim,DoFHandler<dim,spacedim> >::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 <int spacedim>
+ void LaplaceBeltramiProblem<spacedim>::output_results () const
+ {
+ DataOut<dim,DoFHandler<dim,spacedim> > data_out;
+ data_out.attach_dof_handler (dof_handler);
+ data_out.add_data_vector (solution,
+ "solution",
+ DataOut<dim,DoFHandler<dim,spacedim> >::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 <code>Solution</code>
- // 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 <int spacedim>
-void LaplaceBeltramiProblem<spacedim>::compute_error () const
-{
- Vector<float> difference_per_cell (triangulation.n_active_cells());
- VectorTools::integrate_difference (mapping, dof_handler, solution,
- Solution<spacedim>(),
- difference_per_cell,
- QGauss<dim>(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 <code>Solution</code>
+ // 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 <int spacedim>
+ void LaplaceBeltramiProblem<spacedim>::compute_error () const
+ {
+ Vector<float> difference_per_cell (triangulation.n_active_cells());
+ VectorTools::integrate_difference (mapping, dof_handler, solution,
+ Solution<spacedim>(),
+ difference_per_cell,
+ QGauss<dim>(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 <int spacedim>
-void LaplaceBeltramiProblem<spacedim>::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 <int spacedim>
+ void LaplaceBeltramiProblem<spacedim>::run ()
+ {
+ make_grid_and_dofs();
+ assemble_system ();
+ solve ();
+ output_results ();
+ compute_error ();
+ }
}
{
try
{
+ using namespace dealii;
+ using namespace Step38;
+
deallog.depth_console (0);
LaplaceBeltramiProblem<3> laplace_beltrami;
// 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<dim>. 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 <int dim>
-class MatrixIntegrator : public Subscriptor
-{
- public:
- static void cell(MeshWorker::DoFInfo<dim>& dinfo,
- typename MeshWorker::IntegrationInfo<dim>& info);
- static void boundary(MeshWorker::DoFInfo<dim>& dinfo,
- typename MeshWorker::IntegrationInfo<dim>& info);
- static void face(MeshWorker::DoFInfo<dim>& dinfo1,
- MeshWorker::DoFInfo<dim>& dinfo2,
- typename MeshWorker::IntegrationInfo<dim>& info1,
- typename MeshWorker::IntegrationInfo<dim>& 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 <int dim>
-void MatrixIntegrator<dim>::cell(
- MeshWorker::DoFInfo<dim>& dinfo,
- typename MeshWorker::IntegrationInfo<dim>& info)
-{
- LocalIntegrators::Laplace::cell_matrix(dinfo.matrix(0,false).matrix, info.fe_values());
-}
-
-
-template <int dim>
-void MatrixIntegrator<dim>::boundary(
- MeshWorker::DoFInfo<dim>& dinfo,
- typename MeshWorker::IntegrationInfo<dim>& 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 <int dim>
-void MatrixIntegrator<dim>::face(
- MeshWorker::DoFInfo<dim>& dinfo1,
- MeshWorker::DoFInfo<dim>& dinfo2,
- typename MeshWorker::IntegrationInfo<dim>& info1,
- typename MeshWorker::IntegrationInfo<dim>& 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 <int dim>
-class RHSIntegrator : public Subscriptor
-{
- public:
- static void cell(MeshWorker::DoFInfo<dim>& dinfo, typename MeshWorker::IntegrationInfo<dim>& info);
- static void boundary(MeshWorker::DoFInfo<dim>& dinfo, typename MeshWorker::IntegrationInfo<dim>& info);
- static void face(MeshWorker::DoFInfo<dim>& dinfo1,
- MeshWorker::DoFInfo<dim>& dinfo2,
- typename MeshWorker::IntegrationInfo<dim>& info1,
- typename MeshWorker::IntegrationInfo<dim>& info2);
-};
-
-
-template <int dim>
-void RHSIntegrator<dim>::cell(MeshWorker::DoFInfo<dim>&, typename MeshWorker::IntegrationInfo<dim>&)
-{}
-
-
-template <int dim>
-void RHSIntegrator<dim>::boundary(MeshWorker::DoFInfo<dim>& dinfo, typename MeshWorker::IntegrationInfo<dim>& info)
-{
- const FEValuesBase<dim>& fe = info.fe_values();
- Vector<double>& local_vector = dinfo.vector(0).block(0);
-
- std::vector<double> 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<fe.n_quadrature_points;++k)
- for (unsigned int i=0; i<fe.dofs_per_cell; ++i)
- local_vector(i) += (- fe.shape_value(i,k) * penalty * boundary_values[k]
- + (fe.normal_vector(k) * fe.shape_grad(i,k)) * boundary_values[k])
- * fe.JxW(k);
-}
-
-
-template <int dim>
-void RHSIntegrator<dim>::face(MeshWorker::DoFInfo<dim>&,
- MeshWorker::DoFInfo<dim>&,
- typename MeshWorker::IntegrationInfo<dim>&,
- typename MeshWorker::IntegrationInfo<dim>&)
-{}
-
-
- // 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 <int dim>
-class Estimator : public Subscriptor
-{
- public:
- static void cell(MeshWorker::DoFInfo<dim>& dinfo, typename MeshWorker::IntegrationInfo<dim>& info);
- static void boundary(MeshWorker::DoFInfo<dim>& dinfo, typename MeshWorker::IntegrationInfo<dim>& info);
- static void face(MeshWorker::DoFInfo<dim>& dinfo1,
- MeshWorker::DoFInfo<dim>& dinfo2,
- typename MeshWorker::IntegrationInfo<dim>& info1,
- typename MeshWorker::IntegrationInfo<dim>& info2);
-};
-
-
- // The cell contribution is the
- // Laplacian of the discrete
- // solution, since the right hand
- // side is zero.
-template <int dim>
-void Estimator<dim>::cell(MeshWorker::DoFInfo<dim>& dinfo, typename MeshWorker::IntegrationInfo<dim>& info)
-{
- const FEValuesBase<dim>& fe = info.fe_values();
-
- const std::vector<Tensor<2,dim> >& DDuh = info.hessians[0][0];
- for (unsigned k=0;k<fe.n_quadrature_points;++k)
- {
- const double t = dinfo.cell->diameter() * 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 <int dim>
-void Estimator<dim>::boundary(MeshWorker::DoFInfo<dim>& dinfo, typename MeshWorker::IntegrationInfo<dim>& info)
-{
- const FEValuesBase<dim>& fe = info.fe_values();
-
- std::vector<double> boundary_values(fe.n_quadrature_points);
- exact_solution.value_list(fe.get_quadrature_points(), boundary_values);
-
- const std::vector<double>& 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<fe.n_quadrature_points;++k)
- dinfo.value(0) += penalty * (boundary_values[k] - uh[k]) * (boundary_values[k] - uh[k])
- * fe.JxW(k);
- dinfo.value(0) = std::sqrt(dinfo.value(0));
-}
-
-
- // Finally, on interior faces, the
- // estimator consists of the jumps of
- // the solution and its normal
- // derivative, weighted appropriately.
-template <int dim>
-void Estimator<dim>::face(MeshWorker::DoFInfo<dim>& dinfo1,
- MeshWorker::DoFInfo<dim>& dinfo2,
- typename MeshWorker::IntegrationInfo<dim>& info1,
- typename MeshWorker::IntegrationInfo<dim>& info2)
-{
- const FEValuesBase<dim>& fe = info1.fe_values();
- const std::vector<double>& uh1 = info1.values[0][0];
- const std::vector<double>& uh2 = info2.values[0][0];
- const std::vector<Tensor<1,dim> >& Duh1 = info1.gradients[0][0];
- const std::vector<Tensor<1,dim> >& 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<fe.n_quadrature_points;++k)
- {
- double diff1 = uh1[k] - uh2[k];
- double diff2 = fe.normal_vector(k) * Duh1[k] - fe.normal_vector(k) * Duh2[k];
- dinfo1.value(0) += (penalty * diff1*diff1 + h * diff2*diff2)
- * fe.JxW(k);
- }
- dinfo1.value(0) = std::sqrt(dinfo1.value(0));
- dinfo2.value(0) = dinfo1.value(0);
-}
-
- // Finally we have an integrator for
- // the error. Since the energy norm
- // for discontinuous Galerkin
- // problems not only involves the
- // difference of the gradient inside
- // the cells, but also the jump terms
- // across faces and at the boundary,
- // we cannot just use
- // VectorTools::integrate_difference().
- // Instead, we use the MeshWorker
- // interface to compute the error
- // ourselves.
-
- // There are several different ways
- // to define this energy norm, but
- // all of them are equivalent to each
- // other uniformly with mesh size
- // (some not uniformly with
- // polynomial degree). Here, we
- // choose
- // @f[
- // \|u\|_{1,h} = \sum_{K\in \mathbb
- // T_h} \|\nabla u\|_K^2
- // + \sum_{F \in F_h^i}
- // 4\sigma_F\|\{\!\{ u \mathbf
- // n\}\!\}\|^2_F
- // + \sum_{F \in F_h^b} 2\sigma_F\|u\|^2_F
- // @f]
-
-template <int dim>
-class ErrorIntegrator : public Subscriptor
-{
- public:
- static void cell(MeshWorker::DoFInfo<dim>& dinfo, typename MeshWorker::IntegrationInfo<dim>& info);
- static void boundary(MeshWorker::DoFInfo<dim>& dinfo, typename MeshWorker::IntegrationInfo<dim>& info);
- static void face(MeshWorker::DoFInfo<dim>& dinfo1,
- MeshWorker::DoFInfo<dim>& dinfo2,
- typename MeshWorker::IntegrationInfo<dim>& info1,
- typename MeshWorker::IntegrationInfo<dim>& 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
- // <i>L<sup>2</sup></i>-error in the
- // same loop. Obviously, this one
- // does not have any jump terms and
- // only appears in the integration on
- // cells.
-template <int dim>
-void ErrorIntegrator<dim>::cell(
- MeshWorker::DoFInfo<dim>& dinfo,
- typename MeshWorker::IntegrationInfo<dim>& info)
-{
- const FEValuesBase<dim>& fe = info.fe_values();
- std::vector<Tensor<1,dim> > exact_gradients(fe.n_quadrature_points);
- std::vector<double> 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<Tensor<1,dim> >& Duh = info.gradients[0][0];
- const std::vector<double>& uh = info.values[0][0];
-
- for (unsigned k=0;k<fe.n_quadrature_points;++k)
- {
- double sum = 0;
- for (unsigned int d=0;d<dim;++d)
- {
- const double diff = exact_gradients[k][d] - Duh[k][d];
- sum += diff*diff;
- }
- const double diff = exact_values[k] - uh[k];
- dinfo.value(0) += sum * fe.JxW(k);
- dinfo.value(1) += diff*diff * fe.JxW(k);
- }
- dinfo.value(0) = std::sqrt(dinfo.value(0));
- dinfo.value(1) = std::sqrt(dinfo.value(1));
-}
-
-
-template <int dim>
-void ErrorIntegrator<dim>::boundary(
- MeshWorker::DoFInfo<dim>& dinfo,
- typename MeshWorker::IntegrationInfo<dim>& info)
-{
- const FEValuesBase<dim>& fe = info.fe_values();
-
- std::vector<double> exact_values(fe.n_quadrature_points);
- exact_solution.value_list(fe.get_quadrature_points(), exact_values);
-
- const std::vector<double>& 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<fe.n_quadrature_points;++k)
- {
- const double diff = exact_values[k] - uh[k];
- dinfo.value(0) += penalty * diff * diff * fe.JxW(k);
- }
- dinfo.value(0) = std::sqrt(dinfo.value(0));
-}
-
-
-template <int dim>
-void ErrorIntegrator<dim>::face(
- MeshWorker::DoFInfo<dim>& dinfo1,
- MeshWorker::DoFInfo<dim>& dinfo2,
- typename MeshWorker::IntegrationInfo<dim>& info1,
- typename MeshWorker::IntegrationInfo<dim>& info2)
-{
- const FEValuesBase<dim>& fe = info1.fe_values();
- const std::vector<double>& uh1 = info1.values[0][0];
- const std::vector<double>& 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<fe.n_quadrature_points;++k)
- {
- double diff = uh1[k] - uh2[k];
- dinfo1.value(0) += (penalty * diff*diff)
- * fe.JxW(k);
- }
- dinfo1.value(0) = std::sqrt(dinfo1.value(0));
- dinfo2.value(0) = dinfo1.value(0);
-}
-
-
-
- // @sect3{The main class}
-
- // This class does the main job, like
- // in previous examples. For a
- // description of the functions
- // declared here, please refer to
- // the implementation below.
-template <int dim>
-class Step39
-{
- public:
- typedef MeshWorker::IntegrationInfo<dim> CellInfo;
-
- Step39(const FiniteElement<dim>& 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<dim> triangulation;
- const MappingQ1<dim> mapping;
- const FiniteElement<dim>& fe;
- MGDoFHandler<dim> mg_dof_handler;
- DoFHandler<dim>& dof_handler;
-
- // Then, we have the matrices and
- // vectors related to the global
- // discrete system.
- SparsityPattern sparsity;
- SparseMatrix<double> matrix;
- Vector<double> solution;
- Vector<double> right_hand_side;
- BlockVector<double> 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<SparsityPattern> mg_sparsity;
- MGLevelObject<SparseMatrix<double> > 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<SparsityPattern> mg_sparsity_dg_interface;
- // The flux matrix at the
- // refinement edge, coupling fine
- // level degrees of freedom to
- // coarse level.
- MGLevelObject<SparseMatrix<double> > mg_matrix_dg_down;
- // The transpose of the flux
- // matrix at the refinement edge,
- // coupling coarse level degrees
- // of freedom to fine level.
- MGLevelObject<SparseMatrix<double> > 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 <int dim>
-Step39<dim>::Step39(const FiniteElement<dim>& 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 <int dim>
-void
-Step39<dim>::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
- // <tt>clear()</tt> 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 <int dim>
-void
-Step39<dim>::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<dim>. 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 <int dim>
+ class MatrixIntegrator : public Subscriptor
+ {
+ public:
+ static void cell(MeshWorker::DoFInfo<dim>& dinfo,
+ typename MeshWorker::IntegrationInfo<dim>& info);
+ static void boundary(MeshWorker::DoFInfo<dim>& dinfo,
+ typename MeshWorker::IntegrationInfo<dim>& info);
+ static void face(MeshWorker::DoFInfo<dim>& dinfo1,
+ MeshWorker::DoFInfo<dim>& dinfo2,
+ typename MeshWorker::IntegrationInfo<dim>& info1,
+ typename MeshWorker::IntegrationInfo<dim>& 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<dim> 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<dim> dof_info(dof_handler);
-
- // Finally, we need an object that
- // assembles the local matrix into
- // the global matrix.
- MeshWorker::Assembler::MatrixSimple<SparseMatrix<double> > 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 <tt>dof_handler</tt>
- // here in order to use the global
- // numbering of degrees of freedom.
- MeshWorker::integration_loop<dim, dim>(
- dof_handler.begin_active(), dof_handler.end(),
- dof_info, info_box,
- &MatrixIntegrator<dim>::cell,
- &MatrixIntegrator<dim>::boundary,
- &MatrixIntegrator<dim>::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 <int dim>
-void
-Step39<dim>::assemble_mg_matrix()
-{
- MeshWorker::IntegrationInfoBox<dim> info_box;
- UpdateFlags update_flags = update_values | update_gradients;
- info_box.add_update_flags_all(update_flags);
- info_box.initialize(fe, mapping);
-
- MeshWorker::DoFInfo<dim> 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<SparseMatrix<double> > 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
- // <tt>mg_dof_handler</tt>, since
- // we need the degrees of freedom
- // on each level, not the global
- // numbering.
- MeshWorker::integration_loop<dim, dim> (
- mg_dof_handler.begin(), mg_dof_handler.end(),
- dof_info, info_box,
- &MatrixIntegrator<dim>::cell,
- &MatrixIntegrator<dim>::boundary,
- &MatrixIntegrator<dim>::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 <int dim>
-void
-Step39<dim>::assemble_right_hand_side()
-{
- MeshWorker::IntegrationInfoBox<dim> 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<dim> 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<Vector<double> > assembler;
- NamedData<Vector<double>* > data;
- Vector<double>* rhs = &right_hand_side;
- data.add(rhs, "RHS");
- assembler.initialize(data);
-
- MeshWorker::integration_loop<dim, dim>(
- dof_handler.begin_active(), dof_handler.end(),
- dof_info, info_box,
- &RHSIntegrator<dim>::cell,
- &RHSIntegrator<dim>::boundary,
- &RHSIntegrator<dim>::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 <int dim>
-void
-Step39<dim>::solve()
-{
- // The solver of choice is
- // conjugate gradient.
- SolverControl control(1000, 1.e-12);
- SolverCG<Vector<double> > 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<Vector<double> > mg_transfer;
- mg_transfer.build_matrices(mg_dof_handler);
-
- // Then, we need an exact solver
- // for the matrix on the coarsest
- // level.
- FullMatrix<double> coarse_matrix;
- coarse_matrix.copy_from (mg_matrix[0]);
- MGCoarseGridHouseholder<double, Vector<double> > 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<Vector<double> > mem;
- typedef PreconditionSOR<SparseMatrix<double> > RELAXATION;
- MGSmootherRelaxation<SparseMatrix<double>, RELAXATION, Vector<double> >
- 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<SparseMatrix<double>, Vector<double> > mgmatrix(&mg_matrix);
- MGMatrix<SparseMatrix<double>, Vector<double> > mgdown(&mg_matrix_dg_down);
- MGMatrix<SparseMatrix<double>, Vector<double> > mgup(&mg_matrix_dg_up);
-
- // Now, we are ready to set up the
- // V-cycle operator and the
- // multilevel preconditioner.
- Multigrid<Vector<double> > 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<dim, Vector<double>,
- MGTransferPrebuilt<Vector<double> > >
+ // 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 <int dim>
+ void MatrixIntegrator<dim>::cell(
+ MeshWorker::DoFInfo<dim>& dinfo,
+ typename MeshWorker::IntegrationInfo<dim>& info)
+ {
+ LocalIntegrators::Laplace::cell_matrix(dinfo.matrix(0,false).matrix, info.fe_values());
+ }
+
+
+ template <int dim>
+ void MatrixIntegrator<dim>::boundary(
+ MeshWorker::DoFInfo<dim>& dinfo,
+ typename MeshWorker::IntegrationInfo<dim>& 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 <int dim>
+ void MatrixIntegrator<dim>::face(
+ MeshWorker::DoFInfo<dim>& dinfo1,
+ MeshWorker::DoFInfo<dim>& dinfo2,
+ typename MeshWorker::IntegrationInfo<dim>& info1,
+ typename MeshWorker::IntegrationInfo<dim>& 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 <int dim>
+ class RHSIntegrator : public Subscriptor
+ {
+ public:
+ static void cell(MeshWorker::DoFInfo<dim>& dinfo, typename MeshWorker::IntegrationInfo<dim>& info);
+ static void boundary(MeshWorker::DoFInfo<dim>& dinfo, typename MeshWorker::IntegrationInfo<dim>& info);
+ static void face(MeshWorker::DoFInfo<dim>& dinfo1,
+ MeshWorker::DoFInfo<dim>& dinfo2,
+ typename MeshWorker::IntegrationInfo<dim>& info1,
+ typename MeshWorker::IntegrationInfo<dim>& info2);
+ };
+
+
+ template <int dim>
+ void RHSIntegrator<dim>::cell(MeshWorker::DoFInfo<dim>&, typename MeshWorker::IntegrationInfo<dim>&)
+ {}
+
+
+ template <int dim>
+ void RHSIntegrator<dim>::boundary(MeshWorker::DoFInfo<dim>& dinfo, typename MeshWorker::IntegrationInfo<dim>& info)
+ {
+ const FEValuesBase<dim>& fe = info.fe_values();
+ Vector<double>& local_vector = dinfo.vector(0).block(0);
+
+ std::vector<double> 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<fe.n_quadrature_points;++k)
+ for (unsigned int i=0; i<fe.dofs_per_cell; ++i)
+ local_vector(i) += (- fe.shape_value(i,k) * penalty * boundary_values[k]
+ + (fe.normal_vector(k) * fe.shape_grad(i,k)) * boundary_values[k])
+ * fe.JxW(k);
+ }
+
+
+ template <int dim>
+ void RHSIntegrator<dim>::face(MeshWorker::DoFInfo<dim>&,
+ MeshWorker::DoFInfo<dim>&,
+ typename MeshWorker::IntegrationInfo<dim>&,
+ typename MeshWorker::IntegrationInfo<dim>&)
+ {}
+
+
+ // 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 <int dim>
+ class Estimator : public Subscriptor
+ {
+ public:
+ static void cell(MeshWorker::DoFInfo<dim>& dinfo, typename MeshWorker::IntegrationInfo<dim>& info);
+ static void boundary(MeshWorker::DoFInfo<dim>& dinfo, typename MeshWorker::IntegrationInfo<dim>& info);
+ static void face(MeshWorker::DoFInfo<dim>& dinfo1,
+ MeshWorker::DoFInfo<dim>& dinfo2,
+ typename MeshWorker::IntegrationInfo<dim>& info1,
+ typename MeshWorker::IntegrationInfo<dim>& info2);
+ };
+
+
+ // The cell contribution is the
+ // Laplacian of the discrete
+ // solution, since the right hand
+ // side is zero.
+ template <int dim>
+ void Estimator<dim>::cell(MeshWorker::DoFInfo<dim>& dinfo, typename MeshWorker::IntegrationInfo<dim>& info)
+ {
+ const FEValuesBase<dim>& fe = info.fe_values();
+
+ const std::vector<Tensor<2,dim> >& DDuh = info.hessians[0][0];
+ for (unsigned k=0;k<fe.n_quadrature_points;++k)
+ {
+ const double t = dinfo.cell->diameter() * 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 <int dim>
+ void Estimator<dim>::boundary(MeshWorker::DoFInfo<dim>& dinfo, typename MeshWorker::IntegrationInfo<dim>& info)
+ {
+ const FEValuesBase<dim>& fe = info.fe_values();
+
+ std::vector<double> boundary_values(fe.n_quadrature_points);
+ exact_solution.value_list(fe.get_quadrature_points(), boundary_values);
+
+ const std::vector<double>& 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<fe.n_quadrature_points;++k)
+ dinfo.value(0) += penalty * (boundary_values[k] - uh[k]) * (boundary_values[k] - uh[k])
+ * fe.JxW(k);
+ dinfo.value(0) = std::sqrt(dinfo.value(0));
+ }
+
+
+ // Finally, on interior faces, the
+ // estimator consists of the jumps of
+ // the solution and its normal
+ // derivative, weighted appropriately.
+ template <int dim>
+ void Estimator<dim>::face(MeshWorker::DoFInfo<dim>& dinfo1,
+ MeshWorker::DoFInfo<dim>& dinfo2,
+ typename MeshWorker::IntegrationInfo<dim>& info1,
+ typename MeshWorker::IntegrationInfo<dim>& info2)
+ {
+ const FEValuesBase<dim>& fe = info1.fe_values();
+ const std::vector<double>& uh1 = info1.values[0][0];
+ const std::vector<double>& uh2 = info2.values[0][0];
+ const std::vector<Tensor<1,dim> >& Duh1 = info1.gradients[0][0];
+ const std::vector<Tensor<1,dim> >& 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<fe.n_quadrature_points;++k)
+ {
+ double diff1 = uh1[k] - uh2[k];
+ double diff2 = fe.normal_vector(k) * Duh1[k] - fe.normal_vector(k) * Duh2[k];
+ dinfo1.value(0) += (penalty * diff1*diff1 + h * diff2*diff2)
+ * fe.JxW(k);
+ }
+ dinfo1.value(0) = std::sqrt(dinfo1.value(0));
+ dinfo2.value(0) = dinfo1.value(0);
+ }
+
+ // Finally we have an integrator for
+ // the error. Since the energy norm
+ // for discontinuous Galerkin
+ // problems not only involves the
+ // difference of the gradient inside
+ // the cells, but also the jump terms
+ // across faces and at the boundary,
+ // we cannot just use
+ // VectorTools::integrate_difference().
+ // Instead, we use the MeshWorker
+ // interface to compute the error
+ // ourselves.
+
+ // There are several different ways
+ // to define this energy norm, but
+ // all of them are equivalent to each
+ // other uniformly with mesh size
+ // (some not uniformly with
+ // polynomial degree). Here, we
+ // choose
+ // @f[
+ // \|u\|_{1,h} = \sum_{K\in \mathbb
+ // T_h} \|\nabla u\|_K^2
+ // + \sum_{F \in F_h^i}
+ // 4\sigma_F\|\{\!\{ u \mathbf
+ // n\}\!\}\|^2_F
+ // + \sum_{F \in F_h^b} 2\sigma_F\|u\|^2_F
+ // @f]
+
+ template <int dim>
+ class ErrorIntegrator : public Subscriptor
+ {
+ public:
+ static void cell(MeshWorker::DoFInfo<dim>& dinfo, typename MeshWorker::IntegrationInfo<dim>& info);
+ static void boundary(MeshWorker::DoFInfo<dim>& dinfo, typename MeshWorker::IntegrationInfo<dim>& info);
+ static void face(MeshWorker::DoFInfo<dim>& dinfo1,
+ MeshWorker::DoFInfo<dim>& dinfo2,
+ typename MeshWorker::IntegrationInfo<dim>& info1,
+ typename MeshWorker::IntegrationInfo<dim>& 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
+ // <i>L<sup>2</sup></i>-error in the
+ // same loop. Obviously, this one
+ // does not have any jump terms and
+ // only appears in the integration on
+ // cells.
+ template <int dim>
+ void ErrorIntegrator<dim>::cell(
+ MeshWorker::DoFInfo<dim>& dinfo,
+ typename MeshWorker::IntegrationInfo<dim>& info)
+ {
+ const FEValuesBase<dim>& fe = info.fe_values();
+ std::vector<Tensor<1,dim> > exact_gradients(fe.n_quadrature_points);
+ std::vector<double> 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<Tensor<1,dim> >& Duh = info.gradients[0][0];
+ const std::vector<double>& uh = info.values[0][0];
+
+ for (unsigned k=0;k<fe.n_quadrature_points;++k)
+ {
+ double sum = 0;
+ for (unsigned int d=0;d<dim;++d)
+ {
+ const double diff = exact_gradients[k][d] - Duh[k][d];
+ sum += diff*diff;
+ }
+ const double diff = exact_values[k] - uh[k];
+ dinfo.value(0) += sum * fe.JxW(k);
+ dinfo.value(1) += diff*diff * fe.JxW(k);
+ }
+ dinfo.value(0) = std::sqrt(dinfo.value(0));
+ dinfo.value(1) = std::sqrt(dinfo.value(1));
+ }
+
+
+ template <int dim>
+ void ErrorIntegrator<dim>::boundary(
+ MeshWorker::DoFInfo<dim>& dinfo,
+ typename MeshWorker::IntegrationInfo<dim>& info)
+ {
+ const FEValuesBase<dim>& fe = info.fe_values();
+
+ std::vector<double> exact_values(fe.n_quadrature_points);
+ exact_solution.value_list(fe.get_quadrature_points(), exact_values);
+
+ const std::vector<double>& 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<fe.n_quadrature_points;++k)
+ {
+ const double diff = exact_values[k] - uh[k];
+ dinfo.value(0) += penalty * diff * diff * fe.JxW(k);
+ }
+ dinfo.value(0) = std::sqrt(dinfo.value(0));
+ }
+
+
+ template <int dim>
+ void ErrorIntegrator<dim>::face(
+ MeshWorker::DoFInfo<dim>& dinfo1,
+ MeshWorker::DoFInfo<dim>& dinfo2,
+ typename MeshWorker::IntegrationInfo<dim>& info1,
+ typename MeshWorker::IntegrationInfo<dim>& info2)
+ {
+ const FEValuesBase<dim>& fe = info1.fe_values();
+ const std::vector<double>& uh1 = info1.values[0][0];
+ const std::vector<double>& 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<fe.n_quadrature_points;++k)
+ {
+ double diff = uh1[k] - uh2[k];
+ dinfo1.value(0) += (penalty * diff*diff)
+ * fe.JxW(k);
+ }
+ dinfo1.value(0) = std::sqrt(dinfo1.value(0));
+ dinfo2.value(0) = dinfo1.value(0);
+ }
+
+
+
+ // @sect3{The main class}
+
+ // This class does the main job, like
+ // in previous examples. For a
+ // description of the functions
+ // declared here, please refer to
+ // the implementation below.
+ template <int dim>
+ class InteriorPenaltyProblem
+ {
+ public:
+ typedef MeshWorker::IntegrationInfo<dim> CellInfo;
+
+ InteriorPenaltyProblem(const FiniteElement<dim>& 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<dim> triangulation;
+ const MappingQ1<dim> mapping;
+ const FiniteElement<dim>& fe;
+ MGDoFHandler<dim> mg_dof_handler;
+ DoFHandler<dim>& dof_handler;
+
+ // Then, we have the matrices and
+ // vectors related to the global
+ // discrete system.
+ SparsityPattern sparsity;
+ SparseMatrix<double> matrix;
+ Vector<double> solution;
+ Vector<double> right_hand_side;
+ BlockVector<double> 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<SparsityPattern> mg_sparsity;
+ MGLevelObject<SparseMatrix<double> > 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<SparsityPattern> mg_sparsity_dg_interface;
+ // The flux matrix at the
+ // refinement edge, coupling fine
+ // level degrees of freedom to
+ // coarse level.
+ MGLevelObject<SparseMatrix<double> > mg_matrix_dg_down;
+ // The transpose of the flux
+ // matrix at the refinement edge,
+ // coupling coarse level degrees
+ // of freedom to fine level.
+ MGLevelObject<SparseMatrix<double> > 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 <int dim>
+ InteriorPenaltyProblem<dim>::InteriorPenaltyProblem(const FiniteElement<dim>& 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 <int dim>
+ void
+ InteriorPenaltyProblem<dim>::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
+ // <tt>clear()</tt> 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 <int dim>
+ void
+ InteriorPenaltyProblem<dim>::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<dim> 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<dim> dof_info(dof_handler);
+
+ // Finally, we need an object that
+ // assembles the local matrix into
+ // the global matrix.
+ MeshWorker::Assembler::MatrixSimple<SparseMatrix<double> > 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 <tt>dof_handler</tt>
+ // here in order to use the global
+ // numbering of degrees of freedom.
+ MeshWorker::integration_loop<dim, dim>(
+ dof_handler.begin_active(), dof_handler.end(),
+ dof_info, info_box,
+ &MatrixIntegrator<dim>::cell,
+ &MatrixIntegrator<dim>::boundary,
+ &MatrixIntegrator<dim>::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 <int dim>
+ void
+ InteriorPenaltyProblem<dim>::assemble_mg_matrix()
+ {
+ MeshWorker::IntegrationInfoBox<dim> info_box;
+ UpdateFlags update_flags = update_values | update_gradients;
+ info_box.add_update_flags_all(update_flags);
+ info_box.initialize(fe, mapping);
+
+ MeshWorker::DoFInfo<dim> 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<SparseMatrix<double> > 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
+ // <tt>mg_dof_handler</tt>, since
+ // we need the degrees of freedom
+ // on each level, not the global
+ // numbering.
+ MeshWorker::integration_loop<dim, dim> (
+ mg_dof_handler.begin(), mg_dof_handler.end(),
+ dof_info, info_box,
+ &MatrixIntegrator<dim>::cell,
+ &MatrixIntegrator<dim>::boundary,
+ &MatrixIntegrator<dim>::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 <int dim>
+ void
+ InteriorPenaltyProblem<dim>::assemble_right_hand_side()
+ {
+ MeshWorker::IntegrationInfoBox<dim> 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<dim> 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<Vector<double> > assembler;
+ NamedData<Vector<double>* > data;
+ Vector<double>* rhs = &right_hand_side;
+ data.add(rhs, "RHS");
+ assembler.initialize(data);
+
+ MeshWorker::integration_loop<dim, dim>(
+ dof_handler.begin_active(), dof_handler.end(),
+ dof_info, info_box,
+ &RHSIntegrator<dim>::cell,
+ &RHSIntegrator<dim>::boundary,
+ &RHSIntegrator<dim>::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 <int dim>
+ void
+ InteriorPenaltyProblem<dim>::solve()
+ {
+ // The solver of choice is
+ // conjugate gradient.
+ SolverControl control(1000, 1.e-12);
+ SolverCG<Vector<double> > 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<Vector<double> > mg_transfer;
+ mg_transfer.build_matrices(mg_dof_handler);
+
+ // Then, we need an exact solver
+ // for the matrix on the coarsest
+ // level.
+ FullMatrix<double> coarse_matrix;
+ coarse_matrix.copy_from (mg_matrix[0]);
+ MGCoarseGridHouseholder<double, Vector<double> > 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<Vector<double> > mem;
+ typedef PreconditionSOR<SparseMatrix<double> > RELAXATION;
+ MGSmootherRelaxation<SparseMatrix<double>, RELAXATION, Vector<double> >
+ 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<SparseMatrix<double>, Vector<double> > mgmatrix(&mg_matrix);
+ MGMatrix<SparseMatrix<double>, Vector<double> > mgdown(&mg_matrix_dg_down);
+ MGMatrix<SparseMatrix<double>, Vector<double> > mgup(&mg_matrix_dg_up);
+
+ // Now, we are ready to set up the
+ // V-cycle operator and the
+ // multilevel preconditioner.
+ Multigrid<Vector<double> > 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<dim, Vector<double>,
+ MGTransferPrebuilt<Vector<double> > >
preconditioner(mg_dof_handler, mg, mg_transfer);
// and use it to solve the system.
solver.solve(matrix, solution, right_hand_side, preconditioner);
// also have an input vector.
template <int dim>
double
-Step39<dim>::estimate()
+InteriorPenaltyProblem<dim>::estimate()
{
// The results of the estimator are
// stored in a vector with one
// tampering with them.
std::vector<unsigned int> 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<dim>::active_cell_iterator cell = triangulation.begin_active();
// solution we just computed.
NamedData<Vector<double>* > solution_data;
solution_data.add(&solution, "solution");
-
+
// Then, we tell the Meshworker::VectorSelector
// for cells, that we need the
// second derivatives of this
// derivatives we requested above.
info_box.add_update_flags_boundary(update_quadrature_points);
info_box.initialize(fe, mapping, solution_data);
-
+
MeshWorker::DoFInfo<dim> 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<double> assembler;
+ MeshWorker::Assembler::CellsAndFaces<double> assembler;
NamedData<BlockVector<double>* > out_data;
BlockVector<double>* est = &estimates;
out_data.add(est, "cells");
assembler.initialize(out_data, false);
-
+
MeshWorker::integration_loop<dim, dim> (
dof_handler.begin_active(), dof_handler.end(),
dof_info, info_box,
// needs two blocks here.
template <int dim>
void
-Step39<dim>::error()
+InteriorPenaltyProblem<dim>::error()
{
BlockVector<double> errors(2);
errors.block(0).reinit(triangulation.n_active_cells());
NamedData<Vector<double>* > 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<dim> dof_info(dof_handler);
-
- MeshWorker::Assembler::CellsAndFaces<double> assembler;
+
+ MeshWorker::Assembler::CellsAndFaces<double> assembler;
NamedData<BlockVector<double>* > out_data;
BlockVector<double>* est = &errors;
out_data.add(est, "cells");
assembler.initialize(out_data, false);
-
+
MeshWorker::integration_loop<dim, dim> (
dof_handler.begin_active(), dof_handler.end(),
dof_info, info_box,
// Some graphical output
template <int dim>
-void Step39<dim>::output_results (const unsigned int cycle) const
+void InteriorPenaltyProblem<dim>::output_results (const unsigned int cycle) const
{
// Output of the solution in
// gnuplot format.
deallog << "Writing solution to <" << filename << ">..."
<< std::endl << std::endl;
std::ofstream gnuplot_output (filename.c_str());
-
+
DataOut<dim> 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);
}
// examples.
template <int dim>
void
-Step39<dim>::run(unsigned int n_steps)
+InteriorPenaltyProblem<dim>::run(unsigned int n_steps)
{
deallog << "Element: " << fe.get_name() << std::endl;
for (unsigned int s=0;s<n_steps;++s)
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<triangulation.n_levels();++l)
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);
}