/* $Id$ */
/* */
-/* Copyright (C) 2002, 2003, 2004, 2006, 2007, 2008, 2009, 2010 by the deal.II authors */
+/* Copyright (C) 2002, 2003, 2004, 2006, 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 */
// The last step is as in all
// previous programs:
-using namespace dealii;
-
- // The first thing we have here is a helper
- // function that computes an even power $|v|^n$
- // of a vector $v$, by evaluating
- // $(v\cdot v)^{n/2}$. We need this in the
- // computations below where we do not want to
- // dwell on the fact that the gradient of the
- // solution is actually a scalar in the 1d
- // situation we consider in this program (in
- // 1d, the gradient is a vector with a single
- // element, which is easily extracted). Small
- // tricks like this make it significantly
- // simpler to later extend a program so that
- // it also runs in higher space dimensions.
- //
- // While the implementation of the function
- // is obvious, note the assertion at the
- // beginning of the function body, which
- // makes sure that the exponent is indeed an
- // even number (here, we use that <code>n/2</code> is
- // computed in integer arithmetic, i.e. any
- // remainder of the division is
- // lost). <code>ExcMessage</code> is a pre-defined
- // exception class that takes a string
- // argument explaining what goes wrong. It is
- // a simpler way to declare exceptions than
- // the ones shown in step-9 and step-13/14
- // where we explicitly declared exception
- // classes. However, by using a generic
- // exception class, we lose the ability to
- // attach additional information at run-time
- // to the exception message, such as the
- // value of the variable <code>n</code>. By following
- // the way explained in above example
- // programs, adding this feature is simple,
- // though.
-template <int dim>
-inline
-double gradient_power (const Tensor<1,dim> &v,
- const unsigned int n)
+namespace Step15
{
- Assert ((n/2)*2 == n, ExcMessage ("Value of 'n' must be even"));
- double p = 1;
- for (unsigned int k=0; k<n; k+=2)
- p *= (v*v);
- return p;
-}
-
-
+ using namespace dealii;
+
+ // The first thing we have here is a helper
+ // function that computes an even power $|v|^n$
+ // of a vector $v$, by evaluating
+ // $(v\cdot v)^{n/2}$. We need this in the
+ // computations below where we do not want to
+ // dwell on the fact that the gradient of the
+ // solution is actually a scalar in the 1d
+ // situation we consider in this program (in
+ // 1d, the gradient is a vector with a single
+ // element, which is easily extracted). Small
+ // tricks like this make it significantly
+ // simpler to later extend a program so that
+ // it also runs in higher space dimensions.
+ //
+ // While the implementation of the function
+ // is obvious, note the assertion at the
+ // beginning of the function body, which
+ // makes sure that the exponent is indeed an
+ // even number (here, we use that <code>n/2</code> is
+ // computed in integer arithmetic, i.e. any
+ // remainder of the division is
+ // lost). <code>ExcMessage</code> is a pre-defined
+ // exception class that takes a string
+ // argument explaining what goes wrong. It is
+ // a simpler way to declare exceptions than
+ // the ones shown in step-9 and step-13/14
+ // where we explicitly declared exception
+ // classes. However, by using a generic
+ // exception class, we lose the ability to
+ // attach additional information at run-time
+ // to the exception message, such as the
+ // value of the variable <code>n</code>. By following
+ // the way explained in above example
+ // programs, adding this feature is simple,
+ // though.
+ template <int dim>
+ inline
+ double gradient_power (const Tensor<1,dim> &v,
+ const unsigned int n)
+ {
+ Assert ((n/2)*2 == n, ExcMessage ("Value of 'n' must be even"));
+ double p = 1;
+ for (unsigned int k=0; k<n; k+=2)
+ p *= (v*v);
+ return p;
+ }
- // Secondly, we declare a class that defines
- // our initial values for the nonlinear
- // iteration. It is a function object,
- // i.e. it has a member operator that returns
- // for a given point the value of the
- // function. The value we return is a random
- // perturbation of the $x^{1/3}$ function
- // which we know is the optimal solution in a
- // larger function space. To make things a
- // little simpler on the optimizer, we return
- // zero if the proposed random value is
- // negative.
- //
- // Note that this class works strictly only
- // for 1d. If the program is to be extended
- // to higher space dimensions, so has to be
- // this class.
-class InitializationValues : public Function<1>
-{
- public:
- InitializationValues () : Function<1>() {}
-
- virtual double value (const Point<1> &p,
- const unsigned int component = 0) const;
-};
-
-
-
- // So here comes the function that implements
- // the function object. The <code>base</code> value is
- // $x^{1/3}$, while <code>random</code> is a random
- // number between -1 and 1 (note that
- // <code>rand()</code> returns a random integer value
- // between zero and <code>RAND_MAX</code>; to convert
- // it to a floating point value between 0 and
- // 2, we have to divide by <code>RAND_MAX</code> and
- // multiply by two -- note that the first
- // multiplication has to happen in floating
- // point arithmetic, so that the division is
- // done in non-truncating floating point mode
- // as well; the final step is then to shift
- // the interval [0,2] to [-1,1]).
- //
- // In a second step, we add the base value
- // and a random value in [-0.1,0.1] together
- // and return it, unless it is less than
- // zero, in which case we take zero.
-double InitializationValues::value (const Point<1> &p,
- const unsigned int) const
-{
- const double base = std::pow(p(0), 1./3.);
- const double random = 2.*rand()/RAND_MAX-1;
- return std::max (base+.1*random, 0.);
-}
+ // Secondly, we declare a class that defines
+ // our initial values for the nonlinear
+ // iteration. It is a function object,
+ // i.e. it has a member operator that returns
+ // for a given point the value of the
+ // function. The value we return is a random
+ // perturbation of the $x^{1/3}$ function
+ // which we know is the optimal solution in a
+ // larger function space. To make things a
+ // little simpler on the optimizer, we return
+ // zero if the proposed random value is
+ // negative.
+ //
+ // Note that this class works strictly only
+ // for 1d. If the program is to be extended
+ // to higher space dimensions, so has to be
+ // this class.
+ class InitializationValues : public Function<1>
+ {
+ public:
+ InitializationValues () : Function<1>() {}
+
+ virtual double value (const Point<1> &p,
+ const unsigned int component = 0) const;
+ };
+
+
+
+ // So here comes the function that implements
+ // the function object. The <code>base</code> value is
+ // $x^{1/3}$, while <code>random</code> is a random
+ // number between -1 and 1 (note that
+ // <code>rand()</code> returns a random integer value
+ // between zero and <code>RAND_MAX</code>; to convert
+ // it to a floating point value between 0 and
+ // 2, we have to divide by <code>RAND_MAX</code> and
+ // multiply by two -- note that the first
+ // multiplication has to happen in floating
+ // point arithmetic, so that the division is
+ // done in non-truncating floating point mode
+ // as well; the final step is then to shift
+ // the interval [0,2] to [-1,1]).
+ //
+ // In a second step, we add the base value
+ // and a random value in [-0.1,0.1] together
+ // and return it, unless it is less than
+ // zero, in which case we take zero.
+ double InitializationValues::value (const Point<1> &p,
+ const unsigned int) const
+ {
+ const double base = std::pow(p(0), 1./3.);
+ const double random = 2.*rand()/RAND_MAX-1;
+ return std::max (base+.1*random, 0.);
+ }
- // Next is the declaration of the main
- // class. As in most of the previous example
- // programs, the public interface of the
- // class consists only of a constructor and a
- // <code>run</code> function that does the actual
- // work. The constructor takes an additional
- // argument that indicates the number of the
- // run we are presently performing. This
- // value is only used at the very end when we
- // generate graphical output with a filename
- // that matches this number.
- //
- // The private section of the class has the
- // usual assortment of functions setting up
- // the computations, doing one nonlinear
- // step, refineming the mesh, doing a line
- // search for step length computations,
- // etc. The <code>energy</code> function computes the
- // value of the optimization functional on an
- // arbitrary finite element function with
- // nodal values given on the <code>DoFHandler</code>
- // given as an argument. Since it does not
- // depend on the state of this object, we
- // declare this function as <code>static</code>.
- //
- // The member variables of this class are
- // what we have seen before, and the
- // variables that characterize the linear
- // system to be solved in the next nonlinear
- // step, as well as the present approximation
- // of the solution.
-template <int dim>
-class MinimizationProblem
-{
- public:
- MinimizationProblem (const unsigned int run_number);
- void run ();
-
- private:
- void initialize_solution ();
- void setup_system_on_mesh ();
- void assemble_step ();
- double line_search (const Vector<double> & update) const;
- void do_step ();
- void output_results () const;
- void refine_grid ();
-
- static double energy (const DoFHandler<dim> &dof_handler,
- const Vector<double> &function);
-
-
- const unsigned int run_number;
-
- Triangulation<dim> triangulation;
-
- FE_Q<dim> fe;
- DoFHandler<dim> dof_handler;
-
- ConstraintMatrix hanging_node_constraints;
-
- SparsityPattern sparsity_pattern;
- SparseMatrix<double> matrix;
-
- Vector<double> present_solution;
- Vector<double> residual;
-};
-
-
-
- // The constructor of this class is actually
- // somewhat boring:
-template <int dim>
-MinimizationProblem<dim>::MinimizationProblem (const unsigned int run_number)
- :
- run_number (run_number),
- fe (1),
- dof_handler (triangulation)
-{}
-
-
- // Then, here is the function that
- // initializes the solution before the first
- // non-linear iteration, by setting the
- // initial values to the random function
- // described above and making sure that the
- // boundary values are set correctly. We will
- // then only seek updates to this function
- // with zero boundary values, so that the
- // boundary values are always correct.
- //
- // Note how we have specialized this function
- // to 1d only. We do this since the second
- // part of the function, where we deal with
- // boundary values, is only correct if we are
- // in 1d. Not generating a general template
- // for this function prevents the compiler
- // from erroneously compiling this function
- // for other space dimensions, then.
-template <>
-void MinimizationProblem<1>::initialize_solution ()
-{
- // The first part is to assign the correct
- // size to the vector, and use library
- // function that takes a function object,
- // and interpolates the given vector living
- // on a <code>DoFHandler</code> to this function
- // object:
- present_solution.reinit (dof_handler.n_dofs());
- VectorTools::interpolate (dof_handler,
- InitializationValues(),
- present_solution);
-
- // Then we still have to make sure that we
- // get the boundary values right. This
- // could have been done inside the
- // <code>InitializationValues</code> class, but it
- // is instructive to see how it can also be
- // done, in particular since it is so
- // simple in 1d. First, start out with an
- // arbitrary cell on level 0, i.e. the
- // coarse mesh:
- DoFHandler<1>::cell_iterator cell;
- cell = dof_handler.begin(0);
- // Then move as far to the left as
- // possible. Note that while in two or more
- // space dimensions, there is is no
- // guarantee as to the coordinate
- // directions of a given face number of a
- // cell, in 1d the zeroth face (and
- // neighbor) is always the one to the left,
- // and the first one the one to the
- // right. Similarly, the zeroth child is
- // the left one, the first child is the
- // right one.
- while (cell->at_boundary(0) == false)
- cell = cell->neighbor(0);
- // Now that we are at the leftmost coarse
- // grid cell, go recursively through its
- // left children until we find a terminal
- // one:
- while (cell->has_children() == true)
- cell = cell->child(0);
- // Then set the value of the solution
- // corresponding to the zeroth degree of
- // freedom and the zeroth vertex of the
- // cell to zero. Note that the zeroth
- // vertex is the left one, and that zero is
- // the only valid second argument to the
- // call to <code>vertex_dof_index</code>, since we
- // have a scalar finite element; thus,
- // there is only a single component.
- present_solution(cell->vertex_dof_index(0,0)) = 0;
-
- // Now do all the same with the right
- // boundary value, and set it to one:
- cell = dof_handler.begin(0);
- while (cell->at_boundary(1) == false)
- cell = cell->neighbor(1);
- while (cell->has_children())
- cell = cell->child(1);
- present_solution(cell->vertex_dof_index(1,0)) = 1;
-}
- // The function that prepares the member
- // variables of this class for assembling the
- // linear system in each nonlinear step is
- // also not very interesting. This has all
- // been shown before in previous example
- // programs. Note, however, that all this
- // works in 1d just as in any other space
- // dimension, and would not require any
- // changes if we were to use the program in
- // another space dimension.
- //
- // Note that this function is only called
- // when the mesh has been changed (or before
- // the first nonlinear step). It only
- // initializes the variables to their right
- // sizes, but since these sizes don't change
- // as long as we don't change the mesh, we
- // can use them for more than just one
- // nonlinear iteration without reinitializing
- // them.
-template <int dim>
-void MinimizationProblem<dim>::setup_system_on_mesh ()
-{
- hanging_node_constraints.clear ();
- DoFTools::make_hanging_node_constraints (dof_handler,
- hanging_node_constraints);
- hanging_node_constraints.close ();
+ // Next is the declaration of the main
+ // class. As in most of the previous example
+ // programs, the public interface of the
+ // class consists only of a constructor and a
+ // <code>run</code> function that does the actual
+ // work. The constructor takes an additional
+ // argument that indicates the number of the
+ // run we are presently performing. This
+ // value is only used at the very end when we
+ // generate graphical output with a filename
+ // that matches this number.
+ //
+ // The private section of the class has the
+ // usual assortment of functions setting up
+ // the computations, doing one nonlinear
+ // step, refineming the mesh, doing a line
+ // search for step length computations,
+ // etc. The <code>energy</code> function computes the
+ // value of the optimization functional on an
+ // arbitrary finite element function with
+ // nodal values given on the <code>DoFHandler</code>
+ // given as an argument. Since it does not
+ // depend on the state of this object, we
+ // declare this function as <code>static</code>.
+ //
+ // The member variables of this class are
+ // what we have seen before, and the
+ // variables that characterize the linear
+ // system to be solved in the next nonlinear
+ // step, as well as the present approximation
+ // of the solution.
+ template <int dim>
+ class MinimizationProblem
+ {
+ public:
+ MinimizationProblem (const unsigned int run_number);
+ void run ();
+
+ private:
+ void initialize_solution ();
+ void setup_system_on_mesh ();
+ void assemble_step ();
+ double line_search (const Vector<double> & update) const;
+ void do_step ();
+ void output_results () const;
+ void refine_grid ();
+
+ static double energy (const DoFHandler<dim> &dof_handler,
+ const Vector<double> &function);
+
+
+ const unsigned int run_number;
+
+ Triangulation<dim> triangulation;
+
+ FE_Q<dim> fe;
+ DoFHandler<dim> dof_handler;
+
+ ConstraintMatrix hanging_node_constraints;
+
+ SparsityPattern sparsity_pattern;
+ SparseMatrix<double> matrix;
+
+ Vector<double> present_solution;
+ Vector<double> residual;
+ };
+
+
+
+ // The constructor of this class is actually
+ // somewhat boring:
+ template <int dim>
+ MinimizationProblem<dim>::MinimizationProblem (const unsigned int run_number)
+ :
+ run_number (run_number),
+ fe (1),
+ dof_handler (triangulation)
+ {}
+
+
+ // Then, here is the function that
+ // initializes the solution before the first
+ // non-linear iteration, by setting the
+ // initial values to the random function
+ // described above and making sure that the
+ // boundary values are set correctly. We will
+ // then only seek updates to this function
+ // with zero boundary values, so that the
+ // boundary values are always correct.
+ //
+ // Note how we have specialized this function
+ // to 1d only. We do this since the second
+ // part of the function, where we deal with
+ // boundary values, is only correct if we are
+ // in 1d. Not generating a general template
+ // for this function prevents the compiler
+ // from erroneously compiling this function
+ // for other space dimensions, then.
+ template <>
+ void MinimizationProblem<1>::initialize_solution ()
+ {
+ // The first part is to assign the correct
+ // size to the vector, and use library
+ // function that takes a function object,
+ // and interpolates the given vector living
+ // on a <code>DoFHandler</code> to this function
+ // object:
+ present_solution.reinit (dof_handler.n_dofs());
+ VectorTools::interpolate (dof_handler,
+ InitializationValues(),
+ present_solution);
+
+ // Then we still have to make sure that we
+ // get the boundary values right. This
+ // could have been done inside the
+ // <code>InitializationValues</code> class, but it
+ // is instructive to see how it can also be
+ // done, in particular since it is so
+ // simple in 1d. First, start out with an
+ // arbitrary cell on level 0, i.e. the
+ // coarse mesh:
+ DoFHandler<1>::cell_iterator cell;
+ cell = dof_handler.begin(0);
+ // Then move as far to the left as
+ // possible. Note that while in two or more
+ // space dimensions, there is is no
+ // guarantee as to the coordinate
+ // directions of a given face number of a
+ // cell, in 1d the zeroth face (and
+ // neighbor) is always the one to the left,
+ // and the first one the one to the
+ // right. Similarly, the zeroth child is
+ // the left one, the first child is the
+ // right one.
+ while (cell->at_boundary(0) == false)
+ cell = cell->neighbor(0);
+ // Now that we are at the leftmost coarse
+ // grid cell, go recursively through its
+ // left children until we find a terminal
+ // one:
+ while (cell->has_children() == true)
+ cell = cell->child(0);
+ // Then set the value of the solution
+ // corresponding to the zeroth degree of
+ // freedom and the zeroth vertex of the
+ // cell to zero. Note that the zeroth
+ // vertex is the left one, and that zero is
+ // the only valid second argument to the
+ // call to <code>vertex_dof_index</code>, since we
+ // have a scalar finite element; thus,
+ // there is only a single component.
+ present_solution(cell->vertex_dof_index(0,0)) = 0;
+
+ // Now do all the same with the right
+ // boundary value, and set it to one:
+ cell = dof_handler.begin(0);
+ while (cell->at_boundary(1) == false)
+ cell = cell->neighbor(1);
+ while (cell->has_children())
+ cell = cell->child(1);
+ present_solution(cell->vertex_dof_index(1,0)) = 1;
+ }
- sparsity_pattern.reinit (dof_handler.n_dofs(),
- dof_handler.n_dofs(),
- dof_handler.max_couplings_between_dofs());
- DoFTools::make_sparsity_pattern (dof_handler, sparsity_pattern);
- hanging_node_constraints.condense (sparsity_pattern);
+ // The function that prepares the member
+ // variables of this class for assembling the
+ // linear system in each nonlinear step is
+ // also not very interesting. This has all
+ // been shown before in previous example
+ // programs. Note, however, that all this
+ // works in 1d just as in any other space
+ // dimension, and would not require any
+ // changes if we were to use the program in
+ // another space dimension.
+ //
+ // Note that this function is only called
+ // when the mesh has been changed (or before
+ // the first nonlinear step). It only
+ // initializes the variables to their right
+ // sizes, but since these sizes don't change
+ // as long as we don't change the mesh, we
+ // can use them for more than just one
+ // nonlinear iteration without reinitializing
+ // them.
+ template <int dim>
+ void MinimizationProblem<dim>::setup_system_on_mesh ()
+ {
+ hanging_node_constraints.clear ();
+ DoFTools::make_hanging_node_constraints (dof_handler,
+ hanging_node_constraints);
+ hanging_node_constraints.close ();
- sparsity_pattern.compress();
-}
+ sparsity_pattern.reinit (dof_handler.n_dofs(),
+ dof_handler.n_dofs(),
+ dof_handler.max_couplings_between_dofs());
+ DoFTools::make_sparsity_pattern (dof_handler, sparsity_pattern);
+ hanging_node_constraints.condense (sparsity_pattern);
+ sparsity_pattern.compress();
+ }
- // Next is the function that assembles the
- // linear system. The first part,
- // initializing various local variables is
- // what we have been doing previously
- // already.
-template <int dim>
-void MinimizationProblem<dim>::assemble_step ()
-{
- // The first two lines of the function
- // clear the matrix and right hand side
- // values of their prior content, which
- // could possibly still be there from the
- // previous nonlinear step.
- matrix.reinit (sparsity_pattern);
- residual.reinit (dof_handler.n_dofs());
-
- // Then we initialize a <code>FEValues</code> object
- // with a 4-point Gauss quadrature
- // formula. This object will be used to
- // compute the values and gradients of the
- // shape functions at the quadrature
- // points, which we need to assemble the
- // matrix and right hand side of the
- // nonlinear step as outlined in the
- // introduction to this example program. In
- // order to compute values and gradients,
- // we need to pass the <code>update_values</code>
- // and <code>update_gradients</code> flags to the
- // constructor, and the
- // <code>update_JxW_values</code> flag for the
- // Jacobian times the weight at a
- // quadrature point. In addition, we need
- // to have the coordinate values of each
- // quadrature point in real space for the
- // $x-u^3$ terms; to get these from the
- // <code>FEValues</code> object, we need to pass it
- // the <code>update_quadrature_points</code> flag.
- //
- // It is a simple calculation to figure out
- // that for linear elements, the integrals
- // in the right hand side semilinear form
- // is a polynomial of sixth order. Thus,
- // the appropriate quadrature formula is
- // the one we have chosen here.
- QGauss<dim> quadrature_formula(4);
- FEValues<dim> fe_values (fe, quadrature_formula,
- update_values | update_gradients |
- update_quadrature_points | update_JxW_values);
-
- // Next, here are the usual two convenience
- // variables, followed by declarations for
- // the local contributions to matrix and
- // right hand side, as well as an array to
- // hold the indices of the local degrees of
- // freedom on each cell:
- const unsigned int dofs_per_cell = fe.dofs_per_cell;
- const unsigned int n_q_points = quadrature_formula.size();
-
- FullMatrix<double> cell_matrix (dofs_per_cell, dofs_per_cell);
- Vector<double> cell_rhs (dofs_per_cell);
-
- std::vector<unsigned int> local_dof_indices (dofs_per_cell);
-
- // The next two variables are needed since
- // the problem we consider is nonlinear,
- // and thus the right hand side depends on
- // the previous solution (in a Newton
- // method, for example, the left hand side
- // matrix would also depend on the previous
- // solution, but as explained in the
- // introduction, we only use a simple
- // gradient-type method in which the matrix
- // is a scaled Laplace-type matrix). In
- // order to compute the values of the
- // integrand for the right hand side, we
- // therefore need to have the values and
- // gradients of the previous solution at
- // the quadrature points. We will get them
- // from the <code>FEValues</code> object above, and
- // will put them into the following two
- // variables:
- std::vector<double> local_solution_values (n_q_points);
- std::vector<Tensor<1,dim> > local_solution_grads (n_q_points);
-
- // Now, here comes the main loop over all
- // the cells of the mesh:
- typename DoFHandler<dim>::active_cell_iterator
- cell = dof_handler.begin_active(),
- endc = dof_handler.end();
- for (; cell!=endc; ++cell)
- {
- // First, clear the objects that hold
- // the local matrix and right hand side
- // contributions for this cell:
- cell_matrix = 0;
- cell_rhs = 0;
-
- // Then initialize the values and
- // gradients of the shape functions at
- // the quadrature points of this cell:
- fe_values.reinit (cell);
-
- // And get the values and gradients of
- // the previous solution at the
- // quadrature points. To get them, we
- // don't actually have to do much,
- // except for giving the <code>FEValues</code>
- // object the global node vector from
- // which to compute this data, and a
- // reference to the objects into which
- // to put them. After the calls, the
- // <code>local_solution_values</code> and
- // <code>local_solution_values</code> variables
- // will contain values and gradients
- // for each of the quadrature points on
- // this cell.
- fe_values.get_function_values (present_solution,
- local_solution_values);
- fe_values.get_function_grads (present_solution,
- local_solution_grads);
-
- // Then loop over all quadrature
- // points:
- for (unsigned int q_point=0; q_point<n_q_points; ++q_point)
- {
- // Have convenience variables for
- // the values and gradient of the
- // solution at the present
- // quadrature point, as well as the
- // location in real space of this
- // quadrature point, and of the
- // expression $x-u^3$, since it
- // appears so often:
- const double u = local_solution_values[q_point],
- x = fe_values.quadrature_point(q_point)(0);
- const double x_minus_u3 = (x-std::pow(u,3));
- const Tensor<1,dim> u_prime = local_solution_grads[q_point];
-
- // Then do the double loop over all
- // shape functions to compute the
- // local contribution to the
- // matrix. The terms are simple
- // equivalents of the formula
- // stated in the introduction. Note
- // how we extract the size of an
- // element from the iterator to the
- // present cell:
- for (unsigned int i=0; i<dofs_per_cell; ++i)
- for (unsigned int j=0; j<dofs_per_cell; ++j)
- cell_matrix(i,j)
- += (fe_values.shape_grad(i,q_point) *
- fe_values.shape_grad(j,q_point) *
- cell->diameter() *
- cell->diameter()
- +
- fe_values.shape_value(i,q_point) *
- fe_values.shape_value(j,q_point)) *
- fe_values.JxW(q_point);
-
- // And here comes the loop over all
- // local degrees of freedom to form
- // the right hand side. The formula
- // looks a little convoluted, but
- // is again a simple image of what
- // was given in the introduction:
- for (unsigned int i=0; i<dofs_per_cell; ++i)
- cell_rhs(i) += -((6. * x_minus_u3 *
- gradient_power (u_prime, 4) *
- fe_values.shape_value(i,q_point)
- *
- (x_minus_u3 *
- (u_prime *
- fe_values.shape_grad(i,q_point))
- -
- (u_prime*u_prime) * u * u *
- fe_values.shape_value(i,q_point))
- )
- *
- fe_values.JxW(q_point));
- }
- // After summing up all the
- // contributions, we have to transfer
- // them to the global objects. This is
- // done in the same way as always
- // before:
- 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)
- matrix.add (local_dof_indices[i],
- local_dof_indices[j],
- cell_matrix(i,j));
-
- residual(local_dof_indices[i]) += cell_rhs(i);
- }
- }
- // Now that we have all the local
- // contributions summed up, we have to
- // eliminate hanging node constraints and
- // boundary values. Hanging nodes are
- // simple:
- hanging_node_constraints.condense (matrix);
- hanging_node_constraints.condense (residual);
-
- // %Boundary values are, too, but with a
- // twist this time: in all previous example
- // programs, we have used that by default
- // (i.e. unless something else is set), all
- // boundaries have indicator zero. To
- // figure out what boundary indicator a
- // face of a cell had, the library
- // functions would query an iterator
- // designating this face, which would in
- // turn pluck out this value from some of
- // the data structures in the
- // library. Unfortunately, in 1d cells have
- // no faces: these would only be points,
- // and we don't associated anything in the
- // library with points except for their
- // coordinates. Thus there are no face
- // iterators, and no way to figure out
- // which boundary indicator it may have. On
- // the other hand, in 1d, there can only be
- // two boundaries anyway for a connected
- // domain: the left end point and the right
- // end point. And in contrast to the case
- // in higher dimensions, where the
- // (changeable) default is zero for all
- // boundary parts, in 1d the convention is
- // that the left boundary point has
- // indicator zero, while the right boundary
- // point has indicator one. Since there are
- // no face iterators, it is also not
- // possible to change this, but you will
- // hardly ever have to. So in order to
- // assign zero boundary values on both
- // sides, in 1d we not only need to
- // evaluate boundary values for indicator
- // zero, but also for indicator one. If
- // this program is ever going to be run in
- // higher dimensions, then we should only
- // evaluate for indicator zero, which is
- // why we have placed the <code>if</code> statement
- // in front of the second function call.
- //
- // Note that we need zero boundary
- // conditions on both ends, since the space
- // in which search for the solution has
- // fixed boundary conditions zero and one,
- // and we have set the initial values to
- // already satisfy them. Thus, the updates
- // computed in each nonlinear step must
- // have zero boundary values.
- std::map<unsigned int,double> boundary_values;
- VectorTools::interpolate_boundary_values (dof_handler,
- 0,
- ZeroFunction<dim>(),
- boundary_values);
- if (dim == 1)
+ // Next is the function that assembles the
+ // linear system. The first part,
+ // initializing various local variables is
+ // what we have been doing previously
+ // already.
+ template <int dim>
+ void MinimizationProblem<dim>::assemble_step ()
+ {
+ // The first two lines of the function
+ // clear the matrix and right hand side
+ // values of their prior content, which
+ // could possibly still be there from the
+ // previous nonlinear step.
+ matrix.reinit (sparsity_pattern);
+ residual.reinit (dof_handler.n_dofs());
+
+ // Then we initialize a <code>FEValues</code> object
+ // with a 4-point Gauss quadrature
+ // formula. This object will be used to
+ // compute the values and gradients of the
+ // shape functions at the quadrature
+ // points, which we need to assemble the
+ // matrix and right hand side of the
+ // nonlinear step as outlined in the
+ // introduction to this example program. In
+ // order to compute values and gradients,
+ // we need to pass the <code>update_values</code>
+ // and <code>update_gradients</code> flags to the
+ // constructor, and the
+ // <code>update_JxW_values</code> flag for the
+ // Jacobian times the weight at a
+ // quadrature point. In addition, we need
+ // to have the coordinate values of each
+ // quadrature point in real space for the
+ // $x-u^3$ terms; to get these from the
+ // <code>FEValues</code> object, we need to pass it
+ // the <code>update_quadrature_points</code> flag.
+ //
+ // It is a simple calculation to figure out
+ // that for linear elements, the integrals
+ // in the right hand side semilinear form
+ // is a polynomial of sixth order. Thus,
+ // the appropriate quadrature formula is
+ // the one we have chosen here.
+ QGauss<dim> quadrature_formula(4);
+ FEValues<dim> fe_values (fe, quadrature_formula,
+ update_values | update_gradients |
+ update_quadrature_points | update_JxW_values);
+
+ // Next, here are the usual two convenience
+ // variables, followed by declarations for
+ // the local contributions to matrix and
+ // right hand side, as well as an array to
+ // hold the indices of the local degrees of
+ // freedom on each cell:
+ const unsigned int dofs_per_cell = fe.dofs_per_cell;
+ const unsigned int n_q_points = quadrature_formula.size();
+
+ FullMatrix<double> cell_matrix (dofs_per_cell, dofs_per_cell);
+ Vector<double> cell_rhs (dofs_per_cell);
+
+ std::vector<unsigned int> local_dof_indices (dofs_per_cell);
+
+ // The next two variables are needed since
+ // the problem we consider is nonlinear,
+ // and thus the right hand side depends on
+ // the previous solution (in a Newton
+ // method, for example, the left hand side
+ // matrix would also depend on the previous
+ // solution, but as explained in the
+ // introduction, we only use a simple
+ // gradient-type method in which the matrix
+ // is a scaled Laplace-type matrix). In
+ // order to compute the values of the
+ // integrand for the right hand side, we
+ // therefore need to have the values and
+ // gradients of the previous solution at
+ // the quadrature points. We will get them
+ // from the <code>FEValues</code> object above, and
+ // will put them into the following two
+ // variables:
+ std::vector<double> local_solution_values (n_q_points);
+ std::vector<Tensor<1,dim> > local_solution_grads (n_q_points);
+
+ // Now, here comes the main loop over all
+ // the cells of the mesh:
+ typename DoFHandler<dim>::active_cell_iterator
+ cell = dof_handler.begin_active(),
+ endc = dof_handler.end();
+ for (; cell!=endc; ++cell)
+ {
+ // First, clear the objects that hold
+ // the local matrix and right hand side
+ // contributions for this cell:
+ cell_matrix = 0;
+ cell_rhs = 0;
+
+ // Then initialize the values and
+ // gradients of the shape functions at
+ // the quadrature points of this cell:
+ fe_values.reinit (cell);
+
+ // And get the values and gradients of
+ // the previous solution at the
+ // quadrature points. To get them, we
+ // don't actually have to do much,
+ // except for giving the <code>FEValues</code>
+ // object the global node vector from
+ // which to compute this data, and a
+ // reference to the objects into which
+ // to put them. After the calls, the
+ // <code>local_solution_values</code> and
+ // <code>local_solution_values</code> variables
+ // will contain values and gradients
+ // for each of the quadrature points on
+ // this cell.
+ fe_values.get_function_values (present_solution,
+ local_solution_values);
+ fe_values.get_function_grads (present_solution,
+ local_solution_grads);
+
+ // Then loop over all quadrature
+ // points:
+ for (unsigned int q_point=0; q_point<n_q_points; ++q_point)
+ {
+ // Have convenience variables for
+ // the values and gradient of the
+ // solution at the present
+ // quadrature point, as well as the
+ // location in real space of this
+ // quadrature point, and of the
+ // expression $x-u^3$, since it
+ // appears so often:
+ const double u = local_solution_values[q_point],
+ x = fe_values.quadrature_point(q_point)(0);
+ const double x_minus_u3 = (x-std::pow(u,3));
+ const Tensor<1,dim> u_prime = local_solution_grads[q_point];
+
+ // Then do the double loop over all
+ // shape functions to compute the
+ // local contribution to the
+ // matrix. The terms are simple
+ // equivalents of the formula
+ // stated in the introduction. Note
+ // how we extract the size of an
+ // element from the iterator to the
+ // present cell:
+ for (unsigned int i=0; i<dofs_per_cell; ++i)
+ for (unsigned int j=0; j<dofs_per_cell; ++j)
+ cell_matrix(i,j)
+ += (fe_values.shape_grad(i,q_point) *
+ fe_values.shape_grad(j,q_point) *
+ cell->diameter() *
+ cell->diameter()
+ +
+ fe_values.shape_value(i,q_point) *
+ fe_values.shape_value(j,q_point)) *
+ fe_values.JxW(q_point);
+
+ // And here comes the loop over all
+ // local degrees of freedom to form
+ // the right hand side. The formula
+ // looks a little convoluted, but
+ // is again a simple image of what
+ // was given in the introduction:
+ for (unsigned int i=0; i<dofs_per_cell; ++i)
+ cell_rhs(i) += -((6. * x_minus_u3 *
+ gradient_power (u_prime, 4) *
+ fe_values.shape_value(i,q_point)
+ *
+ (x_minus_u3 *
+ (u_prime *
+ fe_values.shape_grad(i,q_point))
+ -
+ (u_prime*u_prime) * u * u *
+ fe_values.shape_value(i,q_point))
+ )
+ *
+ fe_values.JxW(q_point));
+ }
+
+ // After summing up all the
+ // contributions, we have to transfer
+ // them to the global objects. This is
+ // done in the same way as always
+ // before:
+ 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)
+ matrix.add (local_dof_indices[i],
+ local_dof_indices[j],
+ cell_matrix(i,j));
+
+ residual(local_dof_indices[i]) += cell_rhs(i);
+ }
+ }
+
+ // Now that we have all the local
+ // contributions summed up, we have to
+ // eliminate hanging node constraints and
+ // boundary values. Hanging nodes are
+ // simple:
+ hanging_node_constraints.condense (matrix);
+ hanging_node_constraints.condense (residual);
+
+ // %Boundary values are, too, but with a
+ // twist this time: in all previous example
+ // programs, we have used that by default
+ // (i.e. unless something else is set), all
+ // boundaries have indicator zero. To
+ // figure out what boundary indicator a
+ // face of a cell had, the library
+ // functions would query an iterator
+ // designating this face, which would in
+ // turn pluck out this value from some of
+ // the data structures in the
+ // library. Unfortunately, in 1d cells have
+ // no faces: these would only be points,
+ // and we don't associated anything in the
+ // library with points except for their
+ // coordinates. Thus there are no face
+ // iterators, and no way to figure out
+ // which boundary indicator it may have. On
+ // the other hand, in 1d, there can only be
+ // two boundaries anyway for a connected
+ // domain: the left end point and the right
+ // end point. And in contrast to the case
+ // in higher dimensions, where the
+ // (changeable) default is zero for all
+ // boundary parts, in 1d the convention is
+ // that the left boundary point has
+ // indicator zero, while the right boundary
+ // point has indicator one. Since there are
+ // no face iterators, it is also not
+ // possible to change this, but you will
+ // hardly ever have to. So in order to
+ // assign zero boundary values on both
+ // sides, in 1d we not only need to
+ // evaluate boundary values for indicator
+ // zero, but also for indicator one. If
+ // this program is ever going to be run in
+ // higher dimensions, then we should only
+ // evaluate for indicator zero, which is
+ // why we have placed the <code>if</code> statement
+ // in front of the second function call.
+ //
+ // Note that we need zero boundary
+ // conditions on both ends, since the space
+ // in which search for the solution has
+ // fixed boundary conditions zero and one,
+ // and we have set the initial values to
+ // already satisfy them. Thus, the updates
+ // computed in each nonlinear step must
+ // have zero boundary values.
+ std::map<unsigned int,double> boundary_values;
VectorTools::interpolate_boundary_values (dof_handler,
- 1,
- ZeroFunction<dim>(),
- boundary_values);
- Vector<double> dummy (residual.size());
- MatrixTools::apply_boundary_values (boundary_values,
- matrix,
- dummy,
- residual);
-}
-
-
- // Once we have a search (update) direction,
- // we need to figure out how far to go in
- // this direction. This is what line search
- // is good for, and this function does
- // exactly this: compute and return the
- // length of the update step.
- //
- // Since we already know the direction, we
- // only have to solve the one-dimensional
- // problem of minimizing the energy along
- // this direction. Note, however, that in
- // general we do not have the gradient of the
- // energy functional in this direction, so we
- // have to approximate it (and the second
- // derivatives) using finite differences.
- //
- // In most applications, it is sufficient to
- // find an approximate minimizer of this
- // one-dimensional problem, or even just a
- // point that may not be a minimizer but
- // instead just satisfies a few conditions
- // like those of Armijo and Goldstein. The
- // rational for this is generally that
- // evaluating the objective function too
- // often is too expensive. However, here, we
- // are a little more lenient, since the
- // overall run-time is dominated by inverting
- // the system matrix in each nonlinear
- // step. Thus, we will do this minimization
- // by using a fixed number of five Newton
- // steps in this one-dimensional problem, and
- // using a bisection algorithm as a substep
- // in it.
- //
- // As is quite common in step length
- // procedures, this function contains a fair
- // number of heuristics and strategies that
- // might not be obvious at first. Step length
- // determination is notorious for its
- // complications, and this implementation is
- // not an exception. Note that if one tries
- // to omit the special-casing, then one
- // oftentimes encounters situations where the
- // found step length is really not very good.
-template <int dim>
-double
-MinimizationProblem<dim>::line_search (const Vector<double> &update) const
-{
- // Start out with a zero step length:
- double alpha = 0.;
- Vector<double> tmp (present_solution.size());
-
- // Then do at most five Newton steps:
- for (unsigned int step=0; step<5; ++step)
- {
- // At the present location, which is
- // <code>present_solution+alpha*update</code>,
- // evaluate the energy
- tmp = present_solution;
- tmp.add (alpha, update);
- const double f_a = energy (dof_handler, tmp);
-
- // Then determine a finite difference
- // step length <code>dalpha</code>, and also
- // evaluate the energy functional at
- // positions <code>alpha+dalpha</code> and
- // <code>alpha-dalpha</code> along the search
- // direction:
- const double dalpha = (alpha != 0 ? alpha/100 : 0.01);
-
- tmp = present_solution;
- tmp.add (alpha+dalpha, update);
- const double f_a_plus = energy (dof_handler, tmp);
-
- tmp = present_solution;
- tmp.add (alpha-dalpha, update);
- const double f_a_minus = energy (dof_handler, tmp);
-
- // From these three data points, we can
- // compute a finite difference
- // approximation of the first and
- // second derivatives:
- const double f_a_prime = (f_a_plus-f_a_minus) / (2*dalpha);
- const double f_a_doubleprime = ((f_a_plus-2*f_a+f_a_minus) /
- (dalpha*dalpha));
-
- // If the gradient is (relative to the
- // energy value) too small, then this
- // means that we have found a minimum
- // of the energy functional along the
- // search direction. In this case,
- // abort here and return the found step
- // length value:
- if (std::fabs(f_a_prime) < 1e-7*std::fabs(f_a))
- break;
-
- // Alternatively, also abort if the
- // curvature is too small, because we
- // can't compute a Newton step
- // then. This is somewhat
- // unsatisfactory, since we are not at
- // a minimum, and can certainly be
- // improved. There are a number of
- // other strategies for this case,
- // which we leave for interested
- // readers:
- if (std::fabs(f_a_doubleprime) < 1e-7*std::fabs(f_a_prime))
- break;
-
- // Then compute the Newton step as the
- // negative of the inverse Hessian
- // applied to the gradient.
- double step_length = -f_a_prime / f_a_doubleprime;
-
- // And do a number of correcting steps:
- // if the energy at the predicted new
- // position would be larger than at the
- // present position, then halve the
- // step length and try again. If this
- // does not help after three such
- // cycles, then simply give up and use
- // the value we have.
- for (unsigned int i=0; i<3; ++i)
- {
- tmp = present_solution;
- tmp.add (alpha+step_length, update);
- const double e = energy (dof_handler, tmp);
-
- if (e >= f_a)
- step_length /= 2;
- else
- break;
- }
-
- // After all this, update alpha and go
- // on to the next Newton step.
- alpha += step_length;
- }
-
- // Finally, return with the computed step length.
- return alpha;
-}
-
+ 0,
+ ZeroFunction<dim>(),
+ boundary_values);
+ if (dim == 1)
+ VectorTools::interpolate_boundary_values (dof_handler,
+ 1,
+ ZeroFunction<dim>(),
+ boundary_values);
+ Vector<double> dummy (residual.size());
+ MatrixTools::apply_boundary_values (boundary_values,
+ matrix,
+ dummy,
+ residual);
+ }
- // The next function is again a rather boring
- // one: it does one nonlinear step, by
- // calling the function that assembles the
- // linear system, then solving it, computing
- // a step length, and finally updating the
- // solution vector. This should all be mostly
- // self-explanatory, given that we have shown
- // the solution of a linear system before.
-template <int dim>
-void MinimizationProblem<dim>::do_step ()
-{
- assemble_step ();
-
- Vector<double> update (present_solution.size());
+ // Once we have a search (update) direction,
+ // we need to figure out how far to go in
+ // this direction. This is what line search
+ // is good for, and this function does
+ // exactly this: compute and return the
+ // length of the update step.
+ //
+ // Since we already know the direction, we
+ // only have to solve the one-dimensional
+ // problem of minimizing the energy along
+ // this direction. Note, however, that in
+ // general we do not have the gradient of the
+ // energy functional in this direction, so we
+ // have to approximate it (and the second
+ // derivatives) using finite differences.
+ //
+ // In most applications, it is sufficient to
+ // find an approximate minimizer of this
+ // one-dimensional problem, or even just a
+ // point that may not be a minimizer but
+ // instead just satisfies a few conditions
+ // like those of Armijo and Goldstein. The
+ // rational for this is generally that
+ // evaluating the objective function too
+ // often is too expensive. However, here, we
+ // are a little more lenient, since the
+ // overall run-time is dominated by inverting
+ // the system matrix in each nonlinear
+ // step. Thus, we will do this minimization
+ // by using a fixed number of five Newton
+ // steps in this one-dimensional problem, and
+ // using a bisection algorithm as a substep
+ // in it.
+ //
+ // As is quite common in step length
+ // procedures, this function contains a fair
+ // number of heuristics and strategies that
+ // might not be obvious at first. Step length
+ // determination is notorious for its
+ // complications, and this implementation is
+ // not an exception. Note that if one tries
+ // to omit the special-casing, then one
+ // oftentimes encounters situations where the
+ // found step length is really not very good.
+ template <int dim>
+ double
+ MinimizationProblem<dim>::line_search (const Vector<double> &update) const
{
- SolverControl solver_control (residual.size(),
- 1e-2*residual.l2_norm());
- SolverCG<> solver (solver_control);
-
- PreconditionSSOR<> preconditioner;
- preconditioner.initialize(matrix);
-
- solver.solve (matrix, update, residual,
- preconditioner);
- hanging_node_constraints.distribute (update);
+ // Start out with a zero step length:
+ double alpha = 0.;
+ Vector<double> tmp (present_solution.size());
+
+ // Then do at most five Newton steps:
+ for (unsigned int step=0; step<5; ++step)
+ {
+ // At the present location, which is
+ // <code>present_solution+alpha*update</code>,
+ // evaluate the energy
+ tmp = present_solution;
+ tmp.add (alpha, update);
+ const double f_a = energy (dof_handler, tmp);
+
+ // Then determine a finite difference
+ // step length <code>dalpha</code>, and also
+ // evaluate the energy functional at
+ // positions <code>alpha+dalpha</code> and
+ // <code>alpha-dalpha</code> along the search
+ // direction:
+ const double dalpha = (alpha != 0 ? alpha/100 : 0.01);
+
+ tmp = present_solution;
+ tmp.add (alpha+dalpha, update);
+ const double f_a_plus = energy (dof_handler, tmp);
+
+ tmp = present_solution;
+ tmp.add (alpha-dalpha, update);
+ const double f_a_minus = energy (dof_handler, tmp);
+
+ // From these three data points, we can
+ // compute a finite difference
+ // approximation of the first and
+ // second derivatives:
+ const double f_a_prime = (f_a_plus-f_a_minus) / (2*dalpha);
+ const double f_a_doubleprime = ((f_a_plus-2*f_a+f_a_minus) /
+ (dalpha*dalpha));
+
+ // If the gradient is (relative to the
+ // energy value) too small, then this
+ // means that we have found a minimum
+ // of the energy functional along the
+ // search direction. In this case,
+ // abort here and return the found step
+ // length value:
+ if (std::fabs(f_a_prime) < 1e-7*std::fabs(f_a))
+ break;
+
+ // Alternatively, also abort if the
+ // curvature is too small, because we
+ // can't compute a Newton step
+ // then. This is somewhat
+ // unsatisfactory, since we are not at
+ // a minimum, and can certainly be
+ // improved. There are a number of
+ // other strategies for this case,
+ // which we leave for interested
+ // readers:
+ if (std::fabs(f_a_doubleprime) < 1e-7*std::fabs(f_a_prime))
+ break;
+
+ // Then compute the Newton step as the
+ // negative of the inverse Hessian
+ // applied to the gradient.
+ double step_length = -f_a_prime / f_a_doubleprime;
+
+ // And do a number of correcting steps:
+ // if the energy at the predicted new
+ // position would be larger than at the
+ // present position, then halve the
+ // step length and try again. If this
+ // does not help after three such
+ // cycles, then simply give up and use
+ // the value we have.
+ for (unsigned int i=0; i<3; ++i)
+ {
+ tmp = present_solution;
+ tmp.add (alpha+step_length, update);
+ const double e = energy (dof_handler, tmp);
+
+ if (e >= f_a)
+ step_length /= 2;
+ else
+ break;
+ }
+
+ // After all this, update alpha and go
+ // on to the next Newton step.
+ alpha += step_length;
+ }
+
+ // Finally, return with the computed step length.
+ return alpha;
}
- const double step_length = line_search (update);
- present_solution.add (step_length, update);
-}
+ // The next function is again a rather boring
+ // one: it does one nonlinear step, by
+ // calling the function that assembles the
+ // linear system, then solving it, computing
+ // a step length, and finally updating the
+ // solution vector. This should all be mostly
+ // self-explanatory, given that we have shown
+ // the solution of a linear system before.
+ template <int dim>
+ void MinimizationProblem<dim>::do_step ()
+ {
+ assemble_step ();
- // The same holds for the function that
- // outputs the solution in gnuplot format
- // into a file with a name that includes the
- // number of the run we are presently
- // performing.
-template <int dim>
-void
-MinimizationProblem<dim>::output_results () const
-{
- DataOut<dim> data_out;
- data_out.attach_dof_handler (dof_handler);
- data_out.add_data_vector (present_solution, "solution");
- data_out.build_patches ();
-
- std::ostringstream filename;
- filename << "solution-"
- << run_number
- << ".gnuplot"
- << std::ends;
-
- std::ofstream out (filename.str().c_str());
- data_out.write_gnuplot (out);
-}
+ Vector<double> update (present_solution.size());
+ {
+ SolverControl solver_control (residual.size(),
+ 1e-2*residual.l2_norm());
+ SolverCG<> solver (solver_control);
+ PreconditionSSOR<> preconditioner;
+ preconditioner.initialize(matrix);
+ solver.solve (matrix, update, residual,
+ preconditioner);
+ hanging_node_constraints.distribute (update);
+ }
- // The function to compute error indicator
- // and refine the mesh accordingly is a
- // little more interesting. In particular, it
- // shows some more of the techniques usually
- // used in 1d applications. First, note that
- // this again is a specialization that only
- // works in 1d. However, to make later
- // extension to higher space dimensions
- // simpler, we define a constant integer
- // <code>dim</code> at the beginning of the function;
- // by using this constant as template
- // argument in all places, we are actually
- // able to write most of the code as if it
- // were dimension independent, thus
- // minimizing the amount of later changes.
-template <>
-void MinimizationProblem<1>::refine_grid ()
-{
- const unsigned int dim = 1;
-
- Vector<float> error_indicators (triangulation.n_active_cells());
-
- // Then define the quadrature formula, and
- // what values we will want to extract from
- // the solution. Here, we use the two-point
- // trapezoidal rule, i.e. we evaluate the
- // residual only at the end points of the
- // cells. Incidentally, this also makes
- // evaluating the jump terms between cells
- // simpler. Note that for the error
- // indicators, we not only need values and
- // gradients of the solution, but also its
- // second derivatives, as well as the
- // physical location of quadrature points.
- QTrapez<dim> quadrature;
- FEValues<dim> fe_values (fe, quadrature,
- update_values | update_gradients |
- update_hessians |
- update_quadrature_points | update_JxW_values);
-
- // The error indicator formula presented in
- // the introduction requires us to compute
- // jumps of the solution and gradient
- // across cell boundaries. Since the
- // solution itself is continuous, we only
- // need to evaluate the gradient on the
- // neighbor cells. To avoid some of the
- // work needed to reinitialize a
- // <code>FEValues</code> object on a cell, we define
- // another such object here that we will
- // only use for the neighbor cells. The
- // data we need from the side of the
- // present cell is provided by above
- // object.
- FEValues<dim> neighbor_fe_values (fe, quadrature,
- update_gradients);
-
- // Then, as before, we need objects holding
- // values and derivatives of the solution
- // at quadrature points. Here, we also need
- // second derivatives, which is simple,
- // however:
- std::vector<double> local_values (quadrature.size());
- std::vector<Tensor<1,dim> > local_gradients (quadrature.size());
- std::vector<Tensor<2,dim> > local_2nd_derivs (quadrature.size());
-
- // With all this, we can start the loop
- // over all cells. Since we need to write
- // the result for each cell into
- // consecutive elements of a vector, we
- // also keep a running index <code>cell_index</code>
- // that we increase with each cell treated.
- DoFHandler<dim>::active_cell_iterator
- cell = dof_handler.begin_active (),
- endc = dof_handler.end ();
- for (unsigned int cell_index = 0; cell!=endc; ++cell, ++cell_index)
- {
- // After initializing the <code>FEValues</code>
- // object on each cell, use it to
- // evaluate solution and first and
- // second derivatives of it at the
- // quadrature points:
- fe_values.reinit (cell);
- fe_values.get_function_values (present_solution, local_values);
- fe_values.get_function_grads (present_solution, local_gradients);
- fe_values.get_function_2nd_derivatives (present_solution, local_2nd_derivs);
-
- // Given the formula in the
- // introduction, the computation of the
- // cell residuals should actually be
- // relatively obvious. The result,
- // multiplied by the appropriate power
- // of the cell's size is then written
- // into the vector of error indicators.
- //
- // Note that in the following
- // computations, we have already made
- // use of the fact that we are in 1d,
- // since we extract the gradient as a
- // scalar value.
- double cell_residual_norm = 0;
- for (unsigned int q=0; q<quadrature.size(); ++q)
- {
- const double x = fe_values.quadrature_point(q)[0];
- const double u = local_values[q];
- const double u_prime = local_gradients[q][0];
- const double u_doubleprime = local_2nd_derivs[q][0][0];
- const double local_residual_value
- = ((x-u*u*u) * std::pow(u_prime, 4) *
- (u*u*u_prime*u_prime
- +
- 5*(x-u*u*u)*u_doubleprime
- +
- 2*u_prime*(1-3*u*u*u_prime)));
-
- cell_residual_norm += (local_residual_value * local_residual_value *
- fe_values.JxW(q));
- }
- error_indicators(cell_index) = cell_residual_norm *
- cell->diameter() * cell->diameter();
-
- // The next step is to evaluate the
- // jump terms. To make computations
- // somewhat simpler (and to free up the
- // <code>local_*</code> variables for use on
- // neighboring elements), we define
- // some convenience variables for the
- // positions of the left and right cell
- // boundary point, as well as the
- // values and gradients at these
- // points.
- //
- // To be cautious, we don't blindly
- // trust that the trapezoidal rule has
- // its evaluation points as the left
- // and right end point of the cell (it
- // could in principle have them in the
- // reverse order, i.e. the zeroth point
- // is at x=1, and the first one at
- // x=0), and use an assertion to
- // actually check for this. If this
- // would not be the case, an exception
- // of the (predefined) class
- // <code>ExcInternalError</code> would be
- // thrown. Of course, this does not
- // happen in this program, but it shows
- // a way of defensive coding: if you
- // are not sure of an assumption, guard
- // it by a test. This also guards us
- // against possible future changes in
- // the library: the quadrature classes
- // do not promise any particular order
- // of their quadrature points, so the
- // <code>QTrapez</code> class could in principle
- // change the order of its two
- // evaluation points. In that case,
- // your code would tell you that
- // something changed, rather than
- // computing a wrong result when you
- // upgrade to a new version of the
- // library. (The point made here is
- // theoretical: we are not going to
- // change the order of evaluation
- // points; the intent is simply how to
- // add some defensive touches to a
- // program that make sure that it
- // really does what it is hoped to do.)
- //
- // Given that we are now sure that
- // <code>x_left</code> and <code>x_right</code>,
- // extracted from the zeroth and first
- // quadrature point, are indeed the
- // left and right vertex of the cell,
- // we can also be sure that the values
- // we extract for <code>u_left</code> et al. are
- // the ones we expect them to be, since
- // the order of these values must of
- // course match the order of the
- // quadrature points.
- const double x_left = fe_values.quadrature_point(0)[0];
- const double x_right = fe_values.quadrature_point(1)[0];
-
- Assert (x_left == cell->vertex(0)[0], ExcInternalError());
- Assert (x_right == cell->vertex(1)[0], ExcInternalError());
-
- const double u_left = local_values[0];
- const double u_right = local_values[1];
-
- const double u_prime_left = local_gradients[0][0];
- const double u_prime_right = local_gradients[1][0];
-
- // Next, we have to check whether this
- // cell has a left neighbor:
- if (cell->at_boundary(0) == false)
- {
- // If so, find its left
- // neighbor. We do so by asking for
- // the cell that is immediately
- // adjacent to the left (the zeroth
- // neighbor in 1d). However, this
- // may be a cell that in itself has
- // children, so to get to the
- // active left neighbor, we have to
- // recursively check whether that
- // cell has children, and if so
- // take its right child, since that
- // is adjacent to the left of the
- // present cell. Note that unless
- // you are in 1d, there is no safe
- // way to assume that the first
- // child of the zeroth neighbor is
- // indeed adjacent to the present
- // cell. Rather, more than one of
- // the children of a neighbor may
- // be adjacent to the present
- // cell. Also note that in two or
- // higher space dimensions, a
- // neighbor of an active cell may
- // only be at most once refined,
- // since we have the rule that
- // there can only be one hanging
- // node per face. This rule does
- // not exist in 1d: neighboring
- // cells may have totally
- // independent refinement
- // levels. Thus, we really need the
- // <code>while</code> loop, not only an
- // <code>if</code> clause.
- DoFHandler<dim>::cell_iterator left_neighbor = cell->neighbor(0);
- while (left_neighbor->has_children())
- left_neighbor = left_neighbor->child(1);
-
- // With the so-found neighbor,
- // initialize the second
- // <code>FEValues</code> object to it,
- // extract the gradients of the
- // solution there, and from this
- // get the gradient at the
- // interface (this is the first
- // element of <code>local_gradients</code>,
- // since the right end point of the
- // neighbor cell has index 1) as a
- // scalar value (this is the zeroth
- // component of
- // <code>local_gradients[1]</code>.
- neighbor_fe_values.reinit (left_neighbor);
- neighbor_fe_values.get_function_grads (present_solution, local_gradients);
-
- const double neighbor_u_prime_left = local_gradients[1][0];
-
- // Then compute the jump, and add a
- // suitable multiple to the error
- // indicator for this cell:
- const double left_jump = std::pow(x_left-std::pow(u_left,3), 2) *
- (std::pow(neighbor_u_prime_left,5) -
- std::pow(u_prime_left,5));
- error_indicators(cell_index) += left_jump * left_jump *
- cell->diameter();
- }
+ const double step_length = line_search (update);
+ present_solution.add (step_length, update);
+ }
- // Once we have done the left neighbor,
- // we can play exactly the same game
- // with the right neighbor:
- if (cell->at_boundary(1) == false)
- {
- DoFHandler<dim>::cell_iterator right_neighbor = cell->neighbor(1);
- while (right_neighbor->has_children())
- right_neighbor = right_neighbor->child(0);
- neighbor_fe_values.reinit (right_neighbor);
- neighbor_fe_values.get_function_grads (present_solution, local_gradients);
- const double neighbor_u_prime_right = local_gradients[0][0];
+ // The same holds for the function that
+ // outputs the solution in gnuplot format
+ // into a file with a name that includes the
+ // number of the run we are presently
+ // performing.
+ template <int dim>
+ void
+ MinimizationProblem<dim>::output_results () const
+ {
+ DataOut<dim> data_out;
+ data_out.attach_dof_handler (dof_handler);
+ data_out.add_data_vector (present_solution, "solution");
+ data_out.build_patches ();
+
+ std::ostringstream filename;
+ filename << "solution-"
+ << run_number
+ << ".gnuplot"
+ << std::ends;
+
+ std::ofstream out (filename.str().c_str());
+ data_out.write_gnuplot (out);
+ }
- const double right_jump = std::pow(x_right-std::pow(u_right,3), 2) *
- (std::pow(neighbor_u_prime_right,5) -
- std::pow(u_prime_right,5));
- error_indicators(cell_index) += right_jump * right_jump *
- cell->diameter();
- }
- }
- // Now we have all the refinement
- // indicators computed, and want to refine
- // the grid. In contrast to previous
- // examples, however, we would like to
- // transfer the solution vector from the
- // old to the new grid. This is what the
- // <code>SolutionTransfer</code> class is good for,
- // but it requires some preliminary
- // work. First, we need to tag the cells
- // that we want to refine or coarsen, as
- // usual:
- GridRefinement::refine_and_coarsen_fixed_number (triangulation,
- error_indicators,
- 0.3, 0.03);
- // Then, however, we need an additional
- // step: if, for example, you flag a cell
- // that is once more refined than its
- // neighbor, and that neighbor is not
- // flagged for refinement, we would end up
- // with a jump of two refinement levels
- // across a cell interface. In 1d, this
- // would in general be allowed, but not in
- // higher space dimensions, and some mesh
- // smoothing algorithms in 1d may also
- // disallow this. To avoid these
- // situations, the library will silently
- // also have to refine the neighbor cell
- // once. It does so by calling the
- // <code>Triangulation::prepare_coarsening_and_refinement</code>
- // function before actually doing the
- // refinement and coarsening. This function
- // flags a set of additional cells for
- // refinement or coarsening, to enforce
- // rules like the one-hanging-node
- // rule. The cells that are flagged for
- // refinement and coarsening after calling
- // this function are exactly the ones that
- // will actually be refined or
- // coarsened. Since the
- // <code>SolutionTransfer</code> class needs this
- // information in order to store the data
- // from the old mesh and transfer to the
- // new one.
- triangulation.prepare_coarsening_and_refinement();
-
- // With this out of the way, we initialize
- // a <code>SolutionTransfer</code> object with the
- // present <code>DoFHandler</code> and attach the
- // solution vector to it:
- SolutionTransfer<dim> solution_transfer(dof_handler);
- solution_transfer.prepare_for_coarsening_and_refinement (present_solution);
-
- // Then we do the actual refinement, and
- // distribute degrees of freedom on the new
- // mesh:
- triangulation.execute_coarsening_and_refinement ();
- dof_handler.distribute_dofs (fe);
-
- // Finally, we retrieve the old solution
- // interpolated to the new mesh. Since the
- // <code>SolutionTransfer</code> function does not
- // actually store the values of the old
- // solution, but rather indices, we need to
- // preserve the old solution vector until
- // we have gotten the new interpolated
- // values. Thus, we have the new values
- // written into a temporary vector, and
- // only afterwards write them into the
- // solution vector object:
- Vector<double> tmp (dof_handler.n_dofs());
- solution_transfer.interpolate (present_solution, tmp);
- present_solution = tmp;
-
- // Here is some final thing, that is
- // actually unnecessary in 1d, but
- // necessary for higher space dimensions,
- // so we show it anyway: the result of what
- // the <code>SolutionTransfer</code> class provides
- // is a vector that is interpolated from
- // the old to the new mesh. Unfortunately,
- // it does not necessarily have the right
- // values at constrained (hanging) nodes,
- // so we have to fix this up to make the
- // solution conforming again. The simplest
- // way to do this is this:
- hanging_node_constraints.clear ();
- DoFTools::make_hanging_node_constraints (dof_handler,
- hanging_node_constraints);
- hanging_node_constraints.close ();
- hanging_node_constraints.distribute (present_solution);
- // This is wasteful, since we create a
- // <code>ConstraintMatrix</code> object that will be
- // recreated again in the next call to
- // <code>setup_system_on_mesh</code> immediately
- // afterwards. A more efficient
- // implementation would make sure that it
- // is created only once. We don't care so
- // much here, since in 1d there are no
- // constraints, so all of these operations
- // are really cheap, but we do not
- // recommend this as general programming
- // strategy.
-}
+ // The function to compute error indicator
+ // and refine the mesh accordingly is a
+ // little more interesting. In particular, it
+ // shows some more of the techniques usually
+ // used in 1d applications. First, note that
+ // this again is a specialization that only
+ // works in 1d. However, to make later
+ // extension to higher space dimensions
+ // simpler, we define a constant integer
+ // <code>dim</code> at the beginning of the function;
+ // by using this constant as template
+ // argument in all places, we are actually
+ // able to write most of the code as if it
+ // were dimension independent, thus
+ // minimizing the amount of later changes.
+ template <>
+ void MinimizationProblem<1>::refine_grid ()
+ {
+ const unsigned int dim = 1;
+
+ Vector<float> error_indicators (triangulation.n_active_cells());
+
+ // Then define the quadrature formula, and
+ // what values we will want to extract from
+ // the solution. Here, we use the two-point
+ // trapezoidal rule, i.e. we evaluate the
+ // residual only at the end points of the
+ // cells. Incidentally, this also makes
+ // evaluating the jump terms between cells
+ // simpler. Note that for the error
+ // indicators, we not only need values and
+ // gradients of the solution, but also its
+ // second derivatives, as well as the
+ // physical location of quadrature points.
+ QTrapez<dim> quadrature;
+ FEValues<dim> fe_values (fe, quadrature,
+ update_values | update_gradients |
+ update_hessians |
+ update_quadrature_points | update_JxW_values);
+
+ // The error indicator formula presented in
+ // the introduction requires us to compute
+ // jumps of the solution and gradient
+ // across cell boundaries. Since the
+ // solution itself is continuous, we only
+ // need to evaluate the gradient on the
+ // neighbor cells. To avoid some of the
+ // work needed to reinitialize a
+ // <code>FEValues</code> object on a cell, we define
+ // another such object here that we will
+ // only use for the neighbor cells. The
+ // data we need from the side of the
+ // present cell is provided by above
+ // object.
+ FEValues<dim> neighbor_fe_values (fe, quadrature,
+ update_gradients);
+
+ // Then, as before, we need objects holding
+ // values and derivatives of the solution
+ // at quadrature points. Here, we also need
+ // second derivatives, which is simple,
+ // however:
+ std::vector<double> local_values (quadrature.size());
+ std::vector<Tensor<1,dim> > local_gradients (quadrature.size());
+ std::vector<Tensor<2,dim> > local_2nd_derivs (quadrature.size());
+
+ // With all this, we can start the loop
+ // over all cells. Since we need to write
+ // the result for each cell into
+ // consecutive elements of a vector, we
+ // also keep a running index <code>cell_index</code>
+ // that we increase with each cell treated.
+ DoFHandler<dim>::active_cell_iterator
+ cell = dof_handler.begin_active (),
+ endc = dof_handler.end ();
+ for (unsigned int cell_index = 0; cell!=endc; ++cell, ++cell_index)
+ {
+ // After initializing the <code>FEValues</code>
+ // object on each cell, use it to
+ // evaluate solution and first and
+ // second derivatives of it at the
+ // quadrature points:
+ fe_values.reinit (cell);
+ fe_values.get_function_values (present_solution, local_values);
+ fe_values.get_function_grads (present_solution, local_gradients);
+ fe_values.get_function_2nd_derivatives (present_solution, local_2nd_derivs);
+
+ // Given the formula in the
+ // introduction, the computation of the
+ // cell residuals should actually be
+ // relatively obvious. The result,
+ // multiplied by the appropriate power
+ // of the cell's size is then written
+ // into the vector of error indicators.
+ //
+ // Note that in the following
+ // computations, we have already made
+ // use of the fact that we are in 1d,
+ // since we extract the gradient as a
+ // scalar value.
+ double cell_residual_norm = 0;
+ for (unsigned int q=0; q<quadrature.size(); ++q)
+ {
+ const double x = fe_values.quadrature_point(q)[0];
+ const double u = local_values[q];
+ const double u_prime = local_gradients[q][0];
+ const double u_doubleprime = local_2nd_derivs[q][0][0];
+ const double local_residual_value
+ = ((x-u*u*u) * std::pow(u_prime, 4) *
+ (u*u*u_prime*u_prime
+ +
+ 5*(x-u*u*u)*u_doubleprime
+ +
+ 2*u_prime*(1-3*u*u*u_prime)));
+
+ cell_residual_norm += (local_residual_value * local_residual_value *
+ fe_values.JxW(q));
+ }
+ error_indicators(cell_index) = cell_residual_norm *
+ cell->diameter() * cell->diameter();
+
+ // The next step is to evaluate the
+ // jump terms. To make computations
+ // somewhat simpler (and to free up the
+ // <code>local_*</code> variables for use on
+ // neighboring elements), we define
+ // some convenience variables for the
+ // positions of the left and right cell
+ // boundary point, as well as the
+ // values and gradients at these
+ // points.
+ //
+ // To be cautious, we don't blindly
+ // trust that the trapezoidal rule has
+ // its evaluation points as the left
+ // and right end point of the cell (it
+ // could in principle have them in the
+ // reverse order, i.e. the zeroth point
+ // is at x=1, and the first one at
+ // x=0), and use an assertion to
+ // actually check for this. If this
+ // would not be the case, an exception
+ // of the (predefined) class
+ // <code>ExcInternalError</code> would be
+ // thrown. Of course, this does not
+ // happen in this program, but it shows
+ // a way of defensive coding: if you
+ // are not sure of an assumption, guard
+ // it by a test. This also guards us
+ // against possible future changes in
+ // the library: the quadrature classes
+ // do not promise any particular order
+ // of their quadrature points, so the
+ // <code>QTrapez</code> class could in principle
+ // change the order of its two
+ // evaluation points. In that case,
+ // your code would tell you that
+ // something changed, rather than
+ // computing a wrong result when you
+ // upgrade to a new version of the
+ // library. (The point made here is
+ // theoretical: we are not going to
+ // change the order of evaluation
+ // points; the intent is simply how to
+ // add some defensive touches to a
+ // program that make sure that it
+ // really does what it is hoped to do.)
+ //
+ // Given that we are now sure that
+ // <code>x_left</code> and <code>x_right</code>,
+ // extracted from the zeroth and first
+ // quadrature point, are indeed the
+ // left and right vertex of the cell,
+ // we can also be sure that the values
+ // we extract for <code>u_left</code> et al. are
+ // the ones we expect them to be, since
+ // the order of these values must of
+ // course match the order of the
+ // quadrature points.
+ const double x_left = fe_values.quadrature_point(0)[0];
+ const double x_right = fe_values.quadrature_point(1)[0];
+
+ Assert (x_left == cell->vertex(0)[0], ExcInternalError());
+ Assert (x_right == cell->vertex(1)[0], ExcInternalError());
+
+ const double u_left = local_values[0];
+ const double u_right = local_values[1];
+
+ const double u_prime_left = local_gradients[0][0];
+ const double u_prime_right = local_gradients[1][0];
+
+ // Next, we have to check whether this
+ // cell has a left neighbor:
+ if (cell->at_boundary(0) == false)
+ {
+ // If so, find its left
+ // neighbor. We do so by asking for
+ // the cell that is immediately
+ // adjacent to the left (the zeroth
+ // neighbor in 1d). However, this
+ // may be a cell that in itself has
+ // children, so to get to the
+ // active left neighbor, we have to
+ // recursively check whether that
+ // cell has children, and if so
+ // take its right child, since that
+ // is adjacent to the left of the
+ // present cell. Note that unless
+ // you are in 1d, there is no safe
+ // way to assume that the first
+ // child of the zeroth neighbor is
+ // indeed adjacent to the present
+ // cell. Rather, more than one of
+ // the children of a neighbor may
+ // be adjacent to the present
+ // cell. Also note that in two or
+ // higher space dimensions, a
+ // neighbor of an active cell may
+ // only be at most once refined,
+ // since we have the rule that
+ // there can only be one hanging
+ // node per face. This rule does
+ // not exist in 1d: neighboring
+ // cells may have totally
+ // independent refinement
+ // levels. Thus, we really need the
+ // <code>while</code> loop, not only an
+ // <code>if</code> clause.
+ DoFHandler<dim>::cell_iterator left_neighbor = cell->neighbor(0);
+ while (left_neighbor->has_children())
+ left_neighbor = left_neighbor->child(1);
+
+ // With the so-found neighbor,
+ // initialize the second
+ // <code>FEValues</code> object to it,
+ // extract the gradients of the
+ // solution there, and from this
+ // get the gradient at the
+ // interface (this is the first
+ // element of <code>local_gradients</code>,
+ // since the right end point of the
+ // neighbor cell has index 1) as a
+ // scalar value (this is the zeroth
+ // component of
+ // <code>local_gradients[1]</code>.
+ neighbor_fe_values.reinit (left_neighbor);
+ neighbor_fe_values.get_function_grads (present_solution, local_gradients);
+
+ const double neighbor_u_prime_left = local_gradients[1][0];
+
+ // Then compute the jump, and add a
+ // suitable multiple to the error
+ // indicator for this cell:
+ const double left_jump = std::pow(x_left-std::pow(u_left,3), 2) *
+ (std::pow(neighbor_u_prime_left,5) -
+ std::pow(u_prime_left,5));
+ error_indicators(cell_index) += left_jump * left_jump *
+ cell->diameter();
+ }
+
+ // Once we have done the left neighbor,
+ // we can play exactly the same game
+ // with the right neighbor:
+ if (cell->at_boundary(1) == false)
+ {
+ DoFHandler<dim>::cell_iterator right_neighbor = cell->neighbor(1);
+ while (right_neighbor->has_children())
+ right_neighbor = right_neighbor->child(0);
+
+ neighbor_fe_values.reinit (right_neighbor);
+ neighbor_fe_values.get_function_grads (present_solution, local_gradients);
+
+ const double neighbor_u_prime_right = local_gradients[0][0];
+
+ const double right_jump = std::pow(x_right-std::pow(u_right,3), 2) *
+ (std::pow(neighbor_u_prime_right,5) -
+ std::pow(u_prime_right,5));
+ error_indicators(cell_index) += right_jump * right_jump *
+ cell->diameter();
+ }
+ }
+
+ // Now we have all the refinement
+ // indicators computed, and want to refine
+ // the grid. In contrast to previous
+ // examples, however, we would like to
+ // transfer the solution vector from the
+ // old to the new grid. This is what the
+ // <code>SolutionTransfer</code> class is good for,
+ // but it requires some preliminary
+ // work. First, we need to tag the cells
+ // that we want to refine or coarsen, as
+ // usual:
+ GridRefinement::refine_and_coarsen_fixed_number (triangulation,
+ error_indicators,
+ 0.3, 0.03);
+ // Then, however, we need an additional
+ // step: if, for example, you flag a cell
+ // that is once more refined than its
+ // neighbor, and that neighbor is not
+ // flagged for refinement, we would end up
+ // with a jump of two refinement levels
+ // across a cell interface. In 1d, this
+ // would in general be allowed, but not in
+ // higher space dimensions, and some mesh
+ // smoothing algorithms in 1d may also
+ // disallow this. To avoid these
+ // situations, the library will silently
+ // also have to refine the neighbor cell
+ // once. It does so by calling the
+ // <code>Triangulation::prepare_coarsening_and_refinement</code>
+ // function before actually doing the
+ // refinement and coarsening. This function
+ // flags a set of additional cells for
+ // refinement or coarsening, to enforce
+ // rules like the one-hanging-node
+ // rule. The cells that are flagged for
+ // refinement and coarsening after calling
+ // this function are exactly the ones that
+ // will actually be refined or
+ // coarsened. Since the
+ // <code>SolutionTransfer</code> class needs this
+ // information in order to store the data
+ // from the old mesh and transfer to the
+ // new one.
+ triangulation.prepare_coarsening_and_refinement();
+
+ // With this out of the way, we initialize
+ // a <code>SolutionTransfer</code> object with the
+ // present <code>DoFHandler</code> and attach the
+ // solution vector to it:
+ SolutionTransfer<dim> solution_transfer(dof_handler);
+ solution_transfer.prepare_for_coarsening_and_refinement (present_solution);
+
+ // Then we do the actual refinement, and
+ // distribute degrees of freedom on the new
+ // mesh:
+ triangulation.execute_coarsening_and_refinement ();
+ dof_handler.distribute_dofs (fe);
+
+ // Finally, we retrieve the old solution
+ // interpolated to the new mesh. Since the
+ // <code>SolutionTransfer</code> function does not
+ // actually store the values of the old
+ // solution, but rather indices, we need to
+ // preserve the old solution vector until
+ // we have gotten the new interpolated
+ // values. Thus, we have the new values
+ // written into a temporary vector, and
+ // only afterwards write them into the
+ // solution vector object:
+ Vector<double> tmp (dof_handler.n_dofs());
+ solution_transfer.interpolate (present_solution, tmp);
+ present_solution = tmp;
+
+ // Here is some final thing, that is
+ // actually unnecessary in 1d, but
+ // necessary for higher space dimensions,
+ // so we show it anyway: the result of what
+ // the <code>SolutionTransfer</code> class provides
+ // is a vector that is interpolated from
+ // the old to the new mesh. Unfortunately,
+ // it does not necessarily have the right
+ // values at constrained (hanging) nodes,
+ // so we have to fix this up to make the
+ // solution conforming again. The simplest
+ // way to do this is this:
+ hanging_node_constraints.clear ();
+ DoFTools::make_hanging_node_constraints (dof_handler,
+ hanging_node_constraints);
+ hanging_node_constraints.close ();
+ hanging_node_constraints.distribute (present_solution);
+ // This is wasteful, since we create a
+ // <code>ConstraintMatrix</code> object that will be
+ // recreated again in the next call to
+ // <code>setup_system_on_mesh</code> immediately
+ // afterwards. A more efficient
+ // implementation would make sure that it
+ // is created only once. We don't care so
+ // much here, since in 1d there are no
+ // constraints, so all of these operations
+ // are really cheap, but we do not
+ // recommend this as general programming
+ // strategy.
+ }
- // Before going over to the framework
- // functions, we still need to look at the
- // implementation of the function that
- // computes the energy of a nodal vector in
- // the functional considered in this example
- // program. Its idea is simple: take a nodal
- // vector and the <code>DoFHandler</code> object it is
- // living on, then loop over all cells and
- // add up the local contributions to the
- // energy:
-template <int dim>
-double
-MinimizationProblem<dim>::energy (const DoFHandler<dim> &dof_handler,
- const Vector<double> &function)
-{
- // First define the quadrature formula and
- // a <code>FEValues</code> object with which to
- // compute the values of the input function
- // at the quadrature points. Note again
- // that the integrand is a polynomial of
- // degree six, so a 4-point Gauss formula
- // is appropriate:
- QGauss<dim> quadrature_formula(4);
- FEValues<dim> fe_values (dof_handler.get_fe(), quadrature_formula,
- update_values | update_gradients |
- update_quadrature_points | update_JxW_values);
-
- const unsigned int n_q_points = quadrature_formula.size();
-
- // Then, just as when we integrated the
- // linear system, we need two variables
- // that will hold the values and gradients
- // of the given function at the quadrature
- // points:
- std::vector<double> local_solution_values (n_q_points);
- std::vector<Tensor<1,dim> > local_solution_grads (n_q_points);
-
- // With this, define an energy variable,
- // and loop over all the cells:
- double energy = 0.;
-
- typename DoFHandler<dim>::active_cell_iterator
- cell = dof_handler.begin_active(),
- endc = dof_handler.end();
- for (; cell!=endc; ++cell)
- {
- // On each cell, initialize the
- // <code>FEValues</code> object, and extract
- // values and gradients of the given
- // function:
- fe_values.reinit (cell);
- fe_values.get_function_values (function,
- local_solution_values);
- fe_values.get_function_grads (function,
- local_solution_grads);
-
- // Then loop over all quadrature points
- // on this cell, and add up the
- // contribution of each to the global
- // energy:
- for (unsigned int q_point=0; q_point<n_q_points; ++q_point)
- energy += (std::pow (fe_values.quadrature_point(q_point)(0)
- -
- std::pow (local_solution_values[q_point], 3),
- 2) *
- gradient_power (local_solution_grads[q_point], 6) *
- fe_values.JxW (q_point));
- }
- // Once we have done this, return the
- // integrated value.
- return energy;
-}
+ // Before going over to the framework
+ // functions, we still need to look at the
+ // implementation of the function that
+ // computes the energy of a nodal vector in
+ // the functional considered in this example
+ // program. Its idea is simple: take a nodal
+ // vector and the <code>DoFHandler</code> object it is
+ // living on, then loop over all cells and
+ // add up the local contributions to the
+ // energy:
+ template <int dim>
+ double
+ MinimizationProblem<dim>::energy (const DoFHandler<dim> &dof_handler,
+ const Vector<double> &function)
+ {
+ // First define the quadrature formula and
+ // a <code>FEValues</code> object with which to
+ // compute the values of the input function
+ // at the quadrature points. Note again
+ // that the integrand is a polynomial of
+ // degree six, so a 4-point Gauss formula
+ // is appropriate:
+ QGauss<dim> quadrature_formula(4);
+ FEValues<dim> fe_values (dof_handler.get_fe(), quadrature_formula,
+ update_values | update_gradients |
+ update_quadrature_points | update_JxW_values);
+
+ const unsigned int n_q_points = quadrature_formula.size();
+
+ // Then, just as when we integrated the
+ // linear system, we need two variables
+ // that will hold the values and gradients
+ // of the given function at the quadrature
+ // points:
+ std::vector<double> local_solution_values (n_q_points);
+ std::vector<Tensor<1,dim> > local_solution_grads (n_q_points);
+
+ // With this, define an energy variable,
+ // and loop over all the cells:
+ double energy = 0.;
+
+ typename DoFHandler<dim>::active_cell_iterator
+ cell = dof_handler.begin_active(),
+ endc = dof_handler.end();
+ for (; cell!=endc; ++cell)
+ {
+ // On each cell, initialize the
+ // <code>FEValues</code> object, and extract
+ // values and gradients of the given
+ // function:
+ fe_values.reinit (cell);
+ fe_values.get_function_values (function,
+ local_solution_values);
+ fe_values.get_function_grads (function,
+ local_solution_grads);
+
+ // Then loop over all quadrature points
+ // on this cell, and add up the
+ // contribution of each to the global
+ // energy:
+ for (unsigned int q_point=0; q_point<n_q_points; ++q_point)
+ energy += (std::pow (fe_values.quadrature_point(q_point)(0)
+ -
+ std::pow (local_solution_values[q_point], 3),
+ 2) *
+ gradient_power (local_solution_grads[q_point], 6) *
+ fe_values.JxW (q_point));
+ }
+
+ // Once we have done this, return the
+ // integrated value.
+ return energy;
+ }
- // So here is the driver function,
- // <code>run()</code>. It generate a coarse mesh,
- // refines it a couple of times, and
- // initializes the starting values. It then
- // goes into a loop in which we first set up
- // the member variables for the new mesh, and
- // then do a fixed number of five gradient
- // steps. If after this the energy has not
- // significantly decreased compares to the
- // last time we checked, we assume that we
- // have converged and exit, otherwise we
- // refine the mesh and start over. Once we
- // have determined that the computations have
- // converged somewhere, we output the
- // results.
-template <int dim>
-void MinimizationProblem<dim>::run ()
-{
- GridGenerator::hyper_cube (triangulation, 0., 1.);
- triangulation.refine_global (4);
- dof_handler.distribute_dofs (fe);
- initialize_solution ();
+ // So here is the driver function,
+ // <code>run()</code>. It generate a coarse mesh,
+ // refines it a couple of times, and
+ // initializes the starting values. It then
+ // goes into a loop in which we first set up
+ // the member variables for the new mesh, and
+ // then do a fixed number of five gradient
+ // steps. If after this the energy has not
+ // significantly decreased compares to the
+ // last time we checked, we assume that we
+ // have converged and exit, otherwise we
+ // refine the mesh and start over. Once we
+ // have determined that the computations have
+ // converged somewhere, we output the
+ // results.
+ template <int dim>
+ void MinimizationProblem<dim>::run ()
+ {
+ GridGenerator::hyper_cube (triangulation, 0., 1.);
+ triangulation.refine_global (4);
+ dof_handler.distribute_dofs (fe);
+ initialize_solution ();
- double last_energy = energy (dof_handler, present_solution);
+ double last_energy = energy (dof_handler, present_solution);
- while (true)
- {
- setup_system_on_mesh ();
+ while (true)
+ {
+ setup_system_on_mesh ();
- for (unsigned int iteration=0; iteration<5; ++iteration)
- do_step ();
+ for (unsigned int iteration=0; iteration<5; ++iteration)
+ do_step ();
- const double this_energy = energy (dof_handler, present_solution);
- std::cout << " Energy: " << this_energy << std::endl;
+ const double this_energy = energy (dof_handler, present_solution);
+ std::cout << " Energy: " << this_energy << std::endl;
- if ((last_energy-this_energy) < 1e-5*last_energy)
- break;
+ if ((last_energy-this_energy) < 1e-5*last_energy)
+ break;
- last_energy = this_energy;
+ last_energy = this_energy;
- refine_grid ();
- }
+ refine_grid ();
+ }
- output_results ();
+ output_results ();
- std::cout << std::endl;
+ std::cout << std::endl;
+ }
}
{
try
{
+ using namespace dealii;
+ using namespace Step15;
+
deallog.depth_console (0);
const unsigned int n_realizations = 10;
/* $Id$ */
/* */
-/* Copyright (C) 2003, 2004, 2006, 2007, 2008, 2009, 2010 by the deal.II authors */
+/* Copyright (C) 2003, 2004, 2006, 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 */
// The last step is as in all
// previous programs:
-using namespace dealii;
-
-
- // @sect3{The <code>LaplaceProblem</code> class template}
-
- // This main class is basically the same
- // class as in step-6. As far as member
- // functions is concerned, the only addition
- // is the <code>assemble_multigrid</code>
- // function that assembles the matrices that
- // correspond to the discrete operators on
- // intermediate levels:
-template <int dim>
-class LaplaceProblem
+namespace Step16
{
- public:
- LaplaceProblem (const unsigned int deg);
- void run ();
-
- private:
- void setup_system ();
- void assemble_system ();
- void assemble_multigrid ();
- void solve ();
- void refine_grid ();
- void output_results (const unsigned int cycle) const;
-
- Triangulation<dim> triangulation;
- FE_Q<dim> fe;
- MGDoFHandler<dim> mg_dof_handler;
-
- SparsityPattern sparsity_pattern;
- SparseMatrix<double> system_matrix;
-
- // We need an additional object for the
- // hanging nodes constraints. They are
- // handed to the transfer object in the
- // multigrid. Since we call a compress
- // inside the multigrid these constraints
- // are not allowed to be inhomogeneous so
- // we store them in different ConstraintMatrix
- // objects.
- ConstraintMatrix hanging_node_constraints;
- ConstraintMatrix constraints;
-
- Vector<double> solution;
- Vector<double> system_rhs;
-
- const unsigned int degree;
-
- // The following four objects are the
- // only additional member variables,
- // compared to step-6. They first three
- // represent the
- // operators that act on individual
- // levels of the multilevel hierarchy,
- // rather than on the finest mesh as do
- // the objects above while the last object
- // stores information about the boundary
- // indices on each level and information
- // about indices lying on a refinement
- // edge between two different refinement
- // levels.
- //
- // To facilitate having objects on each
- // level of a multilevel hierarchy,
- // deal.II has the MGLevelObject class
- // template that provides storage for
- // objects on each level. What we need
- // here are matrices on each level, which
- // implies that we also need sparsity
- // patterns on each level. As outlined in
- // the @ref mg_paper, the operators
- // (matrices) that we need are actually
- // twofold: one on the interior of each
- // level, and one at the interface
- // between each level and that part of
- // the domain where the mesh is
- // coarser. In fact, we will need the
- // latter in two versions: for the
- // direction from coarse to fine mesh and
- // from fine to coarse. Fortunately,
- // however, we here have a self-adjoint
- // problem for which one of these is the
- // transpose of the other, and so we only
- // have to build one; we choose the one
- // from coarse to fine.
- MGLevelObject<SparsityPattern> mg_sparsity_patterns;
- MGLevelObject<SparseMatrix<double> > mg_matrices;
- MGLevelObject<SparseMatrix<double> > mg_interface_matrices;
- MGConstrainedDoFs mg_constrained_dofs;
-};
-
-
-
- // @sect3{Nonconstant coefficients}
-
- // The implementation of nonconstant
- // coefficients is copied verbatim
- // from step-5 and step-6:
-
-template <int dim>
-class Coefficient : public Function<dim>
-{
- public:
- Coefficient () : Function<dim>() {}
-
- 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>
-double Coefficient<dim>::value (const Point<dim> &p,
- const unsigned int) const
-{
- if (p.square() < 0.5*0.5)
- return 20;
- else
- return 1;
-}
+ using namespace dealii;
+
+
+ // @sect3{The <code>LaplaceProblem</code> class template}
+
+ // This main class is basically the same
+ // class as in step-6. As far as member
+ // functions is concerned, the only addition
+ // is the <code>assemble_multigrid</code>
+ // function that assembles the matrices that
+ // correspond to the discrete operators on
+ // intermediate levels:
+ template <int dim>
+ class LaplaceProblem
+ {
+ public:
+ LaplaceProblem (const unsigned int deg);
+ void run ();
+
+ private:
+ void setup_system ();
+ void assemble_system ();
+ void assemble_multigrid ();
+ void solve ();
+ void refine_grid ();
+ void output_results (const unsigned int cycle) const;
+
+ Triangulation<dim> triangulation;
+ FE_Q<dim> fe;
+ MGDoFHandler<dim> mg_dof_handler;
+
+ SparsityPattern sparsity_pattern;
+ SparseMatrix<double> system_matrix;
+
+ // We need an additional object for the
+ // hanging nodes constraints. They are
+ // handed to the transfer object in the
+ // multigrid. Since we call a compress
+ // inside the multigrid these constraints
+ // are not allowed to be inhomogeneous so
+ // we store them in different ConstraintMatrix
+ // objects.
+ ConstraintMatrix hanging_node_constraints;
+ ConstraintMatrix constraints;
+
+ Vector<double> solution;
+ Vector<double> system_rhs;
+
+ const unsigned int degree;
+
+ // The following four objects are the
+ // only additional member variables,
+ // compared to step-6. They first three
+ // represent the
+ // operators that act on individual
+ // levels of the multilevel hierarchy,
+ // rather than on the finest mesh as do
+ // the objects above while the last object
+ // stores information about the boundary
+ // indices on each level and information
+ // about indices lying on a refinement
+ // edge between two different refinement
+ // levels.
+ //
+ // To facilitate having objects on each
+ // level of a multilevel hierarchy,
+ // deal.II has the MGLevelObject class
+ // template that provides storage for
+ // objects on each level. What we need
+ // here are matrices on each level, which
+ // implies that we also need sparsity
+ // patterns on each level. As outlined in
+ // the @ref mg_paper, the operators
+ // (matrices) that we need are actually
+ // twofold: one on the interior of each
+ // level, and one at the interface
+ // between each level and that part of
+ // the domain where the mesh is
+ // coarser. In fact, we will need the
+ // latter in two versions: for the
+ // direction from coarse to fine mesh and
+ // from fine to coarse. Fortunately,
+ // however, we here have a self-adjoint
+ // problem for which one of these is the
+ // transpose of the other, and so we only
+ // have to build one; we choose the one
+ // from coarse to fine.
+ MGLevelObject<SparsityPattern> mg_sparsity_patterns;
+ MGLevelObject<SparseMatrix<double> > mg_matrices;
+ MGLevelObject<SparseMatrix<double> > mg_interface_matrices;
+ MGConstrainedDoFs mg_constrained_dofs;
+ };
+
+
+
+ // @sect3{Nonconstant coefficients}
+
+ // The implementation of nonconstant
+ // coefficients is copied verbatim
+ // from step-5 and step-6:
+
+ template <int dim>
+ class Coefficient : public Function<dim>
+ {
+ public:
+ Coefficient () : Function<dim>() {}
+
+ 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>
+ double Coefficient<dim>::value (const Point<dim> &p,
+ const unsigned int) const
+ {
+ if (p.square() < 0.5*0.5)
+ return 20;
+ else
+ return 1;
+ }
-template <int dim>
-void Coefficient<dim>::value_list (const std::vector<Point<dim> > &points,
- std::vector<double> &values,
- const unsigned int component) const
-{
- const unsigned int n_points = points.size();
+ template <int dim>
+ void Coefficient<dim>::value_list (const std::vector<Point<dim> > &points,
+ std::vector<double> &values,
+ const unsigned int component) const
+ {
+ const unsigned int n_points = points.size();
- Assert (values.size() == n_points,
- ExcDimensionMismatch (values.size(), n_points));
+ Assert (values.size() == n_points,
+ ExcDimensionMismatch (values.size(), n_points));
- Assert (component == 0,
- ExcIndexRange (component, 0, 1));
+ Assert (component == 0,
+ ExcIndexRange (component, 0, 1));
- for (unsigned int i=0; i<n_points; ++i)
- values[i] = Coefficient<dim>::value (points[i]);
-}
+ for (unsigned int i=0; i<n_points; ++i)
+ values[i] = Coefficient<dim>::value (points[i]);
+ }
- // @sect3{The <code>LaplaceProblem</code> class implementation}
+ // @sect3{The <code>LaplaceProblem</code> class implementation}
- // @sect4{LaplaceProblem::LaplaceProblem}
+ // @sect4{LaplaceProblem::LaplaceProblem}
- // The constructor is left mostly
- // unchanged. We take the polynomial degree
- // of the finite elements to be used as a
- // constructor argument and store it in a
- // member variable.
- //
- // By convention, all adaptively refined
- // triangulations in deal.II never change by
- // more than one level across a face between
- // cells. For our multigrid algorithms,
- // however, we need a slightly stricter
- // guarantee, namely that the mesh also does
- // not change by more than refinement level
- // across vertices that might connect two
- // cells. In other words, we must prevent the
- // following situation:
- //
- // @image html limit_level_difference_at_vertices.png ""
- //
- // This is achieved by passing the
- // Triangulation::limit_level_difference_at_vertices
- // flag to the constructor of the
- // triangulation class.
-template <int dim>
-LaplaceProblem<dim>::LaplaceProblem (const unsigned int degree)
- :
- triangulation (Triangulation<dim>::
- limit_level_difference_at_vertices),
- fe (degree),
- mg_dof_handler (triangulation),
- degree(degree)
-{}
-
-
-
- // @sect4{LaplaceProblem::setup_system}
-
- // The following function extends what the
- // corresponding one in step-6 did. The top
- // part, apart from the additional output,
- // does the same:
-template <int dim>
-void LaplaceProblem<dim>::setup_system ()
-{
- mg_dof_handler.distribute_dofs (fe);
-
- // Here we output not only the
- // degrees of freedom on the finest
- // level, but also in the
- // multilevel structure
- deallog << "Number of degrees of freedom: "
- << mg_dof_handler.n_dofs();
-
- for (unsigned int l=0;l<triangulation.n_levels();++l)
- deallog << " " << 'L' << l << ": "
- << mg_dof_handler.n_dofs(l);
- deallog << std::endl;
-
- sparsity_pattern.reinit (mg_dof_handler.n_dofs(),
- mg_dof_handler.n_dofs(),
- mg_dof_handler.max_couplings_between_dofs());
- DoFTools::make_sparsity_pattern (mg_dof_handler, sparsity_pattern);
-
- solution.reinit (mg_dof_handler.n_dofs());
- system_rhs.reinit (mg_dof_handler.n_dofs());
-
- // But it starts to be a wee bit different
- // here, although this still doesn't have
- // anything to do with multigrid
- // methods. step-6 took care of boundary
- // values and hanging nodes in a separate
- // step after assembling the global matrix
- // from local contributions. This works,
- // but the same can be done in a slightly
- // simpler way if we already take care of
- // these constraints at the time of copying
+ // The constructor is left mostly
+ // unchanged. We take the polynomial degree
+ // of the finite elements to be used as a
+ // constructor argument and store it in a
+ // member variable.
+ //
+ // By convention, all adaptively refined
+ // triangulations in deal.II never change by
+ // more than one level across a face between
+ // cells. For our multigrid algorithms,
+ // however, we need a slightly stricter
+ // guarantee, namely that the mesh also does
+ // not change by more than refinement level
+ // across vertices that might connect two
+ // cells. In other words, we must prevent the
+ // following situation:
+ //
+ // @image html limit_level_difference_at_vertices.png ""
+ //
+ // This is achieved by passing the
+ // Triangulation::limit_level_difference_at_vertices
+ // flag to the constructor of the
+ // triangulation class.
+ template <int dim>
+ LaplaceProblem<dim>::LaplaceProblem (const unsigned int degree)
+ :
+ triangulation (Triangulation<dim>::
+ limit_level_difference_at_vertices),
+ fe (degree),
+ mg_dof_handler (triangulation),
+ degree(degree)
+ {}
+
+
+
+ // @sect4{LaplaceProblem::setup_system}
+
+ // The following function extends what the
+ // corresponding one in step-6 did. The top
+ // part, apart from the additional output,
+ // does the same:
+ template <int dim>
+ void LaplaceProblem<dim>::setup_system ()
+ {
+ mg_dof_handler.distribute_dofs (fe);
+
+ // Here we output not only the
+ // degrees of freedom on the finest
+ // level, but also in the
+ // multilevel structure
+ deallog << "Number of degrees of freedom: "
+ << mg_dof_handler.n_dofs();
+
+ for (unsigned int l=0;l<triangulation.n_levels();++l)
+ deallog << " " << 'L' << l << ": "
+ << mg_dof_handler.n_dofs(l);
+ deallog << std::endl;
+
+ sparsity_pattern.reinit (mg_dof_handler.n_dofs(),
+ mg_dof_handler.n_dofs(),
+ mg_dof_handler.max_couplings_between_dofs());
+ DoFTools::make_sparsity_pattern (mg_dof_handler, sparsity_pattern);
+
+ solution.reinit (mg_dof_handler.n_dofs());
+ system_rhs.reinit (mg_dof_handler.n_dofs());
+
+ // But it starts to be a wee bit different
+ // here, although this still doesn't have
+ // anything to do with multigrid
+ // methods. step-6 took care of boundary
+ // values and hanging nodes in a separate
+ // step after assembling the global matrix
+ // from local contributions. This works,
+ // but the same can be done in a slightly
+ // simpler way if we already take care of
+ // these constraints at the time of copying
+ // local contributions into the global
+ // matrix. To this end, we here do not just
+ // compute the constraints do to hanging
+ // nodes, but also due to zero boundary
+ // conditions. We will
+ // use this set of constraints later on to
+ // help us copy local contributions
+ // correctly into the global linear system
+ // right away, without the need for a later
+ // clean-up stage:
+ constraints.clear ();
+ hanging_node_constraints.clear ();
+ DoFTools::make_hanging_node_constraints (mg_dof_handler, hanging_node_constraints);
+ DoFTools::make_hanging_node_constraints (mg_dof_handler, constraints);
+
+ typename FunctionMap<dim>::type dirichlet_boundary;
+ ZeroFunction<dim> homogeneous_dirichlet_bc (1);
+ dirichlet_boundary[0] = &homogeneous_dirichlet_bc;
+ VectorTools::interpolate_boundary_values (static_cast<const DoFHandler<dim>&>(mg_dof_handler),
+ dirichlet_boundary,
+ constraints);
+ constraints.close ();
+ hanging_node_constraints.close ();
+ constraints.condense (sparsity_pattern);
+ sparsity_pattern.compress();
+ system_matrix.reinit (sparsity_pattern);
+
+ // The multigrid constraints have to be
+ // initialized. They need to know about
+ // the boundary values as well, so we
+ // pass the <code>dirichlet_boundary</code>
+ // here as well.
+ mg_constrained_dofs.clear();
+ mg_constrained_dofs.initialize(mg_dof_handler, dirichlet_boundary);
+
+
+ // Now for the things that concern the
+ // multigrid data structures. First, we
+ // resize the multi-level objects to hold
+ // matrices and sparsity patterns for every
+ // level. The coarse level is zero (this is
+ // mandatory right now but may change in a
+ // future revision). Note that these
+ // functions take a complete, inclusive
+ // range here (not a starting index and
+ // size), so the finest level is
+ // <code>n_levels-1</code>. We first have
+ // to resize the container holding the
+ // SparseMatrix classes, since they have to
+ // release their SparsityPattern before the
+ // can be destroyed upon resizing.
+ const unsigned int n_levels = triangulation.n_levels();
+
+ mg_interface_matrices.resize(0, n_levels-1);
+ mg_interface_matrices.clear ();
+ mg_matrices.resize(0, n_levels-1);
+ mg_matrices.clear ();
+ mg_sparsity_patterns.resize(0, n_levels-1);
+
+ // Now, we have to provide a matrix on each
+ // level. To this end, we first use the
+ // MGTools::make_sparsity_pattern function
+ // to first generate a preliminary
+ // compressed sparsity pattern on each
+ // level (see the @ref Sparsity module for
+ // more information on this topic) and then
+ // copy it over to the one we really
+ // want. The next step is to initialize
+ // both kinds of level matrices with these
+ // sparsity patterns.
+ //
+ // It may be worth pointing out that the
+ // interface matrices only have entries for
+ // degrees of freedom that sit at or next
+ // to the interface between coarser and
+ // finer levels of the mesh. They are
+ // therefore even sparser than the matrices
+ // on the individual levels of our
+ // multigrid hierarchy. If we were more
+ // concerned about memory usage (and
+ // possibly the speed with which we can
+ // multiply with these matrices), we should
+ // use separate and different sparsity
+ // patterns for these two kinds of
+ // matrices.
+ for (unsigned int level=0; level<n_levels; ++level)
+ {
+ CompressedSparsityPattern csp;
+ csp.reinit(mg_dof_handler.n_dofs(level),
+ mg_dof_handler.n_dofs(level));
+ MGTools::make_sparsity_pattern(mg_dof_handler, csp, level);
+
+ mg_sparsity_patterns[level].copy_from (csp);
+
+ mg_matrices[level].reinit(mg_sparsity_patterns[level]);
+ mg_interface_matrices[level].reinit(mg_sparsity_patterns[level]);
+ }
+ }
+
+
+ // @sect4{LaplaceProblem::assemble_system}
+
+ // The following function assembles the
+ // linear system on the finesh level of the
+ // mesh. It is almost exactly the same as in
+ // step-6, with the exception that we don't
+ // eliminate hanging nodes and boundary
+ // values after assembling, but while copying
// local contributions into the global
- // matrix. To this end, we here do not just
- // compute the constraints do to hanging
- // nodes, but also due to zero boundary
- // conditions. We will
- // use this set of constraints later on to
- // help us copy local contributions
- // correctly into the global linear system
- // right away, without the need for a later
- // clean-up stage:
- constraints.clear ();
- hanging_node_constraints.clear ();
- DoFTools::make_hanging_node_constraints (mg_dof_handler, hanging_node_constraints);
- DoFTools::make_hanging_node_constraints (mg_dof_handler, constraints);
-
- typename FunctionMap<dim>::type dirichlet_boundary;
- ZeroFunction<dim> homogeneous_dirichlet_bc (1);
- dirichlet_boundary[0] = &homogeneous_dirichlet_bc;
- VectorTools::interpolate_boundary_values (static_cast<const DoFHandler<dim>&>(mg_dof_handler),
- dirichlet_boundary,
- constraints);
- constraints.close ();
- hanging_node_constraints.close ();
- constraints.condense (sparsity_pattern);
- sparsity_pattern.compress();
- system_matrix.reinit (sparsity_pattern);
-
- // The multigrid constraints have to be
- // initialized. They need to know about
- // the boundary values as well, so we
- // pass the <code>dirichlet_boundary</code>
- // here as well.
- mg_constrained_dofs.clear();
- mg_constrained_dofs.initialize(mg_dof_handler, dirichlet_boundary);
-
-
- // Now for the things that concern the
- // multigrid data structures. First, we
- // resize the multi-level objects to hold
- // matrices and sparsity patterns for every
- // level. The coarse level is zero (this is
- // mandatory right now but may change in a
- // future revision). Note that these
- // functions take a complete, inclusive
- // range here (not a starting index and
- // size), so the finest level is
- // <code>n_levels-1</code>. We first have
- // to resize the container holding the
- // SparseMatrix classes, since they have to
- // release their SparsityPattern before the
- // can be destroyed upon resizing.
- const unsigned int n_levels = triangulation.n_levels();
-
- mg_interface_matrices.resize(0, n_levels-1);
- mg_interface_matrices.clear ();
- mg_matrices.resize(0, n_levels-1);
- mg_matrices.clear ();
- mg_sparsity_patterns.resize(0, n_levels-1);
-
- // Now, we have to provide a matrix on each
- // level. To this end, we first use the
- // MGTools::make_sparsity_pattern function
- // to first generate a preliminary
- // compressed sparsity pattern on each
- // level (see the @ref Sparsity module for
- // more information on this topic) and then
- // copy it over to the one we really
- // want. The next step is to initialize
- // both kinds of level matrices with these
- // sparsity patterns.
+ // matrix. This is not only simpler but also
+ // more efficient for large problems.
//
- // It may be worth pointing out that the
- // interface matrices only have entries for
- // degrees of freedom that sit at or next
- // to the interface between coarser and
- // finer levels of the mesh. They are
- // therefore even sparser than the matrices
- // on the individual levels of our
- // multigrid hierarchy. If we were more
- // concerned about memory usage (and
- // possibly the speed with which we can
- // multiply with these matrices), we should
- // use separate and different sparsity
- // patterns for these two kinds of
- // matrices.
- for (unsigned int level=0; level<n_levels; ++level)
- {
- CompressedSparsityPattern csp;
- csp.reinit(mg_dof_handler.n_dofs(level),
- mg_dof_handler.n_dofs(level));
- MGTools::make_sparsity_pattern(mg_dof_handler, csp, level);
-
- mg_sparsity_patterns[level].copy_from (csp);
-
- mg_matrices[level].reinit(mg_sparsity_patterns[level]);
- mg_interface_matrices[level].reinit(mg_sparsity_patterns[level]);
- }
-}
-
-
- // @sect4{LaplaceProblem::assemble_system}
-
- // The following function assembles the
- // linear system on the finesh level of the
- // mesh. It is almost exactly the same as in
- // step-6, with the exception that we don't
- // eliminate hanging nodes and boundary
- // values after assembling, but while copying
- // local contributions into the global
- // matrix. This is not only simpler but also
- // more efficient for large problems.
- //
- // This latter trick is something that only
- // found its way into deal.II over time and
- // wasn't used in the initial version of this
- // tutorial program. There is, however, a
- // discussion of this function in the
- // introduction of step-27.
-template <int dim>
-void LaplaceProblem<dim>::assemble_system ()
-{
- const QGauss<dim> quadrature_formula(degree+1);
-
- 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_matrix (dofs_per_cell, dofs_per_cell);
- Vector<double> cell_rhs (dofs_per_cell);
-
- std::vector<unsigned int> local_dof_indices (dofs_per_cell);
-
- const Coefficient<dim> coefficient;
- std::vector<double> coefficient_values (n_q_points);
-
- typename MGDoFHandler<dim>::active_cell_iterator
- cell = mg_dof_handler.begin_active(),
- endc = mg_dof_handler.end();
- for (; cell!=endc; ++cell)
- {
- cell_matrix = 0;
- cell_rhs = 0;
-
- fe_values.reinit (cell);
-
- coefficient.value_list (fe_values.get_quadrature_points(),
- coefficient_values);
-
- for (unsigned int q_point=0; q_point<n_q_points; ++q_point)
- for (unsigned int i=0; i<dofs_per_cell; ++i)
- {
+ // This latter trick is something that only
+ // found its way into deal.II over time and
+ // wasn't used in the initial version of this
+ // tutorial program. There is, however, a
+ // discussion of this function in the
+ // introduction of step-27.
+ template <int dim>
+ void LaplaceProblem<dim>::assemble_system ()
+ {
+ const QGauss<dim> quadrature_formula(degree+1);
+
+ 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_matrix (dofs_per_cell, dofs_per_cell);
+ Vector<double> cell_rhs (dofs_per_cell);
+
+ std::vector<unsigned int> local_dof_indices (dofs_per_cell);
+
+ const Coefficient<dim> coefficient;
+ std::vector<double> coefficient_values (n_q_points);
+
+ typename MGDoFHandler<dim>::active_cell_iterator
+ cell = mg_dof_handler.begin_active(),
+ endc = mg_dof_handler.end();
+ for (; cell!=endc; ++cell)
+ {
+ cell_matrix = 0;
+ cell_rhs = 0;
+
+ fe_values.reinit (cell);
+
+ coefficient.value_list (fe_values.get_quadrature_points(),
+ coefficient_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_matrix(i,j) += (coefficient_values[q_point] *
+ fe_values.shape_grad(i,q_point) *
+ fe_values.shape_grad(j,q_point) *
+ fe_values.JxW(q_point));
+
+ cell_rhs(i) += (fe_values.shape_value(i,q_point) *
+ 1.0 *
+ fe_values.JxW(q_point));
+ }
+
+ cell->get_dof_indices (local_dof_indices);
+ constraints.distribute_local_to_global (cell_matrix, cell_rhs,
+ local_dof_indices,
+ system_matrix, system_rhs);
+ }
+ }
+
+
+ // @sect4{LaplaceProblem::assemble_multigrid}
+
+ // The next function is the one that builds
+ // the linear operators (matrices) that
+ // define the multigrid method on each level
+ // of the mesh. The integration core is the
+ // same as above, but the loop below will go
+ // over all existing cells instead of just
+ // the active ones, and the results must be
+ // entered into the correct matrix. Note also
+ // that since we only do multi-level
+ // preconditioning, no right-hand side needs
+ // to be assembled here.
+ //
+ // Before we go there, however, we have to
+ // take care of a significant amount of book
+ // keeping:
+ template <int dim>
+ void LaplaceProblem<dim>::assemble_multigrid ()
+ {
+ QGauss<dim> quadrature_formula(1+degree);
+
+ 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_matrix (dofs_per_cell, dofs_per_cell);
+
+ std::vector<unsigned int> local_dof_indices (dofs_per_cell);
+
+ const Coefficient<dim> coefficient;
+ std::vector<double> coefficient_values (n_q_points);
+
+ // Next a few things that are specific to
+ // building the multigrid data structures
+ // (since we only need them in the current
+ // function, rather than also elsewhere, we
+ // build them here instead of the
+ // <code>setup_system</code>
+ // function). Some of the following may be
+ // a bit obscure if you're not familiar
+ // with the algorithm actually implemented
+ // in deal.II to support multilevel
+ // algorithms on adaptive meshes; if some
+ // of the things below seem strange, take a
+ // look at the @ref mg_paper.
+ //
+ // Our first job is to identify those
+ // degrees of freedom on each level that
+ // are located on interfaces between
+ // adaptively refined levels, and those
+ // that lie on the interface but also on
+ // the exterior boundary of the domain. As
+ // in many other parts of the library, we
+ // do this by using boolean masks,
+ // i.e. vectors of booleans each element of
+ // which indicates whether the
+ // corresponding degree of freedom index is
+ // an interface DoF or not. The <code>MGConstraints</code>
+ // already computed the information for us
+ // when we called initialize in <code>setup_system()</code>.
+ std::vector<std::vector<bool> > interface_dofs
+ = mg_constrained_dofs.get_refinement_edge_indices ();
+ std::vector<std::vector<bool> > boundary_interface_dofs
+ = mg_constrained_dofs.get_refinement_edge_boundary_indices ();
+
+ // The indices just identified will later
+ // be used to decide where the assembled value
+ // has to be added into on each level.
+ // On the other hand,
+ // we also have to impose zero boundary
+ // conditions on the external boundary of
+ // each level. But this the <code>MGConstraints</code>
+ // knows it. So we simply ask for them by calling
+ // <code>get_boundary_indices ()</code>.
+ // The third step is to construct
+ // constraints on all those degrees of
+ // freedom: their value should be zero
+ // after each application of the level
+ // operators. To this end, we construct
+ // ConstraintMatrix objects for each level,
+ // and add to each of these constraints for
+ // each degree of freedom. Due to the way
+ // the ConstraintMatrix stores its data,
+ // the function to add a constraint on a
+ // single degree of freedom and force it to
+ // be zero is called
+ // Constraintmatrix::add_line(); doing so
+ // for several degrees of freedom at once
+ // can be done using
+ // Constraintmatrix::add_lines():
+ std::vector<ConstraintMatrix> boundary_constraints (triangulation.n_levels());
+ std::vector<ConstraintMatrix> boundary_interface_constraints (triangulation.n_levels());
+ for (unsigned int level=0; level<triangulation.n_levels(); ++level)
+ {
+ boundary_constraints[level].add_lines (interface_dofs[level]);
+ boundary_constraints[level].add_lines (mg_constrained_dofs.get_boundary_indices()[level]);
+ boundary_constraints[level].close ();
+
+ boundary_interface_constraints[level]
+ .add_lines (boundary_interface_dofs[level]);
+ boundary_interface_constraints[level].close ();
+ }
+
+ // Now that we're done with most of our
+ // preliminaries, let's start the
+ // integration loop. It looks mostly like
+ // the loop in
+ // <code>assemble_system</code>, with two
+ // exceptions: (i) we don't need a right
+ // hand side, and more significantly (ii) we
+ // don't just loop over all active cells,
+ // but in fact all cells, active or
+ // not. Consequently, the correct iterator
+ // to use is MGDoFHandler::cell_iterator
+ // rather than
+ // MGDoFHandler::active_cell_iterator. Let's
+ // go about it:
+ typename MGDoFHandler<dim>::cell_iterator cell = mg_dof_handler.begin(),
+ endc = mg_dof_handler.end();
+
+ for (; cell!=endc; ++cell)
+ {
+ cell_matrix = 0;
+ fe_values.reinit (cell);
+
+ coefficient.value_list (fe_values.get_quadrature_points(),
+ coefficient_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_matrix(i,j) += (coefficient_values[q_point] *
fe_values.shape_grad(i,q_point) *
fe_values.shape_grad(j,q_point) *
fe_values.JxW(q_point));
- cell_rhs(i) += (fe_values.shape_value(i,q_point) *
- 1.0 *
- fe_values.JxW(q_point));
- }
-
- cell->get_dof_indices (local_dof_indices);
- constraints.distribute_local_to_global (cell_matrix, cell_rhs,
- local_dof_indices,
- system_matrix, system_rhs);
- }
-}
-
-
- // @sect4{LaplaceProblem::assemble_multigrid}
-
- // The next function is the one that builds
- // the linear operators (matrices) that
- // define the multigrid method on each level
- // of the mesh. The integration core is the
- // same as above, but the loop below will go
- // over all existing cells instead of just
- // the active ones, and the results must be
- // entered into the correct matrix. Note also
- // that since we only do multi-level
- // preconditioning, no right-hand side needs
- // to be assembled here.
- //
- // Before we go there, however, we have to
- // take care of a significant amount of book
- // keeping:
-template <int dim>
-void LaplaceProblem<dim>::assemble_multigrid ()
-{
- QGauss<dim> quadrature_formula(1+degree);
-
- 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_matrix (dofs_per_cell, dofs_per_cell);
-
- std::vector<unsigned int> local_dof_indices (dofs_per_cell);
-
- const Coefficient<dim> coefficient;
- std::vector<double> coefficient_values (n_q_points);
-
- // Next a few things that are specific to
- // building the multigrid data structures
- // (since we only need them in the current
- // function, rather than also elsewhere, we
- // build them here instead of the
- // <code>setup_system</code>
- // function). Some of the following may be
- // a bit obscure if you're not familiar
- // with the algorithm actually implemented
- // in deal.II to support multilevel
- // algorithms on adaptive meshes; if some
- // of the things below seem strange, take a
- // look at the @ref mg_paper.
- //
- // Our first job is to identify those
- // degrees of freedom on each level that
- // are located on interfaces between
- // adaptively refined levels, and those
- // that lie on the interface but also on
- // the exterior boundary of the domain. As
- // in many other parts of the library, we
- // do this by using boolean masks,
- // i.e. vectors of booleans each element of
- // which indicates whether the
- // corresponding degree of freedom index is
- // an interface DoF or not. The <code>MGConstraints</code>
- // already computed the information for us
- // when we called initialize in <code>setup_system()</code>.
- std::vector<std::vector<bool> > interface_dofs
- = mg_constrained_dofs.get_refinement_edge_indices ();
- std::vector<std::vector<bool> > boundary_interface_dofs
- = mg_constrained_dofs.get_refinement_edge_boundary_indices ();
-
- // The indices just identified will later
- // be used to decide where the assembled value
- // has to be added into on each level.
- // On the other hand,
- // we also have to impose zero boundary
- // conditions on the external boundary of
- // each level. But this the <code>MGConstraints</code>
- // knows it. So we simply ask for them by calling
- // <code>get_boundary_indices ()</code>.
- // The third step is to construct
- // constraints on all those degrees of
- // freedom: their value should be zero
- // after each application of the level
- // operators. To this end, we construct
- // ConstraintMatrix objects for each level,
- // and add to each of these constraints for
- // each degree of freedom. Due to the way
- // the ConstraintMatrix stores its data,
- // the function to add a constraint on a
- // single degree of freedom and force it to
- // be zero is called
- // Constraintmatrix::add_line(); doing so
- // for several degrees of freedom at once
- // can be done using
- // Constraintmatrix::add_lines():
- std::vector<ConstraintMatrix> boundary_constraints (triangulation.n_levels());
- std::vector<ConstraintMatrix> boundary_interface_constraints (triangulation.n_levels());
- for (unsigned int level=0; level<triangulation.n_levels(); ++level)
- {
- boundary_constraints[level].add_lines (interface_dofs[level]);
- boundary_constraints[level].add_lines (mg_constrained_dofs.get_boundary_indices()[level]);
- boundary_constraints[level].close ();
-
- boundary_interface_constraints[level]
- .add_lines (boundary_interface_dofs[level]);
- boundary_interface_constraints[level].close ();
- }
-
- // Now that we're done with most of our
- // preliminaries, let's start the
- // integration loop. It looks mostly like
- // the loop in
- // <code>assemble_system</code>, with two
- // exceptions: (i) we don't need a right
- // hand side, and more significantly (ii) we
- // don't just loop over all active cells,
- // but in fact all cells, active or
- // not. Consequently, the correct iterator
- // to use is MGDoFHandler::cell_iterator
- // rather than
- // MGDoFHandler::active_cell_iterator. Let's
- // go about it:
- typename MGDoFHandler<dim>::cell_iterator cell = mg_dof_handler.begin(),
- endc = mg_dof_handler.end();
-
- for (; cell!=endc; ++cell)
- {
- cell_matrix = 0;
- fe_values.reinit (cell);
-
- coefficient.value_list (fe_values.get_quadrature_points(),
- coefficient_values);
-
- for (unsigned int q_point=0; q_point<n_q_points; ++q_point)
+ // The rest of the assembly is again
+ // slightly different. This starts with
+ // a gotcha that is easily forgotten:
+ // The indices of global degrees of
+ // freedom we want here are the ones
+ // for current level, not for the
+ // global matrix. We therefore need the
+ // function
+ // MGDoFAccessorLLget_mg_dof_indices,
+ // not MGDoFAccessor::get_dof_indices
+ // as used in the assembly of the
+ // global system:
+ cell->get_mg_dof_indices (local_dof_indices);
+
+ // Next, we need to copy local
+ // contributions into the level
+ // objects. We can do this in the same
+ // way as in the global assembly, using
+ // a constraint object that takes care
+ // of constrained degrees (which here
+ // are only boundary nodes, as the
+ // individual levels have no hanging
+ // node constraints). Note that the
+ // <code>boundary_constraints</code>
+ // object makes sure that the level
+ // matrices contains no contributions
+ // from degrees of freedom at the
+ // interface between cells of different
+ // refinement level.
+ boundary_constraints[cell->level()]
+ .distribute_local_to_global (cell_matrix,
+ local_dof_indices,
+ mg_matrices[cell->level()]);
+
+ // The next step is again slightly more
+ // obscure (but explained in the @ref
+ // mg_paper): We need the remainder of
+ // the operator that we just copied
+ // into the <code>mg_matrices</code>
+ // object, namely the part on the
+ // interface between cells at the
+ // current level and cells one level
+ // coarser. This matrix exists in two
+ // directions: for interior DoFs (index
+ // $i$) of the current level to those
+ // sitting on the interface (index
+ // $j$), and the other way around. Of
+ // course, since we have a symmetric
+ // operator, one of these matrices is
+ // the transpose of the other.
+ //
+ // The way we assemble these matrices
+ // is as follows: since the are formed
+ // from parts of the local
+ // contributions, we first delete all
+ // those parts of the local
+ // contributions that we are not
+ // interested in, namely all those
+ // elements of the local matrix for
+ // which not $i$ is an interface DoF
+ // and $j$ is not. The result is one of
+ // the two matrices that we are
+ // interested in, and we then copy it
+ // into the
+ // <code>mg_interface_matrices</code>
+ // object. The
+ // <code>boundary_interface_constraints</code>
+ // object at the same time makes sure
+ // that we delete contributions from
+ // all degrees of freedom that are not
+ // only on the interface but also on
+ // the external boundary of the domain.
+ //
+ // The last part to remember is how to
+ // get the other matrix. Since it is
+ // only the transpose, we will later
+ // (in the <code>solve()</code>
+ // function) be able to just pass the
+ // transpose matrix where necessary.
for (unsigned int i=0; i<dofs_per_cell; ++i)
for (unsigned int j=0; j<dofs_per_cell; ++j)
- cell_matrix(i,j) += (coefficient_values[q_point] *
- fe_values.shape_grad(i,q_point) *
- fe_values.shape_grad(j,q_point) *
- fe_values.JxW(q_point));
-
- // The rest of the assembly is again
- // slightly different. This starts with
- // a gotcha that is easily forgotten:
- // The indices of global degrees of
- // freedom we want here are the ones
- // for current level, not for the
- // global matrix. We therefore need the
- // function
- // MGDoFAccessorLLget_mg_dof_indices,
- // not MGDoFAccessor::get_dof_indices
- // as used in the assembly of the
- // global system:
- cell->get_mg_dof_indices (local_dof_indices);
-
- // Next, we need to copy local
- // contributions into the level
- // objects. We can do this in the same
- // way as in the global assembly, using
- // a constraint object that takes care
- // of constrained degrees (which here
- // are only boundary nodes, as the
- // individual levels have no hanging
- // node constraints). Note that the
- // <code>boundary_constraints</code>
- // object makes sure that the level
- // matrices contains no contributions
- // from degrees of freedom at the
- // interface between cells of different
- // refinement level.
- boundary_constraints[cell->level()]
- .distribute_local_to_global (cell_matrix,
- local_dof_indices,
- mg_matrices[cell->level()]);
-
- // The next step is again slightly more
- // obscure (but explained in the @ref
- // mg_paper): We need the remainder of
- // the operator that we just copied
- // into the <code>mg_matrices</code>
- // object, namely the part on the
- // interface between cells at the
- // current level and cells one level
- // coarser. This matrix exists in two
- // directions: for interior DoFs (index
- // $i$) of the current level to those
- // sitting on the interface (index
- // $j$), and the other way around. Of
- // course, since we have a symmetric
- // operator, one of these matrices is
- // the transpose of the other.
- //
- // The way we assemble these matrices
- // is as follows: since the are formed
- // from parts of the local
- // contributions, we first delete all
- // those parts of the local
- // contributions that we are not
- // interested in, namely all those
- // elements of the local matrix for
- // which not $i$ is an interface DoF
- // and $j$ is not. The result is one of
- // the two matrices that we are
- // interested in, and we then copy it
- // into the
- // <code>mg_interface_matrices</code>
- // object. The
- // <code>boundary_interface_constraints</code>
- // object at the same time makes sure
- // that we delete contributions from
- // all degrees of freedom that are not
- // only on the interface but also on
- // the external boundary of the domain.
- //
- // The last part to remember is how to
- // get the other matrix. Since it is
- // only the transpose, we will later
- // (in the <code>solve()</code>
- // function) be able to just pass the
- // transpose matrix where necessary.
- for (unsigned int i=0; i<dofs_per_cell; ++i)
- for (unsigned int j=0; j<dofs_per_cell; ++j)
- if( !(interface_dofs[cell->level()][local_dof_indices[i]]==true &&
- interface_dofs[cell->level()][local_dof_indices[j]]==false))
- cell_matrix(i,j) = 0;
-
- boundary_interface_constraints[cell->level()]
- .distribute_local_to_global (cell_matrix,
- local_dof_indices,
- mg_interface_matrices[cell->level()]);
- }
-}
+ if( !(interface_dofs[cell->level()][local_dof_indices[i]]==true &&
+ interface_dofs[cell->level()][local_dof_indices[j]]==false))
+ cell_matrix(i,j) = 0;
+ boundary_interface_constraints[cell->level()]
+ .distribute_local_to_global (cell_matrix,
+ local_dof_indices,
+ mg_interface_matrices[cell->level()]);
+ }
+ }
- // @sect4{LaplaceProblem::solve}
- // This is the other function that is
- // significantly different in support of the
- // multigrid solver (or, in fact, the
- // preconditioner for which we use the
- // multigrid method).
- //
- // Let us start out by setting up two of the
- // components of multilevel methods: transfer
- // operators between levels, and a solver on
- // the coarsest level. In finite element
- // methods, the transfer operators are
- // derived from the finite element function
- // spaces involved and can often be computed
- // in a generic way independent of the
- // problem under consideration. In that case,
- // we can use the MGTransferPrebuilt class
- // that, given the constraints on the global
- // level and an MGDoFHandler object computes
- // the matrices corresponding to these
- // transfer operators.
- //
- // The second part of the following lines
- // deals with the coarse grid solver. Since
- // our coarse grid is very coarse indeed, we
- // decide for a direct solver (a Householder
- // decomposition of the coarsest level
- // matrix), even if its implementation is not
- // particularly sophisticated. If our coarse
- // mesh had many more cells than the five we
- // have here, something better suited would
- // obviously be necessary here.
-template <int dim>
-void LaplaceProblem<dim>::solve ()
-{
+ // @sect4{LaplaceProblem::solve}
- // Create the object that deals with the transfer
- // between different refinement levels. We need to
- // pass it the hanging node constraints.
- MGTransferPrebuilt<Vector<double> > mg_transfer(hanging_node_constraints, mg_constrained_dofs);
- // Now the prolongation matrix has to be built.
- // This matrix needs to take the boundary values on
- // each level into account and needs to know about
- // the indices at the refinement egdes. The
- // <code>MGConstraints</code> knows about that so
- // pass it as an argument.
- mg_transfer.build_matrices(mg_dof_handler);
-
- FullMatrix<double> coarse_matrix;
- coarse_matrix.copy_from (mg_matrices[0]);
- MGCoarseGridHouseholder<> coarse_grid_solver;
- coarse_grid_solver.initialize (coarse_matrix);
-
- // The next component of a multilevel
- // solver or preconditioner is that we need
- // a smoother on each level. A common
- // choice for this is to use the
- // application of a relaxation method (such
- // as the SOR, Jacobi or Richardson method)
- // or a small number of iterations of a
- // solver method (such as CG or GMRES). The
- // MGSmootherRelaxation and
- // MGSmootherPrecondition classes provide
- // support for these two kinds of
- // smoothers. Here, we opt for the
- // application of a single SOR
- // iteration. To this end, we define an
- // appropriate <code>typedef</code> and
- // then setup a smoother object.
- //
- // Since this smoother needs temporary
- // vectors to store intermediate results,
- // we need to provide a VectorMemory
- // object. Since these vectors will be
- // reused over and over, the
- // GrowingVectorMemory is more time
- // efficient than the PrimitiveVectorMemory
- // class in the current case.
+ // This is the other function that is
+ // significantly different in support of the
+ // multigrid solver (or, in fact, the
+ // preconditioner for which we use the
+ // multigrid method).
//
- // The last step is to initialize the
- // smoother object with our level matrices
- // and to set some smoothing parameters.
- // The <code>initialize()</code> function
- // can optionally take additional arguments
- // that will be passed to the smoother
- // object on each level. In the current
- // case for the SOR smoother, this could,
- // for example, include a relaxation
- // parameter. However, we here leave these
- // at their default values. The call to
- // <code>set_steps()</code> indicates that
- // we will use two pre- and two
- // post-smoothing steps on each level; to
- // use a variable number of smoother steps
- // on different levels, more options can be
- // set in the constructor call to the
- // <code>mg_smoother</code> object.
+ // Let us start out by setting up two of the
+ // components of multilevel methods: transfer
+ // operators between levels, and a solver on
+ // the coarsest level. In finite element
+ // methods, the transfer operators are
+ // derived from the finite element function
+ // spaces involved and can often be computed
+ // in a generic way independent of the
+ // problem under consideration. In that case,
+ // we can use the MGTransferPrebuilt class
+ // that, given the constraints on the global
+ // level and an MGDoFHandler object computes
+ // the matrices corresponding to these
+ // transfer operators.
//
- // The last step results from the fact that
- // we use the SOR method as a smoother -
- // which is not symmetric - but we use the
- // conjugate gradient iteration (which
- // requires a symmetric preconditioner)
- // below, we need to let the multilevel
- // preconditioner make sure that we get a
- // symmetric operator even for nonsymmetric
- // smoothers:
- typedef PreconditionSOR<SparseMatrix<double> > Smoother;
- GrowingVectorMemory<> vector_memory;
- MGSmootherRelaxation<SparseMatrix<double>, Smoother, Vector<double> >
- mg_smoother(vector_memory);
- mg_smoother.initialize(mg_matrices);
- mg_smoother.set_steps(2);
- mg_smoother.set_symmetric(true);
-
- // The next preparatory step is that we
- // must wrap our level and interface
- // matrices in an object having the
- // required multiplication functions. We
- // will create two objects for the
- // interface objects going from coarse to
- // fine and the other way around; the
- // multigrid algorithm will later use the
- // transpose operator for the latter
- // operation, allowing us to initialize
- // both up and down versions of the
- // operator with the matrices we already
- // built:
- MGMatrix<> mg_matrix(&mg_matrices);
- MGMatrix<> mg_interface_up(&mg_interface_matrices);
- MGMatrix<> mg_interface_down(&mg_interface_matrices);
-
- // Now, we are ready to set up the
- // V-cycle operator and the
- // multilevel preconditioner.
- Multigrid<Vector<double> > mg(mg_dof_handler,
- mg_matrix,
- coarse_grid_solver,
- mg_transfer,
- mg_smoother,
- mg_smoother);
- mg.set_edge_matrices(mg_interface_down, mg_interface_up);
-
- PreconditionMG<dim, Vector<double>, MGTransferPrebuilt<Vector<double> > >
- preconditioner(mg_dof_handler, mg, mg_transfer);
-
- // With all this together, we can finally
- // get about solving the linear system in
- // the usual way:
- SolverControl solver_control (1000, 1e-12);
- SolverCG<> cg (solver_control);
-
- solution = 0;
-
- cg.solve (system_matrix, solution, system_rhs,
- preconditioner);
- constraints.distribute (solution);
-
- std::cout << " " << solver_control.last_step()
- << " CG iterations needed to obtain convergence."
- << std::endl;
-}
-
-
-
- // @sect4{Postprocessing}
-
- // The following two functions postprocess a
- // solution once it is computed. In
- // particular, the first one refines the mesh
- // at the beginning of each cycle while the
- // second one outputs results at the end of
- // each such cycle. The functions are almost
- // unchanged from those in step-6, with the
- // exception of two minor differences: The
- // KellyErrorEstimator::estimate function
- // wants an argument of type DoFHandler, not
- // MGDoFHandler, and so we have to cast from
- // derived to base class; and we generate
- // output in VTK format, to use the more
- // modern visualization programs available
- // today compared to those that were
- // available when step-6 was written.
-template <int dim>
-void LaplaceProblem<dim>::refine_grid ()
-{
- Vector<float> estimated_error_per_cell (triangulation.n_active_cells());
-
- KellyErrorEstimator<dim>::estimate (static_cast<DoFHandler<dim>&>(mg_dof_handler),
- QGauss<dim-1>(3),
- typename FunctionMap<dim>::type(),
- solution,
- estimated_error_per_cell);
- GridRefinement::refine_and_coarsen_fixed_number (triangulation,
- estimated_error_per_cell,
- 0.3, 0.03);
- triangulation.execute_coarsening_and_refinement ();
-}
-
-
-
-template <int dim>
-void LaplaceProblem<dim>::output_results (const unsigned int cycle) const
-{
- DataOut<dim> data_out;
-
- data_out.attach_dof_handler (mg_dof_handler);
- data_out.add_data_vector (solution, "solution");
- data_out.build_patches ();
-
- std::ostringstream filename;
- filename << "solution-"
- << cycle
- << ".vtk";
-
- std::ofstream output (filename.str().c_str());
- data_out.write_vtk (output);
-}
-
-
- // @sect4{LaplaceProblem::run}
-
- // Like several of the functions above, this
- // is almost exactly a copy of of the
- // corresponding function in step-6. The only
- // difference is the call to
- // <code>assemble_multigrid</code> that takes
- // care of forming the matrices on every
- // level that we need in the multigrid
- // method.
-template <int dim>
-void LaplaceProblem<dim>::run ()
-{
- for (unsigned int cycle=0; cycle<8; ++cycle)
- {
- std::cout << "Cycle " << cycle << ':' << std::endl;
-
- if (cycle == 0)
- {
- GridGenerator::hyper_ball (triangulation);
+ // The second part of the following lines
+ // deals with the coarse grid solver. Since
+ // our coarse grid is very coarse indeed, we
+ // decide for a direct solver (a Householder
+ // decomposition of the coarsest level
+ // matrix), even if its implementation is not
+ // particularly sophisticated. If our coarse
+ // mesh had many more cells than the five we
+ // have here, something better suited would
+ // obviously be necessary here.
+ template <int dim>
+ void LaplaceProblem<dim>::solve ()
+ {
+
+ // Create the object that deals with the transfer
+ // between different refinement levels. We need to
+ // pass it the hanging node constraints.
+ MGTransferPrebuilt<Vector<double> > mg_transfer(hanging_node_constraints, mg_constrained_dofs);
+ // Now the prolongation matrix has to be built.
+ // This matrix needs to take the boundary values on
+ // each level into account and needs to know about
+ // the indices at the refinement egdes. The
+ // <code>MGConstraints</code> knows about that so
+ // pass it as an argument.
+ mg_transfer.build_matrices(mg_dof_handler);
+
+ FullMatrix<double> coarse_matrix;
+ coarse_matrix.copy_from (mg_matrices[0]);
+ MGCoarseGridHouseholder<> coarse_grid_solver;
+ coarse_grid_solver.initialize (coarse_matrix);
+
+ // The next component of a multilevel
+ // solver or preconditioner is that we need
+ // a smoother on each level. A common
+ // choice for this is to use the
+ // application of a relaxation method (such
+ // as the SOR, Jacobi or Richardson method)
+ // or a small number of iterations of a
+ // solver method (such as CG or GMRES). The
+ // MGSmootherRelaxation and
+ // MGSmootherPrecondition classes provide
+ // support for these two kinds of
+ // smoothers. Here, we opt for the
+ // application of a single SOR
+ // iteration. To this end, we define an
+ // appropriate <code>typedef</code> and
+ // then setup a smoother object.
+ //
+ // Since this smoother needs temporary
+ // vectors to store intermediate results,
+ // we need to provide a VectorMemory
+ // object. Since these vectors will be
+ // reused over and over, the
+ // GrowingVectorMemory is more time
+ // efficient than the PrimitiveVectorMemory
+ // class in the current case.
+ //
+ // The last step is to initialize the
+ // smoother object with our level matrices
+ // and to set some smoothing parameters.
+ // The <code>initialize()</code> function
+ // can optionally take additional arguments
+ // that will be passed to the smoother
+ // object on each level. In the current
+ // case for the SOR smoother, this could,
+ // for example, include a relaxation
+ // parameter. However, we here leave these
+ // at their default values. The call to
+ // <code>set_steps()</code> indicates that
+ // we will use two pre- and two
+ // post-smoothing steps on each level; to
+ // use a variable number of smoother steps
+ // on different levels, more options can be
+ // set in the constructor call to the
+ // <code>mg_smoother</code> object.
+ //
+ // The last step results from the fact that
+ // we use the SOR method as a smoother -
+ // which is not symmetric - but we use the
+ // conjugate gradient iteration (which
+ // requires a symmetric preconditioner)
+ // below, we need to let the multilevel
+ // preconditioner make sure that we get a
+ // symmetric operator even for nonsymmetric
+ // smoothers:
+ typedef PreconditionSOR<SparseMatrix<double> > Smoother;
+ GrowingVectorMemory<> vector_memory;
+ MGSmootherRelaxation<SparseMatrix<double>, Smoother, Vector<double> >
+ mg_smoother(vector_memory);
+ mg_smoother.initialize(mg_matrices);
+ mg_smoother.set_steps(2);
+ mg_smoother.set_symmetric(true);
+
+ // The next preparatory step is that we
+ // must wrap our level and interface
+ // matrices in an object having the
+ // required multiplication functions. We
+ // will create two objects for the
+ // interface objects going from coarse to
+ // fine and the other way around; the
+ // multigrid algorithm will later use the
+ // transpose operator for the latter
+ // operation, allowing us to initialize
+ // both up and down versions of the
+ // operator with the matrices we already
+ // built:
+ MGMatrix<> mg_matrix(&mg_matrices);
+ MGMatrix<> mg_interface_up(&mg_interface_matrices);
+ MGMatrix<> mg_interface_down(&mg_interface_matrices);
+
+ // Now, we are ready to set up the
+ // V-cycle operator and the
+ // multilevel preconditioner.
+ Multigrid<Vector<double> > mg(mg_dof_handler,
+ mg_matrix,
+ coarse_grid_solver,
+ mg_transfer,
+ mg_smoother,
+ mg_smoother);
+ mg.set_edge_matrices(mg_interface_down, mg_interface_up);
+
+ PreconditionMG<dim, Vector<double>, MGTransferPrebuilt<Vector<double> > >
+ preconditioner(mg_dof_handler, mg, mg_transfer);
+
+ // With all this together, we can finally
+ // get about solving the linear system in
+ // the usual way:
+ SolverControl solver_control (1000, 1e-12);
+ SolverCG<> cg (solver_control);
+
+ solution = 0;
+
+ cg.solve (system_matrix, solution, system_rhs,
+ preconditioner);
+ constraints.distribute (solution);
+
+ std::cout << " " << solver_control.last_step()
+ << " CG iterations needed to obtain convergence."
+ << std::endl;
+ }
+
+
+
+ // @sect4{Postprocessing}
+
+ // The following two functions postprocess a
+ // solution once it is computed. In
+ // particular, the first one refines the mesh
+ // at the beginning of each cycle while the
+ // second one outputs results at the end of
+ // each such cycle. The functions are almost
+ // unchanged from those in step-6, with the
+ // exception of two minor differences: The
+ // KellyErrorEstimator::estimate function
+ // wants an argument of type DoFHandler, not
+ // MGDoFHandler, and so we have to cast from
+ // derived to base class; and we generate
+ // output in VTK format, to use the more
+ // modern visualization programs available
+ // today compared to those that were
+ // available when step-6 was written.
+ template <int dim>
+ void LaplaceProblem<dim>::refine_grid ()
+ {
+ Vector<float> estimated_error_per_cell (triangulation.n_active_cells());
+
+ KellyErrorEstimator<dim>::estimate (static_cast<DoFHandler<dim>&>(mg_dof_handler),
+ QGauss<dim-1>(3),
+ typename FunctionMap<dim>::type(),
+ solution,
+ estimated_error_per_cell);
+ GridRefinement::refine_and_coarsen_fixed_number (triangulation,
+ estimated_error_per_cell,
+ 0.3, 0.03);
+ triangulation.execute_coarsening_and_refinement ();
+ }
+
+
+
+ template <int dim>
+ void LaplaceProblem<dim>::output_results (const unsigned int cycle) const
+ {
+ DataOut<dim> data_out;
+
+ data_out.attach_dof_handler (mg_dof_handler);
+ data_out.add_data_vector (solution, "solution");
+ data_out.build_patches ();
+
+ std::ostringstream filename;
+ filename << "solution-"
+ << cycle
+ << ".vtk";
+
+ std::ofstream output (filename.str().c_str());
+ data_out.write_vtk (output);
+ }
+
+
+ // @sect4{LaplaceProblem::run}
+
+ // Like several of the functions above, this
+ // is almost exactly a copy of of the
+ // corresponding function in step-6. The only
+ // difference is the call to
+ // <code>assemble_multigrid</code> that takes
+ // care of forming the matrices on every
+ // level that we need in the multigrid
+ // method.
+ template <int dim>
+ void LaplaceProblem<dim>::run ()
+ {
+ for (unsigned int cycle=0; cycle<8; ++cycle)
+ {
+ std::cout << "Cycle " << cycle << ':' << std::endl;
+
+ if (cycle == 0)
+ {
+ GridGenerator::hyper_ball (triangulation);
- static const HyperBallBoundary<dim> boundary;
- triangulation.set_boundary (0, boundary);
+ static const HyperBallBoundary<dim> boundary;
+ triangulation.set_boundary (0, boundary);
- triangulation.refine_global (1);
- }
- else
- refine_grid ();
+ triangulation.refine_global (1);
+ }
+ else
+ refine_grid ();
- std::cout << " Number of active cells: "
- << triangulation.n_active_cells()
- << std::endl;
+ std::cout << " Number of active cells: "
+ << triangulation.n_active_cells()
+ << std::endl;
- setup_system ();
+ setup_system ();
- std::cout << " Number of degrees of freedom: "
- << mg_dof_handler.n_dofs()
- << " (by level: ";
- for (unsigned int level=0; level<triangulation.n_levels(); ++level)
- std::cout << mg_dof_handler.n_dofs(level)
- << (level == triangulation.n_levels()-1
- ? ")" : ", ");
- std::cout << std::endl;
+ std::cout << " Number of degrees of freedom: "
+ << mg_dof_handler.n_dofs()
+ << " (by level: ";
+ for (unsigned int level=0; level<triangulation.n_levels(); ++level)
+ std::cout << mg_dof_handler.n_dofs(level)
+ << (level == triangulation.n_levels()-1
+ ? ")" : ", ");
+ std::cout << std::endl;
- assemble_system ();
- assemble_multigrid ();
+ assemble_system ();
+ assemble_multigrid ();
- solve ();
- output_results (cycle);
- }
+ solve ();
+ output_results (cycle);
+ }
+ }
}
{
try
{
+ using namespace dealii;
+ using namespace Step16;
+
deallog.depth_console (0);
LaplaceProblem<2> laplace_problem(1);
/* $Id$ */
/* */
-/* Copyright (C) 2000, 2004, 2005, 2006, 2007, 2008, 2009 by the deal.II authors */
+/* Copyright (C) 2000, 2004, 2005, 2006, 2007, 2008, 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 */
// The last step is as in all
// previous programs:
-using namespace dealii;
-
- // Now, here comes the declaration of the
- // main class and of various other things
- // below it. As mentioned in the
- // introduction, almost all of this has been
- // copied verbatim from step-8, so we only
- // comment on the few things that are
- // different. There is one (cosmetic) change
- // in that we let <code>solve</code> return a value,
- // namely the number of iterations it took to
- // converge, so that we can output this to
- // the screen at the appropriate place. In
- // addition, we introduce a stream-like
- // variable <code>pcout</code>, explained below:
-template <int dim>
-class ElasticProblem
+namespace Step17
{
- public:
- ElasticProblem ();
- ~ElasticProblem ();
- void run ();
-
- private:
- void setup_system ();
- void assemble_system ();
- unsigned int solve ();
- void refine_grid ();
- void output_results (const unsigned int cycle) const;
-
- // The first variable is basically only
- // for convenience: in %parallel program,
- // if each process outputs status
- // information, then there quickly is a
- // lot of clutter. Rather, we would want
- // to only have one process output
- // everything once, for example the one
- // with process number
- // zero. <code>ConditionalOStream</code> does
- // exactly this: it acts as if it were a
- // stream, but only forwards to a real,
- // underlying stream if a flag is set. By
- // setting this condition to
- // <code>this_mpi_process==0</code>, we make sure
- // that output is only generated from the
- // first process and that we don't get
- // the same lines of output over and over
- // again, once per process.
- //
- // With this simple trick, we make sure
- // that we don't have to guard each and
- // every write to <code>std::cout</code> by a
- // prefixed <code>if(this_mpi_process==0)</code>.
- ConditionalOStream pcout;
-
- // The next few variables are taken
- // verbatim from step-8:
- Triangulation<dim> triangulation;
- DoFHandler<dim> dof_handler;
-
- FESystem<dim> fe;
-
- ConstraintMatrix hanging_node_constraints;
-
- // In step-8, this would have been the
- // place where we would have declared the
- // member variables for the sparsity
- // pattern, the system matrix, right
- // hand, and solution vector. We change
- // these declarations to use %parallel
- // PETSc objects instead (note that the
- // fact that we use the %parallel versions
- // is denoted the fact that we use the
- // classes from the
- // <code>PETScWrappers::MPI</code> namespace;
- // sequential versions of these classes
- // are in the <code>PETScWrappers</code>
- // namespace, i.e. without the <code>MPI</code>
- // part). Note also that we do not use a
- // separate sparsity pattern, since PETSc
- // manages that as part of its matrix
- // data structures.
- PETScWrappers::MPI::SparseMatrix system_matrix;
-
- PETScWrappers::MPI::Vector solution;
- PETScWrappers::MPI::Vector system_rhs;
-
- // The next change is that we have to
- // declare a variable that indicates the
- // MPI communicator over which we are
- // supposed to distribute our
- // computations. Note that if this is a
- // sequential job without support by MPI,
- // then PETSc provides some dummy type
- // for <code>MPI_Comm</code>, so we do not have to
- // care here whether the job is really a
- // %parallel one:
- MPI_Comm mpi_communicator;
-
- // Then we have two variables that tell
- // us where in the %parallel world we
- // are. The first of the following
- // variables, <code>n_mpi_processes</code> tells
- // us how many MPI processes there exist
- // in total, while the second one,
- // <code>this_mpi_process</code>, indicates which
- // is the number of the present process
- // within this space of processes. The
- // latter variable will have a unique
- // value for each process between zero
- // and (less than)
- // <code>n_mpi_processes</code>. If this program
- // is run on a single machine without MPI
- // support, then their values are <code>1</code>
- // and <code>0</code>, respectively.
- const unsigned int n_mpi_processes;
- const unsigned int this_mpi_process;
-};
-
-
- // The following is again taken from step-8
- // without change:
-template <int dim>
-class RightHandSide : public Function<dim>
-{
- public:
- RightHandSide ();
-
- virtual void vector_value (const Point<dim> &p,
- Vector<double> &values) const;
-
- virtual void vector_value_list (const std::vector<Point<dim> > &points,
- std::vector<Vector<double> > &value_list) const;
-};
-
-
-template <int dim>
-RightHandSide<dim>::RightHandSide () :
- Function<dim> (dim)
-{}
-
-
-template <int dim>
-inline
-void RightHandSide<dim>::vector_value (const Point<dim> &p,
- Vector<double> &values) const
-{
- Assert (values.size() == dim,
- ExcDimensionMismatch (values.size(), dim));
- Assert (dim >= 2, ExcInternalError());
-
- Point<dim> point_1, point_2;
- point_1(0) = 0.5;
- point_2(0) = -0.5;
-
- if (((p-point_1).square() < 0.2*0.2) ||
- ((p-point_2).square() < 0.2*0.2))
- values(0) = 1;
- else
- values(0) = 0;
-
- if (p.square() < 0.2*0.2)
- values(1) = 1;
- else
- values(1) = 0;
-}
-
-
-
-template <int dim>
-void RightHandSide<dim>::vector_value_list (const std::vector<Point<dim> > &points,
- std::vector<Vector<double> > &value_list) const
-{
- const unsigned int n_points = points.size();
-
- Assert (value_list.size() == n_points,
- ExcDimensionMismatch (value_list.size(), n_points));
-
- for (unsigned int p=0; p<n_points; ++p)
- RightHandSide<dim>::vector_value (points[p],
- value_list[p]);
-}
-
-
- // The first step in the actual
- // implementation of things is the
- // constructor of the main class. Apart from
- // initializing the same member variables
- // that we already had in step-8, we here
- // initialize the MPI communicator variable
- // we shall use with the global MPI
- // communicator linking all processes
- // together (in more complex applications,
- // one could here use a communicator object
- // that only links a subset of all
- // processes), and call the Utilities helper
- // functions to determine the number of
- // processes and where the present one fits
- // into this picture. In addition, we make
- // sure that output is only generated by the
- // (globally) first process. As,
- // this_mpi_process is determined after
- // creation of pcout, we cannot set the
- // condition through the constructor, i.e. by
- // pcout(std::cout, this_mpi_process==0), but
- // set the condition separately.
-template <int dim>
-ElasticProblem<dim>::ElasticProblem ()
- :
- pcout (std::cout),
- dof_handler (triangulation),
- fe (FE_Q<dim>(1), dim),
- mpi_communicator (MPI_COMM_WORLD),
- n_mpi_processes (Utilities::System::get_n_mpi_processes(mpi_communicator)),
- this_mpi_process (Utilities::System::get_this_mpi_process(mpi_communicator))
-{
- pcout.set_condition(this_mpi_process == 0);
-}
-
-
-
-template <int dim>
-ElasticProblem<dim>::~ElasticProblem ()
-{
- dof_handler.clear ();
-}
-
-
- // The second step is the function in which
- // we set up the various variables for the
- // global linear system to be solved.
-template <int dim>
-void ElasticProblem<dim>::setup_system ()
-{
- // Before we even start out setting up the
- // system, there is one thing to do for a
- // %parallel program: we need to assign
- // cells to each of the processes. We do
- // this by splitting (<code>partitioning</code>) the
- // mesh cells into as many chunks
- // (<code>subdomains</code>) as there are processes
- // in this MPI job (if this is a sequential
- // job, then there is only one job and all
- // cells will get a zero as subdomain
- // indicator). This is done using an
- // interface to the METIS library that does
- // this in a very efficient way, trying to
- // minimize the number of nodes on the
- // interfaces between subdomains. All this
- // is hidden behind the following call to a
- // deal.II library function:
- GridTools::partition_triangulation (n_mpi_processes, triangulation);
-
- // As for the linear system: First, we need
- // to generate an enumeration for the
- // degrees of freedom in our
- // problem. Further below, we will show how
- // we assign each cell to one of the MPI
- // processes before we even get here. What
- // we then need to do is to enumerate the
- // degrees of freedom in a way so that all
- // degrees of freedom associated with cells
- // in subdomain zero (which resides on
- // process zero) come before all DoFs
- // associated with cells on subdomain one,
- // before those on cells on process two,
- // and so on. We need this since we have to
- // split the global vectors for right hand
- // side and solution, as well as the matrix
- // into contiguous chunks of rows that live
- // on each of the processors, and we will
- // want to do this in a way that requires
- // minimal communication. This is done
- // using the following two functions, which
- // first generates an initial ordering of
- // all degrees of freedom, and then re-sort
- // them according to above criterion:
- dof_handler.distribute_dofs (fe);
- DoFRenumbering::subdomain_wise (dof_handler);
-
- // While we're at it, let us also count how
- // many degrees of freedom there exist on
- // the present process:
- const unsigned int n_local_dofs
- = DoFTools::count_dofs_with_subdomain_association (dof_handler,
- this_mpi_process);
-
- // Then we initialize the system matrix,
- // solution, and right hand side
- // vectors. Since they all need to work in
- // %parallel, we have to pass them an MPI
- // communication object, as well as their
- // global sizes (both dimensions are equal
- // to the number of degrees of freedom),
- // and also how many rows out of this
- // global size are to be stored locally
- // (<code>n_local_dofs</code>). In addition, PETSc
- // needs to know how to partition the
- // columns in the chunk of the matrix that
- // is stored locally; for square matrices,
- // the columns should be partitioned in the
- // same way as the rows (indicated by the
- // second <code>n_local_dofs</code> in the call) but
- // in the case of rectangular matrices one
- // has to partition the columns in the same
- // way as vectors are partitioned with
- // which the matrix is multiplied, while
- // rows have to partitioned in the same way
- // as destination vectors of matrix-vector
- // multiplications:
- system_matrix.reinit (mpi_communicator,
- dof_handler.n_dofs(),
- dof_handler.n_dofs(),
- n_local_dofs,
- n_local_dofs,
- dof_handler.max_couplings_between_dofs());
-
- solution.reinit (mpi_communicator, dof_handler.n_dofs(), n_local_dofs);
- system_rhs.reinit (mpi_communicator, dof_handler.n_dofs(), n_local_dofs);
-
- // Finally, we need to initialize the
- // objects denoting hanging node
- // constraints for the present grid. Note
- // that since PETSc handles the sparsity
- // pattern internally to the matrix, there
- // is no need to set up an independent
- // sparsity pattern here, and to condense
- // it for constraints, as we have done in
- // all other example programs.
- hanging_node_constraints.clear ();
- DoFTools::make_hanging_node_constraints (dof_handler,
- hanging_node_constraints);
- hanging_node_constraints.close ();
-}
-
+ using namespace dealii;
+
+ // Now, here comes the declaration of the
+ // main class and of various other things
+ // below it. As mentioned in the
+ // introduction, almost all of this has been
+ // copied verbatim from step-8, so we only
+ // comment on the few things that are
+ // different. There is one (cosmetic) change
+ // in that we let <code>solve</code> return a value,
+ // namely the number of iterations it took to
+ // converge, so that we can output this to
+ // the screen at the appropriate place. In
+ // addition, we introduce a stream-like
+ // variable <code>pcout</code>, explained below:
+ template <int dim>
+ class ElasticProblem
+ {
+ public:
+ ElasticProblem ();
+ ~ElasticProblem ();
+ void run ();
+
+ private:
+ void setup_system ();
+ void assemble_system ();
+ unsigned int solve ();
+ void refine_grid ();
+ void output_results (const unsigned int cycle) const;
+
+ // The first variable is basically only
+ // for convenience: in %parallel program,
+ // if each process outputs status
+ // information, then there quickly is a
+ // lot of clutter. Rather, we would want
+ // to only have one process output
+ // everything once, for example the one
+ // with process number
+ // zero. <code>ConditionalOStream</code> does
+ // exactly this: it acts as if it were a
+ // stream, but only forwards to a real,
+ // underlying stream if a flag is set. By
+ // setting this condition to
+ // <code>this_mpi_process==0</code>, we make sure
+ // that output is only generated from the
+ // first process and that we don't get
+ // the same lines of output over and over
+ // again, once per process.
+ //
+ // With this simple trick, we make sure
+ // that we don't have to guard each and
+ // every write to <code>std::cout</code> by a
+ // prefixed <code>if(this_mpi_process==0)</code>.
+ ConditionalOStream pcout;
+
+ // The next few variables are taken
+ // verbatim from step-8:
+ Triangulation<dim> triangulation;
+ DoFHandler<dim> dof_handler;
+
+ FESystem<dim> fe;
+
+ ConstraintMatrix hanging_node_constraints;
+
+ // In step-8, this would have been the
+ // place where we would have declared the
+ // member variables for the sparsity
+ // pattern, the system matrix, right
+ // hand, and solution vector. We change
+ // these declarations to use %parallel
+ // PETSc objects instead (note that the
+ // fact that we use the %parallel versions
+ // is denoted the fact that we use the
+ // classes from the
+ // <code>PETScWrappers::MPI</code> namespace;
+ // sequential versions of these classes
+ // are in the <code>PETScWrappers</code>
+ // namespace, i.e. without the <code>MPI</code>
+ // part). Note also that we do not use a
+ // separate sparsity pattern, since PETSc
+ // manages that as part of its matrix
+ // data structures.
+ PETScWrappers::MPI::SparseMatrix system_matrix;
+
+ PETScWrappers::MPI::Vector solution;
+ PETScWrappers::MPI::Vector system_rhs;
+
+ // The next change is that we have to
+ // declare a variable that indicates the
+ // MPI communicator over which we are
+ // supposed to distribute our
+ // computations. Note that if this is a
+ // sequential job without support by MPI,
+ // then PETSc provides some dummy type
+ // for <code>MPI_Comm</code>, so we do not have to
+ // care here whether the job is really a
+ // %parallel one:
+ MPI_Comm mpi_communicator;
+
+ // Then we have two variables that tell
+ // us where in the %parallel world we
+ // are. The first of the following
+ // variables, <code>n_mpi_processes</code> tells
+ // us how many MPI processes there exist
+ // in total, while the second one,
+ // <code>this_mpi_process</code>, indicates which
+ // is the number of the present process
+ // within this space of processes. The
+ // latter variable will have a unique
+ // value for each process between zero
+ // and (less than)
+ // <code>n_mpi_processes</code>. If this program
+ // is run on a single machine without MPI
+ // support, then their values are <code>1</code>
+ // and <code>0</code>, respectively.
+ const unsigned int n_mpi_processes;
+ const unsigned int this_mpi_process;
+ };
+
+
+ // The following is again taken from step-8
+ // without change:
+ template <int dim>
+ class RightHandSide : public Function<dim>
+ {
+ public:
+ RightHandSide ();
+
+ virtual void vector_value (const Point<dim> &p,
+ Vector<double> &values) const;
+
+ virtual void vector_value_list (const std::vector<Point<dim> > &points,
+ std::vector<Vector<double> > &value_list) const;
+ };
+
+
+ template <int dim>
+ RightHandSide<dim>::RightHandSide () :
+ Function<dim> (dim)
+ {}
+
+
+ template <int dim>
+ inline
+ void RightHandSide<dim>::vector_value (const Point<dim> &p,
+ Vector<double> &values) const
+ {
+ Assert (values.size() == dim,
+ ExcDimensionMismatch (values.size(), dim));
+ Assert (dim >= 2, ExcInternalError());
+
+ Point<dim> point_1, point_2;
+ point_1(0) = 0.5;
+ point_2(0) = -0.5;
+
+ if (((p-point_1).square() < 0.2*0.2) ||
+ ((p-point_2).square() < 0.2*0.2))
+ values(0) = 1;
+ else
+ values(0) = 0;
+
+ if (p.square() < 0.2*0.2)
+ values(1) = 1;
+ else
+ values(1) = 0;
+ }
+
+
+
+ template <int dim>
+ void RightHandSide<dim>::vector_value_list (const std::vector<Point<dim> > &points,
+ std::vector<Vector<double> > &value_list) const
+ {
+ const unsigned int n_points = points.size();
+
+ Assert (value_list.size() == n_points,
+ ExcDimensionMismatch (value_list.size(), n_points));
+
+ for (unsigned int p=0; p<n_points; ++p)
+ RightHandSide<dim>::vector_value (points[p],
+ value_list[p]);
+ }
+
+
+ // The first step in the actual
+ // implementation of things is the
+ // constructor of the main class. Apart from
+ // initializing the same member variables
+ // that we already had in step-8, we here
+ // initialize the MPI communicator variable
+ // we shall use with the global MPI
+ // communicator linking all processes
+ // together (in more complex applications,
+ // one could here use a communicator object
+ // that only links a subset of all
+ // processes), and call the Utilities helper
+ // functions to determine the number of
+ // processes and where the present one fits
+ // into this picture. In addition, we make
+ // sure that output is only generated by the
+ // (globally) first process. As,
+ // this_mpi_process is determined after
+ // creation of pcout, we cannot set the
+ // condition through the constructor, i.e. by
+ // pcout(std::cout, this_mpi_process==0), but
+ // set the condition separately.
+ template <int dim>
+ ElasticProblem<dim>::ElasticProblem ()
+ :
+ pcout (std::cout),
+ dof_handler (triangulation),
+ fe (FE_Q<dim>(1), dim),
+ mpi_communicator (MPI_COMM_WORLD),
+ n_mpi_processes (Utilities::System::get_n_mpi_processes(mpi_communicator)),
+ this_mpi_process (Utilities::System::get_this_mpi_process(mpi_communicator))
+ {
+ pcout.set_condition(this_mpi_process == 0);
+ }
+
+
+
+ template <int dim>
+ ElasticProblem<dim>::~ElasticProblem ()
+ {
+ dof_handler.clear ();
+ }
+
+
+ // The second step is the function in which
+ // we set up the various variables for the
+ // global linear system to be solved.
+ template <int dim>
+ void ElasticProblem<dim>::setup_system ()
+ {
+ // Before we even start out setting up the
+ // system, there is one thing to do for a
+ // %parallel program: we need to assign
+ // cells to each of the processes. We do
+ // this by splitting (<code>partitioning</code>) the
+ // mesh cells into as many chunks
+ // (<code>subdomains</code>) as there are processes
+ // in this MPI job (if this is a sequential
+ // job, then there is only one job and all
+ // cells will get a zero as subdomain
+ // indicator). This is done using an
+ // interface to the METIS library that does
+ // this in a very efficient way, trying to
+ // minimize the number of nodes on the
+ // interfaces between subdomains. All this
+ // is hidden behind the following call to a
+ // deal.II library function:
+ GridTools::partition_triangulation (n_mpi_processes, triangulation);
+
+ // As for the linear system: First, we need
+ // to generate an enumeration for the
+ // degrees of freedom in our
+ // problem. Further below, we will show how
+ // we assign each cell to one of the MPI
+ // processes before we even get here. What
+ // we then need to do is to enumerate the
+ // degrees of freedom in a way so that all
+ // degrees of freedom associated with cells
+ // in subdomain zero (which resides on
+ // process zero) come before all DoFs
+ // associated with cells on subdomain one,
+ // before those on cells on process two,
+ // and so on. We need this since we have to
+ // split the global vectors for right hand
+ // side and solution, as well as the matrix
+ // into contiguous chunks of rows that live
+ // on each of the processors, and we will
+ // want to do this in a way that requires
+ // minimal communication. This is done
+ // using the following two functions, which
+ // first generates an initial ordering of
+ // all degrees of freedom, and then re-sort
+ // them according to above criterion:
+ dof_handler.distribute_dofs (fe);
+ DoFRenumbering::subdomain_wise (dof_handler);
+
+ // While we're at it, let us also count how
+ // many degrees of freedom there exist on
+ // the present process:
+ const unsigned int n_local_dofs
+ = DoFTools::count_dofs_with_subdomain_association (dof_handler,
+ this_mpi_process);
+
+ // Then we initialize the system matrix,
+ // solution, and right hand side
+ // vectors. Since they all need to work in
+ // %parallel, we have to pass them an MPI
+ // communication object, as well as their
+ // global sizes (both dimensions are equal
+ // to the number of degrees of freedom),
+ // and also how many rows out of this
+ // global size are to be stored locally
+ // (<code>n_local_dofs</code>). In addition, PETSc
+ // needs to know how to partition the
+ // columns in the chunk of the matrix that
+ // is stored locally; for square matrices,
+ // the columns should be partitioned in the
+ // same way as the rows (indicated by the
+ // second <code>n_local_dofs</code> in the call) but
+ // in the case of rectangular matrices one
+ // has to partition the columns in the same
+ // way as vectors are partitioned with
+ // which the matrix is multiplied, while
+ // rows have to partitioned in the same way
+ // as destination vectors of matrix-vector
+ // multiplications:
+ system_matrix.reinit (mpi_communicator,
+ dof_handler.n_dofs(),
+ dof_handler.n_dofs(),
+ n_local_dofs,
+ n_local_dofs,
+ dof_handler.max_couplings_between_dofs());
+
+ solution.reinit (mpi_communicator, dof_handler.n_dofs(), n_local_dofs);
+ system_rhs.reinit (mpi_communicator, dof_handler.n_dofs(), n_local_dofs);
+
+ // Finally, we need to initialize the
+ // objects denoting hanging node
+ // constraints for the present grid. Note
+ // that since PETSc handles the sparsity
+ // pattern internally to the matrix, there
+ // is no need to set up an independent
+ // sparsity pattern here, and to condense
+ // it for constraints, as we have done in
+ // all other example programs.
+ hanging_node_constraints.clear ();
+ DoFTools::make_hanging_node_constraints (dof_handler,
+ hanging_node_constraints);
+ hanging_node_constraints.close ();
+ }
+
+
+ // The third step is to actually assemble the
+ // matrix and right hand side of the
+ // problem. There are some things worth
+ // mentioning before we go into
+ // detail. First, we will be assembling the
+ // system in %parallel, i.e. each process will
+ // be responsible for assembling on cells
+ // that belong to this particular
+ // processor. Note that the degrees of
+ // freedom are split in a way such that all
+ // DoFs in the interior of cells and between
+ // cells belonging to the same subdomain
+ // belong to the process that <code>owns</code> the
+ // cell. However, even then we sometimes need
+ // to assemble on a cell with a neighbor that
+ // belongs to a different process, and in
+ // these cases when we write the local
+ // contributions into the global matrix or
+ // right hand side vector, we actually have
+ // to transfer these entries to the other
+ // process. Fortunately, we don't have to do
+ // this by hand, PETSc does all this for us
+ // by caching these elements locally, and
+ // sending them to the other processes as
+ // necessary when we call the <code>compress()</code>
+ // functions on the matrix and vector at the
+ // end of this function.
+ //
+ // The second point is that once we
+ // have handed over matrix and vector
+ // contributions to PETSc, it is a)
+ // hard, and b) very inefficient to
+ // get them back for
+ // modifications. This is not only
+ // the fault of PETSc, it is also a
+ // consequence of the distributed
+ // nature of this program: if an
+ // entry resides on another
+ // processor, then it is necessarily
+ // expensive to get it. The
+ // consequence of this is that where
+ // we previously first assembled the
+ // matrix and right hand side as if
+ // there were no hanging node
+ // constraints and boundary values,
+ // and then eliminated these in a
+ // second step, we should now try to
+ // do that while still assembling the
+ // local systems, and before handing
+ // these entries over to PETSc. At
+ // least as far as eliminating
+ // hanging nodes is concerned, this
+ // is actually possible, though
+ // removing boundary nodes isn't that
+ // simple. deal.II provides functions
+ // to do this first part: instead of
+ // copying elements by hand into the
+ // global matrix, we use the
+ // <code>distribute_local_to_global</code>
+ // functions below to take care of
+ // hanging nodes at the same
+ // time. The second step, elimination
+ // of boundary nodes, is then done in
+ // exactly the same way as in all
+ // previous example programs.
+ //
+ // So, here is the actual implementation:
+ template <int dim>
+ void ElasticProblem<dim>::assemble_system ()
+ {
+ // The infrastructure to assemble linear
+ // systems is the same as in all the other
+ // programs, and in particular unchanged
+ // from step-8. Note that we still use the
+ // deal.II full matrix and vector types for
+ // the local systems.
+ 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_matrix (dofs_per_cell, dofs_per_cell);
+ Vector<double> cell_rhs (dofs_per_cell);
+
+ std::vector<unsigned int> local_dof_indices (dofs_per_cell);
+
+ std::vector<double> lambda_values (n_q_points);
+ std::vector<double> mu_values (n_q_points);
+
+ ConstantFunction<dim> lambda(1.), mu(1.);
+
+ RightHandSide<dim> right_hand_side;
+ std::vector<Vector<double> > rhs_values (n_q_points,
+ Vector<double>(dim));
+
+
+ // The next thing is the loop over all
+ // elements. Note that we do not have to do
+ // all the work: our job here is only to
+ // assemble the system on cells that
+ // actually belong to this MPI process, all
+ // other cells will be taken care of by
+ // other processes. This is what the
+ // if-clause immediately after the for-loop
+ // takes care of: it queries the subdomain
+ // identifier of each cell, which is a
+ // number associated with each cell that
+ // tells which process handles it. In more
+ // generality, the subdomain id is used to
+ // split a domain into several parts (we do
+ // this above, at the beginning of
+ // <code>setup_system</code>), and which allows to
+ // identify which subdomain a cell is
+ // living on. In this application, we have
+ // each process handle exactly one
+ // subdomain, so we identify the terms
+ // <code>subdomain</code> and <code>MPI process</code> with
+ // each other.
+ //
+ // Apart from this, assembling the local
+ // system is relatively uneventful if you
+ // have understood how this is done in
+ // step-8, and only becomes interesting
+ // again once we start distributing it into
+ // the global matrix and right hand sides.
+ typename DoFHandler<dim>::active_cell_iterator
+ cell = dof_handler.begin_active(),
+ endc = dof_handler.end();
+ for (; cell!=endc; ++cell)
+ if (cell->subdomain_id() == this_mpi_process)
+ {
+ cell_matrix = 0;
+ cell_rhs = 0;
+
+ fe_values.reinit (cell);
+
+ lambda.value_list (fe_values.get_quadrature_points(), lambda_values);
+ mu.value_list (fe_values.get_quadrature_points(), mu_values);
+
+ for (unsigned int i=0; i<dofs_per_cell; ++i)
+ {
+ const unsigned int
+ component_i = fe.system_to_component_index(i).first;
+
+ for (unsigned int j=0; j<dofs_per_cell; ++j)
+ {
+ const unsigned int
+ component_j = fe.system_to_component_index(j).first;
+
+ for (unsigned int q_point=0; q_point<n_q_points;
+ ++q_point)
+ {
+//TODO investigate really small values here
+ cell_matrix(i,j)
+ +=
+ (
+ (fe_values.shape_grad(i,q_point)[component_i] *
+ fe_values.shape_grad(j,q_point)[component_j] *
+ lambda_values[q_point])
+ +
+ (fe_values.shape_grad(i,q_point)[component_j] *
+ fe_values.shape_grad(j,q_point)[component_i] *
+ mu_values[q_point])
+ +
+ ((component_i == component_j) ?
+ (fe_values.shape_grad(i,q_point) *
+ fe_values.shape_grad(j,q_point) *
+ mu_values[q_point]) :
+ 0)
+ )
+ *
+ fe_values.JxW(q_point);
+ }
+ }
+ }
+
+ right_hand_side.vector_value_list (fe_values.get_quadrature_points(),
+ rhs_values);
+ for (unsigned int i=0; i<dofs_per_cell; ++i)
+ {
+ const unsigned int
+ component_i = fe.system_to_component_index(i).first;
+
+ 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](component_i) *
+ fe_values.JxW(q_point);
+ }
+
+ // Now we have the local system, and
+ // need to transfer it into the
+ // global objects. However, as
+ // described in the introduction to
+ // this function, we want to avoid
+ // any operations to matrix and
+ // vector entries after handing them
+ // off to PETSc (i.e. after
+ // distributing to the global
+ // objects). Therefore, we will take
+ // care of hanging node constraints
+ // already here. This is not quite
+ // trivial since the rows and columns
+ // of constrained nodes have to be
+ // distributed to the rows and
+ // columns of those nodes to which
+ // they are constrained. This can't
+ // be done on a purely local basis
+ // (because the degrees of freedom to
+ // which hanging nodes are
+ // constrained may not be associated
+ // with the cell we are presently
+ // treating, and are therefore not
+ // represented in the local matrix
+ // and vector), but it can be done
+ // while distributing the local
+ // system to the global one. This is
+ // what the following two calls do,
+ // i.e. they distribute to the global
+ // objects and at the same time make
+ // sure that hanging node constraints
+ // are taken care of:
+ cell->get_dof_indices (local_dof_indices);
+ hanging_node_constraints
+ .distribute_local_to_global (cell_matrix,
+ local_dof_indices,
+ system_matrix);
+
+ hanging_node_constraints
+ .distribute_local_to_global (cell_rhs,
+ local_dof_indices,
+ system_rhs);
+ }
- // The third step is to actually assemble the
- // matrix and right hand side of the
- // problem. There are some things worth
- // mentioning before we go into
- // detail. First, we will be assembling the
- // system in %parallel, i.e. each process will
- // be responsible for assembling on cells
- // that belong to this particular
- // processor. Note that the degrees of
- // freedom are split in a way such that all
- // DoFs in the interior of cells and between
- // cells belonging to the same subdomain
- // belong to the process that <code>owns</code> the
- // cell. However, even then we sometimes need
- // to assemble on a cell with a neighbor that
- // belongs to a different process, and in
- // these cases when we write the local
- // contributions into the global matrix or
- // right hand side vector, we actually have
- // to transfer these entries to the other
- // process. Fortunately, we don't have to do
- // this by hand, PETSc does all this for us
- // by caching these elements locally, and
- // sending them to the other processes as
- // necessary when we call the <code>compress()</code>
- // functions on the matrix and vector at the
- // end of this function.
- //
- // The second point is that once we
- // have handed over matrix and vector
- // contributions to PETSc, it is a)
- // hard, and b) very inefficient to
- // get them back for
- // modifications. This is not only
- // the fault of PETSc, it is also a
- // consequence of the distributed
- // nature of this program: if an
- // entry resides on another
- // processor, then it is necessarily
- // expensive to get it. The
- // consequence of this is that where
- // we previously first assembled the
- // matrix and right hand side as if
- // there were no hanging node
- // constraints and boundary values,
- // and then eliminated these in a
- // second step, we should now try to
- // do that while still assembling the
- // local systems, and before handing
- // these entries over to PETSc. At
- // least as far as eliminating
- // hanging nodes is concerned, this
- // is actually possible, though
- // removing boundary nodes isn't that
- // simple. deal.II provides functions
- // to do this first part: instead of
- // copying elements by hand into the
- // global matrix, we use the
- // <code>distribute_local_to_global</code>
- // functions below to take care of
- // hanging nodes at the same
- // time. The second step, elimination
- // of boundary nodes, is then done in
- // exactly the same way as in all
- // previous example programs.
- //
- // So, here is the actual implementation:
-template <int dim>
-void ElasticProblem<dim>::assemble_system ()
-{
- // The infrastructure to assemble linear
- // systems is the same as in all the other
- // programs, and in particular unchanged
- // from step-8. Note that we still use the
- // deal.II full matrix and vector types for
- // the local systems.
- 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_matrix (dofs_per_cell, dofs_per_cell);
- Vector<double> cell_rhs (dofs_per_cell);
-
- std::vector<unsigned int> local_dof_indices (dofs_per_cell);
-
- std::vector<double> lambda_values (n_q_points);
- std::vector<double> mu_values (n_q_points);
-
- ConstantFunction<dim> lambda(1.), mu(1.);
-
- RightHandSide<dim> right_hand_side;
- std::vector<Vector<double> > rhs_values (n_q_points,
- Vector<double>(dim));
-
-
- // The next thing is the loop over all
- // elements. Note that we do not have to do
- // all the work: our job here is only to
- // assemble the system on cells that
- // actually belong to this MPI process, all
- // other cells will be taken care of by
- // other processes. This is what the
- // if-clause immediately after the for-loop
- // takes care of: it queries the subdomain
- // identifier of each cell, which is a
- // number associated with each cell that
- // tells which process handles it. In more
- // generality, the subdomain id is used to
- // split a domain into several parts (we do
- // this above, at the beginning of
- // <code>setup_system</code>), and which allows to
- // identify which subdomain a cell is
- // living on. In this application, we have
- // each process handle exactly one
- // subdomain, so we identify the terms
- // <code>subdomain</code> and <code>MPI process</code> with
- // each other.
- //
- // Apart from this, assembling the local
- // system is relatively uneventful if you
- // have understood how this is done in
- // step-8, and only becomes interesting
- // again once we start distributing it into
- // the global matrix and right hand sides.
- typename DoFHandler<dim>::active_cell_iterator
- cell = dof_handler.begin_active(),
- endc = dof_handler.end();
- for (; cell!=endc; ++cell)
- if (cell->subdomain_id() == this_mpi_process)
+ // The global matrix and right hand side
+ // vectors have now been formed. Note that
+ // since we took care of this already
+ // above, we do not have to condense away
+ // hanging node constraints any more.
+ //
+ // However, we still have to apply boundary
+ // values, in the same way as we always do:
+ std::map<unsigned int,double> boundary_values;
+ VectorTools::interpolate_boundary_values (dof_handler,
+ 0,
+ ZeroFunction<dim>(dim),
+ boundary_values);
+ MatrixTools::apply_boundary_values (boundary_values,
+ system_matrix, solution,
+ system_rhs, false);
+ // The last argument to the call just
+ // performed allows for some
+ // optimizations. It controls
+ // whether we should also delete the
+ // column corresponding to a boundary
+ // node, or keep it (and passing
+ // <code>true</code> as above means: yes, do
+ // eliminate the column). If we do,
+ // then the resulting matrix will be
+ // symmetric again if it was before;
+ // if we don't, then it won't. The
+ // solution of the resulting system
+ // should be the same, though. The
+ // only reason why we may want to
+ // make the system symmetric again is
+ // that we would like to use the CG
+ // method, which only works with
+ // symmetric matrices. Experience
+ // tells that CG also works (and
+ // works almost as well) if we don't
+ // remove the columns associated with
+ // boundary nodes, which can be
+ // easily explained by the special
+ // structure of the
+ // non-symmetry. Since eliminating
+ // columns from dense matrices is not
+ // expensive, though, we let the
+ // function do it; not doing so is
+ // more important if the linear
+ // system is either non-symmetric
+ // anyway, or we are using the
+ // non-local version of this function
+ // (as in all the other example
+ // programs before) and want to save
+ // a few cycles during this
+ // operation.
+ }
+
+
+
+ // The fourth step is to solve the linear
+ // system, with its distributed matrix and
+ // vector objects. Fortunately, PETSc offers
+ // a variety of sequential and %parallel
+ // solvers, for which we have written
+ // wrappers that have almost the same
+ // interface as is used for the deal.II
+ // solvers used in all previous example
+ // programs.
+ template <int dim>
+ unsigned int ElasticProblem<dim>::solve ()
+ {
+ // First, we have to set up a convergence
+ // monitor, and assign it the accuracy to
+ // which we would like to solve the linear
+ // system. Next, an actual solver object
+ // using PETSc's CG solver which also works
+ // with %parallel (distributed) vectors and
+ // matrices. And finally a preconditioner;
+ // we choose to use a block Jacobi
+ // preconditioner which works by computing
+ // an incomplete LU decomposition on each
+ // block (i.e. the chunk of matrix that is
+ // stored on each MPI process). That means
+ // that if you run the program with only
+ // one process, then you will use an ILU(0)
+ // as a preconditioner, while if it is run
+ // on many processes, then we will have a
+ // number of blocks on the diagonal and the
+ // preconditioner is the ILU(0) of each of
+ // these blocks.
+ SolverControl solver_control (solution.size(),
+ 1e-8*system_rhs.l2_norm());
+ PETScWrappers::SolverCG cg (solver_control,
+ mpi_communicator);
+
+ PETScWrappers::PreconditionBlockJacobi preconditioner(system_matrix);
+
+ // Then solve the system:
+ cg.solve (system_matrix, solution, system_rhs,
+ preconditioner);
+
+ // The next step is to distribute hanging
+ // node constraints. This is a little
+ // tricky, since to fill in the value of a
+ // constrained node you need access to the
+ // values of the nodes to which it is
+ // constrained (for example, for a Q1
+ // element in 2d, we need access to the two
+ // nodes on the big side of a hanging node
+ // face, to compute the value of the
+ // constrained node in the middle). Since
+ // PETSc (and, for that matter, the MPI
+ // model on which it is built) does not
+ // allow to query the value of another node
+ // in a simple way if we should need it,
+ // what we do here is to get a copy of the
+ // distributed vector where we keep all
+ // elements locally. This is simple, since
+ // the deal.II wrappers have a conversion
+ // constructor for the non-MPI vector
+ // class:
+ PETScWrappers::Vector localized_solution (solution);
+
+ // Then we distribute hanging node
+ // constraints on this local copy, i.e. we
+ // compute the values of all constrained
+ // nodes:
+ hanging_node_constraints.distribute (localized_solution);
+
+ // Then transfer everything back
+ // into the global vector. The
+ // following operation copies those
+ // elements of the localized
+ // solution that we store locally
+ // in the distributed solution, and
+ // does not touch the other
+ // ones. Since we do the same
+ // operation on all processors, we
+ // end up with a distributed vector
+ // that has all the constrained
+ // nodes fixed.
+ solution = localized_solution;
+
+ // After this has happened, flush the PETSc
+ // buffers. This may or may not be strictly
+ // necessary here (the PETSc documentation
+ // is not very verbose on these things),
+ // but certainly doesn't hurt either.
+ solution.compress ();
+
+ // Finally return the number of iterations
+ // it took to converge, to allow for some
+ // output:
+ return solver_control.last_step();
+ }
+
+
+
+ // Step five is to output the results we
+ // computed in this iteration. This is
+ // actually the same as done in step-8
+ // before, with two small differences. First,
+ // all processes call this function, but not
+ // all of them need to do the work associated
+ // with generating output. In fact, they
+ // shouldn't, since we would try to write to
+ // the same file multiple times at once. So
+ // we let only the first job do this, and all
+ // the other ones idle around during this
+ // time (or start their work for the next
+ // iteration, or simply yield their CPUs to
+ // other jobs that happen to run at the same
+ // time). The second thing is that we not
+ // only output the solution vector, but also
+ // a vector that indicates which subdomain
+ // each cell belongs to. This will make for
+ // some nice pictures of partitioned domains.
+ //
+ // In practice, the present implementation of
+ // the output function is a major bottleneck
+ // of this program, since generating
+ // graphical output is expensive and doing so
+ // only on one process does, of course, not
+ // scale if we significantly increase the
+ // number of processes. In effect, this
+ // function will consume most of the run-time
+ // if you go to very large numbers of
+ // unknowns and processes, and real
+ // applications should limit the number of
+ // times they generate output through this
+ // function.
+ //
+ // The solution to this is to have
+ // each process generate output data
+ // only for it's own local cells, and
+ // write them to separate files, one
+ // file per process. This would
+ // distribute the work of generating
+ // the output to all processes
+ // equally. In a second step,
+ // separate from running this
+ // program, we would then take all
+ // the output files for a given cycle
+ // and merge these parts into one
+ // single output file. This has to be
+ // done sequentially, but can be done
+ // on a different machine, and should
+ // be relatively cheap. However, the
+ // necessary functionality for this
+ // is not yet implemented in the
+ // library, and since we are too
+ // close to the next release, we do
+ // not want to do such major
+ // destabilizing changes any
+ // more. This has been fixed in the
+ // meantime, though, and a better way
+ // to do things is explained in the
+ // step-18 example program.
+ template <int dim>
+ void ElasticProblem<dim>::output_results (const unsigned int cycle) const
+ {
+ // One point to realize is that when we
+ // want to generate output on process zero
+ // only, we need to have access to all
+ // elements of the solution vector. So we
+ // need to get a local copy of the
+ // distributed vector, which is in fact
+ // simple:
+ const PETScWrappers::Vector localized_solution (solution);
+ // The thing to notice, however, is that
+ // we do this localization operation on all
+ // processes, not only the one that
+ // actually needs the data. This can't be
+ // avoided, however, with the communication
+ // model of MPI: MPI does not have a way to
+ // query data on another process, both
+ // sides have to initiate a communication
+ // at the same time. So even though most of
+ // the processes do not need the localized
+ // solution, we have to place the call here
+ // so that all processes execute it.
+ //
+ // (In reality, part of this work can in
+ // fact be avoided. What we do is send the
+ // local parts of all processes to all
+ // other processes. What we would really
+ // need to do is to initiate an operation
+ // on all processes where each process
+ // simply sends its local chunk of data to
+ // process zero, since this is the only one
+ // that actually needs it, i.e. we need
+ // something like a gather operation. PETSc
+ // can do this, but for simplicity's sake
+ // we don't attempt to make use of this
+ // here. We don't, since what we do is not
+ // very expensive in the grand scheme of
+ // things: it is one vector communication
+ // among all processes , which has to be
+ // compared to the number of communications
+ // we have to do when solving the linear
+ // system, setting up the block-ILU for the
+ // preconditioner, and other operations.)
+
+ // This being done, process zero goes ahead
+ // with setting up the output file as in
+ // step-8, and attaching the (localized)
+ // solution vector to the output
+ // object:. (The code to generate the output
+ // file name is stolen and slightly
+ // modified from step-5, since we expect
+ // that we can do a number of cycles
+ // greater than 10, which is the maximum of
+ // what the code in step-8 could handle.)
+ if (this_mpi_process == 0)
{
- cell_matrix = 0;
- cell_rhs = 0;
-
- fe_values.reinit (cell);
-
- lambda.value_list (fe_values.get_quadrature_points(), lambda_values);
- mu.value_list (fe_values.get_quadrature_points(), mu_values);
-
- for (unsigned int i=0; i<dofs_per_cell; ++i)
- {
- const unsigned int
- component_i = fe.system_to_component_index(i).first;
-
- for (unsigned int j=0; j<dofs_per_cell; ++j)
- {
- const unsigned int
- component_j = fe.system_to_component_index(j).first;
-
- for (unsigned int q_point=0; q_point<n_q_points;
- ++q_point)
- {
-//TODO investigate really small values here
- cell_matrix(i,j)
- +=
- (
- (fe_values.shape_grad(i,q_point)[component_i] *
- fe_values.shape_grad(j,q_point)[component_j] *
- lambda_values[q_point])
- +
- (fe_values.shape_grad(i,q_point)[component_j] *
- fe_values.shape_grad(j,q_point)[component_i] *
- mu_values[q_point])
- +
- ((component_i == component_j) ?
- (fe_values.shape_grad(i,q_point) *
- fe_values.shape_grad(j,q_point) *
- mu_values[q_point]) :
- 0)
- )
- *
- fe_values.JxW(q_point);
- }
- }
- }
-
- right_hand_side.vector_value_list (fe_values.get_quadrature_points(),
- rhs_values);
- for (unsigned int i=0; i<dofs_per_cell; ++i)
- {
- const unsigned int
- component_i = fe.system_to_component_index(i).first;
-
- 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](component_i) *
- fe_values.JxW(q_point);
- }
-
- // Now we have the local system, and
- // need to transfer it into the
- // global objects. However, as
- // described in the introduction to
- // this function, we want to avoid
- // any operations to matrix and
- // vector entries after handing them
- // off to PETSc (i.e. after
- // distributing to the global
- // objects). Therefore, we will take
- // care of hanging node constraints
- // already here. This is not quite
- // trivial since the rows and columns
- // of constrained nodes have to be
- // distributed to the rows and
- // columns of those nodes to which
- // they are constrained. This can't
- // be done on a purely local basis
- // (because the degrees of freedom to
- // which hanging nodes are
- // constrained may not be associated
- // with the cell we are presently
- // treating, and are therefore not
- // represented in the local matrix
- // and vector), but it can be done
- // while distributing the local
- // system to the global one. This is
- // what the following two calls do,
- // i.e. they distribute to the global
- // objects and at the same time make
- // sure that hanging node constraints
- // are taken care of:
- cell->get_dof_indices (local_dof_indices);
- hanging_node_constraints
- .distribute_local_to_global (cell_matrix,
- local_dof_indices,
- system_matrix);
-
- hanging_node_constraints
- .distribute_local_to_global (cell_rhs,
- local_dof_indices,
- system_rhs);
+ std::ostringstream filename;
+ filename << "solution-" << cycle << ".gmv";
+
+ std::ofstream output (filename.str().c_str());
+
+ DataOut<dim> data_out;
+ data_out.attach_dof_handler (dof_handler);
+
+ std::vector<std::string> solution_names;
+ switch (dim)
+ {
+ case 1:
+ solution_names.push_back ("displacement");
+ break;
+ case 2:
+ solution_names.push_back ("x_displacement");
+ solution_names.push_back ("y_displacement");
+ break;
+ case 3:
+ solution_names.push_back ("x_displacement");
+ solution_names.push_back ("y_displacement");
+ solution_names.push_back ("z_displacement");
+ break;
+ default:
+ Assert (false, ExcInternalError());
+ }
+
+ data_out.add_data_vector (localized_solution, solution_names);
+
+ // The only thing we do here
+ // additionally is that we also output
+ // one value per cell indicating which
+ // subdomain (i.e. MPI process) it
+ // belongs to. This requires some
+ // conversion work, since the data the
+ // library provides us with is not the
+ // one the output class expects, but
+ // this is not difficult. First, set up
+ // a vector of integers, one per cell,
+ // that is then filled by the number of
+ // subdomain each cell is in:
+ std::vector<unsigned int> partition_int (triangulation.n_active_cells());
+ GridTools::get_subdomain_association (triangulation, partition_int);
+
+ // Then convert this integer vector
+ // into a floating point vector just as
+ // the output functions want to see:
+ const Vector<double> partitioning(partition_int.begin(),
+ partition_int.end());
+
+ // And finally add this vector as well:
+ data_out.add_data_vector (partitioning, "partitioning");
+
+ // This all being done, generate the
+ // intermediate format and write it out
+ // in GMV output format:
+ data_out.build_patches ();
+ data_out.write_gmv (output);
}
-
- // The global matrix and right hand side
- // vectors have now been formed. Note that
- // since we took care of this already
- // above, we do not have to condense away
- // hanging node constraints any more.
- //
- // However, we still have to apply boundary
- // values, in the same way as we always do:
- std::map<unsigned int,double> boundary_values;
- VectorTools::interpolate_boundary_values (dof_handler,
- 0,
- ZeroFunction<dim>(dim),
- boundary_values);
- MatrixTools::apply_boundary_values (boundary_values,
- system_matrix, solution,
- system_rhs, false);
- // The last argument to the call just
- // performed allows for some
- // optimizations. It controls
- // whether we should also delete the
- // column corresponding to a boundary
- // node, or keep it (and passing
- // <code>true</code> as above means: yes, do
- // eliminate the column). If we do,
- // then the resulting matrix will be
- // symmetric again if it was before;
- // if we don't, then it won't. The
- // solution of the resulting system
- // should be the same, though. The
- // only reason why we may want to
- // make the system symmetric again is
- // that we would like to use the CG
- // method, which only works with
- // symmetric matrices. Experience
- // tells that CG also works (and
- // works almost as well) if we don't
- // remove the columns associated with
- // boundary nodes, which can be
- // easily explained by the special
- // structure of the
- // non-symmetry. Since eliminating
- // columns from dense matrices is not
- // expensive, though, we let the
- // function do it; not doing so is
- // more important if the linear
- // system is either non-symmetric
- // anyway, or we are using the
- // non-local version of this function
- // (as in all the other example
- // programs before) and want to save
- // a few cycles during this
- // operation.
-}
-
-
-
- // The fourth step is to solve the linear
- // system, with its distributed matrix and
- // vector objects. Fortunately, PETSc offers
- // a variety of sequential and %parallel
- // solvers, for which we have written
- // wrappers that have almost the same
- // interface as is used for the deal.II
- // solvers used in all previous example
- // programs.
-template <int dim>
-unsigned int ElasticProblem<dim>::solve ()
-{
- // First, we have to set up a convergence
- // monitor, and assign it the accuracy to
- // which we would like to solve the linear
- // system. Next, an actual solver object
- // using PETSc's CG solver which also works
- // with %parallel (distributed) vectors and
- // matrices. And finally a preconditioner;
- // we choose to use a block Jacobi
- // preconditioner which works by computing
- // an incomplete LU decomposition on each
- // block (i.e. the chunk of matrix that is
- // stored on each MPI process). That means
- // that if you run the program with only
- // one process, then you will use an ILU(0)
- // as a preconditioner, while if it is run
- // on many processes, then we will have a
- // number of blocks on the diagonal and the
- // preconditioner is the ILU(0) of each of
- // these blocks.
- SolverControl solver_control (solution.size(),
- 1e-8*system_rhs.l2_norm());
- PETScWrappers::SolverCG cg (solver_control,
- mpi_communicator);
-
- PETScWrappers::PreconditionBlockJacobi preconditioner(system_matrix);
-
- // Then solve the system:
- cg.solve (system_matrix, solution, system_rhs,
- preconditioner);
-
- // The next step is to distribute hanging
- // node constraints. This is a little
- // tricky, since to fill in the value of a
- // constrained node you need access to the
- // values of the nodes to which it is
- // constrained (for example, for a Q1
- // element in 2d, we need access to the two
- // nodes on the big side of a hanging node
- // face, to compute the value of the
- // constrained node in the middle). Since
- // PETSc (and, for that matter, the MPI
- // model on which it is built) does not
- // allow to query the value of another node
- // in a simple way if we should need it,
- // what we do here is to get a copy of the
- // distributed vector where we keep all
- // elements locally. This is simple, since
- // the deal.II wrappers have a conversion
- // constructor for the non-MPI vector
- // class:
- PETScWrappers::Vector localized_solution (solution);
-
- // Then we distribute hanging node
- // constraints on this local copy, i.e. we
- // compute the values of all constrained
- // nodes:
- hanging_node_constraints.distribute (localized_solution);
-
- // Then transfer everything back
- // into the global vector. The
- // following operation copies those
- // elements of the localized
- // solution that we store locally
- // in the distributed solution, and
- // does not touch the other
- // ones. Since we do the same
- // operation on all processors, we
- // end up with a distributed vector
- // that has all the constrained
- // nodes fixed.
- solution = localized_solution;
-
- // After this has happened, flush the PETSc
- // buffers. This may or may not be strictly
- // necessary here (the PETSc documentation
- // is not very verbose on these things),
- // but certainly doesn't hurt either.
- solution.compress ();
-
- // Finally return the number of iterations
- // it took to converge, to allow for some
- // output:
- return solver_control.last_step();
-}
-
-
-
- // Step five is to output the results we
- // computed in this iteration. This is
- // actually the same as done in step-8
- // before, with two small differences. First,
- // all processes call this function, but not
- // all of them need to do the work associated
- // with generating output. In fact, they
- // shouldn't, since we would try to write to
- // the same file multiple times at once. So
- // we let only the first job do this, and all
- // the other ones idle around during this
- // time (or start their work for the next
- // iteration, or simply yield their CPUs to
- // other jobs that happen to run at the same
- // time). The second thing is that we not
- // only output the solution vector, but also
- // a vector that indicates which subdomain
- // each cell belongs to. This will make for
- // some nice pictures of partitioned domains.
- //
- // In practice, the present implementation of
- // the output function is a major bottleneck
- // of this program, since generating
- // graphical output is expensive and doing so
- // only on one process does, of course, not
- // scale if we significantly increase the
- // number of processes. In effect, this
- // function will consume most of the run-time
- // if you go to very large numbers of
- // unknowns and processes, and real
- // applications should limit the number of
- // times they generate output through this
- // function.
- //
- // The solution to this is to have
- // each process generate output data
- // only for it's own local cells, and
- // write them to separate files, one
- // file per process. This would
- // distribute the work of generating
- // the output to all processes
- // equally. In a second step,
- // separate from running this
- // program, we would then take all
- // the output files for a given cycle
- // and merge these parts into one
- // single output file. This has to be
- // done sequentially, but can be done
- // on a different machine, and should
- // be relatively cheap. However, the
- // necessary functionality for this
- // is not yet implemented in the
- // library, and since we are too
- // close to the next release, we do
- // not want to do such major
- // destabilizing changes any
- // more. This has been fixed in the
- // meantime, though, and a better way
- // to do things is explained in the
- // step-18 example program.
-template <int dim>
-void ElasticProblem<dim>::output_results (const unsigned int cycle) const
-{
- // One point to realize is that when we
- // want to generate output on process zero
- // only, we need to have access to all
- // elements of the solution vector. So we
- // need to get a local copy of the
- // distributed vector, which is in fact
- // simple:
- const PETScWrappers::Vector localized_solution (solution);
- // The thing to notice, however, is that
- // we do this localization operation on all
- // processes, not only the one that
- // actually needs the data. This can't be
- // avoided, however, with the communication
- // model of MPI: MPI does not have a way to
- // query data on another process, both
- // sides have to initiate a communication
- // at the same time. So even though most of
- // the processes do not need the localized
- // solution, we have to place the call here
- // so that all processes execute it.
- //
- // (In reality, part of this work can in
- // fact be avoided. What we do is send the
- // local parts of all processes to all
- // other processes. What we would really
- // need to do is to initiate an operation
- // on all processes where each process
- // simply sends its local chunk of data to
- // process zero, since this is the only one
- // that actually needs it, i.e. we need
- // something like a gather operation. PETSc
- // can do this, but for simplicity's sake
- // we don't attempt to make use of this
- // here. We don't, since what we do is not
- // very expensive in the grand scheme of
- // things: it is one vector communication
- // among all processes , which has to be
- // compared to the number of communications
- // we have to do when solving the linear
- // system, setting up the block-ILU for the
- // preconditioner, and other operations.)
-
- // This being done, process zero goes ahead
- // with setting up the output file as in
- // step-8, and attaching the (localized)
- // solution vector to the output
- // object:. (The code to generate the output
- // file name is stolen and slightly
- // modified from step-5, since we expect
- // that we can do a number of cycles
- // greater than 10, which is the maximum of
- // what the code in step-8 could handle.)
- if (this_mpi_process == 0)
- {
- std::ostringstream filename;
- filename << "solution-" << cycle << ".gmv";
-
- std::ofstream output (filename.str().c_str());
-
- DataOut<dim> data_out;
- data_out.attach_dof_handler (dof_handler);
-
- std::vector<std::string> solution_names;
- switch (dim)
- {
- case 1:
- solution_names.push_back ("displacement");
- break;
- case 2:
- solution_names.push_back ("x_displacement");
- solution_names.push_back ("y_displacement");
- break;
- case 3:
- solution_names.push_back ("x_displacement");
- solution_names.push_back ("y_displacement");
- solution_names.push_back ("z_displacement");
- break;
- default:
- Assert (false, ExcInternalError());
- }
-
- data_out.add_data_vector (localized_solution, solution_names);
-
- // The only thing we do here
- // additionally is that we also output
- // one value per cell indicating which
- // subdomain (i.e. MPI process) it
- // belongs to. This requires some
- // conversion work, since the data the
- // library provides us with is not the
- // one the output class expects, but
- // this is not difficult. First, set up
- // a vector of integers, one per cell,
- // that is then filled by the number of
- // subdomain each cell is in:
- std::vector<unsigned int> partition_int (triangulation.n_active_cells());
- GridTools::get_subdomain_association (triangulation, partition_int);
-
- // Then convert this integer vector
- // into a floating point vector just as
- // the output functions want to see:
- const Vector<double> partitioning(partition_int.begin(),
- partition_int.end());
-
- // And finally add this vector as well:
- data_out.add_data_vector (partitioning, "partitioning");
-
- // This all being done, generate the
- // intermediate format and write it out
- // in GMV output format:
- data_out.build_patches ();
- data_out.write_gmv (output);
- }
-}
-
-
-
- // The sixth step is to take the solution
- // just computed, and evaluate some kind of
- // refinement indicator to refine the
- // mesh. The problem is basically the same as
- // with distributing hanging node
- // constraints: in order to compute the error
- // indicator, we need access to all elements
- // of the solution vector. We then compute
- // the indicators for the cells that belong
- // to the present process, but then we need
- // to distribute the refinement indicators
- // into a distributed vector so that all
- // processes have the values of the
- // refinement indicator for all cells. But
- // then, in order for each process to refine
- // its copy of the mesh, they need to have
- // acces to all refinement indicators
- // locally, so they have to copy the global
- // vector back into a local one. That's a
- // little convoluted, but thinking about it
- // quite straightforward nevertheless. So
- // here's how we do it:
-template <int dim>
-void ElasticProblem<dim>::refine_grid ()
-{
- // So, first part: get a local copy of the
- // distributed solution vector. This is
- // necessary since the error estimator
- // needs to get at the value of neighboring
- // cells even if they do not belong to the
- // subdomain associated with the present
- // MPI process:
- const PETScWrappers::Vector localized_solution (solution);
-
- // Second part: set up a vector of error
- // indicators for all cells and let the
- // Kelly class compute refinement
- // indicators for all cells belonging to
- // the present subdomain/process. Note that
- // the last argument of the call indicates
- // which subdomain we are interested
- // in. The three arguments before it are
- // various other default arguments that one
- // usually doesn't need (and doesn't state
- // values for, but rather uses the
- // defaults), but which we have to state
- // here explicitly since we want to modify
- // the value of a following argument
- // (i.e. the one indicating the subdomain):
- Vector<float> local_error_per_cell (triangulation.n_active_cells());
- KellyErrorEstimator<dim>::estimate (dof_handler,
- QGauss<dim-1>(2),
- typename FunctionMap<dim>::type(),
- localized_solution,
- local_error_per_cell,
- std::vector<bool>(),
- 0,
- multithread_info.n_default_threads,
- this_mpi_process);
-
- // Now all processes have computed error
- // indicators for their own cells and
- // stored them in the respective elements
- // of the <code>local_error_per_cell</code>
- // vector. The elements of this vector for
- // cells not on the present process are
- // zero. However, since all processes have
- // a copy of a copy of the entire
- // triangulation and need to keep these
- // copies in synch, they need the values of
- // refinement indicators for all cells of
- // the triangulation. Thus, we need to
- // distribute our results. We do this by
- // creating a distributed vector where each
- // process has its share, and sets the
- // elements it has computed. We will then
- // later generate a local sequential copy
- // of this distributed vector to allow each
- // process to access all elements of this
- // vector.
- //
- // So in the first step, we need to set up
- // a %parallel vector. For simplicity, every
- // process will own a chunk with as many
- // elements as this process owns cells, so
- // that the first chunk of elements is
- // stored with process zero, the next chunk
- // with process one, and so on. It is
- // important to remark, however, that these
- // elements are not necessarily the ones we
- // will write to. This is so, since the
- // order in which cells are arranged,
- // i.e. the order in which the elements of
- // the vector correspond to cells, is not
- // ordered according to the subdomain these
- // cells belong to. In other words, if on
- // this process we compute indicators for
- // cells of a certain subdomain, we may
- // write the results to more or less random
- // elements if the distributed vector, that
- // do not necessarily lie within the chunk
- // of vector we own on the present
- // process. They will subsequently have to
- // be copied into another process's memory
- // space then, an operation that PETSc does
- // for us when we call the <code>compress</code>
- // function. This inefficiency could be
- // avoided with some more code, but we
- // refrain from it since it is not a major
- // factor in the program's total runtime.
- //
- // So here's how we do it: count how many
- // cells belong to this process, set up a
- // distributed vector with that many
- // elements to be stored locally, and copy
- // over the elements we computed locally,
- // then compress the result. In fact, we
- // really only copy the elements that are
- // nonzero, so we may miss a few that we
- // computed to zero, but this won't hurt
- // since the original values of the vector
- // is zero anyway.
- const unsigned int n_local_cells
- = GridTools::count_cells_with_subdomain_association (triangulation,
- this_mpi_process);
- PETScWrappers::MPI::Vector
- distributed_all_errors (mpi_communicator,
- triangulation.n_active_cells(),
- n_local_cells);
-
- for (unsigned int i=0; i<local_error_per_cell.size(); ++i)
- if (local_error_per_cell(i) != 0)
- distributed_all_errors(i) = local_error_per_cell(i);
- distributed_all_errors.compress ();
-
-
- // So now we have this distributed vector
- // out there that contains the refinement
- // indicators for all cells. To use it, we
- // need to obtain a local copy...
- const Vector<float> localized_all_errors (distributed_all_errors);
-
- // ...which we can the subsequently use to
- // finally refine the grid:
- GridRefinement::refine_and_coarsen_fixed_number (triangulation,
- localized_all_errors,
- 0.3, 0.03);
- triangulation.execute_coarsening_and_refinement ();
-}
-
-
-
- // Lastly, here is the driver function. It is
- // almost unchanged from step-8, with the
- // exception that we replace <code>std::cout</code> by
- // the <code>pcout</code> stream. Apart from this, the
- // only other cosmetic change is that we
- // output how many degrees of freedom there
- // are per process, and how many iterations
- // it took for the linear solver to converge:
-template <int dim>
-void ElasticProblem<dim>::run ()
-{
- for (unsigned int cycle=0; cycle<10; ++cycle)
- {
- pcout << "Cycle " << cycle << ':' << std::endl;
-
- if (cycle == 0)
- {
- GridGenerator::hyper_cube (triangulation, -1, 1);
- triangulation.refine_global (3);
- }
- else
- refine_grid ();
-
- pcout << " Number of active cells: "
- << triangulation.n_active_cells()
- << std::endl;
-
- setup_system ();
-
- pcout << " Number of degrees of freedom: "
- << dof_handler.n_dofs()
- << " (by partition:";
- for (unsigned int p=0; p<n_mpi_processes; ++p)
- pcout << (p==0 ? ' ' : '+')
- << (DoFTools::
- count_dofs_with_subdomain_association (dof_handler,
- p));
- pcout << ")" << std::endl;
-
- assemble_system ();
- const unsigned int n_iterations = solve ();
-
- pcout << " Solver converged in " << n_iterations
- << " iterations." << std::endl;
-
- output_results (cycle);
- }
+ }
+
+
+
+ // The sixth step is to take the solution
+ // just computed, and evaluate some kind of
+ // refinement indicator to refine the
+ // mesh. The problem is basically the same as
+ // with distributing hanging node
+ // constraints: in order to compute the error
+ // indicator, we need access to all elements
+ // of the solution vector. We then compute
+ // the indicators for the cells that belong
+ // to the present process, but then we need
+ // to distribute the refinement indicators
+ // into a distributed vector so that all
+ // processes have the values of the
+ // refinement indicator for all cells. But
+ // then, in order for each process to refine
+ // its copy of the mesh, they need to have
+ // acces to all refinement indicators
+ // locally, so they have to copy the global
+ // vector back into a local one. That's a
+ // little convoluted, but thinking about it
+ // quite straightforward nevertheless. So
+ // here's how we do it:
+ template <int dim>
+ void ElasticProblem<dim>::refine_grid ()
+ {
+ // So, first part: get a local copy of the
+ // distributed solution vector. This is
+ // necessary since the error estimator
+ // needs to get at the value of neighboring
+ // cells even if they do not belong to the
+ // subdomain associated with the present
+ // MPI process:
+ const PETScWrappers::Vector localized_solution (solution);
+
+ // Second part: set up a vector of error
+ // indicators for all cells and let the
+ // Kelly class compute refinement
+ // indicators for all cells belonging to
+ // the present subdomain/process. Note that
+ // the last argument of the call indicates
+ // which subdomain we are interested
+ // in. The three arguments before it are
+ // various other default arguments that one
+ // usually doesn't need (and doesn't state
+ // values for, but rather uses the
+ // defaults), but which we have to state
+ // here explicitly since we want to modify
+ // the value of a following argument
+ // (i.e. the one indicating the subdomain):
+ Vector<float> local_error_per_cell (triangulation.n_active_cells());
+ KellyErrorEstimator<dim>::estimate (dof_handler,
+ QGauss<dim-1>(2),
+ typename FunctionMap<dim>::type(),
+ localized_solution,
+ local_error_per_cell,
+ std::vector<bool>(),
+ 0,
+ multithread_info.n_default_threads,
+ this_mpi_process);
+
+ // Now all processes have computed error
+ // indicators for their own cells and
+ // stored them in the respective elements
+ // of the <code>local_error_per_cell</code>
+ // vector. The elements of this vector for
+ // cells not on the present process are
+ // zero. However, since all processes have
+ // a copy of a copy of the entire
+ // triangulation and need to keep these
+ // copies in synch, they need the values of
+ // refinement indicators for all cells of
+ // the triangulation. Thus, we need to
+ // distribute our results. We do this by
+ // creating a distributed vector where each
+ // process has its share, and sets the
+ // elements it has computed. We will then
+ // later generate a local sequential copy
+ // of this distributed vector to allow each
+ // process to access all elements of this
+ // vector.
+ //
+ // So in the first step, we need to set up
+ // a %parallel vector. For simplicity, every
+ // process will own a chunk with as many
+ // elements as this process owns cells, so
+ // that the first chunk of elements is
+ // stored with process zero, the next chunk
+ // with process one, and so on. It is
+ // important to remark, however, that these
+ // elements are not necessarily the ones we
+ // will write to. This is so, since the
+ // order in which cells are arranged,
+ // i.e. the order in which the elements of
+ // the vector correspond to cells, is not
+ // ordered according to the subdomain these
+ // cells belong to. In other words, if on
+ // this process we compute indicators for
+ // cells of a certain subdomain, we may
+ // write the results to more or less random
+ // elements if the distributed vector, that
+ // do not necessarily lie within the chunk
+ // of vector we own on the present
+ // process. They will subsequently have to
+ // be copied into another process's memory
+ // space then, an operation that PETSc does
+ // for us when we call the <code>compress</code>
+ // function. This inefficiency could be
+ // avoided with some more code, but we
+ // refrain from it since it is not a major
+ // factor in the program's total runtime.
+ //
+ // So here's how we do it: count how many
+ // cells belong to this process, set up a
+ // distributed vector with that many
+ // elements to be stored locally, and copy
+ // over the elements we computed locally,
+ // then compress the result. In fact, we
+ // really only copy the elements that are
+ // nonzero, so we may miss a few that we
+ // computed to zero, but this won't hurt
+ // since the original values of the vector
+ // is zero anyway.
+ const unsigned int n_local_cells
+ = GridTools::count_cells_with_subdomain_association (triangulation,
+ this_mpi_process);
+ PETScWrappers::MPI::Vector
+ distributed_all_errors (mpi_communicator,
+ triangulation.n_active_cells(),
+ n_local_cells);
+
+ for (unsigned int i=0; i<local_error_per_cell.size(); ++i)
+ if (local_error_per_cell(i) != 0)
+ distributed_all_errors(i) = local_error_per_cell(i);
+ distributed_all_errors.compress ();
+
+
+ // So now we have this distributed vector
+ // out there that contains the refinement
+ // indicators for all cells. To use it, we
+ // need to obtain a local copy...
+ const Vector<float> localized_all_errors (distributed_all_errors);
+
+ // ...which we can the subsequently use to
+ // finally refine the grid:
+ GridRefinement::refine_and_coarsen_fixed_number (triangulation,
+ localized_all_errors,
+ 0.3, 0.03);
+ triangulation.execute_coarsening_and_refinement ();
+ }
+
+
+
+ // Lastly, here is the driver function. It is
+ // almost unchanged from step-8, with the
+ // exception that we replace <code>std::cout</code> by
+ // the <code>pcout</code> stream. Apart from this, the
+ // only other cosmetic change is that we
+ // output how many degrees of freedom there
+ // are per process, and how many iterations
+ // it took for the linear solver to converge:
+ template <int dim>
+ void ElasticProblem<dim>::run ()
+ {
+ for (unsigned int cycle=0; cycle<10; ++cycle)
+ {
+ pcout << "Cycle " << cycle << ':' << std::endl;
+
+ if (cycle == 0)
+ {
+ GridGenerator::hyper_cube (triangulation, -1, 1);
+ triangulation.refine_global (3);
+ }
+ else
+ refine_grid ();
+
+ pcout << " Number of active cells: "
+ << triangulation.n_active_cells()
+ << std::endl;
+
+ setup_system ();
+
+ pcout << " Number of degrees of freedom: "
+ << dof_handler.n_dofs()
+ << " (by partition:";
+ for (unsigned int p=0; p<n_mpi_processes; ++p)
+ pcout << (p==0 ? ' ' : '+')
+ << (DoFTools::
+ count_dofs_with_subdomain_association (dof_handler,
+ p));
+ pcout << ")" << std::endl;
+
+ assemble_system ();
+ const unsigned int n_iterations = solve ();
+
+ pcout << " Solver converged in " << n_iterations
+ << " iterations." << std::endl;
+
+ output_results (cycle);
+ }
+ }
}
// delegates work to the <code>run</code> function of
// a master object, and only wraps everything
// into some code to catch exceptions:
-int main (int argc, char **argv)
+int main (int argc, char **argv)
{
try
{
+ using namespace dealii;
+ using namespace Step17;
+
// Here is the only real difference:
// PETSc requires that we initialize it
// at the beginning of the program, and
elastic_problem.run ();
}
- PetscFinalize();
+ PetscFinalize();
}
catch (std::exception &exc)
{
<< "Aborting!" << std::endl
<< "----------------------------------------------------"
<< std::endl;
-
+
return 1;
}
- catch (...)
+ catch (...)
{
std::cerr << std::endl << std::endl
<< "----------------------------------------------------"
/* $Id$ */
/* */
-/* Copyright (C) 2000, 2004, 2005, 2006, 2007, 2008, 2009 by the deal.II authors */
+/* Copyright (C) 2000, 2004, 2005, 2006, 2007, 2008, 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 */
// The last step is as in all
// previous programs:
-using namespace dealii;
-
- // So much for the header files. As a
- // matter of good practice, I have
- // started to put everything that
- // corresponds to a certain project
- // into a namespace of its own, named
- // after the problem that we are
- // solving:
-namespace QuasiStaticElasticity
+namespace Step18
{
+ using namespace dealii;
// @sect3{The <code>PointHistory</code> class}
};
- // @sect3{The stress-strain tensor}
-
- // Next, we define the linear relationship
- // between the stress and the strain in
- // elasticity. It is given by a tensor of
- // rank 4 that is usually written in the
- // form $C_{ijkl} = \mu (\delta_{ik}
- // \delta_{jl} + \delta_{il} \delta_{jk}) +
- // \lambda \delta_{ij} \delta_{kl}$. This
- // tensor maps symmetric tensor of rank 2
- // to symmetric tensors of rank 2. A
- // function implementing its creation for
- // given values of the Lame constants
- // lambda and mu is straightforward:
+ // @sect3{The stress-strain tensor}
+
+ // Next, we define the linear relationship
+ // between the stress and the strain in
+ // elasticity. It is given by a tensor of
+ // rank 4 that is usually written in the
+ // form $C_{ijkl} = \mu (\delta_{ik}
+ // \delta_{jl} + \delta_{il} \delta_{jk}) +
+ // \lambda \delta_{ij} \delta_{kl}$. This
+ // tensor maps symmetric tensor of rank 2
+ // to symmetric tensors of rank 2. A
+ // function implementing its creation for
+ // given values of the Lame constants
+ // lambda and mu is straightforward:
template <int dim>
SymmetricTensor<4,dim>
get_stress_strain_tensor (const double lambda, const double mu)
SymmetricTensor<4,dim> tmp;
for (unsigned int i=0; i<dim; ++i)
for (unsigned int j=0; j<dim; ++j)
- for (unsigned int k=0; k<dim; ++k)
- for (unsigned int l=0; l<dim; ++l)
- tmp[i][j][k][l] = (((i==k) && (j==l) ? mu : 0.0) +
- ((i==l) && (j==k) ? mu : 0.0) +
- ((i==j) && (k==l) ? lambda : 0.0));
+ for (unsigned int k=0; k<dim; ++k)
+ for (unsigned int l=0; l<dim; ++l)
+ tmp[i][j][k][l] = (((i==k) && (j==l) ? mu : 0.0) +
+ ((i==l) && (j==k) ? mu : 0.0) +
+ ((i==j) && (k==l) ? lambda : 0.0));
return tmp;
}
- // With this function, we will
- // define a static member variable
- // of the main class below that
- // will be used throughout the
- // program as the stress-strain
- // tensor. Note that
- // in more elaborate programs, this will
- // probably be a member variable of some
- // class instead, or a function that
- // returns the stress-strain relationship
- // depending on other input. For example in
- // damage theory models, the Lame constants
- // are considered a function of the prior
- // stress/strain history of a
- // point. Conversely, in plasticity the
- // form of the stress-strain tensor is
- // modified if the material has reached the
- // yield stress in a certain point, and
- // possibly also depending on its prior
- // history.
- //
- // In the present program, however, we
- // assume that the material is completely
- // elastic and linear, and a constant
- // stress-strain tensor is sufficient for
- // our present purposes.
-
-
+ // With this function, we will
+ // define a static member variable
+ // of the main class below that
+ // will be used throughout the
+ // program as the stress-strain
+ // tensor. Note that
+ // in more elaborate programs, this will
+ // probably be a member variable of some
+ // class instead, or a function that
+ // returns the stress-strain relationship
+ // depending on other input. For example in
+ // damage theory models, the Lame constants
+ // are considered a function of the prior
+ // stress/strain history of a
+ // point. Conversely, in plasticity the
+ // form of the stress-strain tensor is
+ // modified if the material has reached the
+ // yield stress in a certain point, and
+ // possibly also depending on its prior
+ // history.
+ //
+ // In the present program, however, we
+ // assume that the material is completely
+ // elastic and linear, and a constant
+ // stress-strain tensor is sufficient for
+ // our present purposes.
+
+
// @sect3{Auxiliary functions}
for (unsigned int i=0; i<dim; ++i)
for (unsigned int j=i+1; j<dim; ++j)
tmp[i][j]
- = (fe_values.shape_grad_component (shape_func,q_point,i)[j] +
- fe_values.shape_grad_component (shape_func,q_point,j)[i]) / 2;
-
+ = (fe_values.shape_grad_component (shape_func,q_point,i)[j] +
+ fe_values.shape_grad_component (shape_func,q_point,j)[i]) / 2;
+
return tmp;
}
SymmetricTensor<2,dim> strain;
for (unsigned int i=0; i<dim; ++i)
strain[i][i] = grad[i][i];
-
+
for (unsigned int i=0; i<dim; ++i)
for (unsigned int j=i+1; j<dim; ++j)
strain[i][j] = (grad[i][j] + grad[j][i]) / 2;
-
+
return strain;
}
- // Finally, below we will need a function
- // that computes the rotation matrix
- // induced by a displacement at a given
- // point. In fact, of course, the
- // displacement at a single point only has
- // a direction and a magnitude, it is the
- // change in direction and magnitude that
- // induces rotations. In effect, the
- // rotation matrix can be computed from the
- // gradients of a displacement, or, more
- // specifically, from the curl.
- //
- // The formulas by which the rotation
- // matrices are determined are a little
- // awkward, especially in 3d. For 2d, there
- // is a simpler way, so we implement this
- // function twice, once for 2d and once for
- // 3d, so that we can compile and use the
- // program in both space dimensions if so
- // desired -- after all, deal.II is all
- // about dimension independent programming
- // and reuse of algorithm thoroughly tested
- // with cheap computations in 2d, for the
- // more expensive computations in 3d. Here
- // is one case, where we have to implement
- // different algorithms for 2d and 3d, but
- // then can write the rest of the program
- // in a way that is independent of the
- // space dimension.
- //
- // So, without further ado to the 2d
- // implementation:
+ // Finally, below we will need a function
+ // that computes the rotation matrix
+ // induced by a displacement at a given
+ // point. In fact, of course, the
+ // displacement at a single point only has
+ // a direction and a magnitude, it is the
+ // change in direction and magnitude that
+ // induces rotations. In effect, the
+ // rotation matrix can be computed from the
+ // gradients of a displacement, or, more
+ // specifically, from the curl.
+ //
+ // The formulas by which the rotation
+ // matrices are determined are a little
+ // awkward, especially in 3d. For 2d, there
+ // is a simpler way, so we implement this
+ // function twice, once for 2d and once for
+ // 3d, so that we can compile and use the
+ // program in both space dimensions if so
+ // desired -- after all, deal.II is all
+ // about dimension independent programming
+ // and reuse of algorithm thoroughly tested
+ // with cheap computations in 2d, for the
+ // more expensive computations in 3d. Here
+ // is one case, where we have to implement
+ // different algorithms for 2d and 3d, but
+ // then can write the rest of the program
+ // in a way that is independent of the
+ // space dimension.
+ //
+ // So, without further ado to the 2d
+ // implementation:
Tensor<2,2>
get_rotation_matrix (const std::vector<Tensor<1,2> > &grad_u)
{
// gradients. Note that we are in 2d, so
// the rotation is a scalar:
const double curl = (grad_u[1][0] - grad_u[0][1]);
-
+
// From this, compute the angle of
// rotation:
const double angle = std::atan (curl);
- // And from this, build the antisymmetric
- // rotation matrix:
+ // And from this, build the antisymmetric
+ // rotation matrix:
const double t[2][2] = {{ cos(angle), sin(angle) },
{-sin(angle), cos(angle) }};
return Tensor<2,2>(t);
}
- // The 3d case is a little more contrived:
+ // The 3d case is a little more contrived:
Tensor<2,3>
get_rotation_matrix (const std::vector<Tensor<1,3> > &grad_u)
{
// velocity field. This time, it is a
// real vector:
const Point<3> curl (grad_u[2][1] - grad_u[1][2],
- grad_u[0][2] - grad_u[2][0],
- grad_u[1][0] - grad_u[0][1]);
-
+ grad_u[0][2] - grad_u[2][0],
+ grad_u[1][0] - grad_u[0][1]);
+
// From this vector, using its magnitude,
// compute the tangent of the angle of
// rotation, and from it the actual
const double tan_angle = std::sqrt(curl*curl);
const double angle = std::atan (tan_angle);
- // Now, here's one problem: if the angle
- // of rotation is too small, that means
- // that there is no rotation going on
- // (for example a translational
- // motion). In that case, the rotation
- // matrix is the identity matrix.
- //
- // The reason why we stress that is that
- // in this case we have that
- // <code>tan_angle==0</code>. Further down, we
- // need to divide by that number in the
- // computation of the axis of rotation,
- // and we would get into trouble when
- // dividing doing so. Therefore, let's
- // shortcut this and simply return the
- // identity matrix if the angle of
- // rotation is really small:
+ // Now, here's one problem: if the angle
+ // of rotation is too small, that means
+ // that there is no rotation going on
+ // (for example a translational
+ // motion). In that case, the rotation
+ // matrix is the identity matrix.
+ //
+ // The reason why we stress that is that
+ // in this case we have that
+ // <code>tan_angle==0</code>. Further down, we
+ // need to divide by that number in the
+ // computation of the axis of rotation,
+ // and we would get into trouble when
+ // dividing doing so. Therefore, let's
+ // shortcut this and simply return the
+ // identity matrix if the angle of
+ // rotation is really small:
if (angle < 1e-9)
{
- static const double rotation[3][3]
- = {{ 1, 0, 0}, { 0, 1, 0 }, { 0, 0, 1 } };
- static const Tensor<2,3> rot(rotation);
- return rot;
+ static const double rotation[3][3]
+ = {{ 1, 0, 0}, { 0, 1, 0 }, { 0, 0, 1 } };
+ static const Tensor<2,3> rot(rotation);
+ return rot;
}
- // Otherwise compute the real rotation
- // matrix. The algorithm for this is not
- // exactly obvious, but can be found in a
- // number of books, particularly on
- // computer games where rotation is a
- // very frequent operation. Online, you
- // can find a description at
- // http://www.makegames.com/3drotation/
- // and (this particular form, with the
- // signs as here) at
- // http://www.gamedev.net/reference/articles/article1199.asp:
+ // Otherwise compute the real rotation
+ // matrix. The algorithm for this is not
+ // exactly obvious, but can be found in a
+ // number of books, particularly on
+ // computer games where rotation is a
+ // very frequent operation. Online, you
+ // can find a description at
+ // http://www.makegames.com/3drotation/
+ // and (this particular form, with the
+ // signs as here) at
+ // http://www.gamedev.net/reference/articles/article1199.asp:
const double c = std::cos(angle);
const double s = std::sin(angle);
const double t = 1-c;
const Point<3> axis = curl/tan_angle;
const double rotation[3][3]
= {{ t*axis[0]*axis[0]+c,
- t*axis[0]*axis[1]+s*axis[2],
- t*axis[0]*axis[2]-s*axis[1]},
- { t*axis[0]*axis[1]-s*axis[2],
- t*axis[1]*axis[1]+c,
- t*axis[1]*axis[2]+s*axis[0]},
- { t*axis[0]*axis[2]+s*axis[1],
- t*axis[1]*axis[1]-s*axis[0],
- t*axis[2]*axis[2]+c } };
+ t*axis[0]*axis[1]+s*axis[2],
+ t*axis[0]*axis[2]-s*axis[1]},
+ { t*axis[0]*axis[1]-s*axis[2],
+ t*axis[1]*axis[1]+c,
+ t*axis[1]*axis[2]+s*axis[0]},
+ { t*axis[0]*axis[2]+s*axis[1],
+ t*axis[1]*axis[1]-s*axis[0],
+ t*axis[2]*axis[2]+c } };
return Tensor<2,3>(rotation);
}
-
+
// @sect3{The <code>TopLevel</code> class}
-
+
// This is the main class of the
// program. Since the namespace already
// indicates what problem we are solving,
// let's call it by what it does: it
// directs the flow of the program, i.e. it
// is the toplevel driver.
- //
- // The member variables of this class are
- // essentially as before, i.e. it has to
- // have a triangulation, a DoF handler and
- // associated objects such as constraints,
- // variables that describe the linear
- // system, etc. There are a good number of
- // more member functions now, which we will
- // explain below.
- //
- // The external interface of the class,
- // however, is unchanged: it has a public
- // constructor and desctructor, and it has
- // a <code>run</code> function that initiated all
- // the work.
+ //
+ // The member variables of this class are
+ // essentially as before, i.e. it has to
+ // have a triangulation, a DoF handler and
+ // associated objects such as constraints,
+ // variables that describe the linear
+ // system, etc. There are a good number of
+ // more member functions now, which we will
+ // explain below.
+ //
+ // The external interface of the class,
+ // however, is unchanged: it has a public
+ // constructor and desctructor, and it has
+ // a <code>run</code> function that initiated all
+ // the work.
template <int dim>
- class TopLevel
+ class TopLevel
{
public:
TopLevel ();
~TopLevel ();
void run ();
-
+
private:
// The private interface is more
// extensive than in step-17. First, we
// output the solution vector on the
// currect mesh:
void create_coarse_grid ();
-
+
void setup_system ();
-
+
void assemble_system ();
-
+
void solve_timestep ();
unsigned int solve_linear_problem ();
void output_results () const;
- // All, except for the first two, of
- // these functions are called in each
- // timestep. Since the first time step
- // is a little special, we have
- // separate functions that describe
- // what has to happen in a timestep:
- // one for the first, and one for all
- // following timesteps:
+ // All, except for the first two, of
+ // these functions are called in each
+ // timestep. Since the first time step
+ // is a little special, we have
+ // separate functions that describe
+ // what has to happen in a timestep:
+ // one for the first, and one for all
+ // following timesteps:
void do_initial_timestep ();
void do_timestep ();
-
+
// Then we need a whole bunch of
// functions that do various
// things. The first one refines the
// each quadrature point.
void refine_initial_grid ();
- // At the end of each time step, we
- // want to move the mesh vertices
- // around according to the incremental
- // displacement computed in this time
- // step. This is the function in which
- // this is done:
+ // At the end of each time step, we
+ // want to move the mesh vertices
+ // around according to the incremental
+ // displacement computed in this time
+ // step. This is the function in which
+ // this is done:
void move_mesh ();
// Next are two functions that handle
// timestep:
void update_quadrature_point_history ();
- // After the member functions, here are
- // the member variables. The first ones
- // have all been discussed in more
- // detail in previous example programs:
+ // After the member functions, here are
+ // the member variables. The first ones
+ // have all been discussed in more
+ // detail in previous example programs:
Triangulation<dim> triangulation;
FESystem<dim> fe;
// processors).
std::vector<PointHistory<dim> > quadrature_point_history;
- // The way this object is accessed is
- // through a <code>user pointer</code> that each
- // cell, face, or edge holds: it is a
- // <code>void*</code> pointer that can be used
- // by application programs to associate
- // arbitrary data to cells, faces, or
- // edges. What the program actually
- // does with this data is within its
- // own responsibility, the library just
- // allocates some space for these
- // pointers, and application programs
- // can set and read the pointers for
- // each of these objects.
-
-
- // Further: we need the objects of
- // linear systems to be solved,
- // i.e. matrix, right hand side vector,
- // and the solution vector. Since we
- // anticipate solving big problems, we
- // use the same types as in step-17,
- // i.e. distributed %parallel matrices
- // and vectors built on top of the
- // PETSc library. Conveniently, they
- // can also be used when running on
- // only a single machine, in which case
- // this machine happens to be the only
- // one in our %parallel universe.
- //
- // However, as a difference to step-17,
- // we do not store the solution vector
- // -- which here is the incremental
- // displacements computed in each time
- // step -- in a distributed
- // fashion. I.e., of course it must be
- // a distributed vector when computing
- // it, but immediately after that we
- // make sure each processor has a
- // complete copy. The reason is that we
- // had already seen in step-17 that
- // many functions needed a complete
- // copy. While it is not hard to get
- // it, this requires communication on
- // the network, and is thus slow. In
- // addition, these were repeatedly the
- // same operations, which is certainly
- // undesirable unless the gains of not
- // always having to store the entire
- // vector outweighs it. When writing
- // this program, it turned out that we
- // need a complete copy of the solution
- // in so many places that it did not
- // seem worthwhile to only get it when
- // necessary. Instead, we opted to
- // obtain the complete copy once and
- // for all, and instead get rid of the
- // distributed copy immediately. Thus,
- // note that the declaration of
- // <code>inremental_displacement</code> does not
- // denote a distribute vector as would
- // be indicated by the middle namespace
- // <code>MPI</code>:
+ // The way this object is accessed is
+ // through a <code>user pointer</code> that each
+ // cell, face, or edge holds: it is a
+ // <code>void*</code> pointer that can be used
+ // by application programs to associate
+ // arbitrary data to cells, faces, or
+ // edges. What the program actually
+ // does with this data is within its
+ // own responsibility, the library just
+ // allocates some space for these
+ // pointers, and application programs
+ // can set and read the pointers for
+ // each of these objects.
+
+
+ // Further: we need the objects of
+ // linear systems to be solved,
+ // i.e. matrix, right hand side vector,
+ // and the solution vector. Since we
+ // anticipate solving big problems, we
+ // use the same types as in step-17,
+ // i.e. distributed %parallel matrices
+ // and vectors built on top of the
+ // PETSc library. Conveniently, they
+ // can also be used when running on
+ // only a single machine, in which case
+ // this machine happens to be the only
+ // one in our %parallel universe.
+ //
+ // However, as a difference to step-17,
+ // we do not store the solution vector
+ // -- which here is the incremental
+ // displacements computed in each time
+ // step -- in a distributed
+ // fashion. I.e., of course it must be
+ // a distributed vector when computing
+ // it, but immediately after that we
+ // make sure each processor has a
+ // complete copy. The reason is that we
+ // had already seen in step-17 that
+ // many functions needed a complete
+ // copy. While it is not hard to get
+ // it, this requires communication on
+ // the network, and is thus slow. In
+ // addition, these were repeatedly the
+ // same operations, which is certainly
+ // undesirable unless the gains of not
+ // always having to store the entire
+ // vector outweighs it. When writing
+ // this program, it turned out that we
+ // need a complete copy of the solution
+ // in so many places that it did not
+ // seem worthwhile to only get it when
+ // necessary. Instead, we opted to
+ // obtain the complete copy once and
+ // for all, and instead get rid of the
+ // distributed copy immediately. Thus,
+ // note that the declaration of
+ // <code>inremental_displacement</code> does not
+ // denote a distribute vector as would
+ // be indicated by the middle namespace
+ // <code>MPI</code>:
PETScWrappers::MPI::SparseMatrix system_matrix;
PETScWrappers::MPI::Vector system_rhs;
PETScWrappers::Vector incremental_displacement;
- // The next block of variables is then
- // related to the time dependent nature
- // of the problem: they denote the
- // length of the time interval which we
- // want to simulate, the present time
- // and number of time step, and length
- // of present timestep:
+ // The next block of variables is then
+ // related to the time dependent nature
+ // of the problem: they denote the
+ // length of the time interval which we
+ // want to simulate, the present time
+ // and number of time step, and length
+ // of present timestep:
double present_time;
double present_timestep;
double end_time;
unsigned int timestep_no;
- // Then a few variables that have to do
- // with %parallel processing: first, a
- // variable denoting the MPI
- // communicator we use, and then two
- // numbers telling us how many
- // participating processors there are,
- // and where in this world we
- // are. Finally, a stream object that
- // makes sure only one processor is
- // actually generating output to the
- // console. This is all the same as in
- // step-17:
+ // Then a few variables that have to do
+ // with %parallel processing: first, a
+ // variable denoting the MPI
+ // communicator we use, and then two
+ // numbers telling us how many
+ // participating processors there are,
+ // and where in this world we
+ // are. Finally, a stream object that
+ // makes sure only one processor is
+ // actually generating output to the
+ // console. This is all the same as in
+ // step-17:
MPI_Comm mpi_communicator;
const unsigned int n_mpi_processes;
ConditionalOStream pcout;
- // Here is a vector where each entry
- // denotes the numbers of degrees of
- // freedom that are stored on the
- // processor with that particular
- // number:
+ // Here is a vector where each entry
+ // denotes the numbers of degrees of
+ // freedom that are stored on the
+ // processor with that particular
+ // number:
std::vector<unsigned int> local_dofs_per_process;
-
+
// Next, how many degrees of freedom
// the present processor stores. This
// is, of course, an abbreviation to
};
- // @sect3{The <code>BodyForce</code> class}
-
+ // @sect3{The <code>BodyForce</code> class}
+
// Before we go on to the main
// functionality of this program, we have
// to define what forces will act on the
// in the function, and we take as the
// density 7700 kg/m^3, a value commonly
// assumed for steel.
- //
- // To be a little more general and to be
- // able to do computations in 2d as well,
- // we realize that the body force is always
- // a function returning a <code>dim</code>
- // dimensional vector. We assume that
- // gravity acts along the negative
- // direction of the last, i.e. <code>dim-1</code>th
- // coordinate. The rest of the
- // implementation of this function should
- // be mostly self-explanatory given similar
- // definitions in previous example
- // programs. Note that the body force is
- // independent of the location; to avoid
- // compiler warnings about unused function
- // arguments, we therefore comment out the
- // name of the first argument of the
- // <code>vector_value</code> function:
+ //
+ // To be a little more general and to be
+ // able to do computations in 2d as well,
+ // we realize that the body force is always
+ // a function returning a <code>dim</code>
+ // dimensional vector. We assume that
+ // gravity acts along the negative
+ // direction of the last, i.e. <code>dim-1</code>th
+ // coordinate. The rest of the
+ // implementation of this function should
+ // be mostly self-explanatory given similar
+ // definitions in previous example
+ // programs. Note that the body force is
+ // independent of the location; to avoid
+ // compiler warnings about unused function
+ // arguments, we therefore comment out the
+ // name of the first argument of the
+ // <code>vector_value</code> function:
template <int dim>
- class BodyForce : public Function<dim>
+ class BodyForce : public Function<dim>
{
public:
BodyForce ();
-
+
virtual
void
vector_value (const Point<dim> &p,
- Vector<double> &values) const;
+ Vector<double> &values) const;
virtual
void
vector_value_list (const std::vector<Point<dim> > &points,
- std::vector<Vector<double> > &value_list) const;
+ std::vector<Vector<double> > &value_list) const;
};
inline
void
BodyForce<dim>::vector_value (const Point<dim> &/*p*/,
- Vector<double> &values) const
+ Vector<double> &values) const
{
- Assert (values.size() == dim,
+ Assert (values.size() == dim,
ExcDimensionMismatch (values.size(), dim));
const double g = 9.81;
const double rho = 7700;
-
+
values = 0;
values(dim-1) = -rho * g;
}
template <int dim>
void
BodyForce<dim>::vector_value_list (const std::vector<Point<dim> > &points,
- std::vector<Vector<double> > &value_list) const
+ std::vector<Vector<double> > &value_list) const
{
const unsigned int n_points = points.size();
- Assert (value_list.size() == n_points,
+ Assert (value_list.size() == n_points,
ExcDimensionMismatch (value_list.size(), n_points));
for (unsigned int p=0; p<n_points; ++p)
BodyForce<dim>::vector_value (points[p],
- value_list[p]);
+ value_list[p]);
}
- // @sect3{The <code>IncrementalBoundaryValue</code> class}
-
- // In addition to body forces, movement can
- // be induced by boundary forces and forced
- // boundary displacement. The latter case
- // is equivalent to forces being chosen in
- // such a way that they induce certain
- // displacement.
- //
- // For quasistatic displacement, typical
- // boundary forces would be pressure on a
- // body, or tangential friction against
- // another body. We chose a somewhat
- // simpler case here: we prescribe a
- // certain movement of (parts of) the
- // boundary, or at least of certain
- // components of the displacement
- // vector. We describe this by another
- // vector-valued function that, for a given
- // point on the boundary, returns the
- // prescribed displacement.
- //
- // Since we have a time-dependent problem,
- // the displacement increment of the
- // boundary equals the displacement
- // accumulated during the length of the
- // timestep. The class therefore has to
- // know both the present time and the
- // length of the present time step, and can
- // then approximate the incremental
- // displacement as the present velocity
- // times the present timestep.
- //
- // For the purposes of this
- // program, we choose a simple form
- // of boundary displacement: we
- // displace the top boundary with
- // constant velocity downwards. The
- // rest of the boundary is either
- // going to be fixed (and is then
- // described using an object of
- // type <code>ZeroFunction</code>) or free
- // (Neumann-type, in which case
- // nothing special has to be done).
- // The implementation of the
- // class describing the constant
- // downward motion should then be
- // obvious using the knowledge we
- // gained through all the previous
- // example programs:
+ // @sect3{The <code>IncrementalBoundaryValue</code> class}
+
+ // In addition to body forces, movement can
+ // be induced by boundary forces and forced
+ // boundary displacement. The latter case
+ // is equivalent to forces being chosen in
+ // such a way that they induce certain
+ // displacement.
+ //
+ // For quasistatic displacement, typical
+ // boundary forces would be pressure on a
+ // body, or tangential friction against
+ // another body. We chose a somewhat
+ // simpler case here: we prescribe a
+ // certain movement of (parts of) the
+ // boundary, or at least of certain
+ // components of the displacement
+ // vector. We describe this by another
+ // vector-valued function that, for a given
+ // point on the boundary, returns the
+ // prescribed displacement.
+ //
+ // Since we have a time-dependent problem,
+ // the displacement increment of the
+ // boundary equals the displacement
+ // accumulated during the length of the
+ // timestep. The class therefore has to
+ // know both the present time and the
+ // length of the present time step, and can
+ // then approximate the incremental
+ // displacement as the present velocity
+ // times the present timestep.
+ //
+ // For the purposes of this
+ // program, we choose a simple form
+ // of boundary displacement: we
+ // displace the top boundary with
+ // constant velocity downwards. The
+ // rest of the boundary is either
+ // going to be fixed (and is then
+ // described using an object of
+ // type <code>ZeroFunction</code>) or free
+ // (Neumann-type, in which case
+ // nothing special has to be done).
+ // The implementation of the
+ // class describing the constant
+ // downward motion should then be
+ // obvious using the knowledge we
+ // gained through all the previous
+ // example programs:
template <int dim>
- class IncrementalBoundaryValues : public Function<dim>
+ class IncrementalBoundaryValues : public Function<dim>
{
public:
IncrementalBoundaryValues (const double present_time,
- const double present_timestep);
-
+ const double present_timestep);
+
virtual
void
vector_value (const Point<dim> &p,
- Vector<double> &values) const;
+ Vector<double> &values) const;
virtual
void
vector_value_list (const std::vector<Point<dim> > &points,
- std::vector<Vector<double> > &value_list) const;
+ std::vector<Vector<double> > &value_list) const;
private:
const double velocity;
template <int dim>
IncrementalBoundaryValues<dim>::
IncrementalBoundaryValues (const double present_time,
- const double present_timestep)
+ const double present_timestep)
:
Function<dim> (dim),
velocity (.1),
present_time (present_time),
- present_timestep (present_timestep)
+ present_timestep (present_timestep)
{}
void
IncrementalBoundaryValues<dim>::
vector_value (const Point<dim> &/*p*/,
- Vector<double> &values) const
+ Vector<double> &values) const
{
- Assert (values.size() == dim,
+ Assert (values.size() == dim,
ExcDimensionMismatch (values.size(), dim));
values = 0;
void
IncrementalBoundaryValues<dim>::
vector_value_list (const std::vector<Point<dim> > &points,
- std::vector<Vector<double> > &value_list) const
+ std::vector<Vector<double> > &value_list) const
{
const unsigned int n_points = points.size();
- Assert (value_list.size() == n_points,
+ Assert (value_list.size() == n_points,
ExcDimensionMismatch (value_list.size(), n_points));
for (unsigned int p=0; p<n_points; ++p)
- // @sect3{Implementation of the <code>TopLevel</code> class}
+ // @sect3{Implementation of the <code>TopLevel</code> class}
- // Now for the implementation of the main
- // class. First, we initialize the
+ // Now for the implementation of the main
+ // class. First, we initialize the
// stress-strain tensor, which we
// have declared as a static const
// variable. We chose Lame
TopLevel<dim>::stress_strain_tensor
= get_stress_strain_tensor<dim> (/*lambda = */ 9.695e10,
/*mu = */ 7.617e10);
-
- // @sect4{The public interface}
-
+
+ // @sect4{The public interface}
+
// The next step is the definition of
// constructors and descructors. There are
// no surprises here: we choose linear and
template <int dim>
- TopLevel<dim>::~TopLevel ()
+ TopLevel<dim>::~TopLevel ()
{
dof_handler.clear ();
}
-
-
- // The last of the public functions is the
- // one that directs all the work,
- // <code>run()</code>. It initializes the variables
- // that describe where in time we presently
- // are, then runs the first time step, then
- // loops over all the other time
- // steps. Note that for simplicity we use a
- // fixed time step, whereas a more
- // sophisticated program would of course
- // have to choose it in some more
+
+
+ // The last of the public functions is the
+ // one that directs all the work,
+ // <code>run()</code>. It initializes the variables
+ // that describe where in time we presently
+ // are, then runs the first time step, then
+ // loops over all the other time
+ // steps. Note that for simplicity we use a
+ // fixed time step, whereas a more
+ // sophisticated program would of course
+ // have to choose it in some more
// reasonable way adaptively:
template <int dim>
- void TopLevel<dim>::run ()
+ void TopLevel<dim>::run ()
{
present_time = 0;
present_timestep = 1;
end_time = 10;
timestep_no = 0;
-
+
do_initial_timestep ();
while (present_time < end_time)
}
- // @sect4{TopLevel::create_coarse_grid}
-
- // The next function in the order
- // in which they were declared
- // above is the one that creates
- // the coarse grid from which we
- // start. For this example program,
- // we want to compute the
- // deformation of a cylinder under
- // axial compression. The first
- // step therefore is to generate a
- // mesh for a cylinder of length 3
- // and with inner and outer radii
- // of 0.8 and 1,
- // respectively. Fortunately, there
- // is a library function for such a
- // mesh.
+ // @sect4{TopLevel::create_coarse_grid}
+
+ // The next function in the order
+ // in which they were declared
+ // above is the one that creates
+ // the coarse grid from which we
+ // start. For this example program,
+ // we want to compute the
+ // deformation of a cylinder under
+ // axial compression. The first
+ // step therefore is to generate a
+ // mesh for a cylinder of length 3
+ // and with inner and outer radii
+ // of 0.8 and 1,
+ // respectively. Fortunately, there
+ // is a library function for such a
+ // mesh.
//
// In a second step, we have to associated
// boundary conditions with the upper and
void TopLevel<dim>::create_coarse_grid ()
{
const double inner_radius = 0.8,
- outer_radius = 1;
+ outer_radius = 1;
GridGenerator::cylinder_shell (triangulation,
3, inner_radius, outer_radius);
for (typename Triangulation<dim>::active_cell_iterator
for (unsigned int f=0; f<GeometryInfo<dim>::faces_per_cell; ++f)
if (cell->face(f)->at_boundary())
{
- const Point<dim> face_center = cell->face(f)->center();
-
+ const Point<dim> face_center = cell->face(f)->center();
+
if (face_center[2] == 0)
cell->face(f)->set_boundary_indicator (0);
else if (face_center[2] == 3)
cell->face(f)->set_boundary_indicator (1);
else if (std::sqrt(face_center[0]*face_center[0] +
- face_center[1]*face_center[1])
- <
- (inner_radius + outer_radius) / 2)
+ face_center[1]*face_center[1])
+ <
+ (inner_radius + outer_radius) / 2)
cell->face(f)->set_boundary_indicator (2);
- else
- cell->face(f)->set_boundary_indicator (3);
+ else
+ cell->face(f)->set_boundary_indicator (3);
}
- // In order to make sure that new
- // vertices are placed correctly on mesh
- // refinement, we have to associate
- // objects describing those parts of the
- // boundary that do not consist of
- // straight parts. Corresponding to the
- // cylinder shell generator function used
- // above, there are classes that can be
- // used to describe the geometry of
- // cylinders. We need to use different
- // objects for the inner and outer parts
- // of the cylinder, with different radii;
- // the second argument to the constructor
- // indicates the axis around which the
- // cylinder revolves -- in this case the
- // z-axis. Note that the boundary objects
- // need to live as long as the
- // triangulation does; we can achieve
- // this by making the objects static,
- // which means that they live as long as
- // the program runs:
+ // In order to make sure that new
+ // vertices are placed correctly on mesh
+ // refinement, we have to associate
+ // objects describing those parts of the
+ // boundary that do not consist of
+ // straight parts. Corresponding to the
+ // cylinder shell generator function used
+ // above, there are classes that can be
+ // used to describe the geometry of
+ // cylinders. We need to use different
+ // objects for the inner and outer parts
+ // of the cylinder, with different radii;
+ // the second argument to the constructor
+ // indicates the axis around which the
+ // cylinder revolves -- in this case the
+ // z-axis. Note that the boundary objects
+ // need to live as long as the
+ // triangulation does; we can achieve
+ // this by making the objects static,
+ // which means that they live as long as
+ // the program runs:
static const CylinderBoundary<dim> inner_cylinder (inner_radius, 2);
static const CylinderBoundary<dim> outer_cylinder (outer_radius, 2);
- // We then attach these two objects to
- // the triangulation, and make them
- // correspond to boundary indicators 2
- // and 3:
+ // We then attach these two objects to
+ // the triangulation, and make them
+ // correspond to boundary indicators 2
+ // and 3:
triangulation.set_boundary (2, inner_cylinder);
triangulation.set_boundary (3, outer_cylinder);
- // There's one more thing we have to take
- // care of (we should have done so above
- // already, but for didactic reasons it
- // was more appropriate to handle it
- // after discussing boundary
- // objects). %Boundary indicators in
- // deal.II, for mostly historic reasons,
- // serve a dual purpose: they describe
- // the type of a boundary for other
- // places in a program where different
- // boundary conditions are implemented;
- // and they describe which boundary
- // object (as the ones associated above)
- // should be queried when new boundary
- // points need to be placed upon mesh
- // refinement. In the prefix to this
- // function, we have discussed the
- // boundary condition issue, and the
- // boundary geometry issue was mentioned
- // just above. But there is a case where
- // we have to be careful with geometry:
- // what happens if a cell is refined that
- // has two faces with different boundary
- // indicators? For example one at the
- // edges of the cylinder? In that case,
- // the library wouldn't know where to put
- // new points in the middle of edges (one
- // of the twelve lines of a
- // hexahedron). In fact, the library
- // doesn't even care about the boundary
- // indicator of adjacent faces when
- // refining edges: it considers the
- // boundary indicators associated with
- // the edges themselves. So what do we
- // want to happen with the edges of the
- // cylinder shell: they sit on both faces
- // with boundary indicators 2 or 3 (inner
- // or outer shell) and 0 or 1 (for which
- // no boundary objects have been
- // specified, and for which the library
- // therefore assumes straight
- // lines). Obviously, we want these lines
- // to follow the curved shells, so we
- // have to assign all edges along faces
- // with boundary indicators 2 or 3 these
- // same boundary indicators to make sure
- // they are refined using the appropriate
- // geometry objects. This is easily done:
+ // There's one more thing we have to take
+ // care of (we should have done so above
+ // already, but for didactic reasons it
+ // was more appropriate to handle it
+ // after discussing boundary
+ // objects). %Boundary indicators in
+ // deal.II, for mostly historic reasons,
+ // serve a dual purpose: they describe
+ // the type of a boundary for other
+ // places in a program where different
+ // boundary conditions are implemented;
+ // and they describe which boundary
+ // object (as the ones associated above)
+ // should be queried when new boundary
+ // points need to be placed upon mesh
+ // refinement. In the prefix to this
+ // function, we have discussed the
+ // boundary condition issue, and the
+ // boundary geometry issue was mentioned
+ // just above. But there is a case where
+ // we have to be careful with geometry:
+ // what happens if a cell is refined that
+ // has two faces with different boundary
+ // indicators? For example one at the
+ // edges of the cylinder? In that case,
+ // the library wouldn't know where to put
+ // new points in the middle of edges (one
+ // of the twelve lines of a
+ // hexahedron). In fact, the library
+ // doesn't even care about the boundary
+ // indicator of adjacent faces when
+ // refining edges: it considers the
+ // boundary indicators associated with
+ // the edges themselves. So what do we
+ // want to happen with the edges of the
+ // cylinder shell: they sit on both faces
+ // with boundary indicators 2 or 3 (inner
+ // or outer shell) and 0 or 1 (for which
+ // no boundary objects have been
+ // specified, and for which the library
+ // therefore assumes straight
+ // lines). Obviously, we want these lines
+ // to follow the curved shells, so we
+ // have to assign all edges along faces
+ // with boundary indicators 2 or 3 these
+ // same boundary indicators to make sure
+ // they are refined using the appropriate
+ // geometry objects. This is easily done:
for (typename Triangulation<dim>::active_face_iterator
face=triangulation.begin_active_face();
face!=triangulation.end_face(); ++face)
if (face->at_boundary())
- if ((face->boundary_indicator() == 2)
- ||
- (face->boundary_indicator() == 3))
- for (unsigned int edge = 0; edge<GeometryInfo<dim>::lines_per_face;
- ++edge)
- face->line(edge)
- ->set_boundary_indicator (face->boundary_indicator());
-
- // Once all this is done, we can refine
- // the mesh once globally:
+ if ((face->boundary_indicator() == 2)
+ ||
+ (face->boundary_indicator() == 3))
+ for (unsigned int edge = 0; edge<GeometryInfo<dim>::lines_per_face;
+ ++edge)
+ face->line(edge)
+ ->set_boundary_indicator (face->boundary_indicator());
+
+ // Once all this is done, we can refine
+ // the mesh once globally:
triangulation.refine_global (1);
-
+
// As the final step, we need to
// set up a clean state of the
// the following two function
// calls:
GridTools::partition_triangulation (n_mpi_processes, triangulation);
- setup_quadrature_point_history ();
+ setup_quadrature_point_history ();
}
-
- // @sect4{TopLevel::setup_system}
+
+ // @sect4{TopLevel::setup_system}
// The next function is the one
// that sets up the data structures
dof_handler.distribute_dofs (fe);
DoFRenumbering::subdomain_wise (dof_handler);
- // The next thing is to store some
- // information for later use on how many
- // cells or degrees of freedom the
- // present processor, or any of the
- // processors has to work on. First the
- // cells local to this processor...
+ // The next thing is to store some
+ // information for later use on how many
+ // cells or degrees of freedom the
+ // present processor, or any of the
+ // processors has to work on. First the
+ // cells local to this processor...
n_local_cells
= GridTools::count_cells_with_subdomain_association (triangulation,
this_mpi_process);
- // ...and then a list of numbers of how
- // many degrees of freedom each processor
- // has to handle:
+ // ...and then a list of numbers of how
+ // many degrees of freedom each processor
+ // has to handle:
local_dofs_per_process.resize (n_mpi_processes);
for (unsigned int i=0; i<n_mpi_processes; ++i)
local_dofs_per_process[i]
= DoFTools::count_dofs_with_subdomain_association (dof_handler, i);
- // Finally, make it easier to denote how
- // many degrees of freedom the present
- // process has to deal with, by
- // introducing an abbreviation:
+ // Finally, make it easier to denote how
+ // many degrees of freedom the present
+ // process has to deal with, by
+ // introducing an abbreviation:
n_local_dofs = local_dofs_per_process[this_mpi_process];
// The next step is to set up constraints
DoFTools::make_hanging_node_constraints (dof_handler,
hanging_node_constraints);
hanging_node_constraints.close ();
-
+
// And then we have to set up the
// matrix. Here we deviate from step-17,
// in which we simply used PETSc's
// by almost two orders of magnitude if
// we instruct PETSc which elements will
// be used and which are not.
- //
- // To do so, we first generate the
- // sparsity pattern of the matrix we are
- // going to work with, and make sure that
- // the condensation of hanging node
- // constraints add the necessary
- // additional entries in the sparsity
- // pattern:
+ //
+ // To do so, we first generate the
+ // sparsity pattern of the matrix we are
+ // going to work with, and make sure that
+ // the condensation of hanging node
+ // constraints add the necessary
+ // additional entries in the sparsity
+ // pattern:
CompressedSparsityPattern sparsity_pattern (dof_handler.n_dofs(),
dof_handler.n_dofs());
DoFTools::make_sparsity_pattern (dof_handler, sparsity_pattern);
hanging_node_constraints.condense (sparsity_pattern);
- // Note that we have used the
- // <code>CompressedSparsityPattern</code> class
- // here that was already introduced in
- // step-11, rather than the
- // <code>SparsityPattern</code> class that we have
- // used in all other cases. The reason
- // for this is that for the latter class
- // to work we have to give an initial
- // upper bound for the number of entries
- // in each row, a task that is
- // traditionally done by
- // <code>DoFHandler::max_couplings_between_dofs()</code>. However,
- // this function suffers from a serious
- // problem: it has to compute an upper
- // bound to the number of nonzero entries
- // in each row, and this is a rather
- // complicated task, in particular in
- // 3d. In effect, while it is quite
- // accurate in 2d, it often comes up with
- // much too large a number in 3d, and in
- // that case the <code>SparsityPattern</code>
- // allocates much too much memory at
- // first, often several 100 MBs. This is
- // later corrected when
- // <code>DoFTools::make_sparsity_pattern</code> is
- // called and we realize that we don't
- // need all that much memory, but at time
- // it is already too late: for large
- // problems, the temporary allocation of
- // too much memory can lead to
- // out-of-memory situations.
- //
- // In order to avoid this, we resort to
- // the <code>CompressedSparsityPattern</code>
- // class that is slower but does not
- // require any up-front estimate on the
- // number of nonzero entries per row. It
- // therefore only ever allocates as much
- // memory as it needs at any given time,
- // and we can build it even for large 3d
- // problems.
- //
- // It is also worth noting that the
- // sparsity pattern we construct is
- // global, i.e. comprises all degrees of
- // freedom whether they will be owned by
- // the processor we are on or another one
- // (in case this program is run in
- // %parallel via MPI). This of course is
- // not optimal -- it limits the size of
- // the problems we can solve, since
- // storing the entire sparsity pattern
- // (even if only for a short time) on
- // each processor does not scale
- // well. However, there are several more
- // places in the program in which we do
- // this, for example we always keep the
- // global triangulation and DoF handler
- // objects around, even if we only work
- // on part of them. At present, deal.II
- // does not have the necessary facilities
- // to completely distribute these objects
- // (a task that, indeed, is very hard to
- // achieve with adaptive meshes, since
- // well-balanced subdivisions of a domain
- // tend to become unbalanced as the mesh
- // is adaptively refined).
- //
- // With this data structure, we can then
- // go to the PETSc sparse matrix and tell
- // it to pre-allocate all the entries we
- // will later want to write to:
+ // Note that we have used the
+ // <code>CompressedSparsityPattern</code> class
+ // here that was already introduced in
+ // step-11, rather than the
+ // <code>SparsityPattern</code> class that we have
+ // used in all other cases. The reason
+ // for this is that for the latter class
+ // to work we have to give an initial
+ // upper bound for the number of entries
+ // in each row, a task that is
+ // traditionally done by
+ // <code>DoFHandler::max_couplings_between_dofs()</code>. However,
+ // this function suffers from a serious
+ // problem: it has to compute an upper
+ // bound to the number of nonzero entries
+ // in each row, and this is a rather
+ // complicated task, in particular in
+ // 3d. In effect, while it is quite
+ // accurate in 2d, it often comes up with
+ // much too large a number in 3d, and in
+ // that case the <code>SparsityPattern</code>
+ // allocates much too much memory at
+ // first, often several 100 MBs. This is
+ // later corrected when
+ // <code>DoFTools::make_sparsity_pattern</code> is
+ // called and we realize that we don't
+ // need all that much memory, but at time
+ // it is already too late: for large
+ // problems, the temporary allocation of
+ // too much memory can lead to
+ // out-of-memory situations.
+ //
+ // In order to avoid this, we resort to
+ // the <code>CompressedSparsityPattern</code>
+ // class that is slower but does not
+ // require any up-front estimate on the
+ // number of nonzero entries per row. It
+ // therefore only ever allocates as much
+ // memory as it needs at any given time,
+ // and we can build it even for large 3d
+ // problems.
+ //
+ // It is also worth noting that the
+ // sparsity pattern we construct is
+ // global, i.e. comprises all degrees of
+ // freedom whether they will be owned by
+ // the processor we are on or another one
+ // (in case this program is run in
+ // %parallel via MPI). This of course is
+ // not optimal -- it limits the size of
+ // the problems we can solve, since
+ // storing the entire sparsity pattern
+ // (even if only for a short time) on
+ // each processor does not scale
+ // well. However, there are several more
+ // places in the program in which we do
+ // this, for example we always keep the
+ // global triangulation and DoF handler
+ // objects around, even if we only work
+ // on part of them. At present, deal.II
+ // does not have the necessary facilities
+ // to completely distribute these objects
+ // (a task that, indeed, is very hard to
+ // achieve with adaptive meshes, since
+ // well-balanced subdivisions of a domain
+ // tend to become unbalanced as the mesh
+ // is adaptively refined).
+ //
+ // With this data structure, we can then
+ // go to the PETSc sparse matrix and tell
+ // it to pre-allocate all the entries we
+ // will later want to write to:
system_matrix.reinit (mpi_communicator,
sparsity_pattern,
local_dofs_per_process,
local_dofs_per_process,
this_mpi_process);
- // After this point, no further explicit
- // knowledge of the sparsity pattern is
- // required any more and we can let the
- // <code>sparsity_pattern</code> variable go out
- // of scope without any problem.
-
- // The last task in this function
- // is then only to reset the
- // right hand side vector as well
- // as the solution vector to its
- // correct size; remember that
- // the solution vector is a local
- // one, unlike the right hand
- // side that is a distributed
- // %parallel one and therefore
- // needs to know the MPI
- // communicator over which it is
- // supposed to transmit messages:
+ // After this point, no further explicit
+ // knowledge of the sparsity pattern is
+ // required any more and we can let the
+ // <code>sparsity_pattern</code> variable go out
+ // of scope without any problem.
+
+ // The last task in this function
+ // is then only to reset the
+ // right hand side vector as well
+ // as the solution vector to its
+ // correct size; remember that
+ // the solution vector is a local
+ // one, unlike the right hand
+ // side that is a distributed
+ // %parallel one and therefore
+ // needs to know the MPI
+ // communicator over which it is
+ // supposed to transmit messages:
system_rhs.reinit (mpi_communicator, dof_handler.n_dofs(), n_local_dofs);
incremental_displacement.reinit (dof_handler.n_dofs());
}
- // @sect4{TopLevel::assemble_system}
-
- // Again, assembling the system
- // matrix and right hand side
- // follows the same structure as in
- // many example programs before. In
- // particular, it is mostly
- // equivalent to step-17, except
- // for the different right hand
- // side that now only has to take
- // into account internal
- // stresses. In addition,
- // assembling the matrix is made
- // significantly more transparent
- // by using the <code>SymmetricTensor</code>
- // class: note the elegance of
- // forming the scalar products of
- // symmetric tensors of rank 2 and
- // 4. The implementation is also
- // more general since it is
- // independent of the fact that we
- // may or may not be using an
- // isotropic elasticity tensor.
- //
- // The first part of the assembly routine
- // is as always:
+ // @sect4{TopLevel::assemble_system}
+
+ // Again, assembling the system
+ // matrix and right hand side
+ // follows the same structure as in
+ // many example programs before. In
+ // particular, it is mostly
+ // equivalent to step-17, except
+ // for the different right hand
+ // side that now only has to take
+ // into account internal
+ // stresses. In addition,
+ // assembling the matrix is made
+ // significantly more transparent
+ // by using the <code>SymmetricTensor</code>
+ // class: note the elegance of
+ // forming the scalar products of
+ // symmetric tensors of rank 2 and
+ // 4. The implementation is also
+ // more general since it is
+ // independent of the fact that we
+ // may or may not be using an
+ // isotropic elasticity tensor.
+ //
+ // The first part of the assembly routine
+ // is as always:
template <int dim>
- void TopLevel<dim>::assemble_system ()
+ void TopLevel<dim>::assemble_system ()
{
system_rhs = 0;
system_matrix = 0;
- FEValues<dim> fe_values (fe, quadrature_formula,
+ FEValues<dim> fe_values (fe, quadrature_formula,
update_values | update_gradients |
- update_quadrature_points | update_JxW_values);
+ 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();
BodyForce<dim> body_force;
std::vector<Vector<double> > body_force_values (n_q_points,
- Vector<double>(dim));
+ Vector<double>(dim));
- // As in step-17, we only need to loop
- // over all cells that belong to the
- // present processor:
+ // As in step-17, we only need to loop
+ // over all cells that belong to the
+ // present processor:
typename DoFHandler<dim>::active_cell_iterator
cell = dof_handler.begin_active(),
endc = dof_handler.end();
fe_values.reinit (cell);
- // Then loop over all indices i,j
- // and quadrature points and
- // assemble the system matrix
- // contributions from this cell.
- // Note how we extract the
- // symmetric gradients (strains) of
- // the shape functions at a given
- // quadrature point from the
- // <code>FEValues</code> object, and the
- // elegance with which we form the
- // triple contraction <code>eps_phi_i :
- // C : eps_phi_j</code>; the latter
- // needs to be compared to the
- // clumsy computations needed in
- // step-17, both in the
- // introduction as well as in the
- // respective place in the program:
+ // Then loop over all indices i,j
+ // and quadrature points and
+ // assemble the system matrix
+ // contributions from this cell.
+ // Note how we extract the
+ // symmetric gradients (strains) of
+ // the shape functions at a given
+ // quadrature point from the
+ // <code>FEValues</code> object, and the
+ // elegance with which we form the
+ // triple contraction <code>eps_phi_i :
+ // C : eps_phi_j</code>; the latter
+ // needs to be compared to the
+ // clumsy computations needed in
+ // step-17, both in the
+ // introduction as well as in the
+ // respective place in the program:
for (unsigned int i=0; i<dofs_per_cell; ++i)
- for (unsigned int j=0; j<dofs_per_cell; ++j)
+ for (unsigned int j=0; j<dofs_per_cell; ++j)
for (unsigned int q_point=0; q_point<n_q_points;
++q_point)
{
eps_phi_i = get_strain (fe_values, i, q_point),
eps_phi_j = get_strain (fe_values, j, q_point);
- cell_matrix(i,j)
+ cell_matrix(i,j)
+= (eps_phi_i * stress_strain_tensor * eps_phi_j
- *
- fe_values.JxW (q_point));
+ *
+ fe_values.JxW (q_point));
}
- // Then also assemble the local
- // right hand side
- // contributions. For this, we need
- // to access the prior stress value
- // in this quadrature point. To get
- // it, we use the user pointer of
- // this cell that points into the
- // global array to the quadrature
- // point data corresponding to the
- // first quadrature point of the
- // present cell, and then add an
- // offset corresponding to the
- // index of the quadrature point we
- // presently consider:
- const PointHistory<dim> *local_quadrature_points_data
- = reinterpret_cast<PointHistory<dim>*>(cell->user_pointer());
- // In addition, we need the values
- // of the external body forces at
- // the quadrature points on this
- // cell:
- body_force.vector_value_list (fe_values.get_quadrature_points(),
- body_force_values);
- // Then we can loop over all
- // degrees of freedom on this cell
- // and compute local contributions
- // to the right hand side:
- for (unsigned int i=0; i<dofs_per_cell; ++i)
+ // Then also assemble the local
+ // right hand side
+ // contributions. For this, we need
+ // to access the prior stress value
+ // in this quadrature point. To get
+ // it, we use the user pointer of
+ // this cell that points into the
+ // global array to the quadrature
+ // point data corresponding to the
+ // first quadrature point of the
+ // present cell, and then add an
+ // offset corresponding to the
+ // index of the quadrature point we
+ // presently consider:
+ const PointHistory<dim> *local_quadrature_points_data
+ = reinterpret_cast<PointHistory<dim>*>(cell->user_pointer());
+ // In addition, we need the values
+ // of the external body forces at
+ // the quadrature points on this
+ // cell:
+ body_force.vector_value_list (fe_values.get_quadrature_points(),
+ body_force_values);
+ // Then we can loop over all
+ // degrees of freedom on this cell
+ // and compute local contributions
+ // to the right hand side:
+ for (unsigned int i=0; i<dofs_per_cell; ++i)
{
- const unsigned int
+ const unsigned int
component_i = fe.system_to_component_index(i).first;
-
+
for (unsigned int q_point=0; q_point<n_q_points; ++q_point)
{
const SymmetricTensor<2,dim> &old_stress
= local_quadrature_points_data[q_point].old_stress;
-
+
cell_rhs(i) += (body_force_values[q_point](component_i) *
fe_values.shape_value (i,q_point)
-
- old_stress *
+ old_stress *
get_strain (fe_values,i,q_point))
- *
+ *
fe_values.JxW (q_point);
}
}
// done exactly as in step-17:
cell->get_dof_indices (local_dof_indices);
- hanging_node_constraints
+ hanging_node_constraints
.distribute_local_to_global (cell_matrix,
local_dof_indices,
system_matrix);
// vector in the form of a
// temporary vector which we then
// copy into the sequential one.
-
+
// We make up for this
// complication by showing how
// boundary values can be used
std::map<unsigned int,double> boundary_values;
VectorTools::
interpolate_boundary_values (dof_handler,
- 0,
- ZeroFunction<dim> (dim),
- boundary_values);
+ 0,
+ ZeroFunction<dim> (dim),
+ boundary_values);
VectorTools::
interpolate_boundary_values (dof_handler,
- 1,
- IncrementalBoundaryValues<dim>(present_time,
- present_timestep),
- boundary_values,
+ 1,
+ IncrementalBoundaryValues<dim>(present_time,
+ present_timestep),
+ boundary_values,
z_component);
-
+
PETScWrappers::MPI::Vector tmp (mpi_communicator, dof_handler.n_dofs(),
n_local_dofs);
MatrixTools::apply_boundary_values (boundary_values,
- system_matrix, tmp,
- system_rhs, false);
+ system_matrix, tmp,
+ system_rhs, false);
incremental_displacement = tmp;
}
- // @sect4{TopLevel::solve_timestep}
+ // @sect4{TopLevel::solve_timestep}
- // The next function is the one that
- // controls what all has to happen within a
- // timestep. The order of things should be
- // relatively self-explanatory from the
- // function names:
+ // The next function is the one that
+ // controls what all has to happen within a
+ // timestep. The order of things should be
+ // relatively self-explanatory from the
+ // function names:
template <int dim>
void TopLevel<dim>::solve_timestep ()
{
pcout << " Assembling system..." << std::flush;
assemble_system ();
pcout << " norm of rhs is " << system_rhs.l2_norm()
- << std::endl;
-
+ << std::endl;
+
const unsigned int n_iterations = solve_linear_problem ();
-
+
pcout << " Solver converged in " << n_iterations
- << " iterations." << std::endl;
+ << " iterations." << std::endl;
pcout << " Updating quadrature point data..." << std::flush;
update_quadrature_point_history ();
- // @sect4{TopLevel::solve_linear_problem}
-
- // Solving the linear system again
- // works mostly as before. The only
- // difference is that we want to
- // only keep a complete local copy
- // of the solution vector instead
- // of the distributed one that we
- // get as output from PETSc's
- // solver routines. To this end, we
- // declare a local temporary
- // variable for the distributed
- // vector and initialize it with
- // the contents of the local
- // variable (remember that the
- // <code>apply_boundary_values</code>
- // function called in
- // <code>assemble_system</code> preset the
- // values of boundary nodes in this
- // vector), solve with it, and at
- // the end of the function copy it
- // again into the complete local
- // vector that we declared as a
- // member variable. Hanging node
- // constraints are then distributed
- // only on the local copy,
- // i.e. independently of each other
- // on each of the processors:
+ // @sect4{TopLevel::solve_linear_problem}
+
+ // Solving the linear system again
+ // works mostly as before. The only
+ // difference is that we want to
+ // only keep a complete local copy
+ // of the solution vector instead
+ // of the distributed one that we
+ // get as output from PETSc's
+ // solver routines. To this end, we
+ // declare a local temporary
+ // variable for the distributed
+ // vector and initialize it with
+ // the contents of the local
+ // variable (remember that the
+ // <code>apply_boundary_values</code>
+ // function called in
+ // <code>assemble_system</code> preset the
+ // values of boundary nodes in this
+ // vector), solve with it, and at
+ // the end of the function copy it
+ // again into the complete local
+ // vector that we declared as a
+ // member variable. Hanging node
+ // constraints are then distributed
+ // only on the local copy,
+ // i.e. independently of each other
+ // on each of the processors:
template <int dim>
- unsigned int TopLevel<dim>::solve_linear_problem ()
+ unsigned int TopLevel<dim>::solve_linear_problem ()
{
PETScWrappers::MPI::Vector
distributed_incremental_displacement (mpi_communicator,
dof_handler.n_dofs(),
n_local_dofs);
distributed_incremental_displacement = incremental_displacement;
-
+
SolverControl solver_control (dof_handler.n_dofs(),
1e-16*system_rhs.l2_norm());
PETScWrappers::SolverCG cg (solver_control,
incremental_displacement = distributed_incremental_displacement;
hanging_node_constraints.distribute (incremental_displacement);
-
+
return solver_control.last_step();
}
// file in any of the supported
// output files, as mentioned in
// the introduction.
- //
- // The crucial part of this function is to
- // give the <code>DataOut</code> class a way to only
- // work on the cells that the present
- // process owns. This class is already
- // well-equipped for that: it has two
- // virtual functions <code>first_cell</code> and
- // <code>next_cell</code> that return the first cell
- // to be worked on, and given one cell
- // return the next cell to be worked on. By
- // default, these functions return the
- // first active cell (i.e. the first one
- // that has no children) and the next
- // active cell. What we have to do here is
- // derive a class from <code>DataOut</code> that
- // overloads these two functions to only
- // iterate over those cells with the right
- // subdomain indicator.
- //
- // We do this at the beginning of this
- // function. The <code>first_cell</code> function
- // just starts with the first active cell,
- // and then iterates to the next cells
- // while the cell presently under
- // consideration does not yet have the
- // correct subdomain id. The only thing
- // that needs to be taken care of is that
- // we don't try to keep iterating when we
- // have hit the end iterator.
- //
- // The <code>next_cell</code> function could be
- // implemented in a similar way. However,
- // we use this occasion as a pretext to
- // introduce one more thing that the
- // library offers: filtered
- // iterators. These are wrappers for the
- // iterator classes that just skip all
- // cells (or faces, lines, etc) that do not
- // satisfy a certain predicate (a predicate
- // in computer-lingo is a function that
- // when applied to a data element either
- // returns true or false). In the present
- // case, the predicate is that the cell has
- // to have a certain subdomain id, and the
- // library already has this predicate built
- // in. If the cell iterator is not the end
- // iterator, what we then have to do is to
- // initialize such a filtered iterator with
- // the present cell and the predicate, and
- // then increase the iterator exactly
- // once. While the more conventional loop
- // would probably not have been much
- // longer, this is definitely the more
- // elegant way -- and then, these example
- // programs also serve the purpose of
- // introducing what is available in
- // deal.II.
+ //
+ // The crucial part of this function is to
+ // give the <code>DataOut</code> class a way to only
+ // work on the cells that the present
+ // process owns. This class is already
+ // well-equipped for that: it has two
+ // virtual functions <code>first_cell</code> and
+ // <code>next_cell</code> that return the first cell
+ // to be worked on, and given one cell
+ // return the next cell to be worked on. By
+ // default, these functions return the
+ // first active cell (i.e. the first one
+ // that has no children) and the next
+ // active cell. What we have to do here is
+ // derive a class from <code>DataOut</code> that
+ // overloads these two functions to only
+ // iterate over those cells with the right
+ // subdomain indicator.
+ //
+ // We do this at the beginning of this
+ // function. The <code>first_cell</code> function
+ // just starts with the first active cell,
+ // and then iterates to the next cells
+ // while the cell presently under
+ // consideration does not yet have the
+ // correct subdomain id. The only thing
+ // that needs to be taken care of is that
+ // we don't try to keep iterating when we
+ // have hit the end iterator.
+ //
+ // The <code>next_cell</code> function could be
+ // implemented in a similar way. However,
+ // we use this occasion as a pretext to
+ // introduce one more thing that the
+ // library offers: filtered
+ // iterators. These are wrappers for the
+ // iterator classes that just skip all
+ // cells (or faces, lines, etc) that do not
+ // satisfy a certain predicate (a predicate
+ // in computer-lingo is a function that
+ // when applied to a data element either
+ // returns true or false). In the present
+ // case, the predicate is that the cell has
+ // to have a certain subdomain id, and the
+ // library already has this predicate built
+ // in. If the cell iterator is not the end
+ // iterator, what we then have to do is to
+ // initialize such a filtered iterator with
+ // the present cell and the predicate, and
+ // then increase the iterator exactly
+ // once. While the more conventional loop
+ // would probably not have been much
+ // longer, this is definitely the more
+ // elegant way -- and then, these example
+ // programs also serve the purpose of
+ // introducing what is available in
+ // deal.II.
template<int dim>
class FilteredDataOut : public DataOut<dim>
{
:
subdomain_id (subdomain_id)
{}
-
+
virtual typename DoFHandler<dim>::cell_iterator
first_cell ()
{
while ((cell != this->dofs->end()) &&
(cell->subdomain_id() != subdomain_id))
++cell;
-
+
return cell;
}
-
+
virtual typename DoFHandler<dim>::cell_iterator
next_cell (const typename DoFHandler<dim>::cell_iterator &old_cell)
{
{
const IteratorFilters::SubdomainEqualTo
predicate(subdomain_id);
-
+
return
++(FilteredIterator
<typename DoFHandler<dim>::active_cell_iterator>
else
return old_cell;
}
-
+
private:
const unsigned int subdomain_id;
};
-
+
template <int dim>
void TopLevel<dim>::output_results () const
{
- // With this newly defined class, declare
- // an object that is going to generate
- // the graphical output and attach the
- // dof handler with it from which to get
- // the solution vector:
+ // With this newly defined class, declare
+ // an object that is going to generate
+ // the graphical output and attach the
+ // dof handler with it from which to get
+ // the solution vector:
FilteredDataOut<dim> data_out(this_mpi_process);
data_out.attach_dof_handler (dof_handler);
- // Then, just as in step-17, define the
- // names of solution variables (which
- // here are the displacement increments)
- // and queue the solution vector for
- // output. Note in the following switch
- // how we make sure that if the space
- // dimension should be unhandled that we
- // throw an exception saying that we
- // haven't implemented this case yet
- // (another case of defensive
- // programming):
+ // Then, just as in step-17, define the
+ // names of solution variables (which
+ // here are the displacement increments)
+ // and queue the solution vector for
+ // output. Note in the following switch
+ // how we make sure that if the space
+ // dimension should be unhandled that we
+ // throw an exception saying that we
+ // haven't implemented this case yet
+ // (another case of defensive
+ // programming):
std::vector<std::string> solution_names;
switch (dim)
{
solution_names);
- // The next thing is that we wanted to
- // output something like the average norm
- // of the stresses that we have stored in
- // each cell. This may seem complicated,
- // since on the present processor we only
- // store the stresses in quadrature
- // points on those cells that actually
- // belong to the present process. In
- // other words, it seems as if we can't
- // compute the average stresses for all
- // cells. However, remember that our
- // class derived from <code>DataOut</code> only
- // iterates over those cells that
- // actually do belong to the present
- // processor, i.e. we don't have to
- // compute anything for all the other
- // cells as this information would not be
- // touched. The following little loop
- // does this. We enclose the entire block
- // into a pair of braces to make sure
- // that the iterator variables do not
- // remain accidentally visible beyond the
- // end of the block in which they are
- // used:
+ // The next thing is that we wanted to
+ // output something like the average norm
+ // of the stresses that we have stored in
+ // each cell. This may seem complicated,
+ // since on the present processor we only
+ // store the stresses in quadrature
+ // points on those cells that actually
+ // belong to the present process. In
+ // other words, it seems as if we can't
+ // compute the average stresses for all
+ // cells. However, remember that our
+ // class derived from <code>DataOut</code> only
+ // iterates over those cells that
+ // actually do belong to the present
+ // processor, i.e. we don't have to
+ // compute anything for all the other
+ // cells as this information would not be
+ // touched. The following little loop
+ // does this. We enclose the entire block
+ // into a pair of braces to make sure
+ // that the iterator variables do not
+ // remain accidentally visible beyond the
+ // end of the block in which they are
+ // used:
Vector<double> norm_of_stress (triangulation.n_active_cells());
{
- // Loop over all the cells...
+ // Loop over all the cells...
typename Triangulation<dim>::active_cell_iterator
- cell = triangulation.begin_active(),
- endc = triangulation.end();
+ cell = triangulation.begin_active(),
+ endc = triangulation.end();
for (unsigned int index=0; cell!=endc; ++cell, ++index)
- // ... and pick those that are
- // relevant to us:
- if (cell->subdomain_id() == this_mpi_process)
- {
- // On these cells, add up the
- // stresses over all quadrature
- // points...
- SymmetricTensor<2,dim> accumulated_stress;
- for (unsigned int q=0;
- q<quadrature_formula.size();
- ++q)
- accumulated_stress +=
- reinterpret_cast<PointHistory<dim>*>(cell->user_pointer())[q]
- .old_stress;
-
- // ...then write the norm of the
- // average to their destination:
- norm_of_stress(index)
- = (accumulated_stress /
- quadrature_formula.size()).norm();
- }
- // And on the cells that we are not
- // interested in, set the respective
- // value in the vector to a bogus value
- // (norms must be positive, and a large
- // negative value should catch your
- // eye) in order to make sure that if
- // we were somehow wrong about our
- // assumption that these elements would
- // not appear in the output file, that
- // we would find out by looking at the
- // graphical output:
- else
- norm_of_stress(index) = -1e+20;
+ // ... and pick those that are
+ // relevant to us:
+ if (cell->subdomain_id() == this_mpi_process)
+ {
+ // On these cells, add up the
+ // stresses over all quadrature
+ // points...
+ SymmetricTensor<2,dim> accumulated_stress;
+ for (unsigned int q=0;
+ q<quadrature_formula.size();
+ ++q)
+ accumulated_stress +=
+ reinterpret_cast<PointHistory<dim>*>(cell->user_pointer())[q]
+ .old_stress;
+
+ // ...then write the norm of the
+ // average to their destination:
+ norm_of_stress(index)
+ = (accumulated_stress /
+ quadrature_formula.size()).norm();
+ }
+ // And on the cells that we are not
+ // interested in, set the respective
+ // value in the vector to a bogus value
+ // (norms must be positive, and a large
+ // negative value should catch your
+ // eye) in order to make sure that if
+ // we were somehow wrong about our
+ // assumption that these elements would
+ // not appear in the output file, that
+ // we would find out by looking at the
+ // graphical output:
+ else
+ norm_of_stress(index) = -1e+20;
}
- // Finally attach this vector as well to
- // be treated for output:
+ // Finally attach this vector as well to
+ // be treated for output:
data_out.add_data_vector (norm_of_stress, "norm_of_stress");
// As a last piece of data, let
// vectors:
data_out.build_patches ();
-
+
// Now that we have generated the
// intermediate format, let us
// determine the name of the file
data_out.write_deal_II_intermediate (output);
}
-
-
- // @sect4{TopLevel::do_initial_timestep}
-
- // This and the next function handle the
- // overall structure of the first and
- // following timesteps, respectively. The
- // first timestep is slightly more involved
- // because we want to compute it multiple
- // times on successively refined meshes,
- // each time starting from a clean
- // state. At the end of these computations,
- // in which we compute the incremental
- // displacements each time, we use the last
- // results obtained for the incremental
- // displacements to compute the resulting
- // stress updates and move the mesh
- // accordingly. On this new mesh, we then
- // output the solution and any additional
- // data we consider important.
- //
- // All this is interspersed by generating
- // output to the console to update the
- // person watching the screen on what is
- // going on. As in step-17, the use of
- // <code>pcout</code> instead of <code>std::cout</code> makes
- // sure that only one of the parallel
- // processes is actually writing to the
- // console, without having to explicitly
- // code an if-statement in each place where
- // we generate output:
+
+
+ // @sect4{TopLevel::do_initial_timestep}
+
+ // This and the next function handle the
+ // overall structure of the first and
+ // following timesteps, respectively. The
+ // first timestep is slightly more involved
+ // because we want to compute it multiple
+ // times on successively refined meshes,
+ // each time starting from a clean
+ // state. At the end of these computations,
+ // in which we compute the incremental
+ // displacements each time, we use the last
+ // results obtained for the incremental
+ // displacements to compute the resulting
+ // stress updates and move the mesh
+ // accordingly. On this new mesh, we then
+ // output the solution and any additional
+ // data we consider important.
+ //
+ // All this is interspersed by generating
+ // output to the console to update the
+ // person watching the screen on what is
+ // going on. As in step-17, the use of
+ // <code>pcout</code> instead of <code>std::cout</code> makes
+ // sure that only one of the parallel
+ // processes is actually writing to the
+ // console, without having to explicitly
+ // code an if-statement in each place where
+ // we generate output:
template <int dim>
void TopLevel<dim>::do_initial_timestep ()
{
++timestep_no;
pcout << "Timestep " << timestep_no << " at time " << present_time
<< std::endl;
-
+
for (unsigned int cycle=0; cycle<2; ++cycle)
{
pcout << " Cycle " << cycle << ':' << std::endl;
pcout << std::endl;
}
-
- // @sect4{TopLevel::do_timestep}
- // Subsequent timesteps are simpler, and
- // probably do not require any more
- // documentation given the explanations for
- // the previous function above:
+ // @sect4{TopLevel::do_timestep}
+
+ // Subsequent timesteps are simpler, and
+ // probably do not require any more
+ // documentation given the explanations for
+ // the previous function above:
template <int dim>
void TopLevel<dim>::do_timestep ()
{
present_time = end_time;
}
-
+
solve_timestep ();
move_mesh ();
}
- // @sect4{TopLevel::refine_initial_grid}
-
+ // @sect4{TopLevel::refine_initial_grid}
+
// The following function is called when
// solving the first time step on
// successively refined meshes. After each
template <int dim>
void TopLevel<dim>::refine_initial_grid ()
{
- // First, let each process compute error
- // indicators for the cells it owns:
+ // First, let each process compute error
+ // indicators for the cells it owns:
Vector<float> error_per_cell (triangulation.n_active_cells());
KellyErrorEstimator<dim>::estimate (dof_handler,
QGauss<dim-1>(2),
multithread_info.n_default_threads,
this_mpi_process);
- // Then set up a global vector into which
- // we merge the local indicators from
- // each of the %parallel processes:
+ // Then set up a global vector into which
+ // we merge the local indicators from
+ // each of the %parallel processes:
const unsigned int n_local_cells
= GridTools::count_cells_with_subdomain_association (triangulation,
this_mpi_process);
PETScWrappers::MPI::Vector
distributed_error_per_cell (mpi_communicator,
- triangulation.n_active_cells(),
- n_local_cells);
-
+ triangulation.n_active_cells(),
+ n_local_cells);
+
for (unsigned int i=0; i<error_per_cell.size(); ++i)
if (error_per_cell(i) != 0)
distributed_error_per_cell(i) = error_per_cell(i);
distributed_error_per_cell.compress ();
- // Once we have that, copy it back into
- // local copies on all processors and
- // refine the mesh accordingly:
+ // Once we have that, copy it back into
+ // local copies on all processors and
+ // refine the mesh accordingly:
error_per_cell = distributed_error_per_cell;
GridRefinement::refine_and_coarsen_fixed_number (triangulation,
error_per_cell,
0.35, 0.03);
triangulation.execute_coarsening_and_refinement ();
- // Finally, set up quadrature
- // point data again on the new
- // mesh, and only on those cells
- // that we have determined to be
- // ours:
+ // Finally, set up quadrature
+ // point data again on the new
+ // mesh, and only on those cells
+ // that we have determined to be
+ // ours:
GridTools::partition_triangulation (n_mpi_processes, triangulation);
setup_quadrature_point_history ();
}
-
-
-
- // @sect4{TopLevel::move_mesh}
-
- // At the end of each time step, we move
- // the nodes of the mesh according to the
- // incremental displacements computed in
- // this time step. To do this, we keep a
- // vector of flags that indicate for each
- // vertex whether we have already moved it
- // around, and then loop over all cells and
- // move those vertices of the cell that
- // have not been moved yet. It is worth
- // noting that it does not matter from
- // which of the cells adjacent to a vertex
- // we move this vertex: since we compute
- // the displacement using a continuous
- // finite element, the displacement field
- // is continuous as well and we can compute
- // the displacement of a given vertex from
- // each of the adjacent cells. We only have
- // to make sure that we move each node
- // exactly once, which is why we keep the
- // vector of flags.
- //
- // There are two noteworthy things in this
- // function. First, how we get the
- // displacement field at a given vertex
- // using the
- // <code>cell-@>vertex_dof_index(v,d)</code> function
- // that returns the index of the <code>d</code>th
- // degree of freedom at vertex <code>v</code> of the
- // given cell. In the present case,
- // displacement in the k-th coordinate
- // direction corresonds to the kth
- // component of the finite element. Using a
- // function like this bears a certain risk,
- // because it uses knowledge of the order
- // of elements that we have taken together
- // for this program in the <code>FESystem</code>
- // element. If we decided to add an
- // additional variable, for example a
- // pressure variable for stabilization, and
- // happened to insert it as the first
- // variable of the element, then the
- // computation below will start to produce
- // non-sensical results. In addition, this
- // computation rests on other assumptions:
- // first, that the element we use has,
- // indeed, degrees of freedom that are
- // associated with vertices. This is indeed
- // the case for the present Q1 element, as
- // would be for all Qp elements of
- // polynomial order <code>p</code>. However, it
- // would not hold for discontinuous
- // elements, or elements for mixed
- // formulations. Secondly, it also rests on
- // the assumption that the displacement at
- // a vertex is determined solely by the
- // value of the degree of freedom
- // associated with this vertex; in other
- // words, all shape functions corresponding
- // to other degrees of freedom are zero at
- // this particular vertex. Again, this is
- // the case for the present element, but is
- // not so for all elements that are
- // presently available in deal.II. Despite
- // its risks, we choose to use this way in
- // order to present a way to query
- // individual degrees of freedom associated
- // with vertices.
- //
- // In this context, it is instructive to
- // point out what a more general way would
- // be. For general finite elements, the way
- // to go would be to take a quadrature
- // formula with the quadrature points in
- // the vertices of a cell. The <code>QTrapez</code>
- // formula for the trapezoidal rule does
- // exactly this. With this quadrature
- // formula, we would then initialize an
- // <code>FEValues</code> object in each cell, and
- // use the
- // <code>FEValues::get_function_values</code>
- // function to obtain the values of the
- // solution function in the quadrature
- // points, i.e. the vertices of the
- // cell. These are the only values that we
- // really need, i.e. we are not at all
- // interested in the weights (or the
- // <code>JxW</code> values) associated with this
- // particular quadrature formula, and this
- // can be specified as the last argument in
- // the constructor to <code>FEValues</code>. The
- // only point of minor inconvenience in
- // this scheme is that we have to figure
- // out which quadrature point corresponds
- // to the vertex we consider at present, as
- // they may or may not be ordered in the
- // same order.
- //
- // Another point worth explaining about
- // this short function is the way in which
- // the triangulation class exports
- // information about its vertices: through
- // the <code>Triangulation::n_vertices</code>
- // function, it advertises how many
- // vertices there are in the
- // triangulation. Not all of them are
- // actually in use all the time -- some are
- // left-overs from cells that have been
- // coarsened previously and remain in
- // existence since deal.II never changes
- // the number of a vertex once it has come
- // into existence, even if vertices with
- // lower number go away. Secondly, the
- // location returned by <code>cell-@>vertex(v)</code>
- // is not only a read-only object of type
- // <code>Point@<dim@></code>, but in fact a reference
- // that can be written to. This allows to
- // move around the nodes of a mesh with
- // relative ease, but it is worth pointing
- // out that it is the responsibility of an
- // application program using this feature
- // to make sure that the resulting cells
- // are still useful, i.e. are not distorted
- // so much that the cell is degenerated
- // (indicated, for example, by negative
- // Jacobians). Note that we do not have any
- // provisions in this function to actually
- // ensure this, we just have faith.
- //
- // After this lengthy introduction, here
- // are the full 20 or so lines of code:
+
+
+
+ // @sect4{TopLevel::move_mesh}
+
+ // At the end of each time step, we move
+ // the nodes of the mesh according to the
+ // incremental displacements computed in
+ // this time step. To do this, we keep a
+ // vector of flags that indicate for each
+ // vertex whether we have already moved it
+ // around, and then loop over all cells and
+ // move those vertices of the cell that
+ // have not been moved yet. It is worth
+ // noting that it does not matter from
+ // which of the cells adjacent to a vertex
+ // we move this vertex: since we compute
+ // the displacement using a continuous
+ // finite element, the displacement field
+ // is continuous as well and we can compute
+ // the displacement of a given vertex from
+ // each of the adjacent cells. We only have
+ // to make sure that we move each node
+ // exactly once, which is why we keep the
+ // vector of flags.
+ //
+ // There are two noteworthy things in this
+ // function. First, how we get the
+ // displacement field at a given vertex
+ // using the
+ // <code>cell-@>vertex_dof_index(v,d)</code> function
+ // that returns the index of the <code>d</code>th
+ // degree of freedom at vertex <code>v</code> of the
+ // given cell. In the present case,
+ // displacement in the k-th coordinate
+ // direction corresonds to the kth
+ // component of the finite element. Using a
+ // function like this bears a certain risk,
+ // because it uses knowledge of the order
+ // of elements that we have taken together
+ // for this program in the <code>FESystem</code>
+ // element. If we decided to add an
+ // additional variable, for example a
+ // pressure variable for stabilization, and
+ // happened to insert it as the first
+ // variable of the element, then the
+ // computation below will start to produce
+ // non-sensical results. In addition, this
+ // computation rests on other assumptions:
+ // first, that the element we use has,
+ // indeed, degrees of freedom that are
+ // associated with vertices. This is indeed
+ // the case for the present Q1 element, as
+ // would be for all Qp elements of
+ // polynomial order <code>p</code>. However, it
+ // would not hold for discontinuous
+ // elements, or elements for mixed
+ // formulations. Secondly, it also rests on
+ // the assumption that the displacement at
+ // a vertex is determined solely by the
+ // value of the degree of freedom
+ // associated with this vertex; in other
+ // words, all shape functions corresponding
+ // to other degrees of freedom are zero at
+ // this particular vertex. Again, this is
+ // the case for the present element, but is
+ // not so for all elements that are
+ // presently available in deal.II. Despite
+ // its risks, we choose to use this way in
+ // order to present a way to query
+ // individual degrees of freedom associated
+ // with vertices.
+ //
+ // In this context, it is instructive to
+ // point out what a more general way would
+ // be. For general finite elements, the way
+ // to go would be to take a quadrature
+ // formula with the quadrature points in
+ // the vertices of a cell. The <code>QTrapez</code>
+ // formula for the trapezoidal rule does
+ // exactly this. With this quadrature
+ // formula, we would then initialize an
+ // <code>FEValues</code> object in each cell, and
+ // use the
+ // <code>FEValues::get_function_values</code>
+ // function to obtain the values of the
+ // solution function in the quadrature
+ // points, i.e. the vertices of the
+ // cell. These are the only values that we
+ // really need, i.e. we are not at all
+ // interested in the weights (or the
+ // <code>JxW</code> values) associated with this
+ // particular quadrature formula, and this
+ // can be specified as the last argument in
+ // the constructor to <code>FEValues</code>. The
+ // only point of minor inconvenience in
+ // this scheme is that we have to figure
+ // out which quadrature point corresponds
+ // to the vertex we consider at present, as
+ // they may or may not be ordered in the
+ // same order.
+ //
+ // Another point worth explaining about
+ // this short function is the way in which
+ // the triangulation class exports
+ // information about its vertices: through
+ // the <code>Triangulation::n_vertices</code>
+ // function, it advertises how many
+ // vertices there are in the
+ // triangulation. Not all of them are
+ // actually in use all the time -- some are
+ // left-overs from cells that have been
+ // coarsened previously and remain in
+ // existence since deal.II never changes
+ // the number of a vertex once it has come
+ // into existence, even if vertices with
+ // lower number go away. Secondly, the
+ // location returned by <code>cell-@>vertex(v)</code>
+ // is not only a read-only object of type
+ // <code>Point@<dim@></code>, but in fact a reference
+ // that can be written to. This allows to
+ // move around the nodes of a mesh with
+ // relative ease, but it is worth pointing
+ // out that it is the responsibility of an
+ // application program using this feature
+ // to make sure that the resulting cells
+ // are still useful, i.e. are not distorted
+ // so much that the cell is degenerated
+ // (indicated, for example, by negative
+ // Jacobians). Note that we do not have any
+ // provisions in this function to actually
+ // ensure this, we just have faith.
+ //
+ // After this lengthy introduction, here
+ // are the full 20 or so lines of code:
template <int dim>
void TopLevel<dim>::move_mesh ()
{
if (vertex_touched[cell->vertex_index(v)] == false)
{
vertex_touched[cell->vertex_index(v)] = true;
-
+
Point<dim> vertex_displacement;
for (unsigned int d=0; d<dim; ++d)
vertex_displacement[d]
= incremental_displacement(cell->vertex_dof_index(v,d));
-
+
cell->vertex(v) += vertex_displacement;
}
}
- // @sect4{TopLevel::setup_quadrature_point_history}
-
- // At the beginning of our computations, we
- // needed to set up initial values of the
- // history variables, such as the existing
- // stresses in the material, that we store
- // in each quadrature point. As mentioned
- // above, we use the <code>user_pointer</code> for
- // this that is available in each cell.
- //
- // To put this into larger perspective, we
- // note that if we had previously available
- // stresses in our model (which we assume
- // do not exist for the purpose of this
- // program), then we would need to
- // interpolate the field of pre-existing
- // stresses to the quadrature
- // points. Likewise, if we were to simulate
- // elasto-plastic materials with
- // hardening/softening, then we would have
- // to store additional history variables
- // like the present yield stress of the
- // accumulated plastic strains in each
- // quadrature points. Pre-existing
- // hardening or weakening would then be
- // implemented by interpolating these
- // variables in the present function as
- // well.
+ // @sect4{TopLevel::setup_quadrature_point_history}
+
+ // At the beginning of our computations, we
+ // needed to set up initial values of the
+ // history variables, such as the existing
+ // stresses in the material, that we store
+ // in each quadrature point. As mentioned
+ // above, we use the <code>user_pointer</code> for
+ // this that is available in each cell.
+ //
+ // To put this into larger perspective, we
+ // note that if we had previously available
+ // stresses in our model (which we assume
+ // do not exist for the purpose of this
+ // program), then we would need to
+ // interpolate the field of pre-existing
+ // stresses to the quadrature
+ // points. Likewise, if we were to simulate
+ // elasto-plastic materials with
+ // hardening/softening, then we would have
+ // to store additional history variables
+ // like the present yield stress of the
+ // accumulated plastic strains in each
+ // quadrature points. Pre-existing
+ // hardening or weakening would then be
+ // implemented by interpolating these
+ // variables in the present function as
+ // well.
template <int dim>
void TopLevel<dim>::setup_quadrature_point_history ()
{
- // What we need to do here is to first
- // count how many quadrature points are
- // within the responsibility of this
- // processor. This, of course, equals the
- // number of cells that belong to this
- // processor times the number of
- // quadrature points our quadrature
- // formula has on each cell.
- //
- // For good measure, we also set all user
- // pointers of all cells, whether ours of
- // not, to the null pointer. This way, if
- // we ever access the user pointer of a
- // cell which we should not have
- // accessed, a segmentation fault will
- // let us know that this should not have
- // happened:
+ // What we need to do here is to first
+ // count how many quadrature points are
+ // within the responsibility of this
+ // processor. This, of course, equals the
+ // number of cells that belong to this
+ // processor times the number of
+ // quadrature points our quadrature
+ // formula has on each cell.
+ //
+ // For good measure, we also set all user
+ // pointers of all cells, whether ours of
+ // not, to the null pointer. This way, if
+ // we ever access the user pointer of a
+ // cell which we should not have
+ // accessed, a segmentation fault will
+ // let us know that this should not have
+ // happened:
unsigned int our_cells = 0;
for (typename Triangulation<dim>::active_cell_iterator
cell = triangulation.begin_active();
cell != triangulation.end(); ++cell)
if (cell->subdomain_id() == this_mpi_process)
++our_cells;
-
+
triangulation.clear_user_data();
// Next, allocate as many quadrature
history_index += quadrature_formula.size();
}
- // At the end, for good measure make sure
- // that our count of elements was correct
- // and that we have both used up all
- // objects we allocated previously, and
- // not point to any objects beyond the
- // end of the vector. Such defensive
- // programming strategies are always good
- // checks to avoid accidental errors and
- // to guard against future changes to
- // this function that forget to update
- // all uses of a variable at the same
- // time. Recall that constructs using the
- // <code>Assert</code> macro are optimized away in
- // optimized mode, so do not affect the
- // run time of optimized runs:
+ // At the end, for good measure make sure
+ // that our count of elements was correct
+ // and that we have both used up all
+ // objects we allocated previously, and
+ // not point to any objects beyond the
+ // end of the vector. Such defensive
+ // programming strategies are always good
+ // checks to avoid accidental errors and
+ // to guard against future changes to
+ // this function that forget to update
+ // all uses of a variable at the same
+ // time. Recall that constructs using the
+ // <code>Assert</code> macro are optimized away in
+ // optimized mode, so do not affect the
+ // run time of optimized runs:
Assert (history_index == quadrature_point_history.size(),
ExcInternalError());
}
- // @sect4{TopLevel::update_quadrature_point_history}
-
- // At the end of each time step, we
- // should have computed an
- // incremental displacement update
- // so that the material in its new
- // configuration accomodates for
- // the difference between the
- // external body and boundary
- // forces applied during this time
- // step minus the forces exerted
- // through pre-existing internal
- // stresses. In order to have the
- // pre-existing stresses available
- // at the next time step, we
- // therefore have to update the
- // pre-existing stresses with the
- // stresses due to the incremental
- // displacement computed during the
- // present time step. Ideally, the
- // resulting sum of internal
- // stresses would exactly counter
- // all external forces. Indeed, a
- // simple experiment can make sure
- // that this is so: if we choose
- // boundary conditions and body
- // forces to be time independent,
- // then the forcing terms (the sum
- // of external forces and internal
- // stresses) should be exactly
- // zero. If you make this
- // experiment, you will realize
- // from the output of the norm of
- // the right hand side in each time
- // step that this is almost the
- // case: it is not exactly zero,
- // since in the first time step the
- // incremental displacement and
- // stress updates were computed
- // relative to the undeformed mesh,
- // which was then deformed. In the
- // second time step, we again
- // compute displacement and stress
- // updates, but this time in the
- // deformed mesh -- there, the
- // resulting updates are very small
- // but not quite zero. This can be
- // iterated, and in each such
- // iteration the residual, i.e. the
- // norm of the right hand side
- // vector, is reduced; if one makes
- // this little experiment, one
- // realizes that the norm of this
- // residual decays exponentially
- // with the number of iterations,
- // and after an initial very rapid
- // decline is reduced by roughly a
- // factor of about 3.5 in each
- // iteration (for one testcase I
- // looked at, other testcases, and
- // other numbers of unknowns change
- // the factor, but not the
- // exponential decay).
-
- // In a sense, this can then be considered
- // as a quasi-timestepping scheme to
- // resolve the nonlinear problem of solving
- // large-deformation elasticity on a mesh
- // that is moved along in a Lagrangian
- // manner.
- //
- // Another complication is that the
- // existing (old) stresses are defined on
- // the old mesh, which we will move around
- // after updating the stresses. If this
- // mesh update involves rotations of the
- // cell, then we need to also rotate the
- // updated stress, since it was computed
- // relative to the coordinate system of the
- // old cell.
- //
- // Thus, what we need is the following: on
- // each cell which the present processor
- // owns, we need to extract the old stress
- // from the data stored with each
- // quadrature point, compute the stress
- // update, add the two together, and then
- // rotate the result together with the
- // incremental rotation computed from the
- // incremental displacement at the present
- // quadrature point. We will detail these
- // steps below:
+ // @sect4{TopLevel::update_quadrature_point_history}
+
+ // At the end of each time step, we
+ // should have computed an
+ // incremental displacement update
+ // so that the material in its new
+ // configuration accomodates for
+ // the difference between the
+ // external body and boundary
+ // forces applied during this time
+ // step minus the forces exerted
+ // through pre-existing internal
+ // stresses. In order to have the
+ // pre-existing stresses available
+ // at the next time step, we
+ // therefore have to update the
+ // pre-existing stresses with the
+ // stresses due to the incremental
+ // displacement computed during the
+ // present time step. Ideally, the
+ // resulting sum of internal
+ // stresses would exactly counter
+ // all external forces. Indeed, a
+ // simple experiment can make sure
+ // that this is so: if we choose
+ // boundary conditions and body
+ // forces to be time independent,
+ // then the forcing terms (the sum
+ // of external forces and internal
+ // stresses) should be exactly
+ // zero. If you make this
+ // experiment, you will realize
+ // from the output of the norm of
+ // the right hand side in each time
+ // step that this is almost the
+ // case: it is not exactly zero,
+ // since in the first time step the
+ // incremental displacement and
+ // stress updates were computed
+ // relative to the undeformed mesh,
+ // which was then deformed. In the
+ // second time step, we again
+ // compute displacement and stress
+ // updates, but this time in the
+ // deformed mesh -- there, the
+ // resulting updates are very small
+ // but not quite zero. This can be
+ // iterated, and in each such
+ // iteration the residual, i.e. the
+ // norm of the right hand side
+ // vector, is reduced; if one makes
+ // this little experiment, one
+ // realizes that the norm of this
+ // residual decays exponentially
+ // with the number of iterations,
+ // and after an initial very rapid
+ // decline is reduced by roughly a
+ // factor of about 3.5 in each
+ // iteration (for one testcase I
+ // looked at, other testcases, and
+ // other numbers of unknowns change
+ // the factor, but not the
+ // exponential decay).
+
+ // In a sense, this can then be considered
+ // as a quasi-timestepping scheme to
+ // resolve the nonlinear problem of solving
+ // large-deformation elasticity on a mesh
+ // that is moved along in a Lagrangian
+ // manner.
+ //
+ // Another complication is that the
+ // existing (old) stresses are defined on
+ // the old mesh, which we will move around
+ // after updating the stresses. If this
+ // mesh update involves rotations of the
+ // cell, then we need to also rotate the
+ // updated stress, since it was computed
+ // relative to the coordinate system of the
+ // old cell.
+ //
+ // Thus, what we need is the following: on
+ // each cell which the present processor
+ // owns, we need to extract the old stress
+ // from the data stored with each
+ // quadrature point, compute the stress
+ // update, add the two together, and then
+ // rotate the result together with the
+ // incremental rotation computed from the
+ // incremental displacement at the present
+ // quadrature point. We will detail these
+ // steps below:
template <int dim>
void TopLevel<dim>::update_quadrature_point_history ()
{
- // First, set up an <code>FEValues</code> object
- // by which we will evaluate the
- // incremental displacements and the
- // gradients thereof at the quadrature
- // points, together with a vector that
- // will hold this information:
- FEValues<dim> fe_values (fe, quadrature_formula,
+ // First, set up an <code>FEValues</code> object
+ // by which we will evaluate the
+ // incremental displacements and the
+ // gradients thereof at the quadrature
+ // points, together with a vector that
+ // will hold this information:
+ FEValues<dim> fe_values (fe, quadrature_formula,
update_values | update_gradients);
std::vector<std::vector<Tensor<1,dim> > >
displacement_increment_grads (quadrature_formula.size(),
std::vector<Tensor<1,dim> >(dim));
-
+
// Then loop over all cells and do the
// job in the cells that belong to our
// subdomain:
cell != dof_handler.end(); ++cell)
if (cell->subdomain_id() == this_mpi_process)
{
- // Next, get a pointer to the
- // quadrature point history data
- // local to the present cell, and,
- // as a defensive measure, make
- // sure that this pointer is within
- // the bounds of the global array:
+ // Next, get a pointer to the
+ // quadrature point history data
+ // local to the present cell, and,
+ // as a defensive measure, make
+ // sure that this pointer is within
+ // the bounds of the global array:
PointHistory<dim> *local_quadrature_points_history
= reinterpret_cast<PointHistory<dim> *>(cell->user_pointer());
Assert (local_quadrature_points_history >=
- &quadrature_point_history.front(),
+ &quadrature_point_history.front(),
ExcInternalError());
Assert (local_quadrature_points_history <
- &quadrature_point_history.back(),
+ &quadrature_point_history.back(),
ExcInternalError());
- // Then initialize the <code>FEValues</code>
- // object on the present cell, and
- // extract the gradients of the
- // displacement at the quadrature
- // points for later computation of
- // the strains
+ // Then initialize the <code>FEValues</code>
+ // object on the present cell, and
+ // extract the gradients of the
+ // displacement at the quadrature
+ // points for later computation of
+ // the strains
fe_values.reinit (cell);
fe_values.get_function_grads (incremental_displacement,
displacement_increment_grads);
// points of this cell:
for (unsigned int q=0; q<quadrature_formula.size(); ++q)
{
- // On each quadrature point,
- // compute the strain increment
- // from the gradients, and
- // multiply it by the
- // stress-strain tensor to get
- // the stress update. Then add
- // this update to the already
- // existing strain at this
- // point:
- const SymmetricTensor<2,dim> new_stress
- = (local_quadrature_points_history[q].old_stress
- +
- (stress_strain_tensor *
- get_strain (displacement_increment_grads[q])));
-
- // Finally, we have to rotate
- // the result. For this, we
- // first have to compute a
- // rotation matrix at the
- // present quadrature point
- // from the incremental
- // displacements. In fact, it
- // can be computed from the
- // gradients, and we already
- // have a function for that
- // purpose:
- const Tensor<2,dim> rotation
- = get_rotation_matrix (displacement_increment_grads[q]);
- // Note that the result, a
- // rotation matrix, is in
- // general an antisymmetric
- // tensor of rank 2, so we must
- // store it as a full tensor.
-
- // With this rotation matrix,
- // we can compute the rotated
- // tensor by contraction from
- // the left and right, after we
- // expand the symmetric tensor
- // <code>new_stress</code> into a full
- // tensor:
- const SymmetricTensor<2,dim> rotated_new_stress
- = symmetrize(transpose(rotation) *
+ // On each quadrature point,
+ // compute the strain increment
+ // from the gradients, and
+ // multiply it by the
+ // stress-strain tensor to get
+ // the stress update. Then add
+ // this update to the already
+ // existing strain at this
+ // point:
+ const SymmetricTensor<2,dim> new_stress
+ = (local_quadrature_points_history[q].old_stress
+ +
+ (stress_strain_tensor *
+ get_strain (displacement_increment_grads[q])));
+
+ // Finally, we have to rotate
+ // the result. For this, we
+ // first have to compute a
+ // rotation matrix at the
+ // present quadrature point
+ // from the incremental
+ // displacements. In fact, it
+ // can be computed from the
+ // gradients, and we already
+ // have a function for that
+ // purpose:
+ const Tensor<2,dim> rotation
+ = get_rotation_matrix (displacement_increment_grads[q]);
+ // Note that the result, a
+ // rotation matrix, is in
+ // general an antisymmetric
+ // tensor of rank 2, so we must
+ // store it as a full tensor.
+
+ // With this rotation matrix,
+ // we can compute the rotated
+ // tensor by contraction from
+ // the left and right, after we
+ // expand the symmetric tensor
+ // <code>new_stress</code> into a full
+ // tensor:
+ const SymmetricTensor<2,dim> rotated_new_stress
+ = symmetrize(transpose(rotation) *
static_cast<Tensor<2,dim> >(new_stress) *
rotation);
// Note that while the
// result to make it
// exactly symmetric.
- // The result of all these
- // operations is then written
- // back into the original
- // place:
- local_quadrature_points_history[q].old_stress
- = rotated_new_stress;
+ // The result of all these
+ // operations is then written
+ // back into the original
+ // place:
+ local_quadrature_points_history[q].old_stress
+ = rotated_new_stress;
}
}
}
// This ends the project specific
// namespace
- // <code>QuasiStaticElasticity</code>. The
+ // <code>Step18</code>. The
// rest is as usual and as already
// shown in step-17: A <code>main()</code>
// function that initializes and
}
-int main (int argc, char **argv)
+int main (int argc, char **argv)
{
try
{
+ using namespace dealii;
+ using namespace Step18;
+
PetscInitialize(&argc,&argv,0,0);
{
deallog.depth_console (0);
- QuasiStaticElasticity::TopLevel<3> elastic_problem;
+ TopLevel<3> elastic_problem;
elastic_problem.run ();
}
- PetscFinalize();
+ PetscFinalize();
}
catch (std::exception &exc)
{
<< "Aborting!" << std::endl
<< "----------------------------------------------------"
<< std::endl;
-
+
return 1;
}
- catch (...)
+ catch (...)
{
std::cerr << std::endl << std::endl
<< "----------------------------------------------------"
/* $Id$ */
/* */
-/* Copyright (C) 2005, 2006 by the deal.II authors */
+/* Copyright (C) 2005, 2006, 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 <iostream>
#include <fstream>
- // As mentioned in the first few
- // tutorial programs, all names in
- // deal.II are declared in a
- // namespace <code>dealii</code>. To
- // make using these function and
- // class names simpler, we import the
- // entire content of that namespace
- // into the global scope:
-using namespace dealii;
-
- // Before we start with the actual program,
- // let us declare a few global variables that
- // will be used to hold the parameters this
- // program is going to use. Usually, global
- // variables are frowned upon for a good
- // reason, but since we have such a short
- // program here that does only a single
- // thing, we may stray from our usual line
- // and make these variables global, rather
- // than passing them around to all functions
- // or encapsulating them into a class.
- //
- // The variables we have are: first, an
- // object that will hold parameters of
- // operation, such as output format (unless
- // given on the command line); second, the
- // names of input and output files; and third,
- // the format in which the output is to be
- // written:
-ParameterHandler prm;
-std::vector<std::string> input_file_names;
-std::string output_file;
-std::string output_format;
-
-
- // All the stuff this program does can be
- // done from here on. As described in the
- // introduction, what we have to do is
- // declare what values the parameter file can
- // have, parse the command line, read the
- // input files, then write the output. We
- // will do this in this order of operation,
- // but before that let us declare a function
- // that prints a message about how this
- // program is to be used; the function first
- // prints a general message, and then goes on
- // to list the parameters that are allowed in
- // the parameter file (the
- // <code>ParameterHandler</code> class has a function
- // to do exactly this; see the results
- // section for what it prints):
-void
-print_usage_message ()
+ // As mentioned in the first few tutorial
+ // programs, all names in deal.II are
+ // declared in a namespace
+ // <code>dealii</code>. To make using these
+ // function and class names simpler, we
+ // import the entire content of that
+ // namespace into the global scope. As done
+ // for all previous programs already, we'll
+ // also place everything we do here into a
+ // namespace of its own:
+namespace Step19
{
- static const char* message
- =
- "\n"
- "Converter from deal.II intermediate format to other graphics formats.\n"
- "\n"
- "Usage:\n"
- " ./step-19 [-p parameter_file] list_of_input_files \n"
- " [-x output_format] [-o output_file]\n"
- "\n"
- "Parameter sequences in brackets can be omitted if a parameter file is\n"
- "specified on the command line and if it provides values for these\n"
- "missing parameters.\n"
- "\n"
- "The parameter file has the following format and allows the following\n"
- "values (you can cut and paste this and use it for your own parameter\n"
- "file):\n"
- "\n";
- std::cout << message;
-
- prm.print_parameters (std::cout, ParameterHandler::Text);
-}
-
-
- // @sect4{Declaring parameters for the input file}
-
- // The second function is used to declare the
- // parameters this program accepts from the
- // input file. While we don't actually take
- // many parameters from the input file except
- // for, possibly, the output file name and
- // format, we nevertheless want to show how
- // to work with parameter files.
- //
- // In short, the <code>ParameterHandler</code> class
- // works as follows: one declares the entries
- // of parameters that can be given in input
- // files together, and later on one can read
- // an input file in which these parameters
- // are set to their values. If a parameter is
- // not listed in the input file, the default
- // value specified in the declaration of that
- // parameter is used. After that, the program
- // can query the values assigned to certain
- // parameters from the <code>ParameterHandler</code>
- // object.
- //
- // Declaring parameters can be done using the
- // <code>ParameterHandler::declare_entry</code>
- // function. It's arguments are the name of a
- // parameter, a default value (given as a
- // string, even if the parameter is numeric
- // in nature, and thirdly an object that
- // describes constraints on values that may
- // be passed to this parameter. In the
- // example below, we use an object of type
- // <code>Patterns::Anything</code> to denote that
- // there are no constraints on file names
- // (this is, of course, not true -- the
- // operating system does have constraints,
- // but from an application standpoint, almost
- // all names are valid). In other cases, one
- // may, for example, use
- // <code>Patterns::Integer</code> to make sure that
- // only parameters are accepted that can be
- // interpreted as integer values (it is also
- // possible to specify bounds for integer
- // values, and all values outside this range
- // are rejected), <code>Patterns::Double</code> for
- // floating point values, classes that make
- // sure that the given parameter value is a
- // comma separated list of things, etc. Take
- // a look at the <code>Patterns</code> namespace to
- // see what is possible.
- //
- // The fourth argument to <code>declare_entry</code>
- // is a help string that can be printed to
- // document what this parameter is meant to
- // be used for and other information you may
- // consider important when declaring this
- // parameter. The default value of this
- // fourth argument is the empty string.
- //
- // I always wanted to have an example program
- // describing the <code>ParameterHandler</code> class,
- // because it is so particularly useful. It
- // would have been useful in a number of
- // previous example programs (for example, in
- // order to let the tolerance for linear
- // solvers, or the number of refinement steps
- // be determined by a run-time parameter,
- // rather than hard-coding them into the
- // program), but it turned out that trying to
- // explain this class there would have
- // overloaded them with things that would
- // have distracted from the main
- // purpose. However, while writing this
- // program, I realized that there aren't all
- // that many parameters this program can
- // usefully ask for, or better, it turned
- // out: declaring and querying these
- // parameters was already done centralized in
- // one place of the libray, namely the
- // <code>DataOutInterface</code> class that handles
- // exactly this -- managing parameters for
- // input and output.
- //
- // So the second function call in this
- // function is to let the
- // <code>DataOutInterface</code> declare a good number
- // of parameters that control everything from
- // the output format to what kind of output
- // should be generated if output is written
- // in a specific graphical format. For
- // example, when writing data in encapsulated
- // postscript (EPS) format, the result is
- // just a 2d projection, not data that can be
- // viewed and rotated with a
- // viewer. Therefore, one has to choose the
- // viewing angle and a number of other
- // options up front, when output is
- // generated, rather than playing around with
- // them later on. The call to
- // <code>DataOutInterface::declare_parameters</code>
- // declares entries that allow to specify
- // them in the parameter input file during
- // run-time. If the parameter file does not
- // contain entries for them, defaults are
- // taken.
- //
- // As a final note: <code>DataOutInterface</code> is a
- // template, because it is usually used to
- // write output for a specific space
- // dimension. However, this program is
- // supposed to be used for all dimensions at
- // the same time, so we don't know at compile
- // time what the right dimension is when
- // specifying the template
- // parameter. Fortunately, declaring
- // parameters is something that is space
- // dimension independent, so we can just pick
- // one arbitrarily. We pick <code>1</code>, but it
- // could have been any other number as well.
-void declare_parameters ()
-{
- prm.declare_entry ("Output file", "",
- Patterns::Anything(),
- "The name of the output file to be generated");
-
- DataOutInterface<1>::declare_parameters (prm);
-
- // Since everything that this program can
- // usefully request in terms of input
- // parameters is already handled by now,
- // let us nevertheless show how to use
- // input parameters in other
- // circumstances. First, parameters are
- // like files in a directory tree: they can
- // be in the top-level directory, but you
- // can also group them into subdirectories
- // to make it easier to find them or to be
- // able to use the same parameter name in
- // different contexts.
- //
- // Let us first declare a dummy parameter
- // in the top-level section; we assume that
- // it will denote the number of iterations,
- // and that useful numbers of iterations
- // that a user should be able to specify
- // are in the range 1...1000, with a
- // default value of 42:
- prm.declare_entry ("Dummy iterations", "42",
- Patterns::Integer (1,1000),
- "A dummy parameter asking for an integer");
-
- // Next, let us declare a sub-section (the
- // equivalent to a subdirectory). When
- // entered, all following parameter
- // declarations will be within this
- // subsection. To also visually group these
- // declarations with the subsection name, I
- // like to use curly braces to force my
- // editor to indent everything that goes
- // into this sub-section by one level of
- // indentation. In this sub-section, we
- // shall have two entries, one that takes a
- // boolean parameter and one that takes a
- // selection list of values, separated by
- // the '|' character:
- prm.enter_subsection ("Dummy subsection");
+ using namespace dealii;
+
+ // Before we start with the actual program,
+ // let us declare a few global variables that
+ // will be used to hold the parameters this
+ // program is going to use. Usually, global
+ // variables are frowned upon for a good
+ // reason, but since we have such a short
+ // program here that does only a single
+ // thing, we may stray from our usual line
+ // and make these variables global, rather
+ // than passing them around to all functions
+ // or encapsulating them into a class.
+ //
+ // The variables we have are: first, an
+ // object that will hold parameters of
+ // operation, such as output format (unless
+ // given on the command line); second, the
+ // names of input and output files; and third,
+ // the format in which the output is to be
+ // written:
+ ParameterHandler prm;
+ std::vector<std::string> input_file_names;
+ std::string output_file;
+ std::string output_format;
+
+
+ // All the stuff this program does can be
+ // done from here on. As described in the
+ // introduction, what we have to do is
+ // declare what values the parameter file can
+ // have, parse the command line, read the
+ // input files, then write the output. We
+ // will do this in this order of operation,
+ // but before that let us declare a function
+ // that prints a message about how this
+ // program is to be used; the function first
+ // prints a general message, and then goes on
+ // to list the parameters that are allowed in
+ // the parameter file (the
+ // <code>ParameterHandler</code> class has a function
+ // to do exactly this; see the results
+ // section for what it prints):
+ void
+ print_usage_message ()
{
- prm.declare_entry ("Dummy generate output", "true",
- Patterns::Bool(),
- "A dummy parameter that can be fed with either "
- "'true' or 'false'");
- prm.declare_entry ("Dummy color of output", "red",
- Patterns::Selection("red|black|blue"),
- "A dummy parameter that shows how one can define a "
- "parameter that can be assigned values from a finite "
- "set of values");
+ static const char* message
+ =
+ "\n"
+ "Converter from deal.II intermediate format to other graphics formats.\n"
+ "\n"
+ "Usage:\n"
+ " ./step-19 [-p parameter_file] list_of_input_files \n"
+ " [-x output_format] [-o output_file]\n"
+ "\n"
+ "Parameter sequences in brackets can be omitted if a parameter file is\n"
+ "specified on the command line and if it provides values for these\n"
+ "missing parameters.\n"
+ "\n"
+ "The parameter file has the following format and allows the following\n"
+ "values (you can cut and paste this and use it for your own parameter\n"
+ "file):\n"
+ "\n";
+ std::cout << message;
+
+ prm.print_parameters (std::cout, ParameterHandler::Text);
}
- prm.leave_subsection ();
- // After this, we have left the subsection
- // again. You should have gotten the idea
- // by now how one can nest subsections to
- // separate parameters. There are a number
- // of other possible patterns describing
- // possible values of parameters; in all
- // cases, if you try to pass a parameter to
- // the program that does not match the
- // expectations of the pattern, it will
- // reject the parameter file and ask you to
- // fix it. After all, it does not make much
- // sense if you had an entry that contained
- // the entry "red" for the parameter
- // "Generate output".
-}
-
- // @sect4{Parsing the command line}
-
- // Our next task is to see what information
- // has been provided on the command
- // line. First, we need to be sure that there
- // is at least one parameter: an input
- // file. The format and the output file can
- // be specified in the parameter file, but
- // the list of input files can't, so at least
- // one parameter needs to be there. Together
- // with the name of the program (the zeroth
- // parameter), <code>argc</code> must therefore be at
- // least 2. If this is not the case, we print
- // an error message and exit:
-void
-parse_command_line (const int argc,
- char *const * argv)
-{
- if (argc < 2)
- {
- print_usage_message ();
- exit (1);
- }
- // Next, collect all parameters in a list
- // that will be somewhat simpler to handle
- // than the <code>argc</code>/<code>argv</code> mechanism. We
- // omit the name of the executable at the
- // zeroth index:
- std::list<std::string> args;
- for (int i=1; i<argc; ++i)
- args.push_back (argv[i]);
-
- // Then process all these
- // parameters. If the parameter is
- // <code>-p</code>, then there must be a
- // parameter file following (which
- // we should then read), in case of
- // <code>-x</code> it is the name of an
- // output format. Finally, for
- // <code>-o</code> it is the name of the
- // output file. In all cases, once
- // we've treated a parameter, we
- // remove it from the list of
- // parameters:
- while (args.size())
+ // @sect4{Declaring parameters for the input file}
+
+ // The second function is used to declare the
+ // parameters this program accepts from the
+ // input file. While we don't actually take
+ // many parameters from the input file except
+ // for, possibly, the output file name and
+ // format, we nevertheless want to show how
+ // to work with parameter files.
+ //
+ // In short, the <code>ParameterHandler</code> class
+ // works as follows: one declares the entries
+ // of parameters that can be given in input
+ // files together, and later on one can read
+ // an input file in which these parameters
+ // are set to their values. If a parameter is
+ // not listed in the input file, the default
+ // value specified in the declaration of that
+ // parameter is used. After that, the program
+ // can query the values assigned to certain
+ // parameters from the <code>ParameterHandler</code>
+ // object.
+ //
+ // Declaring parameters can be done using the
+ // <code>ParameterHandler::declare_entry</code>
+ // function. It's arguments are the name of a
+ // parameter, a default value (given as a
+ // string, even if the parameter is numeric
+ // in nature, and thirdly an object that
+ // describes constraints on values that may
+ // be passed to this parameter. In the
+ // example below, we use an object of type
+ // <code>Patterns::Anything</code> to denote that
+ // there are no constraints on file names
+ // (this is, of course, not true -- the
+ // operating system does have constraints,
+ // but from an application standpoint, almost
+ // all names are valid). In other cases, one
+ // may, for example, use
+ // <code>Patterns::Integer</code> to make sure that
+ // only parameters are accepted that can be
+ // interpreted as integer values (it is also
+ // possible to specify bounds for integer
+ // values, and all values outside this range
+ // are rejected), <code>Patterns::Double</code> for
+ // floating point values, classes that make
+ // sure that the given parameter value is a
+ // comma separated list of things, etc. Take
+ // a look at the <code>Patterns</code> namespace to
+ // see what is possible.
+ //
+ // The fourth argument to <code>declare_entry</code>
+ // is a help string that can be printed to
+ // document what this parameter is meant to
+ // be used for and other information you may
+ // consider important when declaring this
+ // parameter. The default value of this
+ // fourth argument is the empty string.
+ //
+ // I always wanted to have an example program
+ // describing the <code>ParameterHandler</code> class,
+ // because it is so particularly useful. It
+ // would have been useful in a number of
+ // previous example programs (for example, in
+ // order to let the tolerance for linear
+ // solvers, or the number of refinement steps
+ // be determined by a run-time parameter,
+ // rather than hard-coding them into the
+ // program), but it turned out that trying to
+ // explain this class there would have
+ // overloaded them with things that would
+ // have distracted from the main
+ // purpose. However, while writing this
+ // program, I realized that there aren't all
+ // that many parameters this program can
+ // usefully ask for, or better, it turned
+ // out: declaring and querying these
+ // parameters was already done centralized in
+ // one place of the libray, namely the
+ // <code>DataOutInterface</code> class that handles
+ // exactly this -- managing parameters for
+ // input and output.
+ //
+ // So the second function call in this
+ // function is to let the
+ // <code>DataOutInterface</code> declare a good number
+ // of parameters that control everything from
+ // the output format to what kind of output
+ // should be generated if output is written
+ // in a specific graphical format. For
+ // example, when writing data in encapsulated
+ // postscript (EPS) format, the result is
+ // just a 2d projection, not data that can be
+ // viewed and rotated with a
+ // viewer. Therefore, one has to choose the
+ // viewing angle and a number of other
+ // options up front, when output is
+ // generated, rather than playing around with
+ // them later on. The call to
+ // <code>DataOutInterface::declare_parameters</code>
+ // declares entries that allow to specify
+ // them in the parameter input file during
+ // run-time. If the parameter file does not
+ // contain entries for them, defaults are
+ // taken.
+ //
+ // As a final note: <code>DataOutInterface</code> is a
+ // template, because it is usually used to
+ // write output for a specific space
+ // dimension. However, this program is
+ // supposed to be used for all dimensions at
+ // the same time, so we don't know at compile
+ // time what the right dimension is when
+ // specifying the template
+ // parameter. Fortunately, declaring
+ // parameters is something that is space
+ // dimension independent, so we can just pick
+ // one arbitrarily. We pick <code>1</code>, but it
+ // could have been any other number as well.
+ void declare_parameters ()
+ {
+ prm.declare_entry ("Output file", "",
+ Patterns::Anything(),
+ "The name of the output file to be generated");
+
+ DataOutInterface<1>::declare_parameters (prm);
+
+ // Since everything that this program can
+ // usefully request in terms of input
+ // parameters is already handled by now,
+ // let us nevertheless show how to use
+ // input parameters in other
+ // circumstances. First, parameters are
+ // like files in a directory tree: they can
+ // be in the top-level directory, but you
+ // can also group them into subdirectories
+ // to make it easier to find them or to be
+ // able to use the same parameter name in
+ // different contexts.
+ //
+ // Let us first declare a dummy parameter
+ // in the top-level section; we assume that
+ // it will denote the number of iterations,
+ // and that useful numbers of iterations
+ // that a user should be able to specify
+ // are in the range 1...1000, with a
+ // default value of 42:
+ prm.declare_entry ("Dummy iterations", "42",
+ Patterns::Integer (1,1000),
+ "A dummy parameter asking for an integer");
+
+ // Next, let us declare a sub-section (the
+ // equivalent to a subdirectory). When
+ // entered, all following parameter
+ // declarations will be within this
+ // subsection. To also visually group these
+ // declarations with the subsection name, I
+ // like to use curly braces to force my
+ // editor to indent everything that goes
+ // into this sub-section by one level of
+ // indentation. In this sub-section, we
+ // shall have two entries, one that takes a
+ // boolean parameter and one that takes a
+ // selection list of values, separated by
+ // the '|' character:
+ prm.enter_subsection ("Dummy subsection");
{
- if (args.front() == std::string("-p"))
- {
- if (args.size() == 1)
- {
- std::cerr << "Error: flag '-p' must be followed by the "
- << "name of a parameter file."
- << std::endl;
- print_usage_message ();
- exit (1);
- }
- args.pop_front ();
- const std::string parameter_file = args.front ();
- args.pop_front ();
-
- // Now read the input file:
- prm.read_input (parameter_file);
-
- // Both the output file name as
- // well as the format can be
- // specified on the command
- // line. We have therefore given
- // them global variables that hold
- // their values, but they can also
- // be set in the parameter file. We
- // therefore need to extract them
- // from the parameter file here,
- // because they may be overridden
- // by later command line
- // parameters:
- if (output_file == "")
- output_file = prm.get ("Output file");
-
- if (output_format == "")
- output_format = prm.get ("Output format");
-
- // Finally, let us note that if we
- // were interested in the values of
- // the parameters declared above in
- // the dummy subsection, we would
- // write something like this to
- // extract the value of the boolean
- // flag (the <code>prm.get</code> function
- // returns the value of a parameter
- // as a string, whereas the
- // <code>prm.get_X</code> functions return a
- // value already converted to a
- // different type):
- prm.enter_subsection ("Dummy subsection");
- {
- prm.get_bool ("Dummy generate output");
- }
- prm.leave_subsection ();
- // We would assign the result to a
- // variable, or course, but don't
- // here in order not to generate an
- // unused variable that the
- // compiler might warn about.
- //
- // Alas, let's move on to handling
- // of output formats:
- }
- else if (args.front() == std::string("-x"))
- {
- if (args.size() == 1)
- {
- std::cerr << "Error: flag '-x' must be followed by the "
- << "name of an output format."
- << std::endl;
- print_usage_message ();
- exit (1);
- }
- args.pop_front ();
- output_format = args.front();
- args.pop_front ();
- }
- else if (args.front() == std::string("-o"))
- {
- if (args.size() == 1)
- {
- std::cerr << "Error: flag '-o' must be followed by the "
- << "name of an output file."
- << std::endl;
- print_usage_message ();
- exit (1);
- }
- args.pop_front ();
- output_file = args.front();
- args.pop_front ();
- }
-
- // Otherwise, this is not a parameter
- // that starts with a known minus
- // sequence, and we should consider it
- // to be the name of an input file. Let
- // us therefore add this file to the
- // list of input files:
- else
- {
- input_file_names.push_back (args.front());
- args.pop_front ();
- }
+ prm.declare_entry ("Dummy generate output", "true",
+ Patterns::Bool(),
+ "A dummy parameter that can be fed with either "
+ "'true' or 'false'");
+ prm.declare_entry ("Dummy color of output", "red",
+ Patterns::Selection("red|black|blue"),
+ "A dummy parameter that shows how one can define a "
+ "parameter that can be assigned values from a finite "
+ "set of values");
}
+ prm.leave_subsection ();
+ // After this, we have left the subsection
+ // again. You should have gotten the idea
+ // by now how one can nest subsections to
+ // separate parameters. There are a number
+ // of other possible patterns describing
+ // possible values of parameters; in all
+ // cases, if you try to pass a parameter to
+ // the program that does not match the
+ // expectations of the pattern, it will
+ // reject the parameter file and ask you to
+ // fix it. After all, it does not make much
+ // sense if you had an entry that contained
+ // the entry "red" for the parameter
+ // "Generate output".
+ }
- // Next check a few things and create
- // errors if the checks fail. Firstly,
- // there must be at least one input file
- if (input_file_names.size() == 0)
- {
- std::cerr << "Error: No input file specified." << std::endl;
- print_usage_message ();
- exit (1);
- }
-}
+ // @sect4{Parsing the command line}
+
+ // Our next task is to see what information
+ // has been provided on the command
+ // line. First, we need to be sure that there
+ // is at least one parameter: an input
+ // file. The format and the output file can
+ // be specified in the parameter file, but
+ // the list of input files can't, so at least
+ // one parameter needs to be there. Together
+ // with the name of the program (the zeroth
+ // parameter), <code>argc</code> must therefore be at
+ // least 2. If this is not the case, we print
+ // an error message and exit:
+ void
+ parse_command_line (const int argc,
+ char *const * argv)
+ {
+ if (argc < 2)
+ {
+ print_usage_message ();
+ exit (1);
+ }
+
+ // Next, collect all parameters in a list
+ // that will be somewhat simpler to handle
+ // than the <code>argc</code>/<code>argv</code> mechanism. We
+ // omit the name of the executable at the
+ // zeroth index:
+ std::list<std::string> args;
+ for (int i=1; i<argc; ++i)
+ args.push_back (argv[i]);
+
+ // Then process all these
+ // parameters. If the parameter is
+ // <code>-p</code>, then there must be a
+ // parameter file following (which
+ // we should then read), in case of
+ // <code>-x</code> it is the name of an
+ // output format. Finally, for
+ // <code>-o</code> it is the name of the
+ // output file. In all cases, once
+ // we've treated a parameter, we
+ // remove it from the list of
+ // parameters:
+ while (args.size())
+ {
+ if (args.front() == std::string("-p"))
+ {
+ if (args.size() == 1)
+ {
+ std::cerr << "Error: flag '-p' must be followed by the "
+ << "name of a parameter file."
+ << std::endl;
+ print_usage_message ();
+ exit (1);
+ }
+ args.pop_front ();
+ const std::string parameter_file = args.front ();
+ args.pop_front ();
+
+ // Now read the input file:
+ prm.read_input (parameter_file);
+
+ // Both the output file name as
+ // well as the format can be
+ // specified on the command
+ // line. We have therefore given
+ // them global variables that hold
+ // their values, but they can also
+ // be set in the parameter file. We
+ // therefore need to extract them
+ // from the parameter file here,
+ // because they may be overridden
+ // by later command line
+ // parameters:
+ if (output_file == "")
+ output_file = prm.get ("Output file");
+
+ if (output_format == "")
+ output_format = prm.get ("Output format");
+
+ // Finally, let us note that if we
+ // were interested in the values of
+ // the parameters declared above in
+ // the dummy subsection, we would
+ // write something like this to
+ // extract the value of the boolean
+ // flag (the <code>prm.get</code> function
+ // returns the value of a parameter
+ // as a string, whereas the
+ // <code>prm.get_X</code> functions return a
+ // value already converted to a
+ // different type):
+ prm.enter_subsection ("Dummy subsection");
+ {
+ prm.get_bool ("Dummy generate output");
+ }
+ prm.leave_subsection ();
+ // We would assign the result to a
+ // variable, or course, but don't
+ // here in order not to generate an
+ // unused variable that the
+ // compiler might warn about.
+ //
+ // Alas, let's move on to handling
+ // of output formats:
+ }
+ else if (args.front() == std::string("-x"))
+ {
+ if (args.size() == 1)
+ {
+ std::cerr << "Error: flag '-x' must be followed by the "
+ << "name of an output format."
+ << std::endl;
+ print_usage_message ();
+ exit (1);
+ }
+ args.pop_front ();
+ output_format = args.front();
+ args.pop_front ();
+ }
+ else if (args.front() == std::string("-o"))
+ {
+ if (args.size() == 1)
+ {
+ std::cerr << "Error: flag '-o' must be followed by the "
+ << "name of an output file."
+ << std::endl;
+ print_usage_message ();
+ exit (1);
+ }
+ args.pop_front ();
+ output_file = args.front();
+ args.pop_front ();
+ }
+
+ // Otherwise, this is not a parameter
+ // that starts with a known minus
+ // sequence, and we should consider it
+ // to be the name of an input file. Let
+ // us therefore add this file to the
+ // list of input files:
+ else
+ {
+ input_file_names.push_back (args.front());
+ args.pop_front ();
+ }
+ }
+
+ // Next check a few things and create
+ // errors if the checks fail. Firstly,
+ // there must be at least one input file
+ if (input_file_names.size() == 0)
+ {
+ std::cerr << "Error: No input file specified." << std::endl;
+ print_usage_message ();
+ exit (1);
+ }
+ }
- // @sect4{Generating output}
-
- // Now that we have all the information, we
- // need to read all the input files, merge
- // them, and generate a single output
- // file. This, after all, was the motivation,
- // borne from the necessity encountered in
- // the step-18 tutorial program, to write
- // this program in the first place.
- //
- // So what we do first is to declare an
- // object into which we will merge the data
- // from all the input file, and read in the
- // first file through a stream. Note that
- // every time we open a file, we use the
- // <code>AssertThrow</code> macro to check whether the
- // file is really readable -- if it isn't
- // then this will trigger an exception and
- // corresponding output will be generated
- // from the exception handler in <code>main()</code>:
-template <int dim, int spacedim>
-void do_convert ()
-{
- DataOutReader<dim,spacedim> merged_data;
- {
- std::ifstream input (input_file_names[0].c_str());
- AssertThrow (input, ExcIO());
-
- merged_data.read (input);
- }
+ // @sect4{Generating output}
+
+ // Now that we have all the information, we
+ // need to read all the input files, merge
+ // them, and generate a single output
+ // file. This, after all, was the motivation,
+ // borne from the necessity encountered in
+ // the step-18 tutorial program, to write
+ // this program in the first place.
+ //
+ // So what we do first is to declare an
+ // object into which we will merge the data
+ // from all the input file, and read in the
+ // first file through a stream. Note that
+ // every time we open a file, we use the
+ // <code>AssertThrow</code> macro to check whether the
+ // file is really readable -- if it isn't
+ // then this will trigger an exception and
+ // corresponding output will be generated
+ // from the exception handler in <code>main()</code>:
+ template <int dim, int spacedim>
+ void do_convert ()
+ {
+ DataOutReader<dim,spacedim> merged_data;
- // For all the other input files, we read
- // their data into an intermediate object,
- // and then merge that into the first
- // object declared above:
- for (unsigned int i=1; i<input_file_names.size(); ++i)
{
- std::ifstream input (input_file_names[i].c_str());
+ std::ifstream input (input_file_names[0].c_str());
AssertThrow (input, ExcIO());
- DataOutReader<dim,spacedim> additional_data;
- additional_data.read (input);
- merged_data.merge (additional_data);
- }
-
- // Once we have this, let us open an output
- // stream, and parse what we got as the
- // name of the output format into an
- // identifier. Fortunately, the
- // <code>DataOutBase</code> class has a function
- // that does this parsing for us, i.e. it
- // knows about all the presently supported
- // output formats and makes sure that they
- // can be specified in the parameter file
- // or on the command line. Note that this
- // ensures that if the library acquires the
- // ability to output in other output
- // formats, this program will be able to
- // make use of this ability without having
- // to be changed!
- std::ofstream output_stream (output_file.c_str());
- AssertThrow (output_stream, ExcIO());
-
- const DataOutBase::OutputFormat format
- = DataOutBase::parse_output_format (output_format);
-
- // Finally, write the merged data to the
- // output:
- merged_data.write(output_stream, format);
-}
+ merged_data.read (input);
+ }
+ // For all the other input files, we read
+ // their data into an intermediate object,
+ // and then merge that into the first
+ // object declared above:
+ for (unsigned int i=1; i<input_file_names.size(); ++i)
+ {
+ std::ifstream input (input_file_names[i].c_str());
+ AssertThrow (input, ExcIO());
+
+ DataOutReader<dim,spacedim> additional_data;
+ additional_data.read (input);
+ merged_data.merge (additional_data);
+ }
+
+ // Once we have this, let us open an output
+ // stream, and parse what we got as the
+ // name of the output format into an
+ // identifier. Fortunately, the
+ // <code>DataOutBase</code> class has a function
+ // that does this parsing for us, i.e. it
+ // knows about all the presently supported
+ // output formats and makes sure that they
+ // can be specified in the parameter file
+ // or on the command line. Note that this
+ // ensures that if the library acquires the
+ // ability to output in other output
+ // formats, this program will be able to
+ // make use of this ability without having
+ // to be changed!
+ std::ofstream output_stream (output_file.c_str());
+ AssertThrow (output_stream, ExcIO());
+
+ const DataOutBase::OutputFormat format
+ = DataOutBase::parse_output_format (output_format);
+
+ // Finally, write the merged data to the
+ // output:
+ merged_data.write(output_stream, format);
+ }
- // @sect4{Dispatching output generation}
-
- // The function above takes template
- // parameters relating to the space dimension
- // of the output, and the dimension of the
- // objects to be output. (For example, when
- // outputting whole cells, these two
- // dimensions are the same, but the
- // intermediate files may contain only data
- // pertaining to the faces of cells, in which
- // case the first parameter will be one less
- // than the space dimension.)
- //
- // The problem is: at compile time, we of
- // course don't know the dimensions used in
- // the input files. We have to plan for all
- // cases, therefore. This is a little clumsy,
- // since we need to specify the dimensions
- // statically at compile time, even though we
- // will only know about them at run time.
- //
- // So here is what we do: from the first
- // input file, we determine (using a function
- // in <code>DataOutBase</code> that exists for this
- // purpose) these dimensions. We then have a
- // series of switches that dispatch,
- // statically, to the <code>do_convert</code>
- // functions with different template
- // arguments. Not pretty, but works. Apart
- // from this, the function does nothing --
- // except making sure that it covered the
- // dimensions for which it was called, using
- // the <code>AssertThrow</code> macro at places in the
- // code that shouldn't be reached:
-void convert ()
-{
- AssertThrow (input_file_names.size() > 0,
- ExcMessage ("No input files specified."));
-
- std::ifstream input(input_file_names[0].c_str());
- AssertThrow (input, ExcIO());
-
- const std::pair<unsigned int, unsigned int>
- dimensions = DataOutBase::determine_intermediate_format_dimensions (input);
-
- switch (dimensions.first)
- {
- case 1:
- switch (dimensions.second)
- {
- case 1:
- do_convert <1,1> ();
- return;
-
- case 2:
- do_convert <1,2> ();
- return;
- }
- AssertThrow (false, ExcNotImplemented());
-
- case 2:
- switch (dimensions.second)
- {
- case 2:
- do_convert <2,2> ();
- return;
-
- case 3:
- do_convert <2,3> ();
- return;
- }
- AssertThrow (false, ExcNotImplemented());
-
- case 3:
- switch (dimensions.second)
- {
- case 3:
- do_convert <3,3> ();
- return;
- }
- AssertThrow (false, ExcNotImplemented());
- }
-
- AssertThrow (false, ExcNotImplemented());
+
+ // @sect4{Dispatching output generation}
+
+ // The function above takes template
+ // parameters relating to the space dimension
+ // of the output, and the dimension of the
+ // objects to be output. (For example, when
+ // outputting whole cells, these two
+ // dimensions are the same, but the
+ // intermediate files may contain only data
+ // pertaining to the faces of cells, in which
+ // case the first parameter will be one less
+ // than the space dimension.)
+ //
+ // The problem is: at compile time, we of
+ // course don't know the dimensions used in
+ // the input files. We have to plan for all
+ // cases, therefore. This is a little clumsy,
+ // since we need to specify the dimensions
+ // statically at compile time, even though we
+ // will only know about them at run time.
+ //
+ // So here is what we do: from the first
+ // input file, we determine (using a function
+ // in <code>DataOutBase</code> that exists for this
+ // purpose) these dimensions. We then have a
+ // series of switches that dispatch,
+ // statically, to the <code>do_convert</code>
+ // functions with different template
+ // arguments. Not pretty, but works. Apart
+ // from this, the function does nothing --
+ // except making sure that it covered the
+ // dimensions for which it was called, using
+ // the <code>AssertThrow</code> macro at places in the
+ // code that shouldn't be reached:
+ void convert ()
+ {
+ AssertThrow (input_file_names.size() > 0,
+ ExcMessage ("No input files specified."));
+
+ std::ifstream input(input_file_names[0].c_str());
+ AssertThrow (input, ExcIO());
+
+ const std::pair<unsigned int, unsigned int>
+ dimensions = DataOutBase::determine_intermediate_format_dimensions (input);
+
+ switch (dimensions.first)
+ {
+ case 1:
+ switch (dimensions.second)
+ {
+ case 1:
+ do_convert <1,1> ();
+ return;
+
+ case 2:
+ do_convert <1,2> ();
+ return;
+ }
+ AssertThrow (false, ExcNotImplemented());
+
+ case 2:
+ switch (dimensions.second)
+ {
+ case 2:
+ do_convert <2,2> ();
+ return;
+
+ case 3:
+ do_convert <2,3> ();
+ return;
+ }
+ AssertThrow (false, ExcNotImplemented());
+
+ case 3:
+ switch (dimensions.second)
+ {
+ case 3:
+ do_convert <3,3> ();
+ return;
+ }
+ AssertThrow (false, ExcNotImplemented());
+ }
+
+ AssertThrow (false, ExcNotImplemented());
+ }
}
- // @sect4{main()}
+ // @sect4{main()}
- // Finally, the main program. There is not
- // much more to do than to make sure
- // parameters are declared, the command line
- // is parsed (which includes reading
- // parameter files), and finally making sure
- // the input files are read and output is
- // generated. Everything else just has to do
- // with handling exceptions and making sure
- // that appropriate output is generated if
- // one is thrown.
+ // Finally, the main program. There is not
+ // much more to do than to make sure
+ // parameters are declared, the command line
+ // is parsed (which includes reading
+ // parameter files), and finally making sure
+ // the input files are read and output is
+ // generated. Everything else just has to do
+ // with handling exceptions and making sure
+ // that appropriate output is generated if
+ // one is thrown.
int main (int argc, char ** argv)
{
try
{
+ using namespace Step19;
+
declare_parameters ();
parse_command_line (argc, argv);
catch (std::exception &exc)
{
std::cerr << std::endl << std::endl
- << "----------------------------------------------------"
- << std::endl;
+ << "----------------------------------------------------"
+ << std::endl;
std::cerr << "Exception on processing: " << std::endl
- << exc.what() << std::endl
- << "Aborting!" << std::endl
- << "----------------------------------------------------"
- << std::endl;
-
+ << exc.what() << std::endl
+ << "Aborting!" << std::endl
+ << "----------------------------------------------------"
+ << std::endl;
+
return 1;
}
catch (...)
{
std::cerr << std::endl << std::endl
- << "----------------------------------------------------"
- << std::endl;
+ << "----------------------------------------------------"
+ << std::endl;
std::cerr << "Unknown exception!" << std::endl
- << "Aborting!" << std::endl
- << "----------------------------------------------------"
- << std::endl;
+ << "Aborting!" << std::endl
+ << "----------------------------------------------------"
+ << std::endl;
return 1;
};
-
+
return 0;
-}
+}
// The last step is as in all
// previous programs:
-using namespace dealii;
-
- // @sect3{The <code>MixedLaplaceProblem</code> class template}
-
- // Again, since this is an adaptation
- // of step-6, the main class is
- // almost the same as the one in that
- // tutorial program. In terms of
- // member functions, the main
- // differences are that the
- // constructor takes the degree of
- // the Raviart-Thomas element as an
- // argument (and that there is a
- // corresponding member variable to
- // store this value) and the addition
- // of the <code>compute_error</code> function
- // in which, no surprise, we will
- // compute the difference between the
- // exact and the numerical solution
- // to determine convergence of our
- // computations:
-template <int dim>
-class MixedLaplaceProblem
+namespace Step20
{
- public:
- MixedLaplaceProblem (const unsigned int degree);
- void run ();
-
- private:
- void make_grid_and_dofs ();
- void assemble_system ();
- void solve ();
- void compute_errors () const;
- void output_results () const;
-
- const unsigned int degree;
-
- Triangulation<dim> triangulation;
- FESystem<dim> fe;
- DoFHandler<dim> dof_handler;
-
- // The second difference is that
- // the sparsity pattern, the
- // system matrix, and solution
- // and right hand side vectors
- // are now blocked. What this
- // means and what one can do with
- // such objects is explained in
- // the introduction to this
- // program as well as further
- // down below when we explain the
- // linear solvers and
- // preconditioners for this
- // problem:
- BlockSparsityPattern sparsity_pattern;
- BlockSparseMatrix<double> system_matrix;
-
- BlockVector<double> solution;
- BlockVector<double> system_rhs;
-};
-
-
- // @sect3{Right hand side, boundary values, and exact solution}
-
- // Our next task is to define the
- // right hand side of our problem
- // (i.e., the scalar right hand side
- // for the pressure in the original
- // Laplace equation), boundary values
- // for the pressure, as well as a
- // function that describes both the
- // pressure and the velocity of the
- // exact solution for later
- // computations of the error. Note
- // that these functions have one,
- // one, and <code>dim+1</code> components,
- // respectively, and that we pass the
- // number of components down to the
- // <code>Function@<dim@></code> base class. For
- // the exact solution, we only
- // declare the function that actually
- // returns the entire solution vector
- // (i.e. all components of it) at
- // once. Here are the respective
- // declarations:
-template <int dim>
-class RightHandSide : public Function<dim>
-{
- public:
- RightHandSide () : Function<dim>(1) {}
-
- virtual double value (const Point<dim> &p,
- const unsigned int component = 0) const;
-};
-
+ using namespace dealii;
+
+ // @sect3{The <code>MixedLaplaceProblem</code> class template}
+
+ // Again, since this is an adaptation
+ // of step-6, the main class is
+ // almost the same as the one in that
+ // tutorial program. In terms of
+ // member functions, the main
+ // differences are that the
+ // constructor takes the degree of
+ // the Raviart-Thomas element as an
+ // argument (and that there is a
+ // corresponding member variable to
+ // store this value) and the addition
+ // of the <code>compute_error</code> function
+ // in which, no surprise, we will
+ // compute the difference between the
+ // exact and the numerical solution
+ // to determine convergence of our
+ // computations:
+ template <int dim>
+ class MixedLaplaceProblem
+ {
+ public:
+ MixedLaplaceProblem (const unsigned int degree);
+ void run ();
+
+ private:
+ void make_grid_and_dofs ();
+ void assemble_system ();
+ void solve ();
+ void compute_errors () const;
+ void output_results () const;
+
+ const unsigned int degree;
+
+ Triangulation<dim> triangulation;
+ FESystem<dim> fe;
+ DoFHandler<dim> dof_handler;
+
+ // The second difference is that
+ // the sparsity pattern, the
+ // system matrix, and solution
+ // and right hand side vectors
+ // are now blocked. What this
+ // means and what one can do with
+ // such objects is explained in
+ // the introduction to this
+ // program as well as further
+ // down below when we explain the
+ // linear solvers and
+ // preconditioners for this
+ // problem:
+ BlockSparsityPattern sparsity_pattern;
+ BlockSparseMatrix<double> system_matrix;
+
+ BlockVector<double> solution;
+ BlockVector<double> system_rhs;
+ };
+
+
+ // @sect3{Right hand side, boundary values, and exact solution}
+
+ // Our next task is to define the
+ // right hand side of our problem
+ // (i.e., the scalar right hand side
+ // for the pressure in the original
+ // Laplace equation), boundary values
+ // for the pressure, as well as a
+ // function that describes both the
+ // pressure and the velocity of the
+ // exact solution for later
+ // computations of the error. Note
+ // that these functions have one,
+ // one, and <code>dim+1</code> components,
+ // respectively, and that we pass the
+ // number of components down to the
+ // <code>Function@<dim@></code> base class. For
+ // the exact solution, we only
+ // declare the function that actually
+ // returns the entire solution vector
+ // (i.e. all components of it) at
+ // once. Here are the respective
+ // declarations:
+ template <int dim>
+ class RightHandSide : public Function<dim>
+ {
+ public:
+ RightHandSide () : Function<dim>(1) {}
+ virtual double value (const Point<dim> &p,
+ const unsigned int component = 0) const;
+ };
-template <int dim>
-class PressureBoundaryValues : public Function<dim>
-{
- public:
- PressureBoundaryValues () : Function<dim>(1) {}
-
- virtual double value (const Point<dim> &p,
- const unsigned int component = 0) const;
-};
-template <int dim>
-class ExactSolution : public Function<dim>
-{
- public:
- ExactSolution () : Function<dim>(dim+1) {}
-
- virtual void vector_value (const Point<dim> &p,
- Vector<double> &value) const;
-};
-
-
- // And then we also have to define
- // these respective functions, of
- // course. Given our discussion in
- // the introduction of how the
- // solution should look like, the
- // following computations should be
- // straightforward:
-template <int dim>
-double RightHandSide<dim>::value (const Point<dim> &/*p*/,
- const unsigned int /*component*/) const
-{
- return 0;
-}
+ template <int dim>
+ class PressureBoundaryValues : public Function<dim>
+ {
+ public:
+ PressureBoundaryValues () : Function<dim>(1) {}
+ virtual double value (const Point<dim> &p,
+ const unsigned int component = 0) const;
+ };
-template <int dim>
-double PressureBoundaryValues<dim>::value (const Point<dim> &p,
- const unsigned int /*component*/) const
-{
- const double alpha = 0.3;
- const double beta = 1;
- return -(alpha*p[0]*p[1]*p[1]/2 + beta*p[0] - alpha*p[0]*p[0]*p[0]/6);
-}
+ template <int dim>
+ class ExactSolution : public Function<dim>
+ {
+ public:
+ ExactSolution () : Function<dim>(dim+1) {}
+
+ virtual void vector_value (const Point<dim> &p,
+ Vector<double> &value) const;
+ };
+
+
+ // And then we also have to define
+ // these respective functions, of
+ // course. Given our discussion in
+ // the introduction of how the
+ // solution should look like, the
+ // following computations should be
+ // straightforward:
+ template <int dim>
+ double RightHandSide<dim>::value (const Point<dim> &/*p*/,
+ const unsigned int /*component*/) const
+ {
+ return 0;
+ }
-template <int dim>
-void
-ExactSolution<dim>::vector_value (const Point<dim> &p,
- Vector<double> &values) const
-{
- Assert (values.size() == dim+1,
- ExcDimensionMismatch (values.size(), dim+1));
+ template <int dim>
+ double PressureBoundaryValues<dim>::value (const Point<dim> &p,
+ const unsigned int /*component*/) const
+ {
+ const double alpha = 0.3;
+ const double beta = 1;
+ return -(alpha*p[0]*p[1]*p[1]/2 + beta*p[0] - alpha*p[0]*p[0]*p[0]/6);
+ }
- const double alpha = 0.3;
- const double beta = 1;
- values(0) = alpha*p[1]*p[1]/2 + beta - alpha*p[0]*p[0]/2;
- values(1) = alpha*p[0]*p[1];
- values(2) = -(alpha*p[0]*p[1]*p[1]/2 + beta*p[0] - alpha*p[0]*p[0]*p[0]/6);
-}
+ template <int dim>
+ void
+ ExactSolution<dim>::vector_value (const Point<dim> &p,
+ Vector<double> &values) const
+ {
+ Assert (values.size() == dim+1,
+ ExcDimensionMismatch (values.size(), dim+1));
+ const double alpha = 0.3;
+ const double beta = 1;
- // @sect3{The inverse permeability tensor}
-
- // In addition to the other equation
- // data, we also want to use a
- // permeability tensor, or better --
- // because this is all that appears
- // in the weak form -- the inverse of
- // the permeability tensor,
- // <code>KInverse</code>. For the purpose of
- // verifying the exactness of the
- // solution and determining
- // convergence orders, this tensor is
- // more in the way than helpful. We
- // will therefore simply set it to
- // the identity matrix.
- //
- // However, a spatially varying
- // permeability tensor is
- // indispensable in real-life porous
- // media flow simulations, and we
- // would like to use the opportunity
- // to demonstrate the technique to
- // use tensor valued functions.
- //
- // Possibly unsurprising, deal.II
- // also has a base class not only for
- // scalar and generally vector-valued
- // functions (the <code>Function</code> base
- // class) but also for functions that
- // return tensors of fixed dimension
- // and rank, the <code>TensorFunction</code>
- // template. Here, the function under
- // consideration returns a dim-by-dim
- // matrix, i.e. a tensor of rank 2
- // and dimension <code>dim</code>. We then
- // choose the template arguments of
- // the base class appropriately.
- //
- // The interface that the
- // <code>TensorFunction</code> class provides
- // is essentially equivalent to the
- // <code>Function</code> class. In particular,
- // there exists a <code>value_list</code>
- // function that takes a list of
- // points at which to evaluate the
- // function, and returns the values
- // of the function in the second
- // argument, a list of tensors:
-template <int dim>
-class KInverse : public TensorFunction<2,dim>
-{
- public:
- KInverse () : TensorFunction<2,dim>() {}
-
- virtual void value_list (const std::vector<Point<dim> > &points,
- std::vector<Tensor<2,dim> > &values) const;
-};
-
-
- // The implementation is less
- // interesting. As in previous
- // examples, we add a check to the
- // beginning of the class to make
- // sure that the sizes of input and
- // output parameters are the same
- // (see step-5 for a discussion of
- // this technique). Then we loop over
- // all evaluation points, and for
- // each one first clear the output
- // tensor and then set all its
- // diagonal elements to one
- // (i.e. fill the tensor with the
- // identity matrix):
-template <int dim>
-void
-KInverse<dim>::value_list (const std::vector<Point<dim> > &points,
- std::vector<Tensor<2,dim> > &values) const
-{
- Assert (points.size() == values.size(),
- ExcDimensionMismatch (points.size(), values.size()));
+ values(0) = alpha*p[1]*p[1]/2 + beta - alpha*p[0]*p[0]/2;
+ values(1) = alpha*p[0]*p[1];
+ values(2) = -(alpha*p[0]*p[1]*p[1]/2 + beta*p[0] - alpha*p[0]*p[0]*p[0]/6);
+ }
- for (unsigned int p=0; p<points.size(); ++p)
- {
- values[p].clear ();
- for (unsigned int d=0; d<dim; ++d)
- values[p][d][d] = 1.;
- }
-}
+ // @sect3{The inverse permeability tensor}
+
+ // In addition to the other equation
+ // data, we also want to use a
+ // permeability tensor, or better --
+ // because this is all that appears
+ // in the weak form -- the inverse of
+ // the permeability tensor,
+ // <code>KInverse</code>. For the purpose of
+ // verifying the exactness of the
+ // solution and determining
+ // convergence orders, this tensor is
+ // more in the way than helpful. We
+ // will therefore simply set it to
+ // the identity matrix.
+ //
+ // However, a spatially varying
+ // permeability tensor is
+ // indispensable in real-life porous
+ // media flow simulations, and we
+ // would like to use the opportunity
+ // to demonstrate the technique to
+ // use tensor valued functions.
+ //
+ // Possibly unsurprising, deal.II
+ // also has a base class not only for
+ // scalar and generally vector-valued
+ // functions (the <code>Function</code> base
+ // class) but also for functions that
+ // return tensors of fixed dimension
+ // and rank, the <code>TensorFunction</code>
+ // template. Here, the function under
+ // consideration returns a dim-by-dim
+ // matrix, i.e. a tensor of rank 2
+ // and dimension <code>dim</code>. We then
+ // choose the template arguments of
+ // the base class appropriately.
+ //
+ // The interface that the
+ // <code>TensorFunction</code> class provides
+ // is essentially equivalent to the
+ // <code>Function</code> class. In particular,
+ // there exists a <code>value_list</code>
+ // function that takes a list of
+ // points at which to evaluate the
+ // function, and returns the values
+ // of the function in the second
+ // argument, a list of tensors:
+ template <int dim>
+ class KInverse : public TensorFunction<2,dim>
+ {
+ public:
+ KInverse () : TensorFunction<2,dim>() {}
+
+ virtual void value_list (const std::vector<Point<dim> > &points,
+ std::vector<Tensor<2,dim> > &values) const;
+ };
+
+
+ // The implementation is less
+ // interesting. As in previous
+ // examples, we add a check to the
+ // beginning of the class to make
+ // sure that the sizes of input and
+ // output parameters are the same
+ // (see step-5 for a discussion of
+ // this technique). Then we loop over
+ // all evaluation points, and for
+ // each one first clear the output
+ // tensor and then set all its
+ // diagonal elements to one
+ // (i.e. fill the tensor with the
+ // identity matrix):
+ template <int dim>
+ void
+ KInverse<dim>::value_list (const std::vector<Point<dim> > &points,
+ std::vector<Tensor<2,dim> > &values) const
+ {
+ Assert (points.size() == values.size(),
+ ExcDimensionMismatch (points.size(), values.size()));
+ for (unsigned int p=0; p<points.size(); ++p)
+ {
+ values[p].clear ();
- // @sect3{MixedLaplaceProblem class implementation}
-
- // @sect4{MixedLaplaceProblem::MixedLaplaceProblem}
-
- // In the constructor of this class,
- // we first store the value that was
- // passed in concerning the degree of
- // the finite elements we shall use
- // (a degree of zero, for example,
- // means to use RT(0) and DG(0)), and
- // then construct the vector valued
- // element belonging to the space X_h
- // described in the introduction. The
- // rest of the constructor is as in
- // the early tutorial programs.
- //
- // The only thing worth describing
- // here is the constructor call of
- // the <code>fe</code> variable. The
- // <code>FESystem</code> class to which this
- // variable belongs has a number of
- // different constructors that all
- // refer to binding simpler elements
- // together into one larger
- // element. In the present case, we
- // want to couple a single RT(degree)
- // element with a single DQ(degree)
- // element. The constructor to
- // <code>FESystem</code> that does this
- // requires us to specity first the
- // first base element (the
- // <code>FE_RaviartThomas</code> object of
- // given degree) and then the number
- // of copies for this base element,
- // and then similarly the kind and
- // number of <code>FE_DGQ</code>
- // elements. Note that the Raviart
- // Thomas element already has <code>dim</code>
- // vector components, so that the
- // coupled element will have
- // <code>dim+1</code> vector components, the
- // first <code>dim</code> of which correspond
- // to the velocity variable whereas the
- // last one corresponds to the
- // pressure.
- //
- // It is also worth comparing the way
- // we constructed this element from
- // its base elements, with the way we
- // have done so in step-8: there, we
- // have built it as <code>fe
- // (FE_Q@<dim@>(1), dim)</code>, i.e. we
- // have simply used <code>dim</code> copies of
- // the <code>FE_Q(1)</code> element, one copy
- // for the displacement in each
- // coordinate direction.
-template <int dim>
-MixedLaplaceProblem<dim>::MixedLaplaceProblem (const unsigned int degree)
- :
- degree (degree),
- fe (FE_RaviartThomas<dim>(degree), 1,
- FE_DGQ<dim>(degree), 1),
- dof_handler (triangulation)
-{}
-
-
-
- // @sect4{MixedLaplaceProblem::make_grid_and_dofs}
-
- // This next function starts out with
- // well-known functions calls that
- // create and refine a mesh, and then
- // associate degrees of freedom with
- // it:
-template <int dim>
-void MixedLaplaceProblem<dim>::make_grid_and_dofs ()
-{
- GridGenerator::hyper_cube (triangulation, -1, 1);
- triangulation.refine_global (3);
-
- dof_handler.distribute_dofs (fe);
-
- // However, then things become
- // different. As mentioned in the
- // introduction, we want to
- // subdivide the matrix into blocks
- // corresponding to the two
- // different kinds of variables,
- // velocity and pressure. To this end,
- // we first have to make sure that
- // the indices corresponding to
- // velocities and pressures are not
- // intermingled: First all velocity
- // degrees of freedom, then all
- // pressure DoFs. This way, the
- // global matrix separates nicely
- // into a 2x2 system. To achieve
- // this, we have to renumber
- // degrees of freedom base on their
- // vector component, an operation
- // that conveniently is already
- // implemented:
- DoFRenumbering::component_wise (dof_handler);
-
- // The next thing is that we want
- // to figure out the sizes of these
- // blocks, so that we can allocate
- // an appropriate amount of
- // space. To this end, we call the
- // <code>DoFTools::count_dofs_per_component</code>
- // function that counts how many
- // shape functions are non-zero for
- // a particular vector
- // component. We have <code>dim+1</code>
- // vector components, and we have
- // to use the knowledge that for
- // Raviart-Thomas elements all
- // shape functions are nonzero in
- // all components. In other words,
- // the number of velocity shape
- // functions equals the number of
- // overall shape functions that are
- // nonzero in the zeroth vector
- // component. On the other hand,
- // the number of pressure variables
- // equals the number of shape
- // functions that are nonzero in
- // the dim-th component. Let us
- // compute these numbers and then
- // create some nice output with
- // that:
- std::vector<unsigned int> dofs_per_component (dim+1);
- DoFTools::count_dofs_per_component (dof_handler, dofs_per_component);
- const unsigned int n_u = dofs_per_component[0],
- n_p = dofs_per_component[dim];
-
- std::cout << "Number of active cells: "
- << triangulation.n_active_cells()
- << std::endl
- << "Total number of cells: "
- << triangulation.n_cells()
- << std::endl
- << "Number of degrees of freedom: "
- << dof_handler.n_dofs()
- << " (" << n_u << '+' << n_p << ')'
- << std::endl;
-
- // The next task is to allocate a
- // sparsity pattern for the matrix
- // that we will create. The way
- // this works is that we first
- // obtain a guess for the maximal
- // number of nonzero entries per
- // row (this could be done more
- // efficiently in this case, but we
- // only want to solve relatively
- // small problems for which this is
- // not so important). In the second
- // step, we allocate a 2x2 block
- // pattern and then reinitialize
- // each of the blocks to its
- // correct size using the <code>n_u</code>
- // and <code>n_p</code> variables defined
- // above that hold the number of
- // velocity and pressure
- // variables. In this second step,
- // we only operate on the
- // individual blocks of the
- // system. In the third step, we
- // therefore have to instruct the
- // overlying block system to update
- // its knowledge about the sizes of
- // the blocks it manages; this
- // happens with the
- // <code>sparsity_pattern.collect_sizes()</code>
- // call:
- const unsigned int
- n_couplings = dof_handler.max_couplings_between_dofs();
-
- sparsity_pattern.reinit (2,2);
- sparsity_pattern.block(0,0).reinit (n_u, n_u, n_couplings);
- sparsity_pattern.block(1,0).reinit (n_p, n_u, n_couplings);
- sparsity_pattern.block(0,1).reinit (n_u, n_p, n_couplings);
- sparsity_pattern.block(1,1).reinit (n_p, n_p, n_couplings);
- sparsity_pattern.collect_sizes();
-
- // Now that the sparsity pattern
- // and its blocks have the correct
- // sizes, we actually need to
- // construct the content of this
- // pattern, and as usual compress
- // it, before we also initialize a
- // block matrix with this block
- // sparsity pattern:
- DoFTools::make_sparsity_pattern (dof_handler, sparsity_pattern);
- sparsity_pattern.compress();
-
- system_matrix.reinit (sparsity_pattern);
-
- // Then we have to resize the
- // solution and right hand side
- // vectors in exactly the same way:
- solution.reinit (2);
- solution.block(0).reinit (n_u);
- solution.block(1).reinit (n_p);
- solution.collect_sizes ();
-
- system_rhs.reinit (2);
- system_rhs.block(0).reinit (n_u);
- system_rhs.block(1).reinit (n_p);
- system_rhs.collect_sizes ();
-}
+ for (unsigned int d=0; d<dim; ++d)
+ values[p][d][d] = 1.;
+ }
+ }
- // @sect4{MixedLaplaceProblem::assemble_system}
- // Similarly, the function that
- // assembles the linear system has
- // mostly been discussed already in
- // the introduction to this
- // example. At its top, what happens
- // are all the usual steps, with the
- // addition that we do not only
- // allocate quadrature and
- // <code>FEValues</code> objects for the cell
- // terms, but also for face
- // terms. After that, we define the
- // usual abbreviations for variables,
- // and the allocate space for the
- // local matrix and right hand side
- // contributions, and the array that
- // holds the global numbers of the
- // degrees of freedom local to the
- // present cell.
-template <int dim>
-void MixedLaplaceProblem<dim>::assemble_system ()
-{
- QGauss<dim> quadrature_formula(degree+2);
- QGauss<dim-1> face_quadrature_formula(degree+2);
-
- FEValues<dim> fe_values (fe, quadrature_formula,
- update_values | update_gradients |
- update_quadrature_points | update_JxW_values);
- FEFaceValues<dim> fe_face_values (fe, face_quadrature_formula,
- update_values | update_normal_vectors |
- 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();
- const unsigned int n_face_q_points = face_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);
-
- // The next step is to declare
- // objects that represent the
- // source term, pressure boundary
- // value, and coefficient in the
- // equation. In addition to these
- // objects that represent
- // continuous functions, we also
- // need arrays to hold their values
- // at the quadrature points of
- // individual cells (or faces, for
- // the boundary values). Note that
- // in the case of the coefficient,
- // the array has to be one of
- // matrices.
- const RightHandSide<dim> right_hand_side;
- const PressureBoundaryValues<dim> pressure_boundary_values;
- const KInverse<dim> k_inverse;
-
- std::vector<double> rhs_values (n_q_points);
- std::vector<double> boundary_values (n_face_q_points);
- std::vector<Tensor<2,dim> > k_inverse_values (n_q_points);
-
- // Finally, we need a couple of extractors
- // that we will use to get at the velocity
- // and pressure components of vector-valued
- // shape functions. Their function and use
- // is described in detail in the @ref
- // vector_valued report. Essentially, we
- // will use them as subscripts on the
- // FEValues objects below: the FEValues
- // object describes all vector components
- // of shape functions, while after
- // subscription, it will only refer to the
- // velocities (a set of <code>dim</code>
- // components starting at component zero)
- // or the pressure (a scalar component
- // located at position <code>dim</code>):
- const FEValuesExtractors::Vector velocities (0);
- const FEValuesExtractors::Scalar pressure (dim);
-
- // With all this in place, we can
- // go on with the loop over all
- // cells. The body of this loop has
- // been discussed in the
- // introduction, and will not be
- // commented any further here:
- typename DoFHandler<dim>::active_cell_iterator
- cell = dof_handler.begin_active(),
- endc = dof_handler.end();
- for (; cell!=endc; ++cell)
- {
- fe_values.reinit (cell);
- local_matrix = 0;
- local_rhs = 0;
-
- right_hand_side.value_list (fe_values.get_quadrature_points(),
- rhs_values);
- k_inverse.value_list (fe_values.get_quadrature_points(),
- k_inverse_values);
-
- for (unsigned int q=0; q<n_q_points; ++q)
- for (unsigned int i=0; i<dofs_per_cell; ++i)
- {
- const Tensor<1,dim> phi_i_u = fe_values[velocities].value (i, q);
- const double div_phi_i_u = fe_values[velocities].divergence (i, q);
- const double phi_i_p = fe_values[pressure].value (i, q);
-
- for (unsigned int j=0; j<dofs_per_cell; ++j)
- {
- const Tensor<1,dim> phi_j_u = fe_values[velocities].value (j, q);
- const double div_phi_j_u = fe_values[velocities].divergence (j, q);
- const double phi_j_p = fe_values[pressure].value (j, q);
-
- local_matrix(i,j) += (phi_i_u * k_inverse_values[q] * phi_j_u
- - div_phi_i_u * phi_j_p
- - phi_i_p * div_phi_j_u)
- * fe_values.JxW(q);
- }
-
- local_rhs(i) += -phi_i_p *
- rhs_values[q] *
- fe_values.JxW(q);
- }
-
- for (unsigned int face_no=0;
- face_no<GeometryInfo<dim>::faces_per_cell;
- ++face_no)
- if (cell->at_boundary(face_no))
- {
- fe_face_values.reinit (cell, face_no);
-
- pressure_boundary_values
- .value_list (fe_face_values.get_quadrature_points(),
- boundary_values);
-
- for (unsigned int q=0; q<n_face_q_points; ++q)
- for (unsigned int i=0; i<dofs_per_cell; ++i)
- local_rhs(i) += -(fe_face_values[velocities].value (i, q) *
- fe_face_values.normal_vector(q) *
- boundary_values[q] *
- fe_face_values.JxW(q));
- }
-
- // The final step in the loop
- // over all cells is to
- // transfer local contributions
- // into the global matrix and
- // right hand side vector. Note
- // that we use exactly the same
- // interface as in previous
- // examples, although we now
- // use block matrices and
- // vectors instead of the
- // regular ones. In other
- // words, to the outside world,
- // block objects have the same
- // interface as matrices and
- // vectors, but they
- // additionally allow to access
- // individual blocks.
- 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],
- local_matrix(i,j));
- for (unsigned int i=0; i<dofs_per_cell; ++i)
- system_rhs(local_dof_indices[i]) += local_rhs(i);
- }
-}
+ // @sect3{MixedLaplaceProblem class implementation}
+
+ // @sect4{MixedLaplaceProblem::MixedLaplaceProblem}
+
+ // In the constructor of this class,
+ // we first store the value that was
+ // passed in concerning the degree of
+ // the finite elements we shall use
+ // (a degree of zero, for example,
+ // means to use RT(0) and DG(0)), and
+ // then construct the vector valued
+ // element belonging to the space X_h
+ // described in the introduction. The
+ // rest of the constructor is as in
+ // the early tutorial programs.
+ //
+ // The only thing worth describing
+ // here is the constructor call of
+ // the <code>fe</code> variable. The
+ // <code>FESystem</code> class to which this
+ // variable belongs has a number of
+ // different constructors that all
+ // refer to binding simpler elements
+ // together into one larger
+ // element. In the present case, we
+ // want to couple a single RT(degree)
+ // element with a single DQ(degree)
+ // element. The constructor to
+ // <code>FESystem</code> that does this
+ // requires us to specity first the
+ // first base element (the
+ // <code>FE_RaviartThomas</code> object of
+ // given degree) and then the number
+ // of copies for this base element,
+ // and then similarly the kind and
+ // number of <code>FE_DGQ</code>
+ // elements. Note that the Raviart
+ // Thomas element already has <code>dim</code>
+ // vector components, so that the
+ // coupled element will have
+ // <code>dim+1</code> vector components, the
+ // first <code>dim</code> of which correspond
+ // to the velocity variable whereas the
+ // last one corresponds to the
+ // pressure.
+ //
+ // It is also worth comparing the way
+ // we constructed this element from
+ // its base elements, with the way we
+ // have done so in step-8: there, we
+ // have built it as <code>fe
+ // (FE_Q@<dim@>(1), dim)</code>, i.e. we
+ // have simply used <code>dim</code> copies of
+ // the <code>FE_Q(1)</code> element, one copy
+ // for the displacement in each
+ // coordinate direction.
+ template <int dim>
+ MixedLaplaceProblem<dim>::MixedLaplaceProblem (const unsigned int degree)
+ :
+ degree (degree),
+ fe (FE_RaviartThomas<dim>(degree), 1,
+ FE_DGQ<dim>(degree), 1),
+ dof_handler (triangulation)
+ {}
+
+
+
+ // @sect4{MixedLaplaceProblem::make_grid_and_dofs}
+
+ // This next function starts out with
+ // well-known functions calls that
+ // create and refine a mesh, and then
+ // associate degrees of freedom with
+ // it:
+ template <int dim>
+ void MixedLaplaceProblem<dim>::make_grid_and_dofs ()
+ {
+ GridGenerator::hyper_cube (triangulation, -1, 1);
+ triangulation.refine_global (3);
+
+ dof_handler.distribute_dofs (fe);
+
+ // However, then things become
+ // different. As mentioned in the
+ // introduction, we want to
+ // subdivide the matrix into blocks
+ // corresponding to the two
+ // different kinds of variables,
+ // velocity and pressure. To this end,
+ // we first have to make sure that
+ // the indices corresponding to
+ // velocities and pressures are not
+ // intermingled: First all velocity
+ // degrees of freedom, then all
+ // pressure DoFs. This way, the
+ // global matrix separates nicely
+ // into a 2x2 system. To achieve
+ // this, we have to renumber
+ // degrees of freedom base on their
+ // vector component, an operation
+ // that conveniently is already
+ // implemented:
+ DoFRenumbering::component_wise (dof_handler);
+
+ // The next thing is that we want
+ // to figure out the sizes of these
+ // blocks, so that we can allocate
+ // an appropriate amount of
+ // space. To this end, we call the
+ // <code>DoFTools::count_dofs_per_component</code>
+ // function that counts how many
+ // shape functions are non-zero for
+ // a particular vector
+ // component. We have <code>dim+1</code>
+ // vector components, and we have
+ // to use the knowledge that for
+ // Raviart-Thomas elements all
+ // shape functions are nonzero in
+ // all components. In other words,
+ // the number of velocity shape
+ // functions equals the number of
+ // overall shape functions that are
+ // nonzero in the zeroth vector
+ // component. On the other hand,
+ // the number of pressure variables
+ // equals the number of shape
+ // functions that are nonzero in
+ // the dim-th component. Let us
+ // compute these numbers and then
+ // create some nice output with
+ // that:
+ std::vector<unsigned int> dofs_per_component (dim+1);
+ DoFTools::count_dofs_per_component (dof_handler, dofs_per_component);
+ const unsigned int n_u = dofs_per_component[0],
+ n_p = dofs_per_component[dim];
+
+ std::cout << "Number of active cells: "
+ << triangulation.n_active_cells()
+ << std::endl
+ << "Total number of cells: "
+ << triangulation.n_cells()
+ << std::endl
+ << "Number of degrees of freedom: "
+ << dof_handler.n_dofs()
+ << " (" << n_u << '+' << n_p << ')'
+ << std::endl;
+
+ // The next task is to allocate a
+ // sparsity pattern for the matrix
+ // that we will create. The way
+ // this works is that we first
+ // obtain a guess for the maximal
+ // number of nonzero entries per
+ // row (this could be done more
+ // efficiently in this case, but we
+ // only want to solve relatively
+ // small problems for which this is
+ // not so important). In the second
+ // step, we allocate a 2x2 block
+ // pattern and then reinitialize
+ // each of the blocks to its
+ // correct size using the <code>n_u</code>
+ // and <code>n_p</code> variables defined
+ // above that hold the number of
+ // velocity and pressure
+ // variables. In this second step,
+ // we only operate on the
+ // individual blocks of the
+ // system. In the third step, we
+ // therefore have to instruct the
+ // overlying block system to update
+ // its knowledge about the sizes of
+ // the blocks it manages; this
+ // happens with the
+ // <code>sparsity_pattern.collect_sizes()</code>
+ // call:
+ const unsigned int
+ n_couplings = dof_handler.max_couplings_between_dofs();
+
+ sparsity_pattern.reinit (2,2);
+ sparsity_pattern.block(0,0).reinit (n_u, n_u, n_couplings);
+ sparsity_pattern.block(1,0).reinit (n_p, n_u, n_couplings);
+ sparsity_pattern.block(0,1).reinit (n_u, n_p, n_couplings);
+ sparsity_pattern.block(1,1).reinit (n_p, n_p, n_couplings);
+ sparsity_pattern.collect_sizes();
+
+ // Now that the sparsity pattern
+ // and its blocks have the correct
+ // sizes, we actually need to
+ // construct the content of this
+ // pattern, and as usual compress
+ // it, before we also initialize a
+ // block matrix with this block
+ // sparsity pattern:
+ DoFTools::make_sparsity_pattern (dof_handler, sparsity_pattern);
+ sparsity_pattern.compress();
+
+ system_matrix.reinit (sparsity_pattern);
+
+ // Then we have to resize the
+ // solution and right hand side
+ // vectors in exactly the same way:
+ solution.reinit (2);
+ solution.block(0).reinit (n_u);
+ solution.block(1).reinit (n_p);
+ solution.collect_sizes ();
+
+ system_rhs.reinit (2);
+ system_rhs.block(0).reinit (n_u);
+ system_rhs.block(1).reinit (n_p);
+ system_rhs.collect_sizes ();
+ }
- // @sect3{Linear solvers and preconditioners}
-
- // The linear solvers and
- // preconditioners we use in this
- // example have been discussed in
- // significant detail already in the
- // introduction. We will therefore
- // not discuss the rationale for
- // these classes here any more, but
- // rather only comment on
- // implementational aspects.
-
-
- // @sect4{The <code>SchurComplement</code> class template}
-
- // The next class is the Schur
- // complement class. Its rationale
- // has also been discussed in length
- // in the introduction. The only
- // things we would like to note is
- // that the class, too, is derived
- // from the <code>Subscriptor</code> class and
- // that as mentioned above it stores
- // pointers to the entire block
- // matrix and the inverse of the mass
- // matrix block using
- // <code>SmartPointer</code> objects.
- //
- // The <code>vmult</code> function requires
- // two temporary vectors that we do
- // not want to re-allocate and free
- // every time we call this
- // function. Since here, we have full
- // control over the use of these
- // vectors (unlike above, where a
- // class called by the <code>vmult</code>
- // function required these vectors,
- // not the <code>vmult</code> function
- // itself), we allocate them
- // directly, rather than going
- // through the <code>VectorMemory</code>
- // mechanism. However, again, these
- // member variables do not carry any
- // state between successive calls to
- // the member functions of this class
- // (i.e., we never care what values
- // they were set to the last time a
- // member function was called), we
- // mark these vectors as <code>mutable</code>.
- //
- // The rest of the (short)
- // implementation of this class is
- // straightforward if you know the
- // order of matrix-vector
- // multiplications performed by the
- // <code>vmult</code> function:
-class SchurComplement : public Subscriptor
-{
- public:
- SchurComplement (const BlockSparseMatrix<double> &A,
- const IterativeInverse<Vector<double> > &Minv);
- void vmult (Vector<double> &dst,
- const Vector<double> &src) const;
+ // @sect4{MixedLaplaceProblem::assemble_system}
+ // Similarly, the function that
+ // assembles the linear system has
+ // mostly been discussed already in
+ // the introduction to this
+ // example. At its top, what happens
+ // are all the usual steps, with the
+ // addition that we do not only
+ // allocate quadrature and
+ // <code>FEValues</code> objects for the cell
+ // terms, but also for face
+ // terms. After that, we define the
+ // usual abbreviations for variables,
+ // and the allocate space for the
+ // local matrix and right hand side
+ // contributions, and the array that
+ // holds the global numbers of the
+ // degrees of freedom local to the
+ // present cell.
+ template <int dim>
+ void MixedLaplaceProblem<dim>::assemble_system ()
+ {
+ QGauss<dim> quadrature_formula(degree+2);
+ QGauss<dim-1> face_quadrature_formula(degree+2);
+
+ FEValues<dim> fe_values (fe, quadrature_formula,
+ update_values | update_gradients |
+ update_quadrature_points | update_JxW_values);
+ FEFaceValues<dim> fe_face_values (fe, face_quadrature_formula,
+ update_values | update_normal_vectors |
+ 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();
+ const unsigned int n_face_q_points = face_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);
+
+ // The next step is to declare
+ // objects that represent the
+ // source term, pressure boundary
+ // value, and coefficient in the
+ // equation. In addition to these
+ // objects that represent
+ // continuous functions, we also
+ // need arrays to hold their values
+ // at the quadrature points of
+ // individual cells (or faces, for
+ // the boundary values). Note that
+ // in the case of the coefficient,
+ // the array has to be one of
+ // matrices.
+ const RightHandSide<dim> right_hand_side;
+ const PressureBoundaryValues<dim> pressure_boundary_values;
+ const KInverse<dim> k_inverse;
+
+ std::vector<double> rhs_values (n_q_points);
+ std::vector<double> boundary_values (n_face_q_points);
+ std::vector<Tensor<2,dim> > k_inverse_values (n_q_points);
+
+ // Finally, we need a couple of extractors
+ // that we will use to get at the velocity
+ // and pressure components of vector-valued
+ // shape functions. Their function and use
+ // is described in detail in the @ref
+ // vector_valued report. Essentially, we
+ // will use them as subscripts on the
+ // FEValues objects below: the FEValues
+ // object describes all vector components
+ // of shape functions, while after
+ // subscription, it will only refer to the
+ // velocities (a set of <code>dim</code>
+ // components starting at component zero)
+ // or the pressure (a scalar component
+ // located at position <code>dim</code>):
+ const FEValuesExtractors::Vector velocities (0);
+ const FEValuesExtractors::Scalar pressure (dim);
+
+ // With all this in place, we can
+ // go on with the loop over all
+ // cells. The body of this loop has
+ // been discussed in the
+ // introduction, and will not be
+ // commented any further here:
+ typename DoFHandler<dim>::active_cell_iterator
+ cell = dof_handler.begin_active(),
+ endc = dof_handler.end();
+ for (; cell!=endc; ++cell)
+ {
+ fe_values.reinit (cell);
+ local_matrix = 0;
+ local_rhs = 0;
+
+ right_hand_side.value_list (fe_values.get_quadrature_points(),
+ rhs_values);
+ k_inverse.value_list (fe_values.get_quadrature_points(),
+ k_inverse_values);
+
+ for (unsigned int q=0; q<n_q_points; ++q)
+ for (unsigned int i=0; i<dofs_per_cell; ++i)
+ {
+ const Tensor<1,dim> phi_i_u = fe_values[velocities].value (i, q);
+ const double div_phi_i_u = fe_values[velocities].divergence (i, q);
+ const double phi_i_p = fe_values[pressure].value (i, q);
+
+ for (unsigned int j=0; j<dofs_per_cell; ++j)
+ {
+ const Tensor<1,dim> phi_j_u = fe_values[velocities].value (j, q);
+ const double div_phi_j_u = fe_values[velocities].divergence (j, q);
+ const double phi_j_p = fe_values[pressure].value (j, q);
+
+ local_matrix(i,j) += (phi_i_u * k_inverse_values[q] * phi_j_u
+ - div_phi_i_u * phi_j_p
+ - phi_i_p * div_phi_j_u)
+ * fe_values.JxW(q);
+ }
+
+ local_rhs(i) += -phi_i_p *
+ rhs_values[q] *
+ fe_values.JxW(q);
+ }
+
+ for (unsigned int face_no=0;
+ face_no<GeometryInfo<dim>::faces_per_cell;
+ ++face_no)
+ if (cell->at_boundary(face_no))
+ {
+ fe_face_values.reinit (cell, face_no);
+
+ pressure_boundary_values
+ .value_list (fe_face_values.get_quadrature_points(),
+ boundary_values);
+
+ for (unsigned int q=0; q<n_face_q_points; ++q)
+ for (unsigned int i=0; i<dofs_per_cell; ++i)
+ local_rhs(i) += -(fe_face_values[velocities].value (i, q) *
+ fe_face_values.normal_vector(q) *
+ boundary_values[q] *
+ fe_face_values.JxW(q));
+ }
+
+ // The final step in the loop
+ // over all cells is to
+ // transfer local contributions
+ // into the global matrix and
+ // right hand side vector. Note
+ // that we use exactly the same
+ // interface as in previous
+ // examples, although we now
+ // use block matrices and
+ // vectors instead of the
+ // regular ones. In other
+ // words, to the outside world,
+ // block objects have the same
+ // interface as matrices and
+ // vectors, but they
+ // additionally allow to access
+ // individual blocks.
+ 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],
+ local_matrix(i,j));
+ for (unsigned int i=0; i<dofs_per_cell; ++i)
+ system_rhs(local_dof_indices[i]) += local_rhs(i);
+ }
+ }
- private:
- const SmartPointer<const BlockSparseMatrix<double> > system_matrix;
- const SmartPointer<const IterativeInverse<Vector<double> > > m_inverse;
-
- mutable Vector<double> tmp1, tmp2;
-};
+ // @sect3{Linear solvers and preconditioners}
+
+ // The linear solvers and
+ // preconditioners we use in this
+ // example have been discussed in
+ // significant detail already in the
+ // introduction. We will therefore
+ // not discuss the rationale for
+ // these classes here any more, but
+ // rather only comment on
+ // implementational aspects.
+
+
+ // @sect4{The <code>SchurComplement</code> class template}
+
+ // The next class is the Schur
+ // complement class. Its rationale
+ // has also been discussed in length
+ // in the introduction. The only
+ // things we would like to note is
+ // that the class, too, is derived
+ // from the <code>Subscriptor</code> class and
+ // that as mentioned above it stores
+ // pointers to the entire block
+ // matrix and the inverse of the mass
+ // matrix block using
+ // <code>SmartPointer</code> objects.
+ //
+ // The <code>vmult</code> function requires
+ // two temporary vectors that we do
+ // not want to re-allocate and free
+ // every time we call this
+ // function. Since here, we have full
+ // control over the use of these
+ // vectors (unlike above, where a
+ // class called by the <code>vmult</code>
+ // function required these vectors,
+ // not the <code>vmult</code> function
+ // itself), we allocate them
+ // directly, rather than going
+ // through the <code>VectorMemory</code>
+ // mechanism. However, again, these
+ // member variables do not carry any
+ // state between successive calls to
+ // the member functions of this class
+ // (i.e., we never care what values
+ // they were set to the last time a
+ // member function was called), we
+ // mark these vectors as <code>mutable</code>.
+ //
+ // The rest of the (short)
+ // implementation of this class is
+ // straightforward if you know the
+ // order of matrix-vector
+ // multiplications performed by the
+ // <code>vmult</code> function:
+ class SchurComplement : public Subscriptor
+ {
+ public:
+ SchurComplement (const BlockSparseMatrix<double> &A,
+ const IterativeInverse<Vector<double> > &Minv);
-SchurComplement::SchurComplement (const BlockSparseMatrix<double> &A,
- const IterativeInverse<Vector<double> > &Minv)
- :
- system_matrix (&A),
- m_inverse (&Minv),
- tmp1 (A.block(0,0).m()),
- tmp2 (A.block(0,0).m())
-{}
+ void vmult (Vector<double> &dst,
+ const Vector<double> &src) const;
+ private:
+ const SmartPointer<const BlockSparseMatrix<double> > system_matrix;
+ const SmartPointer<const IterativeInverse<Vector<double> > > m_inverse;
-void SchurComplement::vmult (Vector<double> &dst,
- const Vector<double> &src) const
-{
- system_matrix->block(0,1).vmult (tmp1, src);
- m_inverse->vmult (tmp2, tmp1);
- system_matrix->block(1,0).vmult (dst, tmp2);
-}
+ mutable Vector<double> tmp1, tmp2;
+ };
- // @sect4{The <code>ApproximateSchurComplement</code> class template}
-
- // The third component of our solver
- // and preconditioner system is the
- // class that approximates the Schur
- // complement so we can form a
- // an InverseIterate
- // object that approximates the
- // inverse of the Schur
- // complement. It follows the same
- // pattern as the Schur complement
- // class, with the only exception
- // that we do not multiply with the
- // inverse mass matrix in <code>vmult</code>,
- // but rather just do a single Jacobi
- // step. Consequently, the class also
- // does not have to store a pointer
- // to an inverse mass matrix object.
- //
- // Since InverseIterate follows the
- // standard convention for matrices,
- // we need to provide a
- // <tt>Tvmult</tt> function here as
- // well.
-class ApproximateSchurComplement : public Subscriptor
-{
- public:
- ApproximateSchurComplement (const BlockSparseMatrix<double> &A);
+ SchurComplement::SchurComplement (const BlockSparseMatrix<double> &A,
+ const IterativeInverse<Vector<double> > &Minv)
+ :
+ system_matrix (&A),
+ m_inverse (&Minv),
+ tmp1 (A.block(0,0).m()),
+ tmp2 (A.block(0,0).m())
+ {}
- void vmult (Vector<double> &dst,
- const Vector<double> &src) const;
- void Tvmult (Vector<double> &dst,
- const Vector<double> &src) const;
- private:
- const SmartPointer<const BlockSparseMatrix<double> > system_matrix;
-
- mutable Vector<double> tmp1, tmp2;
-};
+ void SchurComplement::vmult (Vector<double> &dst,
+ const Vector<double> &src) const
+ {
+ system_matrix->block(0,1).vmult (tmp1, src);
+ m_inverse->vmult (tmp2, tmp1);
+ system_matrix->block(1,0).vmult (dst, tmp2);
+ }
-ApproximateSchurComplement::ApproximateSchurComplement (const BlockSparseMatrix<double> &A)
- :
- system_matrix (&A),
- tmp1 (A.block(0,0).m()),
- tmp2 (A.block(0,0).m())
-{}
+ // @sect4{The <code>ApproximateSchurComplement</code> class template}
+
+ // The third component of our solver
+ // and preconditioner system is the
+ // class that approximates the Schur
+ // complement so we can form a
+ // an InverseIterate
+ // object that approximates the
+ // inverse of the Schur
+ // complement. It follows the same
+ // pattern as the Schur complement
+ // class, with the only exception
+ // that we do not multiply with the
+ // inverse mass matrix in <code>vmult</code>,
+ // but rather just do a single Jacobi
+ // step. Consequently, the class also
+ // does not have to store a pointer
+ // to an inverse mass matrix object.
+ //
+ // Since InverseIterate follows the
+ // standard convention for matrices,
+ // we need to provide a
+ // <tt>Tvmult</tt> function here as
+ // well.
+ class ApproximateSchurComplement : public Subscriptor
+ {
+ public:
+ ApproximateSchurComplement (const BlockSparseMatrix<double> &A);
+ void vmult (Vector<double> &dst,
+ const Vector<double> &src) const;
+ void Tvmult (Vector<double> &dst,
+ const Vector<double> &src) const;
-void ApproximateSchurComplement::vmult (Vector<double> &dst,
- const Vector<double> &src) const
-{
- system_matrix->block(0,1).vmult (tmp1, src);
- system_matrix->block(0,0).precondition_Jacobi (tmp2, tmp1);
- system_matrix->block(1,0).vmult (dst, tmp2);
-}
+ private:
+ const SmartPointer<const BlockSparseMatrix<double> > system_matrix;
+ mutable Vector<double> tmp1, tmp2;
+ };
-void ApproximateSchurComplement::Tvmult (Vector<double> &dst,
- const Vector<double> &src) const
-{
- system_matrix->block(1,0).Tvmult (dst, tmp2);
- system_matrix->block(0,0).precondition_Jacobi (tmp2, tmp1);
- system_matrix->block(0,1).Tvmult (tmp1, src);
-}
+ ApproximateSchurComplement::ApproximateSchurComplement (const BlockSparseMatrix<double> &A)
+ :
+ system_matrix (&A),
+ tmp1 (A.block(0,0).m()),
+ tmp2 (A.block(0,0).m())
+ {}
- // @sect4{MixedLaplace::solve}
-
- // After all these preparations, we
- // can finally write the function
- // that actually solves the linear
- // problem. We will go through the
- // two parts it has that each solve
- // one of the two equations, the
- // first one for the pressure
- // (component 1 of the solution),
- // then the velocities (component 0
- // of the solution). Both parts need
- // an object representing the inverse
- // mass matrix and an auxiliary
- // vector, and we therefore declare
- // these objects at the beginning of
- // this function.
-template <int dim>
-void MixedLaplaceProblem<dim>::solve ()
-{
- PreconditionIdentity identity;
- IterativeInverse<Vector<double> >
- m_inverse;
- m_inverse.initialize(system_matrix.block(0,0), identity);
- m_inverse.solver.select("cg");
- static ReductionControl inner_control(1000, 0., 1.e-13);
- m_inverse.solver.set_control(inner_control);
-
- Vector<double> tmp (solution.block(0).size());
-
- // Now on to the first
- // equation. The right hand side of
- // it is BM^{-1}F-G, which is what
- // we compute in the first few
- // lines. We then declare the
- // objects representing the Schur
- // complement, its approximation,
- // and the inverse of the
- // approximation. Finally, we
- // declare a solver object and hand
- // off all these matrices and
- // vectors to it to compute block 1
- // (the pressure) of the solution:
+ void ApproximateSchurComplement::vmult (Vector<double> &dst,
+ const Vector<double> &src) const
{
- Vector<double> schur_rhs (solution.block(1).size());
-
- m_inverse.vmult (tmp, system_rhs.block(0));
- system_matrix.block(1,0).vmult (schur_rhs, tmp);
- schur_rhs -= system_rhs.block(1);
-
-
- SchurComplement
- schur_complement (system_matrix, m_inverse);
-
- ApproximateSchurComplement
- approximate_schur_complement (system_matrix);
-
- IterativeInverse<Vector<double> >
- preconditioner;
- preconditioner.initialize(approximate_schur_complement, identity);
- preconditioner.solver.select("cg");
- preconditioner.solver.set_control(inner_control);
-
-
- SolverControl solver_control (solution.block(1).size(),
- 1e-12*schur_rhs.l2_norm());
- SolverCG<> cg (solver_control);
-
- cg.solve (schur_complement, solution.block(1), schur_rhs,
- preconditioner);
-
- std::cout << solver_control.last_step()
- << " CG Schur complement iterations to obtain convergence."
- << std::endl;
+ system_matrix->block(0,1).vmult (tmp1, src);
+ system_matrix->block(0,0).precondition_Jacobi (tmp2, tmp1);
+ system_matrix->block(1,0).vmult (dst, tmp2);
}
- // After we have the pressure, we
- // can compute the velocity. The
- // equation reads MU=-B^TP+F, and
- // we solve it by first computing
- // the right hand side, and then
- // multiplying it with the object
- // that represents the inverse of
- // the mass matrix:
+
+ void ApproximateSchurComplement::Tvmult (Vector<double> &dst,
+ const Vector<double> &src) const
{
- system_matrix.block(0,1).vmult (tmp, solution.block(1));
- tmp *= -1;
- tmp += system_rhs.block(0);
-
- m_inverse.vmult (solution.block(0), tmp);
+ system_matrix->block(1,0).Tvmult (dst, tmp2);
+ system_matrix->block(0,0).precondition_Jacobi (tmp2, tmp1);
+ system_matrix->block(0,1).Tvmult (tmp1, src);
}
-}
- // @sect3{MixedLaplaceProblem class implementation (continued)}
-
- // @sect4{MixedLaplace::compute_errors}
-
- // After we have dealt with the
- // linear solver and preconditioners,
- // we continue with the
- // implementation of our main
- // class. In particular, the next
- // task is to compute the errors in
- // our numerical solution, in both
- // the pressures as well as
- // velocities.
- //
- // To compute errors in the solution,
- // we have already introduced the
- // <code>VectorTools::integrate_difference</code>
- // function in step-7 and
- // step-11. However, there we only
- // dealt with scalar solutions,
- // whereas here we have a
- // vector-valued solution with
- // components that even denote
- // different quantities and may have
- // different orders of convergence
- // (this isn't the case here, by
- // choice of the used finite
- // elements, but is frequently the
- // case in mixed finite element
- // applications). What we therefore
- // have to do is to `mask' the
- // components that we are interested
- // in. This is easily done: the
- // <code>VectorTools::integrate_difference</code>
- // function takes as its last
- // argument a pointer to a weight
- // function (the parameter defaults
- // to the null pointer, meaning unit
- // weights). What we simply have to
- // do is to pass a function object
- // that equals one in the components
- // we are interested in, and zero in
- // the other ones. For example, to
- // compute the pressure error, we
- // should pass a function that
- // represents the constant vector
- // with a unit value in component
- // <code>dim</code>, whereas for the velocity
- // the constant vector should be one
- // in the first <code>dim</code> components,
- // and zero in the location of the
- // pressure.
- //
- // In deal.II, the
- // <code>ComponentSelectFunction</code> does
- // exactly this: it wants to know how
- // many vector components the
- // function it is to represent should
- // have (in our case this would be
- // <code>dim+1</code>, for the joint
- // velocity-pressure space) and which
- // individual or range of components
- // should be equal to one. We
- // therefore define two such masks at
- // the beginning of the function,
- // following by an object
- // representing the exact solution
- // and a vector in which we will
- // store the cellwise errors as
- // computed by
- // <code>integrate_difference</code>:
-template <int dim>
-void MixedLaplaceProblem<dim>::compute_errors () const
-{
- const ComponentSelectFunction<dim>
- pressure_mask (dim, dim+1);
- const ComponentSelectFunction<dim>
- velocity_mask(std::make_pair(0, dim), dim+1);
-
- ExactSolution<dim> exact_solution;
- Vector<double> cellwise_errors (triangulation.n_active_cells());
-
- // As already discussed in step-7,
- // we have to realize that it is
- // impossible to integrate the
- // errors exactly. All we can do is
- // approximate this integral using
- // quadrature. This actually
- // presents a slight twist here: if
- // we naively chose an object of
- // type <code>QGauss@<dim@>(degree+1)</code>
- // as one may be inclined to do
- // (this is what we used for
- // integrating the linear system),
- // one realizes that the error is
- // very small and does not follow
- // the expected convergence curves
- // at all. What is happening is
- // that for the mixed finite
- // elements used here, the Gauss
- // points happen to be
- // superconvergence points in which
- // the pointwise error is much
- // smaller (and converges with
- // higher order) than anywhere
- // else. These are therefore not
- // particularly good points for
- // ingration. To avoid this
- // problem, we simply use a
- // trapezoidal rule and iterate it
- // <code>degree+2</code> times in each
- // coordinate direction (again as
- // explained in step-7):
- QTrapez<1> q_trapez;
- QIterated<dim> quadrature (q_trapez, degree+2);
-
- // With this, we can then let the
- // library compute the errors and
- // output them to the screen:
- VectorTools::integrate_difference (dof_handler, solution, exact_solution,
- cellwise_errors, quadrature,
- VectorTools::L2_norm,
- &pressure_mask);
- const double p_l2_error = cellwise_errors.l2_norm();
-
- VectorTools::integrate_difference (dof_handler, solution, exact_solution,
- cellwise_errors, quadrature,
- VectorTools::L2_norm,
- &velocity_mask);
- const double u_l2_error = cellwise_errors.l2_norm();
-
- std::cout << "Errors: ||e_p||_L2 = " << p_l2_error
- << ", ||e_u||_L2 = " << u_l2_error
- << std::endl;
-}
+ // @sect4{MixedLaplace::solve}
+
+ // After all these preparations, we
+ // can finally write the function
+ // that actually solves the linear
+ // problem. We will go through the
+ // two parts it has that each solve
+ // one of the two equations, the
+ // first one for the pressure
+ // (component 1 of the solution),
+ // then the velocities (component 0
+ // of the solution). Both parts need
+ // an object representing the inverse
+ // mass matrix and an auxiliary
+ // vector, and we therefore declare
+ // these objects at the beginning of
+ // this function.
+ template <int dim>
+ void MixedLaplaceProblem<dim>::solve ()
+ {
+ PreconditionIdentity identity;
+ IterativeInverse<Vector<double> >
+ m_inverse;
+ m_inverse.initialize(system_matrix.block(0,0), identity);
+ m_inverse.solver.select("cg");
+ static ReductionControl inner_control(1000, 0., 1.e-13);
+ m_inverse.solver.set_control(inner_control);
+
+ Vector<double> tmp (solution.block(0).size());
+
+ // Now on to the first
+ // equation. The right hand side of
+ // it is BM^{-1}F-G, which is what
+ // we compute in the first few
+ // lines. We then declare the
+ // objects representing the Schur
+ // complement, its approximation,
+ // and the inverse of the
+ // approximation. Finally, we
+ // declare a solver object and hand
+ // off all these matrices and
+ // vectors to it to compute block 1
+ // (the pressure) of the solution:
+ {
+ Vector<double> schur_rhs (solution.block(1).size());
- // @sect4{MixedLaplace::output_results}
-
- // The last interesting function is
- // the one in which we generate
- // graphical output. Everything here
- // looks obvious and familiar. Note
- // how we construct unique names for
- // all the solution variables at the
- // beginning, like we did in step-8
- // and other programs later on. The
- // only thing worth mentioning is
- // that for higher order elements, in
- // seems inappropriate to only show a
- // single bilinear quadrilateral per
- // cell in the graphical output. We
- // therefore generate patches of size
- // (degree+1)x(degree+1) to capture
- // the full information content of
- // the solution. See the step-7
- // tutorial program for more
- // information on this.
- //
- // Note that we output the <code>dim+1</code>
- // components of the solution vector as a
- // collection of individual scalars
- // here. Most visualization programs will
- // then only offer to visualize them
- // individually, rather than allowing us to
- // plot the flow field as a vector
- // field. However, as explained in the
- // corresponding function of step-22 or the
- // @ref VVOutput "Generating graphical output"
- // section of the @ref vector_valued module,
- // instructing the DataOut class to identify
- // components of the FESystem object as
- // elements of a <code>dim</code>-dimensional
- // vector is not actually very difficult and
- // will then allow us to show results as
- // vector plots. We skip this here for
- // simplicity and refer to the links above
- // for more information.
-template <int dim>
-void MixedLaplaceProblem<dim>::output_results () const
-{
- std::vector<std::string> solution_names;
- switch (dim)
+ m_inverse.vmult (tmp, system_rhs.block(0));
+ system_matrix.block(1,0).vmult (schur_rhs, tmp);
+ schur_rhs -= system_rhs.block(1);
+
+
+ SchurComplement
+ schur_complement (system_matrix, m_inverse);
+
+ ApproximateSchurComplement
+ approximate_schur_complement (system_matrix);
+
+ IterativeInverse<Vector<double> >
+ preconditioner;
+ preconditioner.initialize(approximate_schur_complement, identity);
+ preconditioner.solver.select("cg");
+ preconditioner.solver.set_control(inner_control);
+
+
+ SolverControl solver_control (solution.block(1).size(),
+ 1e-12*schur_rhs.l2_norm());
+ SolverCG<> cg (solver_control);
+
+ cg.solve (schur_complement, solution.block(1), schur_rhs,
+ preconditioner);
+
+ std::cout << solver_control.last_step()
+ << " CG Schur complement iterations to obtain convergence."
+ << std::endl;
+ }
+
+ // After we have the pressure, we
+ // can compute the velocity. The
+ // equation reads MU=-B^TP+F, and
+ // we solve it by first computing
+ // the right hand side, and then
+ // multiplying it with the object
+ // that represents the inverse of
+ // the mass matrix:
{
- case 2:
- solution_names.push_back ("u");
- solution_names.push_back ("v");
- solution_names.push_back ("p");
- break;
-
- case 3:
- solution_names.push_back ("u");
- solution_names.push_back ("v");
- solution_names.push_back ("w");
- solution_names.push_back ("p");
- break;
-
- default:
- Assert (false, ExcNotImplemented());
+ system_matrix.block(0,1).vmult (tmp, solution.block(1));
+ tmp *= -1;
+ tmp += system_rhs.block(0);
+
+ m_inverse.vmult (solution.block(0), tmp);
}
-
-
- DataOut<dim> data_out;
+ }
+
+
+ // @sect3{MixedLaplaceProblem class implementation (continued)}
+
+ // @sect4{MixedLaplace::compute_errors}
+
+ // After we have dealt with the
+ // linear solver and preconditioners,
+ // we continue with the
+ // implementation of our main
+ // class. In particular, the next
+ // task is to compute the errors in
+ // our numerical solution, in both
+ // the pressures as well as
+ // velocities.
+ //
+ // To compute errors in the solution,
+ // we have already introduced the
+ // <code>VectorTools::integrate_difference</code>
+ // function in step-7 and
+ // step-11. However, there we only
+ // dealt with scalar solutions,
+ // whereas here we have a
+ // vector-valued solution with
+ // components that even denote
+ // different quantities and may have
+ // different orders of convergence
+ // (this isn't the case here, by
+ // choice of the used finite
+ // elements, but is frequently the
+ // case in mixed finite element
+ // applications). What we therefore
+ // have to do is to `mask' the
+ // components that we are interested
+ // in. This is easily done: the
+ // <code>VectorTools::integrate_difference</code>
+ // function takes as its last
+ // argument a pointer to a weight
+ // function (the parameter defaults
+ // to the null pointer, meaning unit
+ // weights). What we simply have to
+ // do is to pass a function object
+ // that equals one in the components
+ // we are interested in, and zero in
+ // the other ones. For example, to
+ // compute the pressure error, we
+ // should pass a function that
+ // represents the constant vector
+ // with a unit value in component
+ // <code>dim</code>, whereas for the velocity
+ // the constant vector should be one
+ // in the first <code>dim</code> components,
+ // and zero in the location of the
+ // pressure.
+ //
+ // In deal.II, the
+ // <code>ComponentSelectFunction</code> does
+ // exactly this: it wants to know how
+ // many vector components the
+ // function it is to represent should
+ // have (in our case this would be
+ // <code>dim+1</code>, for the joint
+ // velocity-pressure space) and which
+ // individual or range of components
+ // should be equal to one. We
+ // therefore define two such masks at
+ // the beginning of the function,
+ // following by an object
+ // representing the exact solution
+ // and a vector in which we will
+ // store the cellwise errors as
+ // computed by
+ // <code>integrate_difference</code>:
+ template <int dim>
+ void MixedLaplaceProblem<dim>::compute_errors () const
+ {
+ const ComponentSelectFunction<dim>
+ pressure_mask (dim, dim+1);
+ const ComponentSelectFunction<dim>
+ velocity_mask(std::make_pair(0, dim), dim+1);
+
+ ExactSolution<dim> exact_solution;
+ Vector<double> cellwise_errors (triangulation.n_active_cells());
+
+ // As already discussed in step-7,
+ // we have to realize that it is
+ // impossible to integrate the
+ // errors exactly. All we can do is
+ // approximate this integral using
+ // quadrature. This actually
+ // presents a slight twist here: if
+ // we naively chose an object of
+ // type <code>QGauss@<dim@>(degree+1)</code>
+ // as one may be inclined to do
+ // (this is what we used for
+ // integrating the linear system),
+ // one realizes that the error is
+ // very small and does not follow
+ // the expected convergence curves
+ // at all. What is happening is
+ // that for the mixed finite
+ // elements used here, the Gauss
+ // points happen to be
+ // superconvergence points in which
+ // the pointwise error is much
+ // smaller (and converges with
+ // higher order) than anywhere
+ // else. These are therefore not
+ // particularly good points for
+ // ingration. To avoid this
+ // problem, we simply use a
+ // trapezoidal rule and iterate it
+ // <code>degree+2</code> times in each
+ // coordinate direction (again as
+ // explained in step-7):
+ QTrapez<1> q_trapez;
+ QIterated<dim> quadrature (q_trapez, degree+2);
+
+ // With this, we can then let the
+ // library compute the errors and
+ // output them to the screen:
+ VectorTools::integrate_difference (dof_handler, solution, exact_solution,
+ cellwise_errors, quadrature,
+ VectorTools::L2_norm,
+ &pressure_mask);
+ const double p_l2_error = cellwise_errors.l2_norm();
+
+ VectorTools::integrate_difference (dof_handler, solution, exact_solution,
+ cellwise_errors, quadrature,
+ VectorTools::L2_norm,
+ &velocity_mask);
+ const double u_l2_error = cellwise_errors.l2_norm();
+
+ std::cout << "Errors: ||e_p||_L2 = " << p_l2_error
+ << ", ||e_u||_L2 = " << u_l2_error
+ << std::endl;
+ }
+
- data_out.attach_dof_handler (dof_handler);
- data_out.add_data_vector (solution, solution_names);
+ // @sect4{MixedLaplace::output_results}
+
+ // The last interesting function is
+ // the one in which we generate
+ // graphical output. Everything here
+ // looks obvious and familiar. Note
+ // how we construct unique names for
+ // all the solution variables at the
+ // beginning, like we did in step-8
+ // and other programs later on. The
+ // only thing worth mentioning is
+ // that for higher order elements, in
+ // seems inappropriate to only show a
+ // single bilinear quadrilateral per
+ // cell in the graphical output. We
+ // therefore generate patches of size
+ // (degree+1)x(degree+1) to capture
+ // the full information content of
+ // the solution. See the step-7
+ // tutorial program for more
+ // information on this.
+ //
+ // Note that we output the <code>dim+1</code>
+ // components of the solution vector as a
+ // collection of individual scalars
+ // here. Most visualization programs will
+ // then only offer to visualize them
+ // individually, rather than allowing us to
+ // plot the flow field as a vector
+ // field. However, as explained in the
+ // corresponding function of step-22 or the
+ // @ref VVOutput "Generating graphical output"
+ // section of the @ref vector_valued module,
+ // instructing the DataOut class to identify
+ // components of the FESystem object as
+ // elements of a <code>dim</code>-dimensional
+ // vector is not actually very difficult and
+ // will then allow us to show results as
+ // vector plots. We skip this here for
+ // simplicity and refer to the links above
+ // for more information.
+ template <int dim>
+ void MixedLaplaceProblem<dim>::output_results () const
+ {
+ std::vector<std::string> solution_names;
+ switch (dim)
+ {
+ case 2:
+ solution_names.push_back ("u");
+ solution_names.push_back ("v");
+ solution_names.push_back ("p");
+ break;
- data_out.build_patches (degree+1);
+ case 3:
+ solution_names.push_back ("u");
+ solution_names.push_back ("v");
+ solution_names.push_back ("w");
+ solution_names.push_back ("p");
+ break;
- std::ofstream output ("solution.gmv");
- data_out.write_gmv (output);
-}
+ default:
+ Assert (false, ExcNotImplemented());
+ }
+ DataOut<dim> data_out;
- // @sect4{MixedLaplace::run}
+ data_out.attach_dof_handler (dof_handler);
+ data_out.add_data_vector (solution, solution_names);
- // This is the final function of our
- // main class. It's only job is to
- // call the other functions in their
- // natural order:
-template <int dim>
-void MixedLaplaceProblem<dim>::run ()
-{
- make_grid_and_dofs();
- assemble_system ();
- solve ();
- compute_errors ();
- output_results ();
+ data_out.build_patches (degree+1);
+
+ std::ofstream output ("solution.gmv");
+ data_out.write_gmv (output);
+ }
+
+
+
+ // @sect4{MixedLaplace::run}
+
+ // This is the final function of our
+ // main class. It's only job is to
+ // call the other functions in their
+ // natural order:
+ template <int dim>
+ void MixedLaplaceProblem<dim>::run ()
+ {
+ make_grid_and_dofs();
+ assemble_system ();
+ solve ();
+ compute_errors ();
+ output_results ();
+ }
}
-
+
// @sect3{The <code>main</code> function}
// The main function we stole from
// to the constructor of the mixed
// laplace problem (here, we use
// zero-th order elements).
-int main ()
+int main ()
{
try
{
+ using namespace dealii;
+ using namespace Step20;
+
deallog.depth_console (0);
MixedLaplaceProblem<2> mixed_laplace_problem(0);
<< "Aborting!" << std::endl
<< "----------------------------------------------------"
<< std::endl;
-
+
return 1;
}
- catch (...)
+ catch (...)
{
std::cerr << std::endl << std::endl
<< "----------------------------------------------------"
// The last step is as in all
// previous programs:
-using namespace dealii;
-
-
- // @sect3{The <code>TwoPhaseFlowProblem</code> class}
-
- // This is the main class of the program. It
- // is close to the one of step-20, but with a
- // few additional functions:
- //
- // <ul>
- // <li><code>assemble_rhs_S</code> assembles the
- // right hand side of the saturation
- // equation. As explained in the
- // introduction, this can't be integrated
- // into <code>assemble_rhs</code> since it depends
- // on the velocity that is computed in the
- // first part of the time step.
- //
- // <li><code>get_maximal_velocity</code> does as its
- // name suggests. This function is used in
- // the computation of the time step size.
- //
- // <li><code>project_back_saturation</code> resets
- // all saturation degrees of freedom with
- // values less than zero to zero, and all
- // those with saturations greater than one
- // to one.
- // </ul>
- //
- // The rest of the class should be pretty
- // much obvious. The <code>viscosity</code> variable
- // stores the viscosity $\mu$ that enters
- // several of the formulas in the nonlinear
- // equations.
-template <int dim>
-class TwoPhaseFlowProblem
+namespace Step21
{
- public:
- TwoPhaseFlowProblem (const unsigned int degree);
- void run ();
-
- private:
- void make_grid_and_dofs ();
- void assemble_system ();
- void assemble_rhs_S ();
- double get_maximal_velocity () const;
- void solve ();
- void project_back_saturation ();
- void output_results () const;
-
- const unsigned int degree;
-
- Triangulation<dim> triangulation;
- FESystem<dim> fe;
- DoFHandler<dim> dof_handler;
-
- BlockSparsityPattern sparsity_pattern;
- BlockSparseMatrix<double> system_matrix;
-
- const unsigned int n_refinement_steps;
-
- double time_step;
- unsigned int timestep_number;
- double viscosity;
-
- BlockVector<double> solution;
- BlockVector<double> old_solution;
- BlockVector<double> system_rhs;
-};
-
-
- // @sect3{Equation data}
-
- // @sect4{Pressure right hand side}
- // At present, the right hand side of the
- // pressure equation is simply the zero
- // function. However, the rest of the program
- // is fully equipped to deal with anything
- // else, if this is desired:
-template <int dim>
-class PressureRightHandSide : public Function<dim>
-{
- public:
- PressureRightHandSide () : Function<dim>(1) {}
+ using namespace dealii;
- virtual double value (const Point<dim> &p,
- const unsigned int component = 0) const;
-};
+ // @sect3{The <code>TwoPhaseFlowProblem</code> class}
+ // This is the main class of the program. It
+ // is close to the one of step-20, but with a
+ // few additional functions:
+ //
+ // <ul>
+ // <li><code>assemble_rhs_S</code> assembles the
+ // right hand side of the saturation
+ // equation. As explained in the
+ // introduction, this can't be integrated
+ // into <code>assemble_rhs</code> since it depends
+ // on the velocity that is computed in the
+ // first part of the time step.
+ //
+ // <li><code>get_maximal_velocity</code> does as its
+ // name suggests. This function is used in
+ // the computation of the time step size.
+ //
+ // <li><code>project_back_saturation</code> resets
+ // all saturation degrees of freedom with
+ // values less than zero to zero, and all
+ // those with saturations greater than one
+ // to one.
+ // </ul>
+ //
+ // The rest of the class should be pretty
+ // much obvious. The <code>viscosity</code> variable
+ // stores the viscosity $\mu$ that enters
+ // several of the formulas in the nonlinear
+ // equations.
+ template <int dim>
+ class TwoPhaseFlowProblem
+ {
+ public:
+ TwoPhaseFlowProblem (const unsigned int degree);
+ void run ();
-template <int dim>
-double
-PressureRightHandSide<dim>::value (const Point<dim> &/*p*/,
- const unsigned int /*component*/) const
-{
- return 0;
-}
+ private:
+ void make_grid_and_dofs ();
+ void assemble_system ();
+ void assemble_rhs_S ();
+ double get_maximal_velocity () const;
+ void solve ();
+ void project_back_saturation ();
+ void output_results () const;
+ const unsigned int degree;
- // @sect4{Pressure boundary values}
- // The next are pressure boundary values. As
- // mentioned in the introduction, we choose a
- // linear pressure field:
-template <int dim>
-class PressureBoundaryValues : public Function<dim>
-{
- public:
- PressureBoundaryValues () : Function<dim>(1) {}
+ Triangulation<dim> triangulation;
+ FESystem<dim> fe;
+ DoFHandler<dim> dof_handler;
- virtual double value (const Point<dim> &p,
- const unsigned int component = 0) const;
-};
+ BlockSparsityPattern sparsity_pattern;
+ BlockSparseMatrix<double> system_matrix;
+ const unsigned int n_refinement_steps;
-template <int dim>
-double
-PressureBoundaryValues<dim>::value (const Point<dim> &p,
- const unsigned int /*component*/) const
-{
- return 1-p[0];
-}
+ double time_step;
+ unsigned int timestep_number;
+ double viscosity;
+ BlockVector<double> solution;
+ BlockVector<double> old_solution;
+ BlockVector<double> system_rhs;
+ };
- // @sect4{Saturation boundary values}
- // Then we also need boundary values on the
- // inflow portions of the boundary. The
- // question whether something is an inflow
- // part is decided when assembling the right
- // hand side, we only have to provide a
- // functional description of the boundary
- // values. This is as explained in the
- // introduction:
-template <int dim>
-class SaturationBoundaryValues : public Function<dim>
-{
- public:
- SaturationBoundaryValues () : Function<dim>(1) {}
+ // @sect3{Equation data}
- virtual double value (const Point<dim> &p,
- const unsigned int component = 0) const;
-};
+ // @sect4{Pressure right hand side}
+ // At present, the right hand side of the
+ // pressure equation is simply the zero
+ // function. However, the rest of the program
+ // is fully equipped to deal with anything
+ // else, if this is desired:
+ template <int dim>
+ class PressureRightHandSide : public Function<dim>
+ {
+ public:
+ PressureRightHandSide () : Function<dim>(1) {}
+ virtual double value (const Point<dim> &p,
+ const unsigned int component = 0) const;
+ };
-template <int dim>
-double
-SaturationBoundaryValues<dim>::value (const Point<dim> &p,
- const unsigned int /*component*/) const
-{
- if (p[0] == 0)
- return 1;
- else
- return 0;
-}
+ template <int dim>
+ double
+ PressureRightHandSide<dim>::value (const Point<dim> &/*p*/,
+ const unsigned int /*component*/) const
+ {
+ return 0;
+ }
- // @sect4{Initial data}
-
- // Finally, we need initial data. In reality,
- // we only need initial data for the
- // saturation, but we are lazy, so we will
- // later, before the first time step, simply
- // interpolate the entire solution for the
- // previous time step from a function that
- // contains all vector components.
- //
- // We therefore simply create a function that
- // returns zero in all components. We do that
- // by simply forward every function to the
- // ZeroFunction class. Why not use that right
- // away in the places of this program where
- // we presently use the <code>InitialValues</code>
- // class? Because this way it is simpler to
- // later go back and choose a different
- // function for initial values.
-template <int dim>
-class InitialValues : public Function<dim>
-{
- public:
- InitialValues () : Function<dim>(dim+2) {}
+ // @sect4{Pressure boundary values}
+ // The next are pressure boundary values. As
+ // mentioned in the introduction, we choose a
+ // linear pressure field:
+ template <int dim>
+ class PressureBoundaryValues : public Function<dim>
+ {
+ public:
+ PressureBoundaryValues () : 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;
-};
+ template <int dim>
+ double
+ PressureBoundaryValues<dim>::value (const Point<dim> &p,
+ const unsigned int /*component*/) const
+ {
+ return 1-p[0];
+ }
-template <int dim>
-double
-InitialValues<dim>::value (const Point<dim> &p,
- const unsigned int component) const
-{
- return ZeroFunction<dim>(dim+2).value (p, component);
-}
+ // @sect4{Saturation boundary values}
+ // Then we also need boundary values on the
+ // inflow portions of the boundary. The
+ // question whether something is an inflow
+ // part is decided when assembling the right
+ // hand side, we only have to provide a
+ // functional description of the boundary
+ // values. This is as explained in the
+ // introduction:
+ template <int dim>
+ class SaturationBoundaryValues : public Function<dim>
+ {
+ public:
+ SaturationBoundaryValues () : Function<dim>(1) {}
-template <int dim>
-void
-InitialValues<dim>::vector_value (const Point<dim> &p,
- Vector<double> &values) const
-{
- ZeroFunction<dim>(dim+2).vector_value (p, values);
-}
+ virtual double value (const Point<dim> &p,
+ const unsigned int component = 0) const;
+ };
+ template <int dim>
+ double
+ SaturationBoundaryValues<dim>::value (const Point<dim> &p,
+ const unsigned int /*component*/) const
+ {
+ if (p[0] == 0)
+ return 1;
+ else
+ return 0;
+ }
- // @sect3{The inverse permeability tensor}
- // As announced in the introduction, we
- // implement two different permeability
- // tensor fields. Each of them we put into a
- // namespace of its own, so that it will be
- // easy later to replace use of one by the
- // other in the code.
- // @sect4{Single curving crack permeability}
+ // @sect4{Initial data}
- // The first function for the
- // permeability was the one that
- // models a single curving crack. It
- // was already used at the end of
- // step-20, and its functional form
- // is given in the introduction of
- // the present tutorial program. As
- // in some previous programs, we have
- // to declare a (seemingly
- // unnecessary) default constructor
- // of the KInverse class to avoid
- // warnings from some compilers:
-namespace SingleCurvingCrack
-{
+ // Finally, we need initial data. In reality,
+ // we only need initial data for the
+ // saturation, but we are lazy, so we will
+ // later, before the first time step, simply
+ // interpolate the entire solution for the
+ // previous time step from a function that
+ // contains all vector components.
+ //
+ // We therefore simply create a function that
+ // returns zero in all components. We do that
+ // by simply forward every function to the
+ // ZeroFunction class. Why not use that right
+ // away in the places of this program where
+ // we presently use the <code>InitialValues</code>
+ // class? Because this way it is simpler to
+ // later go back and choose a different
+ // function for initial values.
template <int dim>
- class KInverse : public TensorFunction<2,dim>
+ class InitialValues : public Function<dim>
{
public:
- KInverse ()
- :
- TensorFunction<2,dim> ()
- {}
+ InitialValues () : Function<dim>(dim+2) {}
+
+ 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 value_list (const std::vector<Point<dim> > &points,
- std::vector<Tensor<2,dim> > &values) const;
};
+ template <int dim>
+ double
+ InitialValues<dim>::value (const Point<dim> &p,
+ const unsigned int component) const
+ {
+ return ZeroFunction<dim>(dim+2).value (p, component);
+ }
+
+
template <int dim>
void
- KInverse<dim>::value_list (const std::vector<Point<dim> > &points,
- std::vector<Tensor<2,dim> > &values) const
+ InitialValues<dim>::vector_value (const Point<dim> &p,
+ Vector<double> &values) const
{
- Assert (points.size() == values.size(),
- ExcDimensionMismatch (points.size(), values.size()));
+ ZeroFunction<dim>(dim+2).vector_value (p, values);
+ }
- for (unsigned int p=0; p<points.size(); ++p)
- {
- values[p].clear ();
- const double distance_to_flowline
- = std::fabs(points[p][1]-0.5-0.1*std::sin(10*points[p][0]));
- const double permeability = std::max(std::exp(-(distance_to_flowline*
- distance_to_flowline)
- / (0.1 * 0.1)),
- 0.01);
- for (unsigned int d=0; d<dim; ++d)
- values[p][d][d] = 1./permeability;
- }
- }
-}
+ // @sect3{The inverse permeability tensor}
+ // As announced in the introduction, we
+ // implement two different permeability
+ // tensor fields. Each of them we put into a
+ // namespace of its own, so that it will be
+ // easy later to replace use of one by the
+ // other in the code.
- // @sect4{Random medium permeability}
-
- // This function does as announced in the
- // introduction, i.e. it creates an overlay
- // of exponentials at random places. There is
- // one thing worth considering for this
- // class. The issue centers around the
- // problem that the class creates the centers
- // of the exponentials using a random
- // function. If we therefore created the
- // centers each time we create an object of
- // the present type, we would get a different
- // list of centers each time. That's not what
- // we expect from classes of this type: they
- // should reliably represent the same
- // function.
- //
- // The solution to this problem is to make
- // the list of centers a static member
- // variable of this class, i.e. there exists
- // exactly one such variable for the entire
- // program, rather than for each object of
- // this type. That's exactly what we are
- // going to do.
- //
- // The next problem, however, is that we need
- // a way to initialize this variable. Since
- // this variable is initialized at the
- // beginning of the program, we can't use a
- // regular member function for that since
- // there may not be an object of this type
- // around at the time. The C++ standard
- // therefore says that only non-member and
- // static member functions can be used to
- // initialize a static variable. We use the
- // latter possibility by defining a function
- // <code>get_centers</code> that computes the list of
- // center points when called.
- //
- // Note that this class works just fine in
- // both 2d and 3d, with the only difference
- // being that we use more points in 3d: by
- // experimenting we find that we need more
- // exponentials in 3d than in 2d (we have
- // more ground to cover, after all, if we
- // want to keep the distance between centers
- // roughly equal), so we choose 40 in 2d and
- // 100 in 3d. For any other dimension, the
- // function does presently not know what to
- // do so simply throws an exception
- // indicating exactly this.
-namespace RandomMedium
-{
- template <int dim>
- class KInverse : public TensorFunction<2,dim>
- {
- public:
- KInverse ()
- :
- TensorFunction<2,dim> ()
- {}
+ // @sect4{Single curving crack permeability}
- virtual void value_list (const std::vector<Point<dim> > &points,
- std::vector<Tensor<2,dim> > &values) const;
+ // The first function for the
+ // permeability was the one that
+ // models a single curving crack. It
+ // was already used at the end of
+ // step-20, and its functional form
+ // is given in the introduction of
+ // the present tutorial program. As
+ // in some previous programs, we have
+ // to declare a (seemingly
+ // unnecessary) default constructor
+ // of the KInverse class to avoid
+ // warnings from some compilers:
+ namespace SingleCurvingCrack
+ {
+ template <int dim>
+ class KInverse : public TensorFunction<2,dim>
+ {
+ public:
+ KInverse ()
+ :
+ TensorFunction<2,dim> ()
+ {}
- private:
- static std::vector<Point<dim> > centers;
+ virtual void value_list (const std::vector<Point<dim> > &points,
+ std::vector<Tensor<2,dim> > &values) const;
+ };
- static std::vector<Point<dim> > get_centers ();
- };
+ template <int dim>
+ void
+ KInverse<dim>::value_list (const std::vector<Point<dim> > &points,
+ std::vector<Tensor<2,dim> > &values) const
+ {
+ Assert (points.size() == values.size(),
+ ExcDimensionMismatch (points.size(), values.size()));
+ for (unsigned int p=0; p<points.size(); ++p)
+ {
+ values[p].clear ();
- template <int dim>
- std::vector<Point<dim> >
- KInverse<dim>::centers = KInverse<dim>::get_centers();
+ const double distance_to_flowline
+ = std::fabs(points[p][1]-0.5-0.1*std::sin(10*points[p][0]));
+ const double permeability = std::max(std::exp(-(distance_to_flowline*
+ distance_to_flowline)
+ / (0.1 * 0.1)),
+ 0.01);
- template <int dim>
- std::vector<Point<dim> >
- KInverse<dim>::get_centers ()
- {
- const unsigned int N = (dim == 2 ?
- 40 :
- (dim == 3 ?
- 100 :
- throw ExcNotImplemented()));
-
- std::vector<Point<dim> > centers_list (N);
- for (unsigned int i=0; i<N; ++i)
- for (unsigned int d=0; d<dim; ++d)
- centers_list[i][d] = static_cast<double>(rand())/RAND_MAX;
-
- return centers_list;
+ for (unsigned int d=0; d<dim; ++d)
+ values[p][d][d] = 1./permeability;
+ }
+ }
}
-
- template <int dim>
- void
- KInverse<dim>::value_list (const std::vector<Point<dim> > &points,
- std::vector<Tensor<2,dim> > &values) const
+ // @sect4{Random medium permeability}
+
+ // This function does as announced in the
+ // introduction, i.e. it creates an overlay
+ // of exponentials at random places. There is
+ // one thing worth considering for this
+ // class. The issue centers around the
+ // problem that the class creates the centers
+ // of the exponentials using a random
+ // function. If we therefore created the
+ // centers each time we create an object of
+ // the present type, we would get a different
+ // list of centers each time. That's not what
+ // we expect from classes of this type: they
+ // should reliably represent the same
+ // function.
+ //
+ // The solution to this problem is to make
+ // the list of centers a static member
+ // variable of this class, i.e. there exists
+ // exactly one such variable for the entire
+ // program, rather than for each object of
+ // this type. That's exactly what we are
+ // going to do.
+ //
+ // The next problem, however, is that we need
+ // a way to initialize this variable. Since
+ // this variable is initialized at the
+ // beginning of the program, we can't use a
+ // regular member function for that since
+ // there may not be an object of this type
+ // around at the time. The C++ standard
+ // therefore says that only non-member and
+ // static member functions can be used to
+ // initialize a static variable. We use the
+ // latter possibility by defining a function
+ // <code>get_centers</code> that computes the list of
+ // center points when called.
+ //
+ // Note that this class works just fine in
+ // both 2d and 3d, with the only difference
+ // being that we use more points in 3d: by
+ // experimenting we find that we need more
+ // exponentials in 3d than in 2d (we have
+ // more ground to cover, after all, if we
+ // want to keep the distance between centers
+ // roughly equal), so we choose 40 in 2d and
+ // 100 in 3d. For any other dimension, the
+ // function does presently not know what to
+ // do so simply throws an exception
+ // indicating exactly this.
+ namespace RandomMedium
{
- Assert (points.size() == values.size(),
- ExcDimensionMismatch (points.size(), values.size()));
+ template <int dim>
+ class KInverse : public TensorFunction<2,dim>
+ {
+ public:
+ KInverse ()
+ :
+ TensorFunction<2,dim> ()
+ {}
- for (unsigned int p=0; p<points.size(); ++p)
- {
- values[p].clear ();
+ virtual void value_list (const std::vector<Point<dim> > &points,
+ std::vector<Tensor<2,dim> > &values) const;
- double permeability = 0;
- for (unsigned int i=0; i<centers.size(); ++i)
- permeability += std::exp(-(points[p]-centers[i]).square()
- / (0.05 * 0.05));
+ private:
+ static std::vector<Point<dim> > centers;
- const double normalized_permeability
- = std::min (std::max(permeability, 0.01), 4.);
+ static std::vector<Point<dim> > get_centers ();
+ };
- for (unsigned int d=0; d<dim; ++d)
- values[p][d][d] = 1./normalized_permeability;
- }
- }
-}
+ template <int dim>
+ std::vector<Point<dim> >
+ KInverse<dim>::centers = KInverse<dim>::get_centers();
- // @sect3{The inverse mobility and saturation functions}
- // There are two more pieces of data that we
- // need to describe, namely the inverse
- // mobility function and the saturation
- // curve. Their form is also given in the
- // introduction:
-double mobility_inverse (const double S,
- const double viscosity)
-{
- return 1.0 /(1.0/viscosity * S * S + (1-S) * (1-S));
-}
+ template <int dim>
+ std::vector<Point<dim> >
+ KInverse<dim>::get_centers ()
+ {
+ const unsigned int N = (dim == 2 ?
+ 40 :
+ (dim == 3 ?
+ 100 :
+ throw ExcNotImplemented()));
+
+ std::vector<Point<dim> > centers_list (N);
+ for (unsigned int i=0; i<N; ++i)
+ for (unsigned int d=0; d<dim; ++d)
+ centers_list[i][d] = static_cast<double>(rand())/RAND_MAX;
+
+ return centers_list;
+ }
-double f_saturation (const double S,
- const double viscosity)
-{
- return S*S /( S * S +viscosity * (1-S) * (1-S));
-}
+ template <int dim>
+ void
+ KInverse<dim>::value_list (const std::vector<Point<dim> > &points,
+ std::vector<Tensor<2,dim> > &values) const
+ {
+ Assert (points.size() == values.size(),
+ ExcDimensionMismatch (points.size(), values.size()));
+ for (unsigned int p=0; p<points.size(); ++p)
+ {
+ values[p].clear ();
+ double permeability = 0;
+ for (unsigned int i=0; i<centers.size(); ++i)
+ permeability += std::exp(-(points[p]-centers[i]).square()
+ / (0.05 * 0.05));
- // @sect3{Linear solvers and preconditioners}
-
- // The linear solvers we use are also
- // completely analogous to the ones
- // used in step-20. The following
- // classes are therefore copied
- // verbatim from there. There is a
- // single change: if the size of a
- // linear system is small, i.e. when
- // the mesh is very coarse, then it
- // is sometimes not sufficient to set
- // a maximum of
- // <code>src.size()</code> CG
- // iterations before the solver in
- // the <code>vmult()</code> function
- // converges. (This is, of course, a
- // result of numerical round-off,
- // since we know that on paper, the
- // CG method converges in at most
- // <code>src.size()</code> steps.) As
- // a consequence, we set the maximum
- // number of iterations equal to the
- // maximum of the size of the linear
- // system and 200.
-template <class Matrix>
-class InverseMatrix : public Subscriptor
-{
- public:
- InverseMatrix (const Matrix &m);
+ const double normalized_permeability
+ = std::min (std::max(permeability, 0.01), 4.);
- void vmult (Vector<double> &dst,
- const Vector<double> &src) const;
+ for (unsigned int d=0; d<dim; ++d)
+ values[p][d][d] = 1./normalized_permeability;
+ }
+ }
+ }
- private:
- const SmartPointer<const Matrix> matrix;
-};
-template <class Matrix>
-InverseMatrix<Matrix>::InverseMatrix (const Matrix &m)
- :
- matrix (&m)
-{}
+ // @sect3{The inverse mobility and saturation functions}
+ // There are two more pieces of data that we
+ // need to describe, namely the inverse
+ // mobility function and the saturation
+ // curve. Their form is also given in the
+ // introduction:
+ double mobility_inverse (const double S,
+ const double viscosity)
+ {
+ return 1.0 /(1.0/viscosity * S * S + (1-S) * (1-S));
+ }
+ double f_saturation (const double S,
+ const double viscosity)
+ {
+ return S*S /( S * S +viscosity * (1-S) * (1-S));
+ }
-template <class Matrix>
-void InverseMatrix<Matrix>::vmult (Vector<double> &dst,
- const Vector<double> &src) const
-{
- SolverControl solver_control (std::max(src.size(), 200U),
- 1e-8*src.l2_norm());
- SolverCG<> cg (solver_control);
- dst = 0;
- cg.solve (*matrix, dst, src, PreconditionIdentity());
-}
+ // @sect3{Linear solvers and preconditioners}
+
+ // The linear solvers we use are also
+ // completely analogous to the ones
+ // used in step-20. The following
+ // classes are therefore copied
+ // verbatim from there. There is a
+ // single change: if the size of a
+ // linear system is small, i.e. when
+ // the mesh is very coarse, then it
+ // is sometimes not sufficient to set
+ // a maximum of
+ // <code>src.size()</code> CG
+ // iterations before the solver in
+ // the <code>vmult()</code> function
+ // converges. (This is, of course, a
+ // result of numerical round-off,
+ // since we know that on paper, the
+ // CG method converges in at most
+ // <code>src.size()</code> steps.) As
+ // a consequence, we set the maximum
+ // number of iterations equal to the
+ // maximum of the size of the linear
+ // system and 200.
+ template <class Matrix>
+ class InverseMatrix : public Subscriptor
+ {
+ public:
+ InverseMatrix (const Matrix &m);
-class SchurComplement : public Subscriptor
-{
- public:
- SchurComplement (const BlockSparseMatrix<double> &A,
- const InverseMatrix<SparseMatrix<double> > &Minv);
+ void vmult (Vector<double> &dst,
+ const Vector<double> &src) const;
- void vmult (Vector<double> &dst,
- const Vector<double> &src) const;
+ private:
+ const SmartPointer<const Matrix> matrix;
+ };
- private:
- const SmartPointer<const BlockSparseMatrix<double> > system_matrix;
- const SmartPointer<const InverseMatrix<SparseMatrix<double> > > m_inverse;
- mutable Vector<double> tmp1, tmp2;
-};
+ template <class Matrix>
+ InverseMatrix<Matrix>::InverseMatrix (const Matrix &m)
+ :
+ matrix (&m)
+ {}
-SchurComplement::
-SchurComplement (const BlockSparseMatrix<double> &A,
- const InverseMatrix<SparseMatrix<double> > &Minv)
- :
- system_matrix (&A),
- m_inverse (&Minv),
- tmp1 (A.block(0,0).m()),
- tmp2 (A.block(0,0).m())
-{}
+ template <class Matrix>
+ void InverseMatrix<Matrix>::vmult (Vector<double> &dst,
+ const Vector<double> &src) const
+ {
+ SolverControl solver_control (std::max(src.size(), 200U),
+ 1e-8*src.l2_norm());
+ SolverCG<> cg (solver_control);
+ dst = 0;
-void SchurComplement::vmult (Vector<double> &dst,
- const Vector<double> &src) const
-{
- system_matrix->block(0,1).vmult (tmp1, src);
- m_inverse->vmult (tmp2, tmp1);
- system_matrix->block(1,0).vmult (dst, tmp2);
-}
+ cg.solve (*matrix, dst, src, PreconditionIdentity());
+ }
-class ApproximateSchurComplement : public Subscriptor
-{
- public:
- ApproximateSchurComplement (const BlockSparseMatrix<double> &A);
+ class SchurComplement : public Subscriptor
+ {
+ public:
+ SchurComplement (const BlockSparseMatrix<double> &A,
+ const InverseMatrix<SparseMatrix<double> > &Minv);
- void vmult (Vector<double> &dst,
- const Vector<double> &src) const;
+ void vmult (Vector<double> &dst,
+ const Vector<double> &src) const;
- private:
- const SmartPointer<const BlockSparseMatrix<double> > system_matrix;
+ private:
+ const SmartPointer<const BlockSparseMatrix<double> > system_matrix;
+ const SmartPointer<const InverseMatrix<SparseMatrix<double> > > m_inverse;
- mutable Vector<double> tmp1, tmp2;
-};
+ mutable Vector<double> tmp1, tmp2;
+ };
-ApproximateSchurComplement::
-ApproximateSchurComplement (const BlockSparseMatrix<double> &A)
- :
- system_matrix (&A),
- tmp1 (A.block(0,0).m()),
- tmp2 (A.block(0,0).m())
-{}
+ SchurComplement::
+ SchurComplement (const BlockSparseMatrix<double> &A,
+ const InverseMatrix<SparseMatrix<double> > &Minv)
+ :
+ system_matrix (&A),
+ m_inverse (&Minv),
+ tmp1 (A.block(0,0).m()),
+ tmp2 (A.block(0,0).m())
+ {}
-void ApproximateSchurComplement::vmult (Vector<double> &dst,
- const Vector<double> &src) const
-{
- system_matrix->block(0,1).vmult (tmp1, src);
- system_matrix->block(0,0).precondition_Jacobi (tmp2, tmp1);
- system_matrix->block(1,0).vmult (dst, tmp2);
-}
+ void SchurComplement::vmult (Vector<double> &dst,
+ const Vector<double> &src) const
+ {
+ system_matrix->block(0,1).vmult (tmp1, src);
+ m_inverse->vmult (tmp2, tmp1);
+ system_matrix->block(1,0).vmult (dst, tmp2);
+ }
+ class ApproximateSchurComplement : public Subscriptor
+ {
+ public:
+ ApproximateSchurComplement (const BlockSparseMatrix<double> &A);
- // @sect3{<code>TwoPhaseFlowProblem</code> class implementation}
+ void vmult (Vector<double> &dst,
+ const Vector<double> &src) const;
- // Here now the implementation of the main
- // class. Much of it is actually copied from
- // step-20, so we won't comment on it in much
- // detail. You should try to get familiar
- // with that program first, then most of what
- // is happening here should be mostly clear.
+ private:
+ const SmartPointer<const BlockSparseMatrix<double> > system_matrix;
- // @sect4{TwoPhaseFlowProblem::TwoPhaseFlowProblem}
- // First for the constructor. We use $RT_k
- // \times DQ_k \times DQ_k$ spaces. The time
- // step is set to zero initially, but will be
- // computed before it is needed first, as
- // described in a subsection of the
- // introduction.
-template <int dim>
-TwoPhaseFlowProblem<dim>::TwoPhaseFlowProblem (const unsigned int degree)
- :
- degree (degree),
- fe (FE_RaviartThomas<dim>(degree), 1,
- FE_DGQ<dim>(degree), 1,
- FE_DGQ<dim>(degree), 1),
- dof_handler (triangulation),
- n_refinement_steps (5),
- time_step (0),
- viscosity (0.2)
-{}
+ mutable Vector<double> tmp1, tmp2;
+ };
+ ApproximateSchurComplement::
+ ApproximateSchurComplement (const BlockSparseMatrix<double> &A)
+ :
+ system_matrix (&A),
+ tmp1 (A.block(0,0).m()),
+ tmp2 (A.block(0,0).m())
+ {}
- // @sect4{TwoPhaseFlowProblem::make_grid_and_dofs}
- // This next function starts out with
- // well-known functions calls that create and
- // refine a mesh, and then associate degrees
- // of freedom with it. It does all the same
- // things as in step-20, just now for three
- // components instead of two.
-template <int dim>
-void TwoPhaseFlowProblem<dim>::make_grid_and_dofs ()
-{
- GridGenerator::hyper_cube (triangulation, 0, 1);
- triangulation.refine_global (n_refinement_steps);
-
- dof_handler.distribute_dofs (fe);
- DoFRenumbering::component_wise (dof_handler);
-
- std::vector<unsigned int> dofs_per_component (dim+2);
- DoFTools::count_dofs_per_component (dof_handler, dofs_per_component);
- const unsigned int n_u = dofs_per_component[0],
- n_p = dofs_per_component[dim],
- n_s = dofs_per_component[dim+1];
-
- std::cout << "Number of active cells: "
- << triangulation.n_active_cells()
- << std::endl
- << "Number of degrees of freedom: "
- << dof_handler.n_dofs()
- << " (" << n_u << '+' << n_p << '+'<< n_s <<')'
- << std::endl
- << std::endl;
-
- const unsigned int
- n_couplings = dof_handler.max_couplings_between_dofs();
-
- sparsity_pattern.reinit (3,3);
- sparsity_pattern.block(0,0).reinit (n_u, n_u, n_couplings);
- sparsity_pattern.block(1,0).reinit (n_p, n_u, n_couplings);
- sparsity_pattern.block(2,0).reinit (n_s, n_u, n_couplings);
- sparsity_pattern.block(0,1).reinit (n_u, n_p, n_couplings);
- sparsity_pattern.block(1,1).reinit (n_p, n_p, n_couplings);
- sparsity_pattern.block(2,1).reinit (n_s, n_p, n_couplings);
- sparsity_pattern.block(0,2).reinit (n_u, n_s, n_couplings);
- sparsity_pattern.block(1,2).reinit (n_p, n_s, n_couplings);
- sparsity_pattern.block(2,2).reinit (n_s, n_s, n_couplings);
-
- sparsity_pattern.collect_sizes();
-
- DoFTools::make_sparsity_pattern (dof_handler, sparsity_pattern);
- sparsity_pattern.compress();
-
-
- system_matrix.reinit (sparsity_pattern);
-
-
- solution.reinit (3);
- solution.block(0).reinit (n_u);
- solution.block(1).reinit (n_p);
- solution.block(2).reinit (n_s);
- solution.collect_sizes ();
-
- old_solution.reinit (3);
- old_solution.block(0).reinit (n_u);
- old_solution.block(1).reinit (n_p);
- old_solution.block(2).reinit (n_s);
- old_solution.collect_sizes ();
-
- system_rhs.reinit (3);
- system_rhs.block(0).reinit (n_u);
- system_rhs.block(1).reinit (n_p);
- system_rhs.block(2).reinit (n_s);
- system_rhs.collect_sizes ();
-}
+ void ApproximateSchurComplement::vmult (Vector<double> &dst,
+ const Vector<double> &src) const
+ {
+ system_matrix->block(0,1).vmult (tmp1, src);
+ system_matrix->block(0,0).precondition_Jacobi (tmp2, tmp1);
+ system_matrix->block(1,0).vmult (dst, tmp2);
+ }
- // @sect4{TwoPhaseFlowProblem::assemble_system}
-
- // This is the function that assembles the
- // linear system, or at least everything
- // except the (1,3) block that depends on the
- // still-unknown velocity computed during
- // this time step (we deal with this in
- // <code>assemble_rhs_S</code>). Much of it
- // is again as in step-20, but we have to
- // deal with some nonlinearity this time.
- // However, the top of the function is pretty
- // much as usual (note that we set matrix and
- // right hand side to zero at the beginning
- // — something we didn't have to do for
- // stationary problems since there we use
- // each matrix object only once and it is
- // empty at the beginning anyway).
- //
- // Note that in its present form, the
- // function uses the permeability implemented
- // in the RandomMedium::KInverse
- // class. Switching to the single curved
- // crack permeability function is as simple
- // as just changing the namespace name.
-template <int dim>
-void TwoPhaseFlowProblem<dim>::assemble_system ()
-{
- system_matrix=0;
- system_rhs=0;
- QGauss<dim> quadrature_formula(degree+2);
- QGauss<dim-1> face_quadrature_formula(degree+2);
- FEValues<dim> fe_values (fe, quadrature_formula,
- update_values | update_gradients |
- update_quadrature_points | update_JxW_values);
- FEFaceValues<dim> fe_face_values (fe, face_quadrature_formula,
- update_values | update_normal_vectors |
- update_quadrature_points | update_JxW_values);
- const unsigned int dofs_per_cell = fe.dofs_per_cell;
+ // @sect3{<code>TwoPhaseFlowProblem</code> class implementation}
- const unsigned int n_q_points = quadrature_formula.size();
- const unsigned int n_face_q_points = face_quadrature_formula.size();
+ // Here now the implementation of the main
+ // class. Much of it is actually copied from
+ // step-20, so we won't comment on it in much
+ // detail. You should try to get familiar
+ // with that program first, then most of what
+ // is happening here should be mostly clear.
- FullMatrix<double> local_matrix (dofs_per_cell, dofs_per_cell);
- Vector<double> local_rhs (dofs_per_cell);
+ // @sect4{TwoPhaseFlowProblem::TwoPhaseFlowProblem}
+ // First for the constructor. We use $RT_k
+ // \times DQ_k \times DQ_k$ spaces. The time
+ // step is set to zero initially, but will be
+ // computed before it is needed first, as
+ // described in a subsection of the
+ // introduction.
+ template <int dim>
+ TwoPhaseFlowProblem<dim>::TwoPhaseFlowProblem (const unsigned int degree)
+ :
+ degree (degree),
+ fe (FE_RaviartThomas<dim>(degree), 1,
+ FE_DGQ<dim>(degree), 1,
+ FE_DGQ<dim>(degree), 1),
+ dof_handler (triangulation),
+ n_refinement_steps (5),
+ time_step (0),
+ viscosity (0.2)
+ {}
+
+
+
+ // @sect4{TwoPhaseFlowProblem::make_grid_and_dofs}
+
+ // This next function starts out with
+ // well-known functions calls that create and
+ // refine a mesh, and then associate degrees
+ // of freedom with it. It does all the same
+ // things as in step-20, just now for three
+ // components instead of two.
+ template <int dim>
+ void TwoPhaseFlowProblem<dim>::make_grid_and_dofs ()
+ {
+ GridGenerator::hyper_cube (triangulation, 0, 1);
+ triangulation.refine_global (n_refinement_steps);
+
+ dof_handler.distribute_dofs (fe);
+ DoFRenumbering::component_wise (dof_handler);
+
+ std::vector<unsigned int> dofs_per_component (dim+2);
+ DoFTools::count_dofs_per_component (dof_handler, dofs_per_component);
+ const unsigned int n_u = dofs_per_component[0],
+ n_p = dofs_per_component[dim],
+ n_s = dofs_per_component[dim+1];
+
+ std::cout << "Number of active cells: "
+ << triangulation.n_active_cells()
+ << std::endl
+ << "Number of degrees of freedom: "
+ << dof_handler.n_dofs()
+ << " (" << n_u << '+' << n_p << '+'<< n_s <<')'
+ << std::endl
+ << std::endl;
+
+ const unsigned int
+ n_couplings = dof_handler.max_couplings_between_dofs();
+
+ sparsity_pattern.reinit (3,3);
+ sparsity_pattern.block(0,0).reinit (n_u, n_u, n_couplings);
+ sparsity_pattern.block(1,0).reinit (n_p, n_u, n_couplings);
+ sparsity_pattern.block(2,0).reinit (n_s, n_u, n_couplings);
+ sparsity_pattern.block(0,1).reinit (n_u, n_p, n_couplings);
+ sparsity_pattern.block(1,1).reinit (n_p, n_p, n_couplings);
+ sparsity_pattern.block(2,1).reinit (n_s, n_p, n_couplings);
+ sparsity_pattern.block(0,2).reinit (n_u, n_s, n_couplings);
+ sparsity_pattern.block(1,2).reinit (n_p, n_s, n_couplings);
+ sparsity_pattern.block(2,2).reinit (n_s, n_s, n_couplings);
+
+ sparsity_pattern.collect_sizes();
+
+ DoFTools::make_sparsity_pattern (dof_handler, sparsity_pattern);
+ sparsity_pattern.compress();
+
+
+ system_matrix.reinit (sparsity_pattern);
+
+
+ solution.reinit (3);
+ solution.block(0).reinit (n_u);
+ solution.block(1).reinit (n_p);
+ solution.block(2).reinit (n_s);
+ solution.collect_sizes ();
+
+ old_solution.reinit (3);
+ old_solution.block(0).reinit (n_u);
+ old_solution.block(1).reinit (n_p);
+ old_solution.block(2).reinit (n_s);
+ old_solution.collect_sizes ();
+
+ system_rhs.reinit (3);
+ system_rhs.block(0).reinit (n_u);
+ system_rhs.block(1).reinit (n_p);
+ system_rhs.block(2).reinit (n_s);
+ system_rhs.collect_sizes ();
+ }
- std::vector<unsigned int> local_dof_indices (dofs_per_cell);
- const PressureRightHandSide<dim> pressure_right_hand_side;
- const PressureBoundaryValues<dim> pressure_boundary_values;
- const RandomMedium::KInverse<dim> k_inverse;
+ // @sect4{TwoPhaseFlowProblem::assemble_system}
+
+ // This is the function that assembles the
+ // linear system, or at least everything
+ // except the (1,3) block that depends on the
+ // still-unknown velocity computed during
+ // this time step (we deal with this in
+ // <code>assemble_rhs_S</code>). Much of it
+ // is again as in step-20, but we have to
+ // deal with some nonlinearity this time.
+ // However, the top of the function is pretty
+ // much as usual (note that we set matrix and
+ // right hand side to zero at the beginning
+ // — something we didn't have to do for
+ // stationary problems since there we use
+ // each matrix object only once and it is
+ // empty at the beginning anyway).
+ //
+ // Note that in its present form, the
+ // function uses the permeability implemented
+ // in the RandomMedium::KInverse
+ // class. Switching to the single curved
+ // crack permeability function is as simple
+ // as just changing the namespace name.
+ template <int dim>
+ void TwoPhaseFlowProblem<dim>::assemble_system ()
+ {
+ system_matrix=0;
+ system_rhs=0;
- std::vector<double> pressure_rhs_values (n_q_points);
- std::vector<double> boundary_values (n_face_q_points);
- std::vector<Tensor<2,dim> > k_inverse_values (n_q_points);
+ QGauss<dim> quadrature_formula(degree+2);
+ QGauss<dim-1> face_quadrature_formula(degree+2);
- std::vector<Vector<double> > old_solution_values(n_q_points, Vector<double>(dim+2));
- std::vector<std::vector<Tensor<1,dim> > > old_solution_grads(n_q_points,
- std::vector<Tensor<1,dim> > (dim+2));
+ FEValues<dim> fe_values (fe, quadrature_formula,
+ update_values | update_gradients |
+ update_quadrature_points | update_JxW_values);
+ FEFaceValues<dim> fe_face_values (fe, face_quadrature_formula,
+ update_values | update_normal_vectors |
+ update_quadrature_points | update_JxW_values);
- const FEValuesExtractors::Vector velocities (0);
- const FEValuesExtractors::Scalar pressure (dim);
- const FEValuesExtractors::Scalar saturation (dim+1);
+ const unsigned int dofs_per_cell = fe.dofs_per_cell;
- typename DoFHandler<dim>::active_cell_iterator
- cell = dof_handler.begin_active(),
- endc = dof_handler.end();
- for (; cell!=endc; ++cell)
- {
- fe_values.reinit (cell);
- local_matrix = 0;
- local_rhs = 0;
-
- // Here's the first significant
- // difference: We have to get the
- // values of the saturation function of
- // the previous time step at the
- // quadrature points. To this end, we
- // can use the
- // FEValues::get_function_values
- // (previously already used in step-9,
- // step-14 and step-15), a function
- // that takes a solution vector and
- // returns a list of function values at
- // the quadrature points of the present
- // cell. In fact, it returns the
- // complete vector-valued solution at
- // each quadrature point, i.e. not only
- // the saturation but also the
- // velocities and pressure:
- fe_values.get_function_values (old_solution, old_solution_values);
-
- // Then we also have to get the values
- // of the pressure right hand side and
- // of the inverse permeability tensor
- // at the quadrature points:
- pressure_right_hand_side.value_list (fe_values.get_quadrature_points(),
- pressure_rhs_values);
- k_inverse.value_list (fe_values.get_quadrature_points(),
- k_inverse_values);
-
- // With all this, we can now loop over
- // all the quadrature points and shape
- // functions on this cell and assemble
- // those parts of the matrix and right
- // hand side that we deal with in this
- // function. The individual terms in
- // the contributions should be
- // self-explanatory given the explicit
- // form of the bilinear form stated in
- // the introduction:
- for (unsigned int q=0; q<n_q_points; ++q)
- for (unsigned int i=0; i<dofs_per_cell; ++i)
- {
- const double old_s = old_solution_values[q](dim+1);
-
- const Tensor<1,dim> phi_i_u = fe_values[velocities].value (i, q);
- const double div_phi_i_u = fe_values[velocities].divergence (i, q);
- const double phi_i_p = fe_values[pressure].value (i, q);
- const double phi_i_s = fe_values[saturation].value (i, q);
-
- for (unsigned int j=0; j<dofs_per_cell; ++j)
- {
- const Tensor<1,dim> phi_j_u = fe_values[velocities].value (j, q);
- const double div_phi_j_u = fe_values[velocities].divergence (j, q);
- const double phi_j_p = fe_values[pressure].value (j, q);
- const double phi_j_s = fe_values[saturation].value (j, q);
-
- local_matrix(i,j) += (phi_i_u * k_inverse_values[q] *
- mobility_inverse(old_s,viscosity) * phi_j_u
- - div_phi_i_u * phi_j_p
- - phi_i_p * div_phi_j_u
- + phi_i_s * phi_j_s)
- * fe_values.JxW(q);
- }
-
- local_rhs(i) += (-phi_i_p * pressure_rhs_values[q])*
- fe_values.JxW(q);
- }
-
-
- // Next, we also have to deal with the
- // pressure boundary values. This,
- // again is as in step-20:
- for (unsigned int face_no=0;
- face_no<GeometryInfo<dim>::faces_per_cell;
- ++face_no)
- if (cell->at_boundary(face_no))
- {
- fe_face_values.reinit (cell, face_no);
-
- pressure_boundary_values
- .value_list (fe_face_values.get_quadrature_points(),
- boundary_values);
-
- for (unsigned int q=0; q<n_face_q_points; ++q)
- for (unsigned int i=0; i<dofs_per_cell; ++i)
- {
- const Tensor<1,dim>
- phi_i_u = fe_face_values[velocities].value (i, q);
-
- local_rhs(i) += -(phi_i_u *
- fe_face_values.normal_vector(q) *
- boundary_values[q] *
- fe_face_values.JxW(q));
- }
- }
-
- // The final step in the loop
- // over all cells is to
- // transfer local contributions
- // into the global matrix and
- // right hand side vector:
- 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],
- local_matrix(i,j));
-
- for (unsigned int i=0; i<dofs_per_cell; ++i)
- system_rhs(local_dof_indices[i]) += local_rhs(i);
- }
-}
+ const unsigned int n_q_points = quadrature_formula.size();
+ const unsigned int n_face_q_points = face_quadrature_formula.size();
+ FullMatrix<double> local_matrix (dofs_per_cell, dofs_per_cell);
+ Vector<double> local_rhs (dofs_per_cell);
- // So much for assembly of matrix and right
- // hand side. Note that we do not have to
- // interpolate and apply boundary values
- // since they have all been taken care of in
- // the weak form already.
+ std::vector<unsigned int> local_dof_indices (dofs_per_cell);
+ const PressureRightHandSide<dim> pressure_right_hand_side;
+ const PressureBoundaryValues<dim> pressure_boundary_values;
+ const RandomMedium::KInverse<dim> k_inverse;
- // @sect4{TwoPhaseFlowProblem::assemble_rhs_S}
+ std::vector<double> pressure_rhs_values (n_q_points);
+ std::vector<double> boundary_values (n_face_q_points);
+ std::vector<Tensor<2,dim> > k_inverse_values (n_q_points);
- // As explained in the introduction, we can
- // only evaluate the right hand side of the
- // saturation equation once the velocity has
- // been computed. We therefore have this
- // separate function to this end.
-template <int dim>
-void TwoPhaseFlowProblem<dim>::assemble_rhs_S ()
-{
- QGauss<dim> quadrature_formula(degree+2);
- QGauss<dim-1> face_quadrature_formula(degree+2);
- FEValues<dim> fe_values (fe, quadrature_formula,
- update_values | update_gradients |
- update_quadrature_points | update_JxW_values);
- FEFaceValues<dim> fe_face_values (fe, face_quadrature_formula,
- update_values | update_normal_vectors |
- update_quadrature_points | update_JxW_values);
- FEFaceValues<dim> fe_face_values_neighbor (fe, face_quadrature_formula,
- update_values);
-
- const unsigned int dofs_per_cell = fe.dofs_per_cell;
- const unsigned int n_q_points = quadrature_formula.size();
- const unsigned int n_face_q_points = face_quadrature_formula.size();
-
- Vector<double> local_rhs (dofs_per_cell);
-
- std::vector<Vector<double> > old_solution_values(n_q_points, Vector<double>(dim+2));
- std::vector<Vector<double> > old_solution_values_face(n_face_q_points, Vector<double>(dim+2));
- std::vector<Vector<double> > old_solution_values_face_neighbor(n_face_q_points, Vector<double>(dim+2));
- std::vector<Vector<double> > present_solution_values(n_q_points, Vector<double>(dim+2));
- std::vector<Vector<double> > present_solution_values_face(n_face_q_points, Vector<double>(dim+2));
-
- std::vector<double> neighbor_saturation (n_face_q_points);
- std::vector<unsigned int> local_dof_indices (dofs_per_cell);
-
- SaturationBoundaryValues<dim> saturation_boundary_values;
-
- const FEValuesExtractors::Scalar saturation (dim+1);
-
- typename DoFHandler<dim>::active_cell_iterator
- cell = dof_handler.begin_active(),
- endc = dof_handler.end();
- for (; cell!=endc; ++cell)
- {
- local_rhs = 0;
- fe_values.reinit (cell);
-
- fe_values.get_function_values (old_solution, old_solution_values);
- fe_values.get_function_values (solution, present_solution_values);
-
- // First for the cell terms. These are,
- // following the formulas in the
- // introduction, $(S^n,\sigma)-(F(S^n)
- // \mathbf{v}^{n+1},\nabla sigma)$,
- // where $\sigma$ is the saturation
- // component of the test function:
- for (unsigned int q=0; q<n_q_points; ++q)
- for (unsigned int i=0; i<dofs_per_cell; ++i)
- {
- const double old_s = old_solution_values[q](dim+1);
- Tensor<1,dim> present_u;
- for (unsigned int d=0; d<dim; ++d)
- present_u[d] = present_solution_values[q](d);
-
- const double phi_i_s = fe_values[saturation].value (i, q);
- const Tensor<1,dim> grad_phi_i_s = fe_values[saturation].gradient (i, q);
-
- local_rhs(i) += (time_step *
- f_saturation(old_s,viscosity) *
- present_u *
- grad_phi_i_s
- +
- old_s * phi_i_s)
- *
- fe_values.JxW(q);
- }
-
- // Secondly, we have to deal with the
- // flux parts on the face
- // boundaries. This was a bit more
- // involved because we first have to
- // determine which are the influx and
- // outflux parts of the cell
- // boundary. If we have an influx
- // boundary, we need to evaluate the
- // saturation on the other side of the
- // face (or the boundary values, if we
- // are at the boundary of the domain).
- //
- // All this is a bit tricky, but has
- // been explained in some detail
- // already in step-9. Take a look there
- // how this is supposed to work!
- for (unsigned int face_no=0; face_no<GeometryInfo<dim>::faces_per_cell;
- ++face_no)
- {
- fe_face_values.reinit (cell, face_no);
-
- fe_face_values.get_function_values (old_solution, old_solution_values_face);
- fe_face_values.get_function_values (solution, present_solution_values_face);
-
- if (cell->at_boundary(face_no))
- saturation_boundary_values
- .value_list (fe_face_values.get_quadrature_points(),
- neighbor_saturation);
- else
- {
- const typename DoFHandler<dim>::active_cell_iterator
- neighbor = cell->neighbor(face_no);
- const unsigned int
- neighbor_face = cell->neighbor_of_neighbor(face_no);
-
- fe_face_values_neighbor.reinit (neighbor, neighbor_face);
-
- fe_face_values_neighbor
- .get_function_values (old_solution,
- old_solution_values_face_neighbor);
-
- for (unsigned int q=0; q<n_face_q_points; ++q)
- neighbor_saturation[q] = old_solution_values_face_neighbor[q](dim+1);
- }
-
-
- for (unsigned int q=0; q<n_face_q_points; ++q)
- {
- Tensor<1,dim> present_u_face;
- for (unsigned int d=0; d<dim; ++d)
- present_u_face[d] = present_solution_values_face[q](d);
-
- const double normal_flux = present_u_face *
- fe_face_values.normal_vector(q);
-
- const bool is_outflow_q_point = (normal_flux >= 0);
-
- for (unsigned int i=0; i<dofs_per_cell; ++i)
- local_rhs(i) -= time_step *
- normal_flux *
- f_saturation((is_outflow_q_point == true
- ?
- old_solution_values_face[q](dim+1)
- :
- neighbor_saturation[q]),
- viscosity) *
- fe_face_values[saturation].value (i,q) *
- fe_face_values.JxW(q);
- }
- }
-
- cell->get_dof_indices (local_dof_indices);
- for (unsigned int i=0; i<dofs_per_cell; ++i)
- system_rhs(local_dof_indices[i]) += local_rhs(i);
- }
-}
+ std::vector<Vector<double> > old_solution_values(n_q_points, Vector<double>(dim+2));
+ std::vector<std::vector<Tensor<1,dim> > > old_solution_grads(n_q_points,
+ std::vector<Tensor<1,dim> > (dim+2));
+
+ const FEValuesExtractors::Vector velocities (0);
+ const FEValuesExtractors::Scalar pressure (dim);
+ const FEValuesExtractors::Scalar saturation (dim+1);
+ typename DoFHandler<dim>::active_cell_iterator
+ cell = dof_handler.begin_active(),
+ endc = dof_handler.end();
+ for (; cell!=endc; ++cell)
+ {
+ fe_values.reinit (cell);
+ local_matrix = 0;
+ local_rhs = 0;
+
+ // Here's the first significant
+ // difference: We have to get the
+ // values of the saturation function of
+ // the previous time step at the
+ // quadrature points. To this end, we
+ // can use the
+ // FEValues::get_function_values
+ // (previously already used in step-9,
+ // step-14 and step-15), a function
+ // that takes a solution vector and
+ // returns a list of function values at
+ // the quadrature points of the present
+ // cell. In fact, it returns the
+ // complete vector-valued solution at
+ // each quadrature point, i.e. not only
+ // the saturation but also the
+ // velocities and pressure:
+ fe_values.get_function_values (old_solution, old_solution_values);
+
+ // Then we also have to get the values
+ // of the pressure right hand side and
+ // of the inverse permeability tensor
+ // at the quadrature points:
+ pressure_right_hand_side.value_list (fe_values.get_quadrature_points(),
+ pressure_rhs_values);
+ k_inverse.value_list (fe_values.get_quadrature_points(),
+ k_inverse_values);
+
+ // With all this, we can now loop over
+ // all the quadrature points and shape
+ // functions on this cell and assemble
+ // those parts of the matrix and right
+ // hand side that we deal with in this
+ // function. The individual terms in
+ // the contributions should be
+ // self-explanatory given the explicit
+ // form of the bilinear form stated in
+ // the introduction:
+ for (unsigned int q=0; q<n_q_points; ++q)
+ for (unsigned int i=0; i<dofs_per_cell; ++i)
+ {
+ const double old_s = old_solution_values[q](dim+1);
+
+ const Tensor<1,dim> phi_i_u = fe_values[velocities].value (i, q);
+ const double div_phi_i_u = fe_values[velocities].divergence (i, q);
+ const double phi_i_p = fe_values[pressure].value (i, q);
+ const double phi_i_s = fe_values[saturation].value (i, q);
+
+ for (unsigned int j=0; j<dofs_per_cell; ++j)
+ {
+ const Tensor<1,dim> phi_j_u = fe_values[velocities].value (j, q);
+ const double div_phi_j_u = fe_values[velocities].divergence (j, q);
+ const double phi_j_p = fe_values[pressure].value (j, q);
+ const double phi_j_s = fe_values[saturation].value (j, q);
+
+ local_matrix(i,j) += (phi_i_u * k_inverse_values[q] *
+ mobility_inverse(old_s,viscosity) * phi_j_u
+ - div_phi_i_u * phi_j_p
+ - phi_i_p * div_phi_j_u
+ + phi_i_s * phi_j_s)
+ * fe_values.JxW(q);
+ }
+
+ local_rhs(i) += (-phi_i_p * pressure_rhs_values[q])*
+ fe_values.JxW(q);
+ }
+
+
+ // Next, we also have to deal with the
+ // pressure boundary values. This,
+ // again is as in step-20:
+ for (unsigned int face_no=0;
+ face_no<GeometryInfo<dim>::faces_per_cell;
+ ++face_no)
+ if (cell->at_boundary(face_no))
+ {
+ fe_face_values.reinit (cell, face_no);
+
+ pressure_boundary_values
+ .value_list (fe_face_values.get_quadrature_points(),
+ boundary_values);
+
+ for (unsigned int q=0; q<n_face_q_points; ++q)
+ for (unsigned int i=0; i<dofs_per_cell; ++i)
+ {
+ const Tensor<1,dim>
+ phi_i_u = fe_face_values[velocities].value (i, q);
+
+ local_rhs(i) += -(phi_i_u *
+ fe_face_values.normal_vector(q) *
+ boundary_values[q] *
+ fe_face_values.JxW(q));
+ }
+ }
+
+ // The final step in the loop
+ // over all cells is to
+ // transfer local contributions
+ // into the global matrix and
+ // right hand side vector:
+ 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],
+ local_matrix(i,j));
+
+ for (unsigned int i=0; i<dofs_per_cell; ++i)
+ system_rhs(local_dof_indices[i]) += local_rhs(i);
+ }
+ }
- // @sect4{TwoPhaseFlowProblem::solve}
+ // So much for assembly of matrix and right
+ // hand side. Note that we do not have to
+ // interpolate and apply boundary values
+ // since they have all been taken care of in
+ // the weak form already.
- // After all these preparations, we finally
- // solve the linear system for velocity and
- // pressure in the same way as in
- // step-20. After that, we have to deal with
- // the saturation equation (see below):
-template <int dim>
-void TwoPhaseFlowProblem<dim>::solve ()
-{
- const InverseMatrix<SparseMatrix<double> >
- m_inverse (system_matrix.block(0,0));
- Vector<double> tmp (solution.block(0).size());
- Vector<double> schur_rhs (solution.block(1).size());
- Vector<double> tmp2 (solution.block(2).size());
+ // @sect4{TwoPhaseFlowProblem::assemble_rhs_S}
- // First the pressure, using the pressure
- // Schur complement of the first two
- // equations:
+ // As explained in the introduction, we can
+ // only evaluate the right hand side of the
+ // saturation equation once the velocity has
+ // been computed. We therefore have this
+ // separate function to this end.
+ template <int dim>
+ void TwoPhaseFlowProblem<dim>::assemble_rhs_S ()
{
- m_inverse.vmult (tmp, system_rhs.block(0));
- system_matrix.block(1,0).vmult (schur_rhs, tmp);
- schur_rhs -= system_rhs.block(1);
+ QGauss<dim> quadrature_formula(degree+2);
+ QGauss<dim-1> face_quadrature_formula(degree+2);
+ FEValues<dim> fe_values (fe, quadrature_formula,
+ update_values | update_gradients |
+ update_quadrature_points | update_JxW_values);
+ FEFaceValues<dim> fe_face_values (fe, face_quadrature_formula,
+ update_values | update_normal_vectors |
+ update_quadrature_points | update_JxW_values);
+ FEFaceValues<dim> fe_face_values_neighbor (fe, face_quadrature_formula,
+ update_values);
+
+ const unsigned int dofs_per_cell = fe.dofs_per_cell;
+ const unsigned int n_q_points = quadrature_formula.size();
+ const unsigned int n_face_q_points = face_quadrature_formula.size();
+
+ Vector<double> local_rhs (dofs_per_cell);
+
+ std::vector<Vector<double> > old_solution_values(n_q_points, Vector<double>(dim+2));
+ std::vector<Vector<double> > old_solution_values_face(n_face_q_points, Vector<double>(dim+2));
+ std::vector<Vector<double> > old_solution_values_face_neighbor(n_face_q_points, Vector<double>(dim+2));
+ std::vector<Vector<double> > present_solution_values(n_q_points, Vector<double>(dim+2));
+ std::vector<Vector<double> > present_solution_values_face(n_face_q_points, Vector<double>(dim+2));
+
+ std::vector<double> neighbor_saturation (n_face_q_points);
+ std::vector<unsigned int> local_dof_indices (dofs_per_cell);
+
+ SaturationBoundaryValues<dim> saturation_boundary_values;
+
+ const FEValuesExtractors::Scalar saturation (dim+1);
+
+ typename DoFHandler<dim>::active_cell_iterator
+ cell = dof_handler.begin_active(),
+ endc = dof_handler.end();
+ for (; cell!=endc; ++cell)
+ {
+ local_rhs = 0;
+ fe_values.reinit (cell);
+
+ fe_values.get_function_values (old_solution, old_solution_values);
+ fe_values.get_function_values (solution, present_solution_values);
+
+ // First for the cell terms. These are,
+ // following the formulas in the
+ // introduction, $(S^n,\sigma)-(F(S^n)
+ // \mathbf{v}^{n+1},\nabla sigma)$,
+ // where $\sigma$ is the saturation
+ // component of the test function:
+ for (unsigned int q=0; q<n_q_points; ++q)
+ for (unsigned int i=0; i<dofs_per_cell; ++i)
+ {
+ const double old_s = old_solution_values[q](dim+1);
+ Tensor<1,dim> present_u;
+ for (unsigned int d=0; d<dim; ++d)
+ present_u[d] = present_solution_values[q](d);
+
+ const double phi_i_s = fe_values[saturation].value (i, q);
+ const Tensor<1,dim> grad_phi_i_s = fe_values[saturation].gradient (i, q);
+
+ local_rhs(i) += (time_step *
+ f_saturation(old_s,viscosity) *
+ present_u *
+ grad_phi_i_s
+ +
+ old_s * phi_i_s)
+ *
+ fe_values.JxW(q);
+ }
+
+ // Secondly, we have to deal with the
+ // flux parts on the face
+ // boundaries. This was a bit more
+ // involved because we first have to
+ // determine which are the influx and
+ // outflux parts of the cell
+ // boundary. If we have an influx
+ // boundary, we need to evaluate the
+ // saturation on the other side of the
+ // face (or the boundary values, if we
+ // are at the boundary of the domain).
+ //
+ // All this is a bit tricky, but has
+ // been explained in some detail
+ // already in step-9. Take a look there
+ // how this is supposed to work!
+ for (unsigned int face_no=0; face_no<GeometryInfo<dim>::faces_per_cell;
+ ++face_no)
+ {
+ fe_face_values.reinit (cell, face_no);
+
+ fe_face_values.get_function_values (old_solution, old_solution_values_face);
+ fe_face_values.get_function_values (solution, present_solution_values_face);
+
+ if (cell->at_boundary(face_no))
+ saturation_boundary_values
+ .value_list (fe_face_values.get_quadrature_points(),
+ neighbor_saturation);
+ else
+ {
+ const typename DoFHandler<dim>::active_cell_iterator
+ neighbor = cell->neighbor(face_no);
+ const unsigned int
+ neighbor_face = cell->neighbor_of_neighbor(face_no);
+
+ fe_face_values_neighbor.reinit (neighbor, neighbor_face);
+
+ fe_face_values_neighbor
+ .get_function_values (old_solution,
+ old_solution_values_face_neighbor);
+
+ for (unsigned int q=0; q<n_face_q_points; ++q)
+ neighbor_saturation[q] = old_solution_values_face_neighbor[q](dim+1);
+ }
+
+
+ for (unsigned int q=0; q<n_face_q_points; ++q)
+ {
+ Tensor<1,dim> present_u_face;
+ for (unsigned int d=0; d<dim; ++d)
+ present_u_face[d] = present_solution_values_face[q](d);
+
+ const double normal_flux = present_u_face *
+ fe_face_values.normal_vector(q);
+
+ const bool is_outflow_q_point = (normal_flux >= 0);
+
+ for (unsigned int i=0; i<dofs_per_cell; ++i)
+ local_rhs(i) -= time_step *
+ normal_flux *
+ f_saturation((is_outflow_q_point == true
+ ?
+ old_solution_values_face[q](dim+1)
+ :
+ neighbor_saturation[q]),
+ viscosity) *
+ fe_face_values[saturation].value (i,q) *
+ fe_face_values.JxW(q);
+ }
+ }
+
+ cell->get_dof_indices (local_dof_indices);
+ for (unsigned int i=0; i<dofs_per_cell; ++i)
+ system_rhs(local_dof_indices[i]) += local_rhs(i);
+ }
+ }
- SchurComplement
- schur_complement (system_matrix, m_inverse);
- ApproximateSchurComplement
- approximate_schur_complement (system_matrix);
+ // @sect4{TwoPhaseFlowProblem::solve}
- InverseMatrix<ApproximateSchurComplement>
- preconditioner (approximate_schur_complement);
+ // After all these preparations, we finally
+ // solve the linear system for velocity and
+ // pressure in the same way as in
+ // step-20. After that, we have to deal with
+ // the saturation equation (see below):
+ template <int dim>
+ void TwoPhaseFlowProblem<dim>::solve ()
+ {
+ const InverseMatrix<SparseMatrix<double> >
+ m_inverse (system_matrix.block(0,0));
+ Vector<double> tmp (solution.block(0).size());
+ Vector<double> schur_rhs (solution.block(1).size());
+ Vector<double> tmp2 (solution.block(2).size());
- SolverControl solver_control (solution.block(1).size(),
- 1e-12*schur_rhs.l2_norm());
- SolverCG<> cg (solver_control);
+ // First the pressure, using the pressure
+ // Schur complement of the first two
+ // equations:
+ {
+ m_inverse.vmult (tmp, system_rhs.block(0));
+ system_matrix.block(1,0).vmult (schur_rhs, tmp);
+ schur_rhs -= system_rhs.block(1);
- cg.solve (schur_complement, solution.block(1), schur_rhs,
- preconditioner);
- std::cout << " "
- << solver_control.last_step()
- << " CG Schur complement iterations for pressure."
- << std::endl;
- }
+ SchurComplement
+ schur_complement (system_matrix, m_inverse);
- // Now the velocity:
- {
- system_matrix.block(0,1).vmult (tmp, solution.block(1));
- tmp *= -1;
- tmp += system_rhs.block(0);
+ ApproximateSchurComplement
+ approximate_schur_complement (system_matrix);
- m_inverse.vmult (solution.block(0), tmp);
- }
+ InverseMatrix<ApproximateSchurComplement>
+ preconditioner (approximate_schur_complement);
- // Finally, we have to take care of the
- // saturation equation. The first business
- // we have here is to determine the time
- // step using the formula in the
- // introduction. Knowing the shape of our
- // domain and that we created the mesh by
- // regular subdivision of cells, we can
- // compute the diameter of each of our
- // cells quite easily (in fact we use the
- // linear extensions in coordinate
- // directions of the cells, not the
- // diameter). Note that we will learn a
- // more general way to do this in step-24,
- // where we use the
- // GridTools::minimal_cell_diameter
- // function.
- //
- // The maximal velocity we compute using a
- // helper function to compute the maximal
- // velocity defined below, and with all
- // this we can evaluate our new time step
- // length:
- time_step = std::pow(0.5, double(n_refinement_steps)) /
- get_maximal_velocity();
-
- // The next step is to assemble the right
- // hand side, and then to pass everything
- // on for solution. At the end, we project
- // back saturations onto the physically
- // reasonable range:
- assemble_rhs_S ();
- {
- SolverControl solver_control (system_matrix.block(2,2).m(),
- 1e-8*system_rhs.block(2).l2_norm());
- SolverCG<> cg (solver_control);
- cg.solve (system_matrix.block(2,2), solution.block(2), system_rhs.block(2),
- PreconditionIdentity());
+ SolverControl solver_control (solution.block(1).size(),
+ 1e-12*schur_rhs.l2_norm());
+ SolverCG<> cg (solver_control);
- project_back_saturation ();
+ cg.solve (schur_complement, solution.block(1), schur_rhs,
+ preconditioner);
- std::cout << " "
- << solver_control.last_step()
- << " CG iterations for saturation."
- << std::endl;
- }
+ std::cout << " "
+ << solver_control.last_step()
+ << " CG Schur complement iterations for pressure."
+ << std::endl;
+ }
+ // Now the velocity:
+ {
+ system_matrix.block(0,1).vmult (tmp, solution.block(1));
+ tmp *= -1;
+ tmp += system_rhs.block(0);
- old_solution = solution;
-}
+ m_inverse.vmult (solution.block(0), tmp);
+ }
+ // Finally, we have to take care of the
+ // saturation equation. The first business
+ // we have here is to determine the time
+ // step using the formula in the
+ // introduction. Knowing the shape of our
+ // domain and that we created the mesh by
+ // regular subdivision of cells, we can
+ // compute the diameter of each of our
+ // cells quite easily (in fact we use the
+ // linear extensions in coordinate
+ // directions of the cells, not the
+ // diameter). Note that we will learn a
+ // more general way to do this in step-24,
+ // where we use the
+ // GridTools::minimal_cell_diameter
+ // function.
+ //
+ // The maximal velocity we compute using a
+ // helper function to compute the maximal
+ // velocity defined below, and with all
+ // this we can evaluate our new time step
+ // length:
+ time_step = std::pow(0.5, double(n_refinement_steps)) /
+ get_maximal_velocity();
+
+ // The next step is to assemble the right
+ // hand side, and then to pass everything
+ // on for solution. At the end, we project
+ // back saturations onto the physically
+ // reasonable range:
+ assemble_rhs_S ();
+ {
- // @sect4{TwoPhaseFlowProblem::output_results}
+ SolverControl solver_control (system_matrix.block(2,2).m(),
+ 1e-8*system_rhs.block(2).l2_norm());
+ SolverCG<> cg (solver_control);
+ cg.solve (system_matrix.block(2,2), solution.block(2), system_rhs.block(2),
+ PreconditionIdentity());
- // There is nothing surprising here. Since
- // the program will do a lot of time steps,
- // we create an output file only every fifth
- // time step.
-template <int dim>
-void TwoPhaseFlowProblem<dim>::output_results () const
-{
- if (timestep_number % 5 != 0)
- return;
+ project_back_saturation ();
- std::vector<std::string> solution_names;
- switch (dim)
- {
- case 2:
- solution_names.push_back ("u");
- solution_names.push_back ("v");
- solution_names.push_back ("p");
- solution_names.push_back ("S");
- break;
-
- case 3:
- solution_names.push_back ("u");
- solution_names.push_back ("v");
- solution_names.push_back ("w");
- solution_names.push_back ("p");
- solution_names.push_back ("S");
- break;
-
- default:
- Assert (false, ExcNotImplemented());
+ std::cout << " "
+ << solver_control.last_step()
+ << " CG iterations for saturation."
+ << std::endl;
}
- DataOut<dim> data_out;
- data_out.attach_dof_handler (dof_handler);
- data_out.add_data_vector (solution, solution_names);
+ old_solution = solution;
+ }
- data_out.build_patches (degree+1);
- std::ostringstream filename;
- filename << "solution-" << timestep_number << ".vtk";
+ // @sect4{TwoPhaseFlowProblem::output_results}
- std::ofstream output (filename.str().c_str());
- data_out.write_vtk (output);
-}
+ // There is nothing surprising here. Since
+ // the program will do a lot of time steps,
+ // we create an output file only every fifth
+ // time step.
+ template <int dim>
+ void TwoPhaseFlowProblem<dim>::output_results () const
+ {
+ if (timestep_number % 5 != 0)
+ return;
+ std::vector<std::string> solution_names;
+ switch (dim)
+ {
+ case 2:
+ solution_names.push_back ("u");
+ solution_names.push_back ("v");
+ solution_names.push_back ("p");
+ solution_names.push_back ("S");
+ break;
+
+ case 3:
+ solution_names.push_back ("u");
+ solution_names.push_back ("v");
+ solution_names.push_back ("w");
+ solution_names.push_back ("p");
+ solution_names.push_back ("S");
+ break;
+
+ default:
+ Assert (false, ExcNotImplemented());
+ }
+ DataOut<dim> data_out;
- // @sect4{TwoPhaseFlowProblem::project_back_saturation}
-
- // In this function, we simply run over all
- // saturation degrees of freedom and make
- // sure that if they should have left the
- // physically reasonable range, that they be
- // reset to the interval $[0,1]$. To do this,
- // we only have to loop over all saturation
- // components of the solution vector; these
- // are stored in the block 2 (block 0 are the
- // velocities, block 1 are the pressures).
- //
- // It may be instructive to note that this
- // function almost never triggers when the
- // time step is chosen as mentioned in the
- // introduction. However, if we choose the
- // timestep only slightly larger, we get
- // plenty of values outside the proper
- // range. Strictly speaking, the function is
- // therefore unnecessary if we choose the
- // time step small enough. In a sense, the
- // function is therefore only a safety device
- // to avoid situations where our entire
- // solution becomes unphysical because
- // individual degrees of freedom have become
- // unphysical a few time steps earlier.
-template <int dim>
-void
-TwoPhaseFlowProblem<dim>::project_back_saturation ()
-{
- for (unsigned int i=0; i<solution.block(2).size(); ++i)
- if (solution.block(2)(i) < 0)
- solution.block(2)(i) = 0;
- else
- if (solution.block(2)(i) > 1)
- solution.block(2)(i) = 1;
-}
+ data_out.attach_dof_handler (dof_handler);
+ data_out.add_data_vector (solution, solution_names);
+ data_out.build_patches (degree+1);
- // @sect4{TwoPhaseFlowProblem::get_maximal_velocity}
+ std::ostringstream filename;
+ filename << "solution-" << timestep_number << ".vtk";
- // The following function is used in
- // determining the maximal allowable time
- // step. What it does is to loop over all
- // quadrature points in the domain and find
- // what the maximal magnitude of the velocity
- // is.
-template <int dim>
-double
-TwoPhaseFlowProblem<dim>::get_maximal_velocity () const
-{
- QGauss<dim> quadrature_formula(degree+2);
- const unsigned int n_q_points
- = quadrature_formula.size();
-
- FEValues<dim> fe_values (fe, quadrature_formula,
- update_values);
- std::vector<Vector<double> > solution_values(n_q_points,
- Vector<double>(dim+2));
- double max_velocity = 0;
-
- typename DoFHandler<dim>::active_cell_iterator
- cell = dof_handler.begin_active(),
- endc = dof_handler.end();
- for (; cell!=endc; ++cell)
- {
- fe_values.reinit (cell);
- fe_values.get_function_values (solution, solution_values);
-
- for (unsigned int q=0; q<n_q_points; ++q)
- {
- Tensor<1,dim> velocity;
- for (unsigned int i=0; i<dim; ++i)
- velocity[i] = solution_values[q](i);
-
- max_velocity = std::max (max_velocity,
- velocity.norm());
- }
- }
+ std::ofstream output (filename.str().c_str());
+ data_out.write_vtk (output);
+ }
- return max_velocity;
-}
- // @sect4{TwoPhaseFlowProblem::run}
-
- // This is the final function of our main
- // class. Its brevity speaks for
- // itself. There are only two points worth
- // noting: First, the function projects the
- // initial values onto the finite element
- // space at the beginning; the
- // VectorTools::project function doing this
- // requires an argument indicating the
- // hanging node constraints. We have none in
- // this program (we compute on a uniformly
- // refined mesh), but the function requires
- // the argument anyway, of course. So we have
- // to create a constraint object. In its
- // original state, constraint objects are
- // unsorted, and have to be sorted (using the
- // ConstraintMatrix::close function) before
- // they can be used. This is what we do here,
- // and which is why we can't simply call the
- // VectorTools::project function with an
- // anonymous temporary object
- // <code>ConstraintMatrix()</code> as the
- // second argument.
- //
- // The second point worth mentioning is that
- // we only compute the length of the present
- // time step in the middle of solving the
- // linear system corresponding to each time
- // step. We can therefore output the present
- // end time of a time step only at the end of
- // the time step.
-template <int dim>
-void TwoPhaseFlowProblem<dim>::run ()
-{
- make_grid_and_dofs();
+ // @sect4{TwoPhaseFlowProblem::project_back_saturation}
+ // In this function, we simply run over all
+ // saturation degrees of freedom and make
+ // sure that if they should have left the
+ // physically reasonable range, that they be
+ // reset to the interval $[0,1]$. To do this,
+ // we only have to loop over all saturation
+ // components of the solution vector; these
+ // are stored in the block 2 (block 0 are the
+ // velocities, block 1 are the pressures).
+ //
+ // It may be instructive to note that this
+ // function almost never triggers when the
+ // time step is chosen as mentioned in the
+ // introduction. However, if we choose the
+ // timestep only slightly larger, we get
+ // plenty of values outside the proper
+ // range. Strictly speaking, the function is
+ // therefore unnecessary if we choose the
+ // time step small enough. In a sense, the
+ // function is therefore only a safety device
+ // to avoid situations where our entire
+ // solution becomes unphysical because
+ // individual degrees of freedom have become
+ // unphysical a few time steps earlier.
+ template <int dim>
+ void
+ TwoPhaseFlowProblem<dim>::project_back_saturation ()
{
- ConstraintMatrix constraints;
- constraints.close();
-
- VectorTools::project (dof_handler,
- constraints,
- QGauss<dim>(degree+2),
- InitialValues<dim>(),
- old_solution);
+ for (unsigned int i=0; i<solution.block(2).size(); ++i)
+ if (solution.block(2)(i) < 0)
+ solution.block(2)(i) = 0;
+ else
+ if (solution.block(2)(i) > 1)
+ solution.block(2)(i) = 1;
}
- timestep_number = 1;
- double time = 0;
- do
- {
- std::cout << "Timestep " << timestep_number
- << std::endl;
+ // @sect4{TwoPhaseFlowProblem::get_maximal_velocity}
- assemble_system ();
+ // The following function is used in
+ // determining the maximal allowable time
+ // step. What it does is to loop over all
+ // quadrature points in the domain and find
+ // what the maximal magnitude of the velocity
+ // is.
+ template <int dim>
+ double
+ TwoPhaseFlowProblem<dim>::get_maximal_velocity () const
+ {
+ QGauss<dim> quadrature_formula(degree+2);
+ const unsigned int n_q_points
+ = quadrature_formula.size();
+
+ FEValues<dim> fe_values (fe, quadrature_formula,
+ update_values);
+ std::vector<Vector<double> > solution_values(n_q_points,
+ Vector<double>(dim+2));
+ double max_velocity = 0;
+
+ typename DoFHandler<dim>::active_cell_iterator
+ cell = dof_handler.begin_active(),
+ endc = dof_handler.end();
+ for (; cell!=endc; ++cell)
+ {
+ fe_values.reinit (cell);
+ fe_values.get_function_values (solution, solution_values);
+
+ for (unsigned int q=0; q<n_q_points; ++q)
+ {
+ Tensor<1,dim> velocity;
+ for (unsigned int i=0; i<dim; ++i)
+ velocity[i] = solution_values[q](i);
+
+ max_velocity = std::max (max_velocity,
+ velocity.norm());
+ }
+ }
- solve ();
+ return max_velocity;
+ }
- output_results ();
- time += time_step;
- ++timestep_number;
- std::cout << " Now at t=" << time
- << ", dt=" << time_step << '.'
- << std::endl
- << std::endl;
+ // @sect4{TwoPhaseFlowProblem::run}
+
+ // This is the final function of our main
+ // class. Its brevity speaks for
+ // itself. There are only two points worth
+ // noting: First, the function projects the
+ // initial values onto the finite element
+ // space at the beginning; the
+ // VectorTools::project function doing this
+ // requires an argument indicating the
+ // hanging node constraints. We have none in
+ // this program (we compute on a uniformly
+ // refined mesh), but the function requires
+ // the argument anyway, of course. So we have
+ // to create a constraint object. In its
+ // original state, constraint objects are
+ // unsorted, and have to be sorted (using the
+ // ConstraintMatrix::close function) before
+ // they can be used. This is what we do here,
+ // and which is why we can't simply call the
+ // VectorTools::project function with an
+ // anonymous temporary object
+ // <code>ConstraintMatrix()</code> as the
+ // second argument.
+ //
+ // The second point worth mentioning is that
+ // we only compute the length of the present
+ // time step in the middle of solving the
+ // linear system corresponding to each time
+ // step. We can therefore output the present
+ // end time of a time step only at the end of
+ // the time step.
+ template <int dim>
+ void TwoPhaseFlowProblem<dim>::run ()
+ {
+ make_grid_and_dofs();
+
+ {
+ ConstraintMatrix constraints;
+ constraints.close();
+
+ VectorTools::project (dof_handler,
+ constraints,
+ QGauss<dim>(degree+2),
+ InitialValues<dim>(),
+ old_solution);
}
- while (time <= 250);
+
+ timestep_number = 1;
+ double time = 0;
+
+ do
+ {
+ std::cout << "Timestep " << timestep_number
+ << std::endl;
+
+ assemble_system ();
+
+ solve ();
+
+ output_results ();
+
+ time += time_step;
+ ++timestep_number;
+ std::cout << " Now at t=" << time
+ << ", dt=" << time_step << '.'
+ << std::endl
+ << std::endl;
+ }
+ while (time <= 250);
+ }
}
{
try
{
+ using namespace dealii;
+ using namespace Step21;
+
deallog.depth_console (0);
TwoPhaseFlowProblem<2> two_phase_flow_problem(0);
// As in all programs, the namespace dealii
// is included:
-using namespace dealii;
-
- // @sect3{Defining the inner preconditioner type}
-
- // As explained in the introduction, we are
- // going to use different preconditioners for
- // two and three space dimensions,
- // respectively. We distinguish between
- // them by the use of the spatial dimension
- // as a template parameter. See step-4 for
- // details on templates. We are not going to
- // create any preconditioner object here, all
- // we do is to create class that holds a
- // local typedef determining the
- // preconditioner class so we can write our
- // program in a dimension-independent way.
-template <int dim>
-struct InnerPreconditioner;
-
- // In 2D, we are going to use a sparse direct
- // solver as preconditioner:
-template <>
-struct InnerPreconditioner<2>
+namespace Step22
{
- typedef SparseDirectUMFPACK type;
-};
-
- // And the ILU preconditioning in 3D, called
- // by SparseILU:
-template <>
-struct InnerPreconditioner<3>
-{
- typedef SparseILU<double> type;
-};
-
-
- // @sect3{The <code>StokesProblem</code> class template}
-
- // This is an adaptation of step-20, so the
- // main class and the data types are the
- // same as used there. In this example we
- // also use adaptive grid refinement, which
- // is handled in analogy to
- // step-6. According to the discussion in
- // the introduction, we are also going to
- // use the ConstraintMatrix for
- // implementing Dirichlet boundary
- // conditions. Hence, we change the name
- // <code>hanging_node_constraints</code>
- // into <code>constraints</code>.
-template <int dim>
-class StokesProblem
-{
- public:
- StokesProblem (const unsigned int degree);
- void run ();
-
- private:
- void setup_dofs ();
- void assemble_system ();
- void solve ();
- void output_results (const unsigned int refinement_cycle) const;
- void refine_mesh ();
-
- const unsigned int degree;
-
- Triangulation<dim> triangulation;
- FESystem<dim> fe;
- DoFHandler<dim> dof_handler;
-
- ConstraintMatrix constraints;
-
- BlockSparsityPattern sparsity_pattern;
- BlockSparseMatrix<double> system_matrix;
-
- BlockVector<double> solution;
- BlockVector<double> system_rhs;
-
- // This one is new: We shall use a
- // so-called shared pointer structure to
- // access the preconditioner. Shared
- // pointers are essentially just a
- // convenient form of pointers. Several
- // shared pointers can point to the same
- // object (just like regular pointers),
- // but when the last shared pointer
- // object to point to a preconditioner
- // object is deleted (for example if a
- // shared pointer object goes out of
- // scope, if the class of which it is a
- // member is destroyed, or if the pointer
- // is assigned a different preconditioner
- // object) then the preconditioner object
- // pointed to is also destroyed. This
- // ensures that we don't have to manually
- // track in how many places a
- // preconditioner object is still
- // referenced, it can never create a
- // memory leak, and can never produce a
- // dangling pointer to an already
- // destroyed object:
- std_cxx1x::shared_ptr<typename InnerPreconditioner<dim>::type> A_preconditioner;
-};
-
- // @sect3{Boundary values and right hand side}
-
- // As in step-20 and most other
- // example programs, the next task is
- // to define the data for the PDE:
- // For the Stokes problem, we are
- // going to use natural boundary
- // values on parts of the boundary
- // (i.e. homogenous Neumann-type) for
- // which we won't have to do anything
- // special (the homogeneity implies
- // that the corresponding terms in
- // the weak form are simply zero),
- // and boundary conditions on the
- // velocity (Dirichlet-type) on the
- // rest of the boundary, as described
- // in the introduction.
- //
- // In order to enforce the Dirichlet
- // boundary values on the velocity,
- // we will use the
- // VectorTools::interpolate_boundary_values
- // function as usual which requires
- // us to write a function object with
- // as many components as the finite
- // element has. In other words, we
- // have to define the function on the
- // $(u,p)$-space, but we are going to
- // filter out the pressure component
- // when interpolating the boundary
- // values.
-
- // The following function object is a
- // representation of the boundary
- // values described in the
- // introduction:
-template <int dim>
-class BoundaryValues : public Function<dim>
-{
- public:
- BoundaryValues () : Function<dim>(dim+1) {}
+ using namespace dealii;
+
+ // @sect3{Defining the inner preconditioner type}
+
+ // As explained in the introduction, we are
+ // going to use different preconditioners for
+ // two and three space dimensions,
+ // respectively. We distinguish between
+ // them by the use of the spatial dimension
+ // as a template parameter. See step-4 for
+ // details on templates. We are not going to
+ // create any preconditioner object here, all
+ // we do is to create class that holds a
+ // local typedef determining the
+ // preconditioner class so we can write our
+ // program in a dimension-independent way.
+ template <int dim>
+ struct InnerPreconditioner;
+
+ // In 2D, we are going to use a sparse direct
+ // solver as preconditioner:
+ template <>
+ struct InnerPreconditioner<2>
+ {
+ typedef SparseDirectUMFPACK type;
+ };
- virtual double value (const Point<dim> &p,
- const unsigned int component = 0) const;
+ // And the ILU preconditioning in 3D, called
+ // by SparseILU:
+ template <>
+ struct InnerPreconditioner<3>
+ {
+ typedef SparseILU<double> type;
+ };
+
+
+ // @sect3{The <code>StokesProblem</code> class template}
+
+ // This is an adaptation of step-20, so the
+ // main class and the data types are the
+ // same as used there. In this example we
+ // also use adaptive grid refinement, which
+ // is handled in analogy to
+ // step-6. According to the discussion in
+ // the introduction, we are also going to
+ // use the ConstraintMatrix for
+ // implementing Dirichlet boundary
+ // conditions. Hence, we change the name
+ // <code>hanging_node_constraints</code>
+ // into <code>constraints</code>.
+ template <int dim>
+ class StokesProblem
+ {
+ public:
+ StokesProblem (const unsigned int degree);
+ void run ();
+
+ private:
+ void setup_dofs ();
+ void assemble_system ();
+ void solve ();
+ void output_results (const unsigned int refinement_cycle) const;
+ void refine_mesh ();
+
+ const unsigned int degree;
+
+ Triangulation<dim> triangulation;
+ FESystem<dim> fe;
+ DoFHandler<dim> dof_handler;
+
+ ConstraintMatrix constraints;
+
+ BlockSparsityPattern sparsity_pattern;
+ BlockSparseMatrix<double> system_matrix;
+
+ BlockVector<double> solution;
+ BlockVector<double> system_rhs;
+
+ // This one is new: We shall use a
+ // so-called shared pointer structure to
+ // access the preconditioner. Shared
+ // pointers are essentially just a
+ // convenient form of pointers. Several
+ // shared pointers can point to the same
+ // object (just like regular pointers),
+ // but when the last shared pointer
+ // object to point to a preconditioner
+ // object is deleted (for example if a
+ // shared pointer object goes out of
+ // scope, if the class of which it is a
+ // member is destroyed, or if the pointer
+ // is assigned a different preconditioner
+ // object) then the preconditioner object
+ // pointed to is also destroyed. This
+ // ensures that we don't have to manually
+ // track in how many places a
+ // preconditioner object is still
+ // referenced, it can never create a
+ // memory leak, and can never produce a
+ // dangling pointer to an already
+ // destroyed object:
+ std_cxx1x::shared_ptr<typename InnerPreconditioner<dim>::type> A_preconditioner;
+ };
+
+ // @sect3{Boundary values and right hand side}
+
+ // As in step-20 and most other
+ // example programs, the next task is
+ // to define the data for the PDE:
+ // For the Stokes problem, we are
+ // going to use natural boundary
+ // values on parts of the boundary
+ // (i.e. homogenous Neumann-type) for
+ // which we won't have to do anything
+ // special (the homogeneity implies
+ // that the corresponding terms in
+ // the weak form are simply zero),
+ // and boundary conditions on the
+ // velocity (Dirichlet-type) on the
+ // rest of the boundary, as described
+ // in the introduction.
+ //
+ // In order to enforce the Dirichlet
+ // boundary values on the velocity,
+ // we will use the
+ // VectorTools::interpolate_boundary_values
+ // function as usual which requires
+ // us to write a function object with
+ // as many components as the finite
+ // element has. In other words, we
+ // have to define the function on the
+ // $(u,p)$-space, but we are going to
+ // filter out the pressure component
+ // when interpolating the boundary
+ // values.
- virtual void vector_value (const Point<dim> &p,
- Vector<double> &value) const;
-};
+ // The following function object is a
+ // representation of the boundary
+ // values described in the
+ // introduction:
+ template <int dim>
+ class BoundaryValues : public Function<dim>
+ {
+ public:
+ BoundaryValues () : Function<dim>(dim+1) {}
+ virtual double value (const Point<dim> &p,
+ const unsigned int component = 0) const;
-template <int dim>
-double
-BoundaryValues<dim>::value (const Point<dim> &p,
- const unsigned int component) const
-{
- Assert (component < this->n_components,
- ExcIndexRange (component, 0, this->n_components));
+ virtual void vector_value (const Point<dim> &p,
+ Vector<double> &value) const;
+ };
- if (component == 0)
- return (p[0] < 0 ? -1 : (p[0] > 0 ? 1 : 0));
- return 0;
-}
+ template <int dim>
+ double
+ BoundaryValues<dim>::value (const Point<dim> &p,
+ const unsigned int component) const
+ {
+ Assert (component < this->n_components,
+ ExcIndexRange (component, 0, this->n_components));
-template <int dim>
-void
-BoundaryValues<dim>::vector_value (const Point<dim> &p,
- Vector<double> &values) const
-{
- for (unsigned int c=0; c<this->n_components; ++c)
- values(c) = BoundaryValues<dim>::value (p, c);
-}
+ if (component == 0)
+ return (p[0] < 0 ? -1 : (p[0] > 0 ? 1 : 0));
+ return 0;
+ }
+ template <int dim>
+ void
+ BoundaryValues<dim>::vector_value (const Point<dim> &p,
+ Vector<double> &values) const
+ {
+ for (unsigned int c=0; c<this->n_components; ++c)
+ values(c) = BoundaryValues<dim>::value (p, c);
+ }
- // We implement similar functions for
- // the right hand side which for the
- // current example is simply zero:
-template <int dim>
-class RightHandSide : public Function<dim>
-{
- public:
- RightHandSide () : Function<dim>(dim+1) {}
- 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;
+ // We implement similar functions for
+ // the right hand side which for the
+ // current example is simply zero:
+ template <int dim>
+ class RightHandSide : public Function<dim>
+ {
+ public:
+ RightHandSide () : Function<dim>(dim+1) {}
-};
+ 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;
-template <int dim>
-double
-RightHandSide<dim>::value (const Point<dim> &/*p*/,
- const unsigned int /*component*/) const
-{
- return 0;
-}
+ };
-template <int dim>
-void
-RightHandSide<dim>::vector_value (const Point<dim> &p,
- Vector<double> &values) const
-{
- for (unsigned int c=0; c<this->n_components; ++c)
- values(c) = RightHandSide<dim>::value (p, c);
-}
+ template <int dim>
+ double
+ RightHandSide<dim>::value (const Point<dim> &/*p*/,
+ const unsigned int /*component*/) const
+ {
+ return 0;
+ }
- // @sect3{Linear solvers and preconditioners}
-
- // The linear solvers and preconditioners are
- // discussed extensively in the
- // introduction. Here, we create the
- // respective objects that will be used.
-
- // @sect4{The <code>InverseMatrix</code> class template}
-
- // The <code>InverseMatrix</code>
- // class represents the data
- // structure for an inverse
- // matrix. It is derived from the one
- // in step-20. The only difference is
- // that we now do include a
- // preconditioner to the matrix since
- // we will apply this class to
- // different kinds of matrices that
- // will require different
- // preconditioners (in step-20 we did
- // not use a preconditioner in this
- // class at all). The types of matrix
- // and preconditioner are passed to
- // this class via template
- // parameters, and matrix and
- // preconditioner objects of these
- // types will then be passed to the
- // constructor when an
- // <code>InverseMatrix</code> object
- // is created. The member function
- // <code>vmult</code> is, as in
- // step-20, a multiplication with a
- // vector, obtained by solving a
- // linear system:
-template <class Matrix, class Preconditioner>
-class InverseMatrix : public Subscriptor
-{
- public:
- InverseMatrix (const Matrix &m,
- const Preconditioner &preconditioner);
-
- void vmult (Vector<double> &dst,
- const Vector<double> &src) const;
-
- private:
- const SmartPointer<const Matrix> matrix;
- const SmartPointer<const Preconditioner> preconditioner;
-};
-
-
-template <class Matrix, class Preconditioner>
-InverseMatrix<Matrix,Preconditioner>::InverseMatrix (const Matrix &m,
- const Preconditioner &preconditioner)
- :
- matrix (&m),
- preconditioner (&preconditioner)
-{}
-
-
- // This is the implementation of the
- // <code>vmult</code> function.
-
- // In this class we use a rather large
- // tolerance for the solver control. The
- // reason for this is that the function is
- // used very frequently, and hence, any
- // additional effort to make the residual
- // in the CG solve smaller makes the
- // solution more expensive. Note that we do
- // not only use this class as a
- // preconditioner for the Schur complement,
- // but also when forming the inverse of the
- // Laplace matrix – which is hence
- // directly responsible for the accuracy of
- // the solution itself, so we can't choose
- // a too large tolerance, either.
-template <class Matrix, class Preconditioner>
-void InverseMatrix<Matrix,Preconditioner>::vmult (Vector<double> &dst,
- const Vector<double> &src) const
-{
- SolverControl solver_control (src.size(), 1e-6*src.l2_norm());
- SolverCG<> cg (solver_control);
+ template <int dim>
+ void
+ RightHandSide<dim>::vector_value (const Point<dim> &p,
+ Vector<double> &values) const
+ {
+ for (unsigned int c=0; c<this->n_components; ++c)
+ values(c) = RightHandSide<dim>::value (p, c);
+ }
- dst = 0;
- cg.solve (*matrix, dst, src, *preconditioner);
-}
+ // @sect3{Linear solvers and preconditioners}
+
+ // The linear solvers and preconditioners are
+ // discussed extensively in the
+ // introduction. Here, we create the
+ // respective objects that will be used.
+
+ // @sect4{The <code>InverseMatrix</code> class template}
+
+ // The <code>InverseMatrix</code>
+ // class represents the data
+ // structure for an inverse
+ // matrix. It is derived from the one
+ // in step-20. The only difference is
+ // that we now do include a
+ // preconditioner to the matrix since
+ // we will apply this class to
+ // different kinds of matrices that
+ // will require different
+ // preconditioners (in step-20 we did
+ // not use a preconditioner in this
+ // class at all). The types of matrix
+ // and preconditioner are passed to
+ // this class via template
+ // parameters, and matrix and
+ // preconditioner objects of these
+ // types will then be passed to the
+ // constructor when an
+ // <code>InverseMatrix</code> object
+ // is created. The member function
+ // <code>vmult</code> is, as in
+ // step-20, a multiplication with a
+ // vector, obtained by solving a
+ // linear system:
+ template <class Matrix, class Preconditioner>
+ class InverseMatrix : public Subscriptor
+ {
+ public:
+ InverseMatrix (const Matrix &m,
+ const Preconditioner &preconditioner);
+
+ void vmult (Vector<double> &dst,
+ const Vector<double> &src) const;
+
+ private:
+ const SmartPointer<const Matrix> matrix;
+ const SmartPointer<const Preconditioner> preconditioner;
+ };
+
+
+ template <class Matrix, class Preconditioner>
+ InverseMatrix<Matrix,Preconditioner>::InverseMatrix (const Matrix &m,
+ const Preconditioner &preconditioner)
+ :
+ matrix (&m),
+ preconditioner (&preconditioner)
+ {}
+
+
+ // This is the implementation of the
+ // <code>vmult</code> function.
+
+ // In this class we use a rather large
+ // tolerance for the solver control. The
+ // reason for this is that the function is
+ // used very frequently, and hence, any
+ // additional effort to make the residual
+ // in the CG solve smaller makes the
+ // solution more expensive. Note that we do
+ // not only use this class as a
+ // preconditioner for the Schur complement,
+ // but also when forming the inverse of the
+ // Laplace matrix – which is hence
+ // directly responsible for the accuracy of
+ // the solution itself, so we can't choose
+ // a too large tolerance, either.
+ template <class Matrix, class Preconditioner>
+ void InverseMatrix<Matrix,Preconditioner>::vmult (Vector<double> &dst,
+ const Vector<double> &src) const
+ {
+ SolverControl solver_control (src.size(), 1e-6*src.l2_norm());
+ SolverCG<> cg (solver_control);
+ dst = 0;
- // @sect4{The <code>SchurComplement</code> class template}
-
- // This class implements the Schur complement
- // discussed in the introduction. It is in
- // analogy to step-20. Though, we now call
- // it with a template parameter
- // <code>Preconditioner</code> in order to
- // access that when specifying the respective
- // type of the inverse matrix class. As a
- // consequence of the definition above, the
- // declaration <code>InverseMatrix</code> now
- // contains the second template parameter
- // for a preconditioner class as above, which
- // affects the <code>SmartPointer</code>
- // object <code>m_inverse</code> as well.
-template <class Preconditioner>
-class SchurComplement : public Subscriptor
-{
- public:
- SchurComplement (const BlockSparseMatrix<double> &system_matrix,
- const InverseMatrix<SparseMatrix<double>, Preconditioner> &A_inverse);
+ cg.solve (*matrix, dst, src, *preconditioner);
+ }
- void vmult (Vector<double> &dst,
- const Vector<double> &src) const;
- private:
- const SmartPointer<const BlockSparseMatrix<double> > system_matrix;
- const SmartPointer<const InverseMatrix<SparseMatrix<double>, Preconditioner> > A_inverse;
+ // @sect4{The <code>SchurComplement</code> class template}
+
+ // This class implements the Schur complement
+ // discussed in the introduction. It is in
+ // analogy to step-20. Though, we now call
+ // it with a template parameter
+ // <code>Preconditioner</code> in order to
+ // access that when specifying the respective
+ // type of the inverse matrix class. As a
+ // consequence of the definition above, the
+ // declaration <code>InverseMatrix</code> now
+ // contains the second template parameter
+ // for a preconditioner class as above, which
+ // affects the <code>SmartPointer</code>
+ // object <code>m_inverse</code> as well.
+ template <class Preconditioner>
+ class SchurComplement : public Subscriptor
+ {
+ public:
+ SchurComplement (const BlockSparseMatrix<double> &system_matrix,
+ const InverseMatrix<SparseMatrix<double>, Preconditioner> &A_inverse);
- mutable Vector<double> tmp1, tmp2;
-};
+ void vmult (Vector<double> &dst,
+ const Vector<double> &src) const;
+ private:
+ const SmartPointer<const BlockSparseMatrix<double> > system_matrix;
+ const SmartPointer<const InverseMatrix<SparseMatrix<double>, Preconditioner> > A_inverse;
+ mutable Vector<double> tmp1, tmp2;
+ };
-template <class Preconditioner>
-SchurComplement<Preconditioner>::
-SchurComplement (const BlockSparseMatrix<double> &system_matrix,
- const InverseMatrix<SparseMatrix<double>,Preconditioner> &A_inverse)
- :
- system_matrix (&system_matrix),
- A_inverse (&A_inverse),
- tmp1 (system_matrix.block(0,0).m()),
- tmp2 (system_matrix.block(0,0).m())
-{}
-template <class Preconditioner>
-void SchurComplement<Preconditioner>::vmult (Vector<double> &dst,
- const Vector<double> &src) const
-{
- system_matrix->block(0,1).vmult (tmp1, src);
- A_inverse->vmult (tmp2, tmp1);
- system_matrix->block(1,0).vmult (dst, tmp2);
-}
+ template <class Preconditioner>
+ SchurComplement<Preconditioner>::
+ SchurComplement (const BlockSparseMatrix<double> &system_matrix,
+ const InverseMatrix<SparseMatrix<double>,Preconditioner> &A_inverse)
+ :
+ system_matrix (&system_matrix),
+ A_inverse (&A_inverse),
+ tmp1 (system_matrix.block(0,0).m()),
+ tmp2 (system_matrix.block(0,0).m())
+ {}
- // @sect3{StokesProblem class implementation}
-
- // @sect4{StokesProblem::StokesProblem}
-
- // The constructor of this class
- // looks very similar to the one of
- // step-20. The constructor
- // initializes the variables for the
- // polynomial degree, triangulation,
- // finite element system and the dof
- // handler. The underlying polynomial
- // functions are of order
- // <code>degree+1</code> for the
- // vector-valued velocity components
- // and of order <code>degree</code>
- // for the pressure. This gives the
- // LBB-stable element pair
- // $Q_{degree+1}^d\times Q_{degree}$,
- // often referred to as the
- // Taylor-Hood element.
- //
- // Note that we initialize the triangulation
- // with a MeshSmoothing argument, which
- // ensures that the refinement of cells is
- // done in a way that the approximation of
- // the PDE solution remains well-behaved
- // (problems arise if grids are too
- // unstructered), see the documentation of
- // <code>Triangulation::MeshSmoothing</code>
- // for details.
-template <int dim>
-StokesProblem<dim>::StokesProblem (const unsigned int degree)
- :
- degree (degree),
- triangulation (Triangulation<dim>::maximum_smoothing),
- fe (FE_Q<dim>(degree+1), dim,
- FE_Q<dim>(degree), 1),
- dof_handler (triangulation)
-{}
-
-
- // @sect4{StokesProblem::setup_dofs}
-
- // Given a mesh, this function
- // associates the degrees of freedom
- // with it and creates the
- // corresponding matrices and
- // vectors. At the beginning it also
- // releases the pointer to the
- // preconditioner object (if the
- // shared pointer pointed at anything
- // at all at this point) since it
- // will definitely not be needed any
- // more after this point and will
- // have to be re-computed after
- // assembling the matrix, and unties
- // the sparse matrix from its
- // sparsity pattern object.
- //
- // We then proceed with distributing
- // degrees of freedom and renumbering
- // them: In order to make the ILU
- // preconditioner (in 3D) work
- // efficiently, it is important to
- // enumerate the degrees of freedom
- // in such a way that it reduces the
- // bandwidth of the matrix, or maybe
- // more importantly: in such a way
- // that the ILU is as close as
- // possible to a real LU
- // decomposition. On the other hand,
- // we need to preserve the block
- // structure of velocity and pressure
- // already seen in in step-20 and
- // step-21. This is done in two
- // steps: First, all dofs are
- // renumbered to improve the ILU and
- // then we renumber once again by
- // components. Since
- // <code>DoFRenumbering::component_wise</code>
- // does not touch the renumbering
- // within the individual blocks, the
- // basic renumbering from the first
- // step remains. As for how the
- // renumber degrees of freedom to
- // improve the ILU: deal.II has a
- // number of algorithms that attempt
- // to find orderings to improve ILUs,
- // or reduce the bandwidth of
- // matrices, or optimize some other
- // aspect. The DoFRenumbering
- // namespace shows a comparison of
- // the results we obtain with several
- // of these algorithms based on the
- // testcase discussed here in this
- // tutorial program. Here, we will
- // use the traditional Cuthill-McKee
- // algorithm already used in some of
- // the previous tutorial programs.
- // In the
- // <a href="#improved-ilu">section on improved ILU</a>
- // we're going to discuss this issue
- // in more detail.
-
- // There is one more change compared
- // to previous tutorial programs:
- // There is no reason in sorting the
- // <code>dim</code> velocity
- // components individually. In fact,
- // rather than first enumerating all
- // $x$-velocities, then all
- // $y$-velocities, etc, we would like
- // to keep all velocities at the same
- // location together and only
- // separate between velocities (all
- // components) and pressures. By
- // default, this is not what the
- // DoFRenumbering::component_wise
- // function does: it treats each
- // vector component separately; what
- // we have to do is group several
- // components into "blocks" and pass
- // this block structure to that
- // function. Consequently, we
- // allocate a vector
- // <code>block_component</code> with
- // as many elements as there are
- // components and describe all
- // velocity components to correspond
- // to block 0, while the pressure
- // component will form block 1:
-template <int dim>
-void StokesProblem<dim>::setup_dofs ()
-{
- A_preconditioner.reset ();
- system_matrix.clear ();
-
- dof_handler.distribute_dofs (fe);
- DoFRenumbering::Cuthill_McKee (dof_handler);
-
- std::vector<unsigned int> block_component (dim+1,0);
- block_component[dim] = 1;
- DoFRenumbering::component_wise (dof_handler, block_component);
-
- // Now comes the implementation of
- // Dirichlet boundary conditions, which
- // should be evident after the discussion
- // in the introduction. All that changed is
- // that the function already appears in the
- // setup functions, whereas we were used to
- // see it in some assembly routine. Further
- // down below where we set up the mesh, we
- // will associate the top boundary where we
- // impose Dirichlet boundary conditions
- // with boundary indicator 1. We will have
- // to pass this boundary indicator as
- // second argument to the function below
- // interpolating boundary values. There is
- // one more thing, though. The function
- // describing the Dirichlet conditions was
- // defined for all components, both
- // velocity and pressure. However, the
- // Dirichlet conditions are to be set for
- // the velocity only. To this end, we use
- // a <code>component_mask</code> that
- // filters out the pressure component, so
- // that the condensation is performed on
- // velocity degrees of freedom only. Since
- // we use adaptively refined grids the
- // constraint matrix needs to be first
- // filled with hanging node constraints
- // generated from the DoF handler. Note the
- // order of the two functions — we
- // first compute the hanging node
- // constraints, and then insert the
- // boundary values into the constraint
- // matrix. This makes sure that we respect
- // H<sup>1</sup> conformity on boundaries
- // with hanging nodes (in three space
- // dimensions), where the hanging node
- // needs to dominate the Dirichlet boundary
- // values.
+ template <class Preconditioner>
+ void SchurComplement<Preconditioner>::vmult (Vector<double> &dst,
+ const Vector<double> &src) const
{
- constraints.clear ();
- std::vector<bool> component_mask (dim+1, true);
- component_mask[dim] = false;
- DoFTools::make_hanging_node_constraints (dof_handler,
- constraints);
- VectorTools::interpolate_boundary_values (dof_handler,
- 1,
- BoundaryValues<dim>(),
- constraints,
- component_mask);
+ system_matrix->block(0,1).vmult (tmp1, src);
+ A_inverse->vmult (tmp2, tmp1);
+ system_matrix->block(1,0).vmult (dst, tmp2);
}
- constraints.close ();
-
- // In analogy to step-20, we count the dofs
- // in the individual components. We could
- // do this in the same way as there, but we
- // want to operate on the block structure
- // we used already for the renumbering: The
- // function
- // <code>DoFTools::count_dofs_per_block</code>
- // does the same as
- // <code>DoFTools::count_dofs_per_component</code>,
- // but now grouped as velocity and pressure
- // block via <code>block_component</code>.
- std::vector<unsigned int> dofs_per_block (2);
- DoFTools::count_dofs_per_block (dof_handler, dofs_per_block, block_component);
- const unsigned int n_u = dofs_per_block[0],
- n_p = dofs_per_block[1];
-
- std::cout << " Number of active cells: "
- << triangulation.n_active_cells()
- << std::endl
- << " Number of degrees of freedom: "
- << dof_handler.n_dofs()
- << " (" << n_u << '+' << n_p << ')'
- << std::endl;
-
- // The next task is to allocate a
- // sparsity pattern for the system matrix
- // we will create. We could do this in
- // the same way as in step-20,
- // i.e. directly build an object of type
- // SparsityPattern through
- // DoFTools::make_sparsity_pattern. However,
- // there is a major reason not to do so:
- // In 3D, the function
- // DoFTools::max_couplings_between_dofs
- // yields a conservative but rather large
- // number for the coupling between the
- // individual dofs, so that the memory
- // initially provided for the creation of
- // the sparsity pattern of the matrix is
- // far too much -- so much actually that
- // the initial sparsity pattern won't
- // even fit into the physical memory of
- // most systems already for
- // moderately-sized 3D problems, see also
- // the discussion in step-18. Instead,
- // we first build a temporary object that
- // uses a different data structure that
- // doesn't require allocating more memory
- // than necessary but isn't suitable for
- // use as a basis of SparseMatrix or
- // BlockSparseMatrix objects; in a second
- // step we then copy this object into an
- // object of BlockSparsityPattern. This
- // is entirely analgous to what we
- // already did in step-11 and step-18.
+
+ // @sect3{StokesProblem class implementation}
+
+ // @sect4{StokesProblem::StokesProblem}
+
+ // The constructor of this class
+ // looks very similar to the one of
+ // step-20. The constructor
+ // initializes the variables for the
+ // polynomial degree, triangulation,
+ // finite element system and the dof
+ // handler. The underlying polynomial
+ // functions are of order
+ // <code>degree+1</code> for the
+ // vector-valued velocity components
+ // and of order <code>degree</code>
+ // for the pressure. This gives the
+ // LBB-stable element pair
+ // $Q_{degree+1}^d\times Q_{degree}$,
+ // often referred to as the
+ // Taylor-Hood element.
//
- // There is one snag again here, though:
- // it turns out that using the
- // CompressedSparsityPattern (or the
- // block version
- // BlockCompressedSparsityPattern we
- // would use here) has a bottleneck that
- // makes the algorithm to build the
- // sparsity pattern be quadratic in the
- // number of degrees of freedom. This
- // doesn't become noticable until we get
- // well into the range of several 100,000
- // degrees of freedom, but eventually
- // dominates the setup of the linear
- // system when we get to more than a
- // million degrees of freedom. This is
- // due to the data structures used in the
- // CompressedSparsityPattern class,
- // nothing that can easily be
- // changed. Fortunately, there is an easy
- // solution: the
- // CompressedSimpleSparsityPattern class
- // (and its block variant
- // BlockCompressedSimpleSparsityPattern)
- // has exactly the same interface, uses a
- // different %internal data structure and
- // is linear in the number of degrees of
- // freedom and therefore much more
- // efficient for large problems. As
- // another alternative, we could also
- // have chosen the class
- // BlockCompressedSetSparsityPattern that
- // uses yet another strategy for %internal
- // memory management. Though, that class
- // turns out to be more memory-demanding
- // than
- // BlockCompressedSimpleSparsityPattern
- // for this example.
+ // Note that we initialize the triangulation
+ // with a MeshSmoothing argument, which
+ // ensures that the refinement of cells is
+ // done in a way that the approximation of
+ // the PDE solution remains well-behaved
+ // (problems arise if grids are too
+ // unstructered), see the documentation of
+ // <code>Triangulation::MeshSmoothing</code>
+ // for details.
+ template <int dim>
+ StokesProblem<dim>::StokesProblem (const unsigned int degree)
+ :
+ degree (degree),
+ triangulation (Triangulation<dim>::maximum_smoothing),
+ fe (FE_Q<dim>(degree+1), dim,
+ FE_Q<dim>(degree), 1),
+ dof_handler (triangulation)
+ {}
+
+
+ // @sect4{StokesProblem::setup_dofs}
+
+ // Given a mesh, this function
+ // associates the degrees of freedom
+ // with it and creates the
+ // corresponding matrices and
+ // vectors. At the beginning it also
+ // releases the pointer to the
+ // preconditioner object (if the
+ // shared pointer pointed at anything
+ // at all at this point) since it
+ // will definitely not be needed any
+ // more after this point and will
+ // have to be re-computed after
+ // assembling the matrix, and unties
+ // the sparse matrix from its
+ // sparsity pattern object.
//
- // Consequently, this is the class that
- // we will use for our intermediate
- // sparsity representation. All this is
- // done inside a new scope, which means
- // that the memory of <code>csp</code>
- // will be released once the information
- // has been copied to
- // <code>sparsity_pattern</code>.
+ // We then proceed with distributing
+ // degrees of freedom and renumbering
+ // them: In order to make the ILU
+ // preconditioner (in 3D) work
+ // efficiently, it is important to
+ // enumerate the degrees of freedom
+ // in such a way that it reduces the
+ // bandwidth of the matrix, or maybe
+ // more importantly: in such a way
+ // that the ILU is as close as
+ // possible to a real LU
+ // decomposition. On the other hand,
+ // we need to preserve the block
+ // structure of velocity and pressure
+ // already seen in in step-20 and
+ // step-21. This is done in two
+ // steps: First, all dofs are
+ // renumbered to improve the ILU and
+ // then we renumber once again by
+ // components. Since
+ // <code>DoFRenumbering::component_wise</code>
+ // does not touch the renumbering
+ // within the individual blocks, the
+ // basic renumbering from the first
+ // step remains. As for how the
+ // renumber degrees of freedom to
+ // improve the ILU: deal.II has a
+ // number of algorithms that attempt
+ // to find orderings to improve ILUs,
+ // or reduce the bandwidth of
+ // matrices, or optimize some other
+ // aspect. The DoFRenumbering
+ // namespace shows a comparison of
+ // the results we obtain with several
+ // of these algorithms based on the
+ // testcase discussed here in this
+ // tutorial program. Here, we will
+ // use the traditional Cuthill-McKee
+ // algorithm already used in some of
+ // the previous tutorial programs.
+ // In the
+ // <a href="#improved-ilu">section on improved ILU</a>
+ // we're going to discuss this issue
+ // in more detail.
+
+ // There is one more change compared
+ // to previous tutorial programs:
+ // There is no reason in sorting the
+ // <code>dim</code> velocity
+ // components individually. In fact,
+ // rather than first enumerating all
+ // $x$-velocities, then all
+ // $y$-velocities, etc, we would like
+ // to keep all velocities at the same
+ // location together and only
+ // separate between velocities (all
+ // components) and pressures. By
+ // default, this is not what the
+ // DoFRenumbering::component_wise
+ // function does: it treats each
+ // vector component separately; what
+ // we have to do is group several
+ // components into "blocks" and pass
+ // this block structure to that
+ // function. Consequently, we
+ // allocate a vector
+ // <code>block_component</code> with
+ // as many elements as there are
+ // components and describe all
+ // velocity components to correspond
+ // to block 0, while the pressure
+ // component will form block 1:
+ template <int dim>
+ void StokesProblem<dim>::setup_dofs ()
{
- BlockCompressedSimpleSparsityPattern csp (2,2);
-
- csp.block(0,0).reinit (n_u, n_u);
- csp.block(1,0).reinit (n_p, n_u);
- csp.block(0,1).reinit (n_u, n_p);
- csp.block(1,1).reinit (n_p, n_p);
-
- csp.collect_sizes();
+ A_preconditioner.reset ();
+ system_matrix.clear ();
+
+ dof_handler.distribute_dofs (fe);
+ DoFRenumbering::Cuthill_McKee (dof_handler);
+
+ std::vector<unsigned int> block_component (dim+1,0);
+ block_component[dim] = 1;
+ DoFRenumbering::component_wise (dof_handler, block_component);
+
+ // Now comes the implementation of
+ // Dirichlet boundary conditions, which
+ // should be evident after the discussion
+ // in the introduction. All that changed is
+ // that the function already appears in the
+ // setup functions, whereas we were used to
+ // see it in some assembly routine. Further
+ // down below where we set up the mesh, we
+ // will associate the top boundary where we
+ // impose Dirichlet boundary conditions
+ // with boundary indicator 1. We will have
+ // to pass this boundary indicator as
+ // second argument to the function below
+ // interpolating boundary values. There is
+ // one more thing, though. The function
+ // describing the Dirichlet conditions was
+ // defined for all components, both
+ // velocity and pressure. However, the
+ // Dirichlet conditions are to be set for
+ // the velocity only. To this end, we use
+ // a <code>component_mask</code> that
+ // filters out the pressure component, so
+ // that the condensation is performed on
+ // velocity degrees of freedom only. Since
+ // we use adaptively refined grids the
+ // constraint matrix needs to be first
+ // filled with hanging node constraints
+ // generated from the DoF handler. Note the
+ // order of the two functions — we
+ // first compute the hanging node
+ // constraints, and then insert the
+ // boundary values into the constraint
+ // matrix. This makes sure that we respect
+ // H<sup>1</sup> conformity on boundaries
+ // with hanging nodes (in three space
+ // dimensions), where the hanging node
+ // needs to dominate the Dirichlet boundary
+ // values.
+ {
+ constraints.clear ();
+ std::vector<bool> component_mask (dim+1, true);
+ component_mask[dim] = false;
+ DoFTools::make_hanging_node_constraints (dof_handler,
+ constraints);
+ VectorTools::interpolate_boundary_values (dof_handler,
+ 1,
+ BoundaryValues<dim>(),
+ constraints,
+ component_mask);
+ }
- DoFTools::make_sparsity_pattern (dof_handler, csp, constraints, false);
- sparsity_pattern.copy_from (csp);
- }
+ constraints.close ();
+
+ // In analogy to step-20, we count the dofs
+ // in the individual components. We could
+ // do this in the same way as there, but we
+ // want to operate on the block structure
+ // we used already for the renumbering: The
+ // function
+ // <code>DoFTools::count_dofs_per_block</code>
+ // does the same as
+ // <code>DoFTools::count_dofs_per_component</code>,
+ // but now grouped as velocity and pressure
+ // block via <code>block_component</code>.
+ std::vector<unsigned int> dofs_per_block (2);
+ DoFTools::count_dofs_per_block (dof_handler, dofs_per_block, block_component);
+ const unsigned int n_u = dofs_per_block[0],
+ n_p = dofs_per_block[1];
+
+ std::cout << " Number of active cells: "
+ << triangulation.n_active_cells()
+ << std::endl
+ << " Number of degrees of freedom: "
+ << dof_handler.n_dofs()
+ << " (" << n_u << '+' << n_p << ')'
+ << std::endl;
- // Finally, the system matrix,
- // solution and right hand side are
- // created from the block
- // structure as in step-20:
- system_matrix.reinit (sparsity_pattern);
-
- solution.reinit (2);
- solution.block(0).reinit (n_u);
- solution.block(1).reinit (n_p);
- solution.collect_sizes ();
-
- system_rhs.reinit (2);
- system_rhs.block(0).reinit (n_u);
- system_rhs.block(1).reinit (n_p);
- system_rhs.collect_sizes ();
-}
+ // The next task is to allocate a
+ // sparsity pattern for the system matrix
+ // we will create. We could do this in
+ // the same way as in step-20,
+ // i.e. directly build an object of type
+ // SparsityPattern through
+ // DoFTools::make_sparsity_pattern. However,
+ // there is a major reason not to do so:
+ // In 3D, the function
+ // DoFTools::max_couplings_between_dofs
+ // yields a conservative but rather large
+ // number for the coupling between the
+ // individual dofs, so that the memory
+ // initially provided for the creation of
+ // the sparsity pattern of the matrix is
+ // far too much -- so much actually that
+ // the initial sparsity pattern won't
+ // even fit into the physical memory of
+ // most systems already for
+ // moderately-sized 3D problems, see also
+ // the discussion in step-18. Instead,
+ // we first build a temporary object that
+ // uses a different data structure that
+ // doesn't require allocating more memory
+ // than necessary but isn't suitable for
+ // use as a basis of SparseMatrix or
+ // BlockSparseMatrix objects; in a second
+ // step we then copy this object into an
+ // object of BlockSparsityPattern. This
+ // is entirely analgous to what we
+ // already did in step-11 and step-18.
+ //
+ // There is one snag again here, though:
+ // it turns out that using the
+ // CompressedSparsityPattern (or the
+ // block version
+ // BlockCompressedSparsityPattern we
+ // would use here) has a bottleneck that
+ // makes the algorithm to build the
+ // sparsity pattern be quadratic in the
+ // number of degrees of freedom. This
+ // doesn't become noticable until we get
+ // well into the range of several 100,000
+ // degrees of freedom, but eventually
+ // dominates the setup of the linear
+ // system when we get to more than a
+ // million degrees of freedom. This is
+ // due to the data structures used in the
+ // CompressedSparsityPattern class,
+ // nothing that can easily be
+ // changed. Fortunately, there is an easy
+ // solution: the
+ // CompressedSimpleSparsityPattern class
+ // (and its block variant
+ // BlockCompressedSimpleSparsityPattern)
+ // has exactly the same interface, uses a
+ // different %internal data structure and
+ // is linear in the number of degrees of
+ // freedom and therefore much more
+ // efficient for large problems. As
+ // another alternative, we could also
+ // have chosen the class
+ // BlockCompressedSetSparsityPattern that
+ // uses yet another strategy for %internal
+ // memory management. Though, that class
+ // turns out to be more memory-demanding
+ // than
+ // BlockCompressedSimpleSparsityPattern
+ // for this example.
+ //
+ // Consequently, this is the class that
+ // we will use for our intermediate
+ // sparsity representation. All this is
+ // done inside a new scope, which means
+ // that the memory of <code>csp</code>
+ // will be released once the information
+ // has been copied to
+ // <code>sparsity_pattern</code>.
+ {
+ BlockCompressedSimpleSparsityPattern csp (2,2);
+ csp.block(0,0).reinit (n_u, n_u);
+ csp.block(1,0).reinit (n_p, n_u);
+ csp.block(0,1).reinit (n_u, n_p);
+ csp.block(1,1).reinit (n_p, n_p);
- // @sect4{StokesProblem::assemble_system}
+ csp.collect_sizes();
- // The assembly process follows the
- // discussion in step-20 and in the
- // introduction. We use the well-known
- // abbreviations for the data structures
- // that hold the local matrix, right
- // hand side, and global
- // numbering of the degrees of freedom
- // for the present cell.
-template <int dim>
-void StokesProblem<dim>::assemble_system ()
-{
- system_matrix=0;
- system_rhs=0;
-
- QGauss<dim> quadrature_formula(degree+2);
-
- FEValues<dim> fe_values (fe, quadrature_formula,
- update_values |
- update_quadrature_points |
- update_JxW_values |
- update_gradients);
-
- const unsigned int dofs_per_cell = 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);
-
- const RightHandSide<dim> right_hand_side;
- std::vector<Vector<double> > rhs_values (n_q_points,
- Vector<double>(dim+1));
-
- // Next, we need two objects that work as
- // extractors for the FEValues
- // object. Their use is explained in detail
- // in the report on @ref vector_valued :
- const FEValuesExtractors::Vector velocities (0);
- const FEValuesExtractors::Scalar pressure (dim);
-
- // As an extension over step-20 and
- // step-21, we include a few
- // optimizations that make assembly
- // much faster for this particular
- // problem. The improvements are
- // based on the observation that we
- // do a few calculations too many
- // times when we do as in step-20:
- // The symmetric gradient actually
- // has <code>dofs_per_cell</code>
- // different values per quadrature
- // point, but we extract it
- // <code>dofs_per_cell*dofs_per_cell</code>
- // times from the FEValues object -
- // for both the loop over
- // <code>i</code> and the inner
- // loop over <code>j</code>. In 3d,
- // that means evaluating it
- // $89^2=7921$ instead of $89$
- // times, a not insignificant
- // difference.
- //
- // So what we're
- // going to do here is to avoid
- // such repeated calculations by
- // getting a vector of rank-2
- // tensors (and similarly for
- // the divergence and the basis
- // function value on pressure)
- // at the quadrature point prior
- // to starting the loop over the
- // dofs on the cell. First, we
- // create the respective objects
- // that will hold these
- // values. Then, we start the
- // loop over all cells and the loop
- // over the quadrature points,
- // where we first extract these
- // values. There is one more
- // optimization we implement here:
- // the local matrix (as well as
- // the global one) is going to
- // be symmetric, since all
- // the operations involved are
- // symmetric with respect to $i$
- // and $j$. This is implemented by
- // simply running the inner loop
- // not to <code>dofs_per_cell</code>,
- // but only up to <code>i</code>,
- // the index of the outer loop.
- std::vector<SymmetricTensor<2,dim> > phi_grads_u (dofs_per_cell);
- std::vector<double> div_phi_u (dofs_per_cell);
- std::vector<double> phi_p (dofs_per_cell);
-
- typename DoFHandler<dim>::active_cell_iterator
- cell = dof_handler.begin_active(),
- endc = dof_handler.end();
- for (; cell!=endc; ++cell)
- {
- fe_values.reinit (cell);
- local_matrix = 0;
- local_rhs = 0;
-
- right_hand_side.vector_value_list(fe_values.get_quadrature_points(),
- rhs_values);
-
- for (unsigned int q=0; q<n_q_points; ++q)
- {
- for (unsigned int k=0; k<dofs_per_cell; ++k)
- {
- phi_grads_u[k] = fe_values[velocities].symmetric_gradient (k, q);
- div_phi_u[k] = fe_values[velocities].divergence (k, q);
- phi_p[k] = fe_values[pressure].value (k, q);
- }
-
- for (unsigned int i=0; i<dofs_per_cell; ++i)
- {
- for (unsigned int j=0; j<=i; ++j)
- {
- local_matrix(i,j) += (phi_grads_u[i] * phi_grads_u[j]
- - div_phi_u[i] * phi_p[j]
- - phi_p[i] * div_phi_u[j]
- + phi_p[i] * phi_p[j])
- * fe_values.JxW(q);
-
- }
-
- const unsigned int component_i =
- fe.system_to_component_index(i).first;
- local_rhs(i) += fe_values.shape_value(i,q) *
- rhs_values[q](component_i) *
- fe_values.JxW(q);
- }
- }
-
- // Note that in the above computation
- // of the local matrix contribution
- // we added the term <code> phi_p[i]
- // * phi_p[j] </code>, yielding a
- // pressure mass matrix in the
- // $(1,1)$ block of the matrix as
- // discussed in the
- // introduction. That this term only
- // ends up in the $(1,1)$ block stems
- // from the fact that both of the
- // factors in <code>phi_p[i] *
- // phi_p[j]</code> are only non-zero
- // when all the other terms vanish
- // (and the other way around).
- //
- // Note also that operator* is
- // overloaded for symmetric
- // tensors, yielding the scalar
- // product between the two
- // tensors in the first line of
- // the local matrix
- // contribution.
-
- // Before we can write the local data
- // into the global matrix (and
- // simultaneously use the
- // ConstraintMatrix object to apply
- // Dirichlet boundary conditions and
- // eliminate hanging node
- // constraints, as we discussed in
- // the introduction), we have to be
- // careful about one thing,
- // though. We have only build up half
- // of the local matrix because of
- // symmetry, but we're going to save
- // the full system matrix in order to
- // use the standard functions for
- // solution. This is done by flipping
- // the indices in case we are
- // pointing into the empty part of
- // the local matrix.
- for (unsigned int i=0; i<dofs_per_cell; ++i)
- for (unsigned int j=i+1; j<dofs_per_cell; ++j)
- local_matrix(i,j) = local_matrix(j,i);
-
- cell->get_dof_indices (local_dof_indices);
- constraints.distribute_local_to_global (local_matrix, local_rhs,
- local_dof_indices,
- system_matrix, system_rhs);
+ DoFTools::make_sparsity_pattern (dof_handler, csp, constraints, false);
+ sparsity_pattern.copy_from (csp);
}
- // Before we're going to solve this
- // linear system, we generate a
- // preconditioner for the
- // velocity-velocity matrix, i.e.,
- // <code>block(0,0)</code> in the
- // system matrix. As mentioned
- // above, this depends on the
- // spatial dimension. Since the two
- // classes described by the
- // <code>InnerPreconditioner::type</code>
- // typedef have the same interface,
- // we do not have to do anything
- // different whether we want to use
- // a sparse direct solver or an
- // ILU:
- std::cout << " Computing preconditioner..." << std::endl << std::flush;
-
- A_preconditioner
- = std_cxx1x::shared_ptr<typename InnerPreconditioner<dim>::type>(new typename InnerPreconditioner<dim>::type());
- A_preconditioner->initialize (system_matrix.block(0,0),
- typename InnerPreconditioner<dim>::type::AdditionalData());
-
-}
+ // Finally, the system matrix,
+ // solution and right hand side are
+ // created from the block
+ // structure as in step-20:
+ system_matrix.reinit (sparsity_pattern);
+
+ solution.reinit (2);
+ solution.block(0).reinit (n_u);
+ solution.block(1).reinit (n_p);
+ solution.collect_sizes ();
+
+ system_rhs.reinit (2);
+ system_rhs.block(0).reinit (n_u);
+ system_rhs.block(1).reinit (n_p);
+ system_rhs.collect_sizes ();
+ }
+ // @sect4{StokesProblem::assemble_system}
- // @sect4{StokesProblem::solve}
-
- // After the discussion in the introduction
- // and the definition of the respective
- // classes above, the implementation of the
- // <code>solve</code> function is rather
- // straigt-forward and done in a similar way
- // as in step-20. To start with, we need an
- // object of the <code>InverseMatrix</code>
- // class that represents the inverse of the
- // matrix A. As described in the
- // introduction, the inverse is generated
- // with the help of an inner preconditioner
- // of type
- // <code>InnerPreconditioner::type</code>.
-template <int dim>
-void StokesProblem<dim>::solve ()
-{
- const InverseMatrix<SparseMatrix<double>,
- typename InnerPreconditioner<dim>::type>
- A_inverse (system_matrix.block(0,0), *A_preconditioner);
- Vector<double> tmp (solution.block(0).size());
-
- // This is as in step-20. We generate the
- // right hand side $B A^{-1} F - G$ for the
- // Schur complement and an object that
- // represents the respective linear
- // operation $B A^{-1} B^T$, now with a
- // template parameter indicating the
- // preconditioner - in accordance with the
- // definition of the class.
+ // The assembly process follows the
+ // discussion in step-20 and in the
+ // introduction. We use the well-known
+ // abbreviations for the data structures
+ // that hold the local matrix, right
+ // hand side, and global
+ // numbering of the degrees of freedom
+ // for the present cell.
+ template <int dim>
+ void StokesProblem<dim>::assemble_system ()
{
- Vector<double> schur_rhs (solution.block(1).size());
- A_inverse.vmult (tmp, system_rhs.block(0));
- system_matrix.block(1,0).vmult (schur_rhs, tmp);
- schur_rhs -= system_rhs.block(1);
-
- SchurComplement<typename InnerPreconditioner<dim>::type>
- schur_complement (system_matrix, A_inverse);
-
- // The usual control structures for
- // the solver call are created...
- SolverControl solver_control (solution.block(1).size(),
- 1e-6*schur_rhs.l2_norm());
- SolverCG<> cg (solver_control);
-
- // Now to the preconditioner to the
- // Schur complement. As explained in
- // the introduction, the
- // preconditioning is done by a mass
- // matrix in the pressure variable. It
- // is stored in the $(1,1)$ block of
- // the system matrix (that is not used
- // anywhere else but in
- // preconditioning).
- //
- // Actually, the solver needs to have
- // the preconditioner in the form
- // $P^{-1}$, so we need to create an
- // inverse operation. Once again, we
- // use an object of the class
- // <code>InverseMatrix</code>, which
- // implements the <code>vmult</code>
- // operation that is needed by the
- // solver. In this case, we have to
- // invert the pressure mass matrix. As
- // it already turned out in earlier
- // tutorial programs, the inversion of
- // a mass matrix is a rather cheap and
- // straight-forward operation (compared
- // to, e.g., a Laplace matrix). The CG
- // method with ILU preconditioning
- // converges in 5-10 steps,
- // independently on the mesh size.
- // This is precisely what we do here:
- // We choose another ILU preconditioner
- // and take it along to the
- // InverseMatrix object via the
- // corresponding template parameter. A
- // CG solver is then called within the
- // vmult operation of the inverse
- // matrix.
+ system_matrix=0;
+ system_rhs=0;
+
+ QGauss<dim> quadrature_formula(degree+2);
+
+ FEValues<dim> fe_values (fe, quadrature_formula,
+ update_values |
+ update_quadrature_points |
+ update_JxW_values |
+ update_gradients);
+
+ const unsigned int dofs_per_cell = 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);
+
+ const RightHandSide<dim> right_hand_side;
+ std::vector<Vector<double> > rhs_values (n_q_points,
+ Vector<double>(dim+1));
+
+ // Next, we need two objects that work as
+ // extractors for the FEValues
+ // object. Their use is explained in detail
+ // in the report on @ref vector_valued :
+ const FEValuesExtractors::Vector velocities (0);
+ const FEValuesExtractors::Scalar pressure (dim);
+
+ // As an extension over step-20 and
+ // step-21, we include a few
+ // optimizations that make assembly
+ // much faster for this particular
+ // problem. The improvements are
+ // based on the observation that we
+ // do a few calculations too many
+ // times when we do as in step-20:
+ // The symmetric gradient actually
+ // has <code>dofs_per_cell</code>
+ // different values per quadrature
+ // point, but we extract it
+ // <code>dofs_per_cell*dofs_per_cell</code>
+ // times from the FEValues object -
+ // for both the loop over
+ // <code>i</code> and the inner
+ // loop over <code>j</code>. In 3d,
+ // that means evaluating it
+ // $89^2=7921$ instead of $89$
+ // times, a not insignificant
+ // difference.
//
- // An alternative that is cheaper to
- // build, but needs more iterations
- // afterwards, would be to choose a
- // SSOR preconditioner with factor
- // 1.2. It needs about twice the number
- // of iterations, but the costs for its
- // generation are almost neglible.
- SparseILU<double> preconditioner;
- preconditioner.initialize (system_matrix.block(1,1),
- SparseILU<double>::AdditionalData());
-
- InverseMatrix<SparseMatrix<double>,SparseILU<double> >
- m_inverse (system_matrix.block(1,1), preconditioner);
-
- // With the Schur complement and an
- // efficient preconditioner at hand, we
- // can solve the respective equation
- // for the pressure (i.e. block 0 in
- // the solution vector) in the usual
- // way:
- cg.solve (schur_complement, solution.block(1), schur_rhs,
- m_inverse);
-
- // After this first solution step, the
- // hanging node constraints have to be
- // distributed to the solution in order
- // to achieve a consistent pressure
- // field.
- constraints.distribute (solution);
-
- std::cout << " "
- << solver_control.last_step()
- << " outer CG Schur complement iterations for pressure"
- << std::endl;
- }
+ // So what we're
+ // going to do here is to avoid
+ // such repeated calculations by
+ // getting a vector of rank-2
+ // tensors (and similarly for
+ // the divergence and the basis
+ // function value on pressure)
+ // at the quadrature point prior
+ // to starting the loop over the
+ // dofs on the cell. First, we
+ // create the respective objects
+ // that will hold these
+ // values. Then, we start the
+ // loop over all cells and the loop
+ // over the quadrature points,
+ // where we first extract these
+ // values. There is one more
+ // optimization we implement here:
+ // the local matrix (as well as
+ // the global one) is going to
+ // be symmetric, since all
+ // the operations involved are
+ // symmetric with respect to $i$
+ // and $j$. This is implemented by
+ // simply running the inner loop
+ // not to <code>dofs_per_cell</code>,
+ // but only up to <code>i</code>,
+ // the index of the outer loop.
+ std::vector<SymmetricTensor<2,dim> > phi_grads_u (dofs_per_cell);
+ std::vector<double> div_phi_u (dofs_per_cell);
+ std::vector<double> phi_p (dofs_per_cell);
+
+ typename DoFHandler<dim>::active_cell_iterator
+ cell = dof_handler.begin_active(),
+ endc = dof_handler.end();
+ for (; cell!=endc; ++cell)
+ {
+ fe_values.reinit (cell);
+ local_matrix = 0;
+ local_rhs = 0;
+
+ right_hand_side.vector_value_list(fe_values.get_quadrature_points(),
+ rhs_values);
+
+ for (unsigned int q=0; q<n_q_points; ++q)
+ {
+ for (unsigned int k=0; k<dofs_per_cell; ++k)
+ {
+ phi_grads_u[k] = fe_values[velocities].symmetric_gradient (k, q);
+ div_phi_u[k] = fe_values[velocities].divergence (k, q);
+ phi_p[k] = fe_values[pressure].value (k, q);
+ }
+
+ for (unsigned int i=0; i<dofs_per_cell; ++i)
+ {
+ for (unsigned int j=0; j<=i; ++j)
+ {
+ local_matrix(i,j) += (phi_grads_u[i] * phi_grads_u[j]
+ - div_phi_u[i] * phi_p[j]
+ - phi_p[i] * div_phi_u[j]
+ + phi_p[i] * phi_p[j])
+ * fe_values.JxW(q);
+
+ }
+
+ const unsigned int component_i =
+ fe.system_to_component_index(i).first;
+ local_rhs(i) += fe_values.shape_value(i,q) *
+ rhs_values[q](component_i) *
+ fe_values.JxW(q);
+ }
+ }
+
+ // Note that in the above computation
+ // of the local matrix contribution
+ // we added the term <code> phi_p[i]
+ // * phi_p[j] </code>, yielding a
+ // pressure mass matrix in the
+ // $(1,1)$ block of the matrix as
+ // discussed in the
+ // introduction. That this term only
+ // ends up in the $(1,1)$ block stems
+ // from the fact that both of the
+ // factors in <code>phi_p[i] *
+ // phi_p[j]</code> are only non-zero
+ // when all the other terms vanish
+ // (and the other way around).
+ //
+ // Note also that operator* is
+ // overloaded for symmetric
+ // tensors, yielding the scalar
+ // product between the two
+ // tensors in the first line of
+ // the local matrix
+ // contribution.
+
+ // Before we can write the local data
+ // into the global matrix (and
+ // simultaneously use the
+ // ConstraintMatrix object to apply
+ // Dirichlet boundary conditions and
+ // eliminate hanging node
+ // constraints, as we discussed in
+ // the introduction), we have to be
+ // careful about one thing,
+ // though. We have only build up half
+ // of the local matrix because of
+ // symmetry, but we're going to save
+ // the full system matrix in order to
+ // use the standard functions for
+ // solution. This is done by flipping
+ // the indices in case we are
+ // pointing into the empty part of
+ // the local matrix.
+ for (unsigned int i=0; i<dofs_per_cell; ++i)
+ for (unsigned int j=i+1; j<dofs_per_cell; ++j)
+ local_matrix(i,j) = local_matrix(j,i);
+
+ cell->get_dof_indices (local_dof_indices);
+ constraints.distribute_local_to_global (local_matrix, local_rhs,
+ local_dof_indices,
+ system_matrix, system_rhs);
+ }
+
+ // Before we're going to solve this
+ // linear system, we generate a
+ // preconditioner for the
+ // velocity-velocity matrix, i.e.,
+ // <code>block(0,0)</code> in the
+ // system matrix. As mentioned
+ // above, this depends on the
+ // spatial dimension. Since the two
+ // classes described by the
+ // <code>InnerPreconditioner::type</code>
+ // typedef have the same interface,
+ // we do not have to do anything
+ // different whether we want to use
+ // a sparse direct solver or an
+ // ILU:
+ std::cout << " Computing preconditioner..." << std::endl << std::flush;
+
+ A_preconditioner
+ = std_cxx1x::shared_ptr<typename InnerPreconditioner<dim>::type>(new typename InnerPreconditioner<dim>::type());
+ A_preconditioner->initialize (system_matrix.block(0,0),
+ typename InnerPreconditioner<dim>::type::AdditionalData());
- // As in step-20, we finally need to
- // solve for the velocity equation where
- // we plug in the solution to the
- // pressure equation. This involves only
- // objects we already know - so we simply
- // multiply $p$ by $B^T$, subtract the
- // right hand side and multiply by the
- // inverse of $A$. At the end, we need to
- // distribute the constraints from
- // hanging nodes in order to obtain a
- // constistent flow field:
- {
- system_matrix.block(0,1).vmult (tmp, solution.block(1));
- tmp *= -1;
- tmp += system_rhs.block(0);
+ }
- A_inverse.vmult (solution.block(0), tmp);
- constraints.distribute (solution);
- }
-}
+ // @sect4{StokesProblem::solve}
+
+ // After the discussion in the introduction
+ // and the definition of the respective
+ // classes above, the implementation of the
+ // <code>solve</code> function is rather
+ // straigt-forward and done in a similar way
+ // as in step-20. To start with, we need an
+ // object of the <code>InverseMatrix</code>
+ // class that represents the inverse of the
+ // matrix A. As described in the
+ // introduction, the inverse is generated
+ // with the help of an inner preconditioner
+ // of type
+ // <code>InnerPreconditioner::type</code>.
+ template <int dim>
+ void StokesProblem<dim>::solve ()
+ {
+ const InverseMatrix<SparseMatrix<double>,
+ typename InnerPreconditioner<dim>::type>
+ A_inverse (system_matrix.block(0,0), *A_preconditioner);
+ Vector<double> tmp (solution.block(0).size());
+
+ // This is as in step-20. We generate the
+ // right hand side $B A^{-1} F - G$ for the
+ // Schur complement and an object that
+ // represents the respective linear
+ // operation $B A^{-1} B^T$, now with a
+ // template parameter indicating the
+ // preconditioner - in accordance with the
+ // definition of the class.
+ {
+ Vector<double> schur_rhs (solution.block(1).size());
+ A_inverse.vmult (tmp, system_rhs.block(0));
+ system_matrix.block(1,0).vmult (schur_rhs, tmp);
+ schur_rhs -= system_rhs.block(1);
+
+ SchurComplement<typename InnerPreconditioner<dim>::type>
+ schur_complement (system_matrix, A_inverse);
+
+ // The usual control structures for
+ // the solver call are created...
+ SolverControl solver_control (solution.block(1).size(),
+ 1e-6*schur_rhs.l2_norm());
+ SolverCG<> cg (solver_control);
+
+ // Now to the preconditioner to the
+ // Schur complement. As explained in
+ // the introduction, the
+ // preconditioning is done by a mass
+ // matrix in the pressure variable. It
+ // is stored in the $(1,1)$ block of
+ // the system matrix (that is not used
+ // anywhere else but in
+ // preconditioning).
+ //
+ // Actually, the solver needs to have
+ // the preconditioner in the form
+ // $P^{-1}$, so we need to create an
+ // inverse operation. Once again, we
+ // use an object of the class
+ // <code>InverseMatrix</code>, which
+ // implements the <code>vmult</code>
+ // operation that is needed by the
+ // solver. In this case, we have to
+ // invert the pressure mass matrix. As
+ // it already turned out in earlier
+ // tutorial programs, the inversion of
+ // a mass matrix is a rather cheap and
+ // straight-forward operation (compared
+ // to, e.g., a Laplace matrix). The CG
+ // method with ILU preconditioning
+ // converges in 5-10 steps,
+ // independently on the mesh size.
+ // This is precisely what we do here:
+ // We choose another ILU preconditioner
+ // and take it along to the
+ // InverseMatrix object via the
+ // corresponding template parameter. A
+ // CG solver is then called within the
+ // vmult operation of the inverse
+ // matrix.
+ //
+ // An alternative that is cheaper to
+ // build, but needs more iterations
+ // afterwards, would be to choose a
+ // SSOR preconditioner with factor
+ // 1.2. It needs about twice the number
+ // of iterations, but the costs for its
+ // generation are almost neglible.
+ SparseILU<double> preconditioner;
+ preconditioner.initialize (system_matrix.block(1,1),
+ SparseILU<double>::AdditionalData());
+
+ InverseMatrix<SparseMatrix<double>,SparseILU<double> >
+ m_inverse (system_matrix.block(1,1), preconditioner);
+
+ // With the Schur complement and an
+ // efficient preconditioner at hand, we
+ // can solve the respective equation
+ // for the pressure (i.e. block 0 in
+ // the solution vector) in the usual
+ // way:
+ cg.solve (schur_complement, solution.block(1), schur_rhs,
+ m_inverse);
+
+ // After this first solution step, the
+ // hanging node constraints have to be
+ // distributed to the solution in order
+ // to achieve a consistent pressure
+ // field.
+ constraints.distribute (solution);
+
+ std::cout << " "
+ << solver_control.last_step()
+ << " outer CG Schur complement iterations for pressure"
+ << std::endl;
+ }
- // @sect4{StokesProblem::output_results}
-
- // The next function generates graphical
- // output. In this example, we are going to
- // use the VTK file format. We attach
- // names to the individual variables in the
- // problem: <code>velocity</code> to the
- // <code>dim</code> components of velocity
- // and <code>pressure</code> to the
- // pressure.
- //
- // Not all visualization programs have the
- // ability to group individual vector
- // components into a vector to provide
- // vector plots; in particular, this holds
- // for some VTK-based visualization
- // programs. In this case, the logical
- // grouping of components into vectors
- // should already be described in the file
- // containing the data. In other words,
- // what we need to do is provide our output
- // writers with a way to know which of the
- // components of the finite element
- // logically form a vector (with $d$
- // components in $d$ space dimensions)
- // rather than letting them assume that we
- // simply have a bunch of scalar fields.
- // This is achieved using the members of
- // the
- // <code>DataComponentInterpretation</code>
- // namespace: as with the filename, we
- // create a vector in which the first
- // <code>dim</code> components refer to the
- // velocities and are given the tag
- // <code>DataComponentInterpretation::component_is_part_of_vector</code>;
- // we finally push one tag
- // <code>DataComponentInterpretation::component_is_scalar</code>
- // to describe the grouping of the pressure
- // variable.
-
- // The rest of the function is then
- // the same as in step-20.
-template <int dim>
-void
-StokesProblem<dim>::output_results (const unsigned int refinement_cycle) const
-{
- std::vector<std::string> solution_names (dim, "velocity");
- solution_names.push_back ("pressure");
+ // As in step-20, we finally need to
+ // solve for the velocity equation where
+ // we plug in the solution to the
+ // pressure equation. This involves only
+ // objects we already know - so we simply
+ // multiply $p$ by $B^T$, subtract the
+ // right hand side and multiply by the
+ // inverse of $A$. At the end, we need to
+ // distribute the constraints from
+ // hanging nodes in order to obtain a
+ // constistent flow field:
+ {
+ system_matrix.block(0,1).vmult (tmp, solution.block(1));
+ tmp *= -1;
+ tmp += system_rhs.block(0);
- std::vector<DataComponentInterpretation::DataComponentInterpretation>
- data_component_interpretation
- (dim, DataComponentInterpretation::component_is_part_of_vector);
- data_component_interpretation
- .push_back (DataComponentInterpretation::component_is_scalar);
-
- DataOut<dim> data_out;
- data_out.attach_dof_handler (dof_handler);
- data_out.add_data_vector (solution, solution_names,
- DataOut<dim>::type_dof_data,
- data_component_interpretation);
- data_out.build_patches ();
-
- std::ostringstream filename;
- filename << "solution-"
- << Utilities::int_to_string (refinement_cycle, 2)
- << ".vtk";
-
- std::ofstream output (filename.str().c_str());
- data_out.write_vtk (output);
-}
+ A_inverse.vmult (solution.block(0), tmp);
+ constraints.distribute (solution);
+ }
+ }
- // @sect4{StokesProblem::refine_mesh}
-
- // This is the last interesting function of
- // the <code>StokesProblem</code> class.
- // As indicated by its name, it takes the
- // solution to the problem and refines the
- // mesh where this is needed. The procedure
- // is the same as in the respective step in
- // step-6, with the exception that we base
- // the refinement only on the change in
- // pressure, i.e., we call the Kelly error
- // estimator with a mask
- // object. Additionally, we do not coarsen
- // the grid again:
-template <int dim>
-void
-StokesProblem<dim>::refine_mesh ()
-{
- Vector<float> estimated_error_per_cell (triangulation.n_active_cells());
-
- std::vector<bool> component_mask (dim+1, false);
- component_mask[dim] = true;
- KellyErrorEstimator<dim>::estimate (dof_handler,
- QGauss<dim-1>(degree+1),
- typename FunctionMap<dim>::type(),
- solution,
- estimated_error_per_cell,
- component_mask);
-
- GridRefinement::refine_and_coarsen_fixed_number (triangulation,
- estimated_error_per_cell,
- 0.3, 0.0);
- triangulation.execute_coarsening_and_refinement ();
-}
+ // @sect4{StokesProblem::output_results}
- // @sect4{StokesProblem::run}
-
- // The last step in the Stokes class is, as
- // usual, the function that generates the
- // initial grid and calls the other
- // functions in the respective order.
- //
- // We start off with a rectangle of size $4
- // \times 1$ (in 2d) or $4 \times 1 \times
- // 1$ (in 3d), placed in $R^2/R^3$ as
- // $(-2,2)\times(-1,0)$ or
- // $(-2,2)\times(0,1)\times(-1,1)$,
- // respectively. It is natural to start
- // with equal mesh size in each direction,
- // so we subdivide the initial rectangle
- // four times in the first coordinate
- // direction. To limit the scope of the
- // variables involved in the creation of
- // the mesh to the range where we actually
- // need them, we put the entire block
- // between a pair of braces:
-template <int dim>
-void StokesProblem<dim>::run ()
-{
+ // The next function generates graphical
+ // output. In this example, we are going to
+ // use the VTK file format. We attach
+ // names to the individual variables in the
+ // problem: <code>velocity</code> to the
+ // <code>dim</code> components of velocity
+ // and <code>pressure</code> to the
+ // pressure.
+ //
+ // Not all visualization programs have the
+ // ability to group individual vector
+ // components into a vector to provide
+ // vector plots; in particular, this holds
+ // for some VTK-based visualization
+ // programs. In this case, the logical
+ // grouping of components into vectors
+ // should already be described in the file
+ // containing the data. In other words,
+ // what we need to do is provide our output
+ // writers with a way to know which of the
+ // components of the finite element
+ // logically form a vector (with $d$
+ // components in $d$ space dimensions)
+ // rather than letting them assume that we
+ // simply have a bunch of scalar fields.
+ // This is achieved using the members of
+ // the
+ // <code>DataComponentInterpretation</code>
+ // namespace: as with the filename, we
+ // create a vector in which the first
+ // <code>dim</code> components refer to the
+ // velocities and are given the tag
+ // <code>DataComponentInterpretation::component_is_part_of_vector</code>;
+ // we finally push one tag
+ // <code>DataComponentInterpretation::component_is_scalar</code>
+ // to describe the grouping of the pressure
+ // variable.
+
+ // The rest of the function is then
+ // the same as in step-20.
+ template <int dim>
+ void
+ StokesProblem<dim>::output_results (const unsigned int refinement_cycle) const
{
- std::vector<unsigned int> subdivisions (dim, 1);
- subdivisions[0] = 4;
-
- const Point<dim> bottom_left = (dim == 2 ?
- Point<dim>(-2,-1) :
- Point<dim>(-2,0,-1));
- const Point<dim> top_right = (dim == 2 ?
- Point<dim>(2,0) :
- Point<dim>(2,1,0));
-
- GridGenerator::subdivided_hyper_rectangle (triangulation,
- subdivisions,
- bottom_left,
- top_right);
- }
+ std::vector<std::string> solution_names (dim, "velocity");
+ solution_names.push_back ("pressure");
- // A boundary indicator of 1 is set to all
- // boundaries that are subject to Dirichlet
- // boundary conditions, i.e. to faces that
- // are located at 0 in the last coordinate
- // direction. See the example description
- // above for details.
- for (typename Triangulation<dim>::active_cell_iterator
- cell = triangulation.begin_active();
- cell != triangulation.end(); ++cell)
- for (unsigned int f=0; f<GeometryInfo<dim>::faces_per_cell; ++f)
- if (cell->face(f)->center()[dim-1] == 0)
- cell->face(f)->set_all_boundary_indicators(1);
-
-
- // We then apply an initial refinement
- // before solving for the first time. In
- // 3D, there are going to be more degrees
- // of freedom, so we refine less there:
- triangulation.refine_global (4-dim);
-
- // As first seen in step-6, we cycle over
- // the different refinement levels and
- // refine (except for the first cycle),
- // setup the degrees of freedom and
- // matrices, assemble, solve and create
- // output:
- for (unsigned int refinement_cycle = 0; refinement_cycle<6;
- ++refinement_cycle)
- {
- std::cout << "Refinement cycle " << refinement_cycle << std::endl;
-
- if (refinement_cycle > 0)
- refine_mesh ();
+ std::vector<DataComponentInterpretation::DataComponentInterpretation>
+ data_component_interpretation
+ (dim, DataComponentInterpretation::component_is_part_of_vector);
+ data_component_interpretation
+ .push_back (DataComponentInterpretation::component_is_scalar);
+
+ DataOut<dim> data_out;
+ data_out.attach_dof_handler (dof_handler);
+ data_out.add_data_vector (solution, solution_names,
+ DataOut<dim>::type_dof_data,
+ data_component_interpretation);
+ data_out.build_patches ();
+
+ std::ostringstream filename;
+ filename << "solution-"
+ << Utilities::int_to_string (refinement_cycle, 2)
+ << ".vtk";
+
+ std::ofstream output (filename.str().c_str());
+ data_out.write_vtk (output);
+ }
- setup_dofs ();
- std::cout << " Assembling..." << std::endl << std::flush;
- assemble_system ();
+ // @sect4{StokesProblem::refine_mesh}
+
+ // This is the last interesting function of
+ // the <code>StokesProblem</code> class.
+ // As indicated by its name, it takes the
+ // solution to the problem and refines the
+ // mesh where this is needed. The procedure
+ // is the same as in the respective step in
+ // step-6, with the exception that we base
+ // the refinement only on the change in
+ // pressure, i.e., we call the Kelly error
+ // estimator with a mask
+ // object. Additionally, we do not coarsen
+ // the grid again:
+ template <int dim>
+ void
+ StokesProblem<dim>::refine_mesh ()
+ {
+ Vector<float> estimated_error_per_cell (triangulation.n_active_cells());
+
+ std::vector<bool> component_mask (dim+1, false);
+ component_mask[dim] = true;
+ KellyErrorEstimator<dim>::estimate (dof_handler,
+ QGauss<dim-1>(degree+1),
+ typename FunctionMap<dim>::type(),
+ solution,
+ estimated_error_per_cell,
+ component_mask);
+
+ GridRefinement::refine_and_coarsen_fixed_number (triangulation,
+ estimated_error_per_cell,
+ 0.3, 0.0);
+ triangulation.execute_coarsening_and_refinement ();
+ }
- std::cout << " Solving..." << std::flush;
- solve ();
- output_results (refinement_cycle);
+ // @sect4{StokesProblem::run}
- std::cout << std::endl;
+ // The last step in the Stokes class is, as
+ // usual, the function that generates the
+ // initial grid and calls the other
+ // functions in the respective order.
+ //
+ // We start off with a rectangle of size $4
+ // \times 1$ (in 2d) or $4 \times 1 \times
+ // 1$ (in 3d), placed in $R^2/R^3$ as
+ // $(-2,2)\times(-1,0)$ or
+ // $(-2,2)\times(0,1)\times(-1,1)$,
+ // respectively. It is natural to start
+ // with equal mesh size in each direction,
+ // so we subdivide the initial rectangle
+ // four times in the first coordinate
+ // direction. To limit the scope of the
+ // variables involved in the creation of
+ // the mesh to the range where we actually
+ // need them, we put the entire block
+ // between a pair of braces:
+ template <int dim>
+ void StokesProblem<dim>::run ()
+ {
+ {
+ std::vector<unsigned int> subdivisions (dim, 1);
+ subdivisions[0] = 4;
+
+ const Point<dim> bottom_left = (dim == 2 ?
+ Point<dim>(-2,-1) :
+ Point<dim>(-2,0,-1));
+ const Point<dim> top_right = (dim == 2 ?
+ Point<dim>(2,0) :
+ Point<dim>(2,1,0));
+
+ GridGenerator::subdivided_hyper_rectangle (triangulation,
+ subdivisions,
+ bottom_left,
+ top_right);
}
+
+ // A boundary indicator of 1 is set to all
+ // boundaries that are subject to Dirichlet
+ // boundary conditions, i.e. to faces that
+ // are located at 0 in the last coordinate
+ // direction. See the example description
+ // above for details.
+ for (typename Triangulation<dim>::active_cell_iterator
+ cell = triangulation.begin_active();
+ cell != triangulation.end(); ++cell)
+ for (unsigned int f=0; f<GeometryInfo<dim>::faces_per_cell; ++f)
+ if (cell->face(f)->center()[dim-1] == 0)
+ cell->face(f)->set_all_boundary_indicators(1);
+
+
+ // We then apply an initial refinement
+ // before solving for the first time. In
+ // 3D, there are going to be more degrees
+ // of freedom, so we refine less there:
+ triangulation.refine_global (4-dim);
+
+ // As first seen in step-6, we cycle over
+ // the different refinement levels and
+ // refine (except for the first cycle),
+ // setup the degrees of freedom and
+ // matrices, assemble, solve and create
+ // output:
+ for (unsigned int refinement_cycle = 0; refinement_cycle<6;
+ ++refinement_cycle)
+ {
+ std::cout << "Refinement cycle " << refinement_cycle << std::endl;
+
+ if (refinement_cycle > 0)
+ refine_mesh ();
+
+ setup_dofs ();
+
+ std::cout << " Assembling..." << std::endl << std::flush;
+ assemble_system ();
+
+ std::cout << " Solving..." << std::flush;
+ solve ();
+
+ output_results (refinement_cycle);
+
+ std::cout << std::endl;
+ }
+ }
}
{
try
{
+ using namespace dealii;
+ using namespace Step22;
+
deallog.depth_console (0);
StokesProblem<2> flow_problem(1);
/* $Id$ */
/* Version: $Name: $ */
/* */
-/* Copyright (C) 2006, 2007, 2008, 2009 by the deal.II authors */
+/* Copyright (C) 2006, 2007, 2008, 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 */
// The last step is as in all
// previous programs:
-using namespace dealii;
-
-
- // @sect3{The <code>WaveEquation</code> class}
-
- // Next comes the declaration of the main
- // class. It's public interface of functions
- // is like in most of the other tutorial
- // programs. Worth mentioning is that we now
- // have to store four matrices instead of
- // one: the mass matrix $M$, the Laplace
- // matrix $A$, the matrix $M+k^2\theta^2A$
- // used for solving for $U^n$, and a copy of
- // the mass matrix with boundary conditions
- // applied used for solving for $V^n$. Note
- // that it is a bit wasteful to have an
- // additional copy of the mass matrix
- // around. We will discuss strategies for how
- // to avoid this in the section on possible
- // improvements.
- //
- // Likewise, we need solution vectors for
- // $U^n,V^n$ as well as for the corresponding
- // vectors at the previous time step,
- // $U^{n-1},V^{n-1}$. The
- // <code>system_rhs</code> will be used for
- // whatever right hand side vector we have
- // when solving one of the two linear systems
- // in each time step. These will be solved in
- // the two functions <code>solve_u</code> and
- // <code>solve_v</code>.
- //
- // Finally, the variable
- // <code>theta</code> is used to
- // indicate the parameter $\theta$
- // that is used to define which time
- // stepping scheme to use, as
- // explained in the introduction. The
- // rest is self-explanatory.
-template <int dim>
-class WaveEquation
+namespace Step23
{
- public:
- WaveEquation ();
- void run ();
-
- private:
- void setup_system ();
- void solve_u ();
- void solve_v ();
- void output_results () const;
-
- Triangulation<dim> triangulation;
- FE_Q<dim> fe;
- DoFHandler<dim> dof_handler;
-
- ConstraintMatrix constraints;
-
- SparsityPattern sparsity_pattern;
- SparseMatrix<double> mass_matrix;
- SparseMatrix<double> laplace_matrix;
- SparseMatrix<double> matrix_u;
- SparseMatrix<double> matrix_v;
-
- Vector<double> solution_u, solution_v;
- Vector<double> old_solution_u, old_solution_v;
- Vector<double> system_rhs;
-
- double time, time_step;
- unsigned int timestep_number;
- const double theta;
-};
-
-
-
- // @sect3{Equation data}
-
- // Before we go on filling in the
- // details of the main class, let us
- // define the equation data
- // corresponding to the problem,
- // i.e. initial and boundary values
- // for both the solution $u$ and its
- // time derivative $v$, as well as a
- // right hand side class. We do so
- // using classes derived from the
- // Function class template that has
- // been used many times before, so
- // the following should not be a
- // surprise.
- //
- // Let's start with initial values
- // and choose zero for both the value
- // $u$ as well as its time
- // derivative, the velocity $v$:
-template <int dim>
-class InitialValuesU : public Function<dim>
-{
- public:
- InitialValuesU () : Function<dim>() {}
-
- virtual double value (const Point<dim> &p,
- const unsigned int component = 0) const;
-};
-
-
-template <int dim>
-class InitialValuesV : public Function<dim>
-{
- public:
- InitialValuesV () : Function<dim>() {}
-
- virtual double value (const Point<dim> &p,
- const unsigned int component = 0) const;
-};
-
-
-
-template <int dim>
-double InitialValuesU<dim>::value (const Point<dim> &/*p*/,
- const unsigned int component) const
-{
- Assert (component == 0, ExcInternalError());
- return 0;
-}
-
-
-
-template <int dim>
-double InitialValuesV<dim>::value (const Point<dim> &/*p*/,
- const unsigned int component) const
-{
- Assert (component == 0, ExcInternalError());
- return 0;
-}
-
-
-
- // Secondly, we have the right hand
- // side forcing term. Boring as we
- // are, we choose zero here as well:
-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>
-double RightHandSide<dim>::value (const Point<dim> &/*p*/,
- const unsigned int component) const
-{
- Assert (component == 0, ExcInternalError());
- return 0;
-}
-
-
-
- // Finally, we have boundary values for $u$
- // and $v$. They are as described in the
- // introduction, one being the time
- // derivative of the other:
-template <int dim>
-class BoundaryValuesU : public Function<dim>
-{
- public:
- BoundaryValuesU () : Function<dim>() {}
-
- virtual double value (const Point<dim> &p,
- const unsigned int component = 0) const;
-};
-
-
-
-
-template <int dim>
-class BoundaryValuesV : public Function<dim>
-{
- public:
- BoundaryValuesV () : Function<dim>() {}
-
- virtual double value (const Point<dim> &p,
- const unsigned int component = 0) const;
-};
-
-
-
-
-template <int dim>
-double BoundaryValuesU<dim>::value (const Point<dim> &p,
- const unsigned int component) const
-{
- Assert (component == 0, ExcInternalError());
-
- if ((this->get_time() <= 0.5) &&
- (p[0] < 0) &&
- (p[1] < 1./3) &&
- (p[1] > -1./3))
- return std::sin (this->get_time() * 4 * numbers::PI);
- else
+ using namespace dealii;
+
+
+ // @sect3{The <code>WaveEquation</code> class}
+
+ // Next comes the declaration of the main
+ // class. It's public interface of functions
+ // is like in most of the other tutorial
+ // programs. Worth mentioning is that we now
+ // have to store four matrices instead of
+ // one: the mass matrix $M$, the Laplace
+ // matrix $A$, the matrix $M+k^2\theta^2A$
+ // used for solving for $U^n$, and a copy of
+ // the mass matrix with boundary conditions
+ // applied used for solving for $V^n$. Note
+ // that it is a bit wasteful to have an
+ // additional copy of the mass matrix
+ // around. We will discuss strategies for how
+ // to avoid this in the section on possible
+ // improvements.
+ //
+ // Likewise, we need solution vectors for
+ // $U^n,V^n$ as well as for the corresponding
+ // vectors at the previous time step,
+ // $U^{n-1},V^{n-1}$. The
+ // <code>system_rhs</code> will be used for
+ // whatever right hand side vector we have
+ // when solving one of the two linear systems
+ // in each time step. These will be solved in
+ // the two functions <code>solve_u</code> and
+ // <code>solve_v</code>.
+ //
+ // Finally, the variable
+ // <code>theta</code> is used to
+ // indicate the parameter $\theta$
+ // that is used to define which time
+ // stepping scheme to use, as
+ // explained in the introduction. The
+ // rest is self-explanatory.
+ template <int dim>
+ class WaveEquation
+ {
+ public:
+ WaveEquation ();
+ void run ();
+
+ private:
+ void setup_system ();
+ void solve_u ();
+ void solve_v ();
+ void output_results () const;
+
+ Triangulation<dim> triangulation;
+ FE_Q<dim> fe;
+ DoFHandler<dim> dof_handler;
+
+ ConstraintMatrix constraints;
+
+ SparsityPattern sparsity_pattern;
+ SparseMatrix<double> mass_matrix;
+ SparseMatrix<double> laplace_matrix;
+ SparseMatrix<double> matrix_u;
+ SparseMatrix<double> matrix_v;
+
+ Vector<double> solution_u, solution_v;
+ Vector<double> old_solution_u, old_solution_v;
+ Vector<double> system_rhs;
+
+ double time, time_step;
+ unsigned int timestep_number;
+ const double theta;
+ };
+
+
+
+ // @sect3{Equation data}
+
+ // Before we go on filling in the
+ // details of the main class, let us
+ // define the equation data
+ // corresponding to the problem,
+ // i.e. initial and boundary values
+ // for both the solution $u$ and its
+ // time derivative $v$, as well as a
+ // right hand side class. We do so
+ // using classes derived from the
+ // Function class template that has
+ // been used many times before, so
+ // the following should not be a
+ // surprise.
+ //
+ // Let's start with initial values
+ // and choose zero for both the value
+ // $u$ as well as its time
+ // derivative, the velocity $v$:
+ template <int dim>
+ class InitialValuesU : public Function<dim>
+ {
+ public:
+ InitialValuesU () : Function<dim>() {}
+
+ virtual double value (const Point<dim> &p,
+ const unsigned int component = 0) const;
+ };
+
+
+ template <int dim>
+ class InitialValuesV : public Function<dim>
+ {
+ public:
+ InitialValuesV () : Function<dim>() {}
+
+ virtual double value (const Point<dim> &p,
+ const unsigned int component = 0) const;
+ };
+
+
+
+ template <int dim>
+ double InitialValuesU<dim>::value (const Point<dim> &/*p*/,
+ const unsigned int component) const
+ {
+ Assert (component == 0, ExcInternalError());
return 0;
-}
+ }
-template <int dim>
-double BoundaryValuesV<dim>::value (const Point<dim> &p,
- const unsigned int component) const
-{
- Assert (component == 0, ExcInternalError());
-
- if ((this->get_time() <= 0.5) &&
- (p[0] < 0) &&
- (p[1] < 1./3) &&
- (p[1] > -1./3))
- return (std::cos (this->get_time() * 4 * numbers::PI) *
- 4 * numbers::PI);
- else
+ template <int dim>
+ double InitialValuesV<dim>::value (const Point<dim> &/*p*/,
+ const unsigned int component) const
+ {
+ Assert (component == 0, ExcInternalError());
return 0;
-}
+ }
+ // Secondly, we have the right hand
+ // side forcing term. Boring as we
+ // are, we choose zero here as well:
+ template <int dim>
+ class RightHandSide : public Function<dim>
+ {
+ public:
+ RightHandSide () : Function<dim>() {}
- // @sect3{Implementation of the <code>WaveEquation</code> class}
-
- // The implementation of the actual logic is
- // actually fairly short, since we relegate
- // things like assembling the matrices and
- // right hand side vectors to the
- // library. The rest boils down to not much
- // more than 130 lines of actual code, a
- // significant fraction of which is
- // boilerplate code that can be taken from
- // previous example programs (e.g. the
- // functions that solve linear systems, or
- // that generate output).
- //
- // Let's start with the constructor (for an
- // explanation of the choice of time step,
- // see the section on Courant, Friedrichs,
- // and Lewy in the introduction):
-template <int dim>
-WaveEquation<dim>::WaveEquation () :
- fe (1),
- dof_handler (triangulation),
- time_step (1./64),
- theta (0.5)
-{}
-
-
- // @sect4{WaveEquation::setup_system}
-
- // The next function is the one that
- // sets up the mesh, DoFHandler, and
- // matrices and vectors at the
- // beginning of the program,
- // i.e. before the first time
- // step. The first few lines are
- // pretty much standard if you've
- // read through the tutorial programs
- // at least up to step-6:
-template <int dim>
-void WaveEquation<dim>::setup_system ()
-{
- GridGenerator::hyper_cube (triangulation, -1, 1);
- triangulation.refine_global (7);
-
- std::cout << "Number of active cells: "
- << triangulation.n_active_cells()
- << std::endl;
-
- dof_handler.distribute_dofs (fe);
-
- std::cout << "Number of degrees of freedom: "
- << dof_handler.n_dofs()
- << std::endl
- << std::endl;
-
- sparsity_pattern.reinit (dof_handler.n_dofs(),
- dof_handler.n_dofs(),
- dof_handler.max_couplings_between_dofs());
- DoFTools::make_sparsity_pattern (dof_handler, sparsity_pattern);
- sparsity_pattern.compress();
-
- // Then comes a block where we have to
- // initialize the 3 matrices we need in the
- // course of the program: the mass matrix,
- // the laplace matrix, and the matrix
- // $M+k^2\theta^2A$ used when solving for
- // $U^n$ in each time step.
- //
- // When setting up these matrices, note
- // that they all make use of the same
- // sparsity pattern object. Finally, the
- // reason why matrices and sparsity
- // patterns are separate objects in deal.II
- // (unlike in many other finite element or
- // linear algebra classes) becomes clear:
- // in a significant fraction of
- // applications, one has to hold several
- // matrices that happen to have the same
- // sparsity pattern, and there is no reason
- // for them not to share this information,
- // rather than re-building and wasting
- // memory on it several times.
- //
- // After initializing all of these
- // matrices, we call library functions that
- // build the Laplace and mass matrices. All
- // they need is a DoFHandler object and a
- // quadrature formula object that is to be
- // used for numerical integration. Note
- // that in many respects these functions
- // are better than what we would usually do
- // in application programs, for example
- // because they automatically parallelize
- // building the matrices if multiple
- // processors are available in a
- // machine. The matrices for solving linear
- // systems will be filled in the run()
- // method because we need to re-apply
- // boundary conditions every time step.
- mass_matrix.reinit (sparsity_pattern);
- laplace_matrix.reinit (sparsity_pattern);
- matrix_u.reinit (sparsity_pattern);
- matrix_v.reinit (sparsity_pattern);
-
- MatrixCreator::create_mass_matrix (dof_handler, QGauss<dim>(3),
- mass_matrix);
- MatrixCreator::create_laplace_matrix (dof_handler, QGauss<dim>(3),
- laplace_matrix);
-
- // The rest of the function is spent on
- // setting vector sizes to the correct
- // value. The final line closes the hanging
- // node constraints object. Since we work
- // on a uniformly refined mesh, no
- // constraints exist or have been computed
- // (i.e. there was no need to call
- // DoFTools::make_hanging_node_constraints
- // as in other programs), but we need a
- // constraints object in one place further
- // down below anyway.
- solution_u.reinit (dof_handler.n_dofs());
- solution_v.reinit (dof_handler.n_dofs());
- old_solution_u.reinit (dof_handler.n_dofs());
- old_solution_v.reinit (dof_handler.n_dofs());
- system_rhs.reinit (dof_handler.n_dofs());
-
- constraints.close ();
-}
+ virtual double value (const Point<dim> &p,
+ const unsigned int component = 0) const;
+ };
- // @sect4{WaveEquation::solve_u and WaveEquation::solve_v}
-
- // The next two functions deal with solving
- // the linear systems associated with the
- // equations for $U^n$ and $V^n$. Both are
- // not particularly interesting as they
- // pretty much follow the scheme used in all
- // the previous tutorial programs.
- //
- // One can make little experiments with
- // preconditioners for the two matrices we
- // have to invert. As it turns out, however,
- // for the matrices at hand here, using
- // Jacobi or SSOR preconditioners reduces the
- // number of iterations necessary to solve
- // the linear system slightly, but due to the
- // cost of applying the preconditioner it is
- // no win in terms of run-time. It is not
- // much of a loss either, but let's keep it
- // simple and just do without:
-template <int dim>
-void WaveEquation<dim>::solve_u ()
-{
- SolverControl solver_control (1000, 1e-8*system_rhs.l2_norm());
- SolverCG<> cg (solver_control);
- cg.solve (matrix_u, solution_u, system_rhs,
- PreconditionIdentity());
+ template <int dim>
+ double RightHandSide<dim>::value (const Point<dim> &/*p*/,
+ const unsigned int component) const
+ {
+ Assert (component == 0, ExcInternalError());
+ return 0;
+ }
- std::cout << " u-equation: " << solver_control.last_step()
- << " CG iterations."
- << std::endl;
-}
-template <int dim>
-void WaveEquation<dim>::solve_v ()
-{
- SolverControl solver_control (1000, 1e-8*system_rhs.l2_norm());
- SolverCG<> cg (solver_control);
+ // Finally, we have boundary values for $u$
+ // and $v$. They are as described in the
+ // introduction, one being the time
+ // derivative of the other:
+ template <int dim>
+ class BoundaryValuesU : public Function<dim>
+ {
+ public:
+ BoundaryValuesU () : Function<dim>() {}
- cg.solve (matrix_v, solution_v, system_rhs,
- PreconditionIdentity());
+ virtual double value (const Point<dim> &p,
+ const unsigned int component = 0) const;
+ };
- std::cout << " v-equation: " << solver_control.last_step()
- << " CG iterations."
- << std::endl;
-}
- // @sect4{WaveEquation::output_results}
+ template <int dim>
+ class BoundaryValuesV : public Function<dim>
+ {
+ public:
+ BoundaryValuesV () : Function<dim>() {}
- // Likewise, the following function is pretty
- // much what we've done before. The only
- // thing worth mentioning is how here we
- // generate a string representation of the
- // time step number padded with leading zeros
- // to 3 character length using the
- // Utilities::int_to_string function's second
- // argument.
-template <int dim>
-void WaveEquation<dim>::output_results () const
-{
- DataOut<dim> data_out;
+ virtual double value (const Point<dim> &p,
+ const unsigned int component = 0) const;
+ };
- data_out.attach_dof_handler (dof_handler);
- data_out.add_data_vector (solution_u, "U");
- data_out.add_data_vector (solution_v, "V");
- data_out.build_patches ();
- const std::string filename = "solution-" +
- Utilities::int_to_string (timestep_number, 3) +
- ".gnuplot";
- std::ofstream output (filename.c_str());
- data_out.write_gnuplot (output);
-}
+ template <int dim>
+ double BoundaryValuesU<dim>::value (const Point<dim> &p,
+ const unsigned int component) const
+ {
+ Assert (component == 0, ExcInternalError());
+ if ((this->get_time() <= 0.5) &&
+ (p[0] < 0) &&
+ (p[1] < 1./3) &&
+ (p[1] > -1./3))
+ return std::sin (this->get_time() * 4 * numbers::PI);
+ else
+ return 0;
+ }
- // @sect4{WaveEquation::run}
- // The following is really the only
- // interesting function of the program. It
- // contains the loop over all time steps, but
- // before we get to that we have to set up
- // the grid, DoFHandler, and matrices. In
- // addition, we have to somehow get started
- // with initial values. To this end, we use
- // the VectorTools::project function that
- // takes an object that describes a
- // continuous function and computes the $L^2$
- // projection of this function onto the
- // finite element space described by the
- // DoFHandler object. Can't be any simpler
- // than that:
-template <int dim>
-void WaveEquation<dim>::run ()
-{
- setup_system();
-
- VectorTools::project (dof_handler, constraints, QGauss<dim>(3),
- InitialValuesU<dim>(),
- old_solution_u);
- VectorTools::project (dof_handler, constraints, QGauss<dim>(3),
- InitialValuesV<dim>(),
- old_solution_v);
-
- // The next thing is to loop over all the
- // time steps until we reach the end time
- // ($T=5$ in this case). In each time step,
- // we first have to solve for $U^n$, using
- // the equation $(M^n + k^2\theta^2 A^n)U^n
- // =$ $(M^{n,n-1} - k^2\theta(1-\theta)
- // A^{n,n-1})U^{n-1} + kM^{n,n-1}V^{n-1} +$
- // $k\theta \left[k \theta F^n + k(1-\theta)
- // F^{n-1} \right]$. Note that we use the
- // same mesh for all time steps, so that
- // $M^n=M^{n,n-1}=M$ and
- // $A^n=A^{n,n-1}=A$. What we therefore
- // have to do first is to add up $MU^{n-1}
- // - k^2\theta(1-\theta) AU^{n-1} + kMV^{n-1}$ and
- // the forcing terms, and put the result
- // into the <code>system_rhs</code>
- // vector. (For these additions, we need a
- // temporary vector that we declare before
- // the loop to avoid repeated memory
- // allocations in each time step.)
- //
- // The one thing to realize here is how we
- // communicate the time variable to the
- // object describing the right hand side:
- // each object derived from the Function
- // class has a time field that can be set
- // using the Function::set_time and read by
- // Function::get_time. In essence, using
- // this mechanism, all functions of space
- // and time are therefore considered
- // functions of space evaluated at a
- // particular time. This matches well what
- // we typically need in finite element
- // programs, where we almost always work on
- // a single time step at a time, and where
- // it never happens that, for example, one
- // would like to evaluate a space-time
- // function for all times at any given
- // spatial location.
- Vector<double> tmp (solution_u.size());
- Vector<double> forcing_terms (solution_u.size());
-
- for (timestep_number=1, time=time_step;
- time<=5;
- time+=time_step, ++timestep_number)
- {
- std::cout << "Time step " << timestep_number
- << " at t=" << time
- << std::endl;
-
- mass_matrix.vmult (system_rhs, old_solution_u);
-
- mass_matrix.vmult (tmp, old_solution_v);
- system_rhs.add (time_step, tmp);
-
- laplace_matrix.vmult (tmp, old_solution_u);
- system_rhs.add (-theta * (1-theta) * time_step * time_step, tmp);
-
- RightHandSide<dim> rhs_function;
- rhs_function.set_time (time);
- VectorTools::create_right_hand_side (dof_handler, QGauss<dim>(2),
- rhs_function, tmp);
- forcing_terms = tmp;
- forcing_terms *= theta * time_step;
-
- rhs_function.set_time (time-time_step);
- VectorTools::create_right_hand_side (dof_handler, QGauss<dim>(2),
- rhs_function, tmp);
-
- forcing_terms.add ((1-theta) * time_step, tmp);
-
- system_rhs.add (theta * time_step, forcing_terms);
-
- // After so constructing the right hand
- // side vector of the first equation,
- // all we have to do is apply the
- // correct boundary values. As for the
- // right hand side, this is a
- // space-time function evaluated at a
- // particular time, which we
- // interpolate at boundary nodes and
- // then use the result to apply
- // boundary values as we usually
- // do. The result is then handed off to
- // the solve_u() function:
- {
- BoundaryValuesU<dim> boundary_values_u_function;
- boundary_values_u_function.set_time (time);
-
- std::map<unsigned int,double> boundary_values;
- VectorTools::interpolate_boundary_values (dof_handler,
- 0,
- boundary_values_u_function,
- boundary_values);
-
- // The matrix for solve_u() is the same in
- // every time steps, so one could think
- // that it is enough to do this only once
- // at the beginning of the
- // simulation. However, since we need to
- // apply boundary values to the linear
- // system (which eliminate some matrix rows
- // and columns and give contributions to
- // the right hand side), we have to refill
- // the matrix in every time steps before we
- // actually apply boundary data. The actual
- // content is very simple: it is the sum of
- // the mass matrix and a weighted Laplace
- // matrix:
- matrix_u.copy_from (mass_matrix);
- matrix_u.add (theta * theta * time_step * time_step, laplace_matrix);
- MatrixTools::apply_boundary_values (boundary_values,
- matrix_u,
- solution_u,
- system_rhs);
- }
- solve_u ();
+ template <int dim>
+ double BoundaryValuesV<dim>::value (const Point<dim> &p,
+ const unsigned int component) const
+ {
+ Assert (component == 0, ExcInternalError());
+ if ((this->get_time() <= 0.5) &&
+ (p[0] < 0) &&
+ (p[1] < 1./3) &&
+ (p[1] > -1./3))
+ return (std::cos (this->get_time() * 4 * numbers::PI) *
+ 4 * numbers::PI);
+ else
+ return 0;
+ }
- // The second step, i.e. solving for
- // $V^n$, works similarly, except that
- // this time the matrix on the left is
- // the mass matrix (which we copy again
- // in order to be able to apply
- // boundary conditions, and the right
- // hand side is $MV^{n-1} - k\left[
- // \theta A U^n + (1-\theta)
- // AU^{n-1}\right]$ plus forcing
- // terms. %Boundary values are applied
- // in the same way as before, except
- // that now we have to use the
- // BoundaryValuesV class:
- laplace_matrix.vmult (system_rhs, solution_u);
- system_rhs *= -theta * time_step;
- mass_matrix.vmult (tmp, old_solution_v);
- system_rhs += tmp;
- laplace_matrix.vmult (tmp, old_solution_u);
- system_rhs.add (-time_step * (1-theta), tmp);
- system_rhs += forcing_terms;
+ // @sect3{Implementation of the <code>WaveEquation</code> class}
+ // The implementation of the actual logic is
+ // actually fairly short, since we relegate
+ // things like assembling the matrices and
+ // right hand side vectors to the
+ // library. The rest boils down to not much
+ // more than 130 lines of actual code, a
+ // significant fraction of which is
+ // boilerplate code that can be taken from
+ // previous example programs (e.g. the
+ // functions that solve linear systems, or
+ // that generate output).
+ //
+ // Let's start with the constructor (for an
+ // explanation of the choice of time step,
+ // see the section on Courant, Friedrichs,
+ // and Lewy in the introduction):
+ template <int dim>
+ WaveEquation<dim>::WaveEquation () :
+ fe (1),
+ dof_handler (triangulation),
+ time_step (1./64),
+ theta (0.5)
+ {}
+
+
+ // @sect4{WaveEquation::setup_system}
+
+ // The next function is the one that
+ // sets up the mesh, DoFHandler, and
+ // matrices and vectors at the
+ // beginning of the program,
+ // i.e. before the first time
+ // step. The first few lines are
+ // pretty much standard if you've
+ // read through the tutorial programs
+ // at least up to step-6:
+ template <int dim>
+ void WaveEquation<dim>::setup_system ()
+ {
+ GridGenerator::hyper_cube (triangulation, -1, 1);
+ triangulation.refine_global (7);
+
+ std::cout << "Number of active cells: "
+ << triangulation.n_active_cells()
+ << std::endl;
+
+ dof_handler.distribute_dofs (fe);
+
+ std::cout << "Number of degrees of freedom: "
+ << dof_handler.n_dofs()
+ << std::endl
+ << std::endl;
+
+ sparsity_pattern.reinit (dof_handler.n_dofs(),
+ dof_handler.n_dofs(),
+ dof_handler.max_couplings_between_dofs());
+ DoFTools::make_sparsity_pattern (dof_handler, sparsity_pattern);
+ sparsity_pattern.compress();
+
+ // Then comes a block where we have to
+ // initialize the 3 matrices we need in the
+ // course of the program: the mass matrix,
+ // the laplace matrix, and the matrix
+ // $M+k^2\theta^2A$ used when solving for
+ // $U^n$ in each time step.
+ //
+ // When setting up these matrices, note
+ // that they all make use of the same
+ // sparsity pattern object. Finally, the
+ // reason why matrices and sparsity
+ // patterns are separate objects in deal.II
+ // (unlike in many other finite element or
+ // linear algebra classes) becomes clear:
+ // in a significant fraction of
+ // applications, one has to hold several
+ // matrices that happen to have the same
+ // sparsity pattern, and there is no reason
+ // for them not to share this information,
+ // rather than re-building and wasting
+ // memory on it several times.
+ //
+ // After initializing all of these
+ // matrices, we call library functions that
+ // build the Laplace and mass matrices. All
+ // they need is a DoFHandler object and a
+ // quadrature formula object that is to be
+ // used for numerical integration. Note
+ // that in many respects these functions
+ // are better than what we would usually do
+ // in application programs, for example
+ // because they automatically parallelize
+ // building the matrices if multiple
+ // processors are available in a
+ // machine. The matrices for solving linear
+ // systems will be filled in the run()
+ // method because we need to re-apply
+ // boundary conditions every time step.
+ mass_matrix.reinit (sparsity_pattern);
+ laplace_matrix.reinit (sparsity_pattern);
+ matrix_u.reinit (sparsity_pattern);
+ matrix_v.reinit (sparsity_pattern);
+
+ MatrixCreator::create_mass_matrix (dof_handler, QGauss<dim>(3),
+ mass_matrix);
+ MatrixCreator::create_laplace_matrix (dof_handler, QGauss<dim>(3),
+ laplace_matrix);
+
+ // The rest of the function is spent on
+ // setting vector sizes to the correct
+ // value. The final line closes the hanging
+ // node constraints object. Since we work
+ // on a uniformly refined mesh, no
+ // constraints exist or have been computed
+ // (i.e. there was no need to call
+ // DoFTools::make_hanging_node_constraints
+ // as in other programs), but we need a
+ // constraints object in one place further
+ // down below anyway.
+ solution_u.reinit (dof_handler.n_dofs());
+ solution_v.reinit (dof_handler.n_dofs());
+ old_solution_u.reinit (dof_handler.n_dofs());
+ old_solution_v.reinit (dof_handler.n_dofs());
+ system_rhs.reinit (dof_handler.n_dofs());
+
+ constraints.close ();
+ }
+
+
+ // @sect4{WaveEquation::solve_u and WaveEquation::solve_v}
+
+ // The next two functions deal with solving
+ // the linear systems associated with the
+ // equations for $U^n$ and $V^n$. Both are
+ // not particularly interesting as they
+ // pretty much follow the scheme used in all
+ // the previous tutorial programs.
+ //
+ // One can make little experiments with
+ // preconditioners for the two matrices we
+ // have to invert. As it turns out, however,
+ // for the matrices at hand here, using
+ // Jacobi or SSOR preconditioners reduces the
+ // number of iterations necessary to solve
+ // the linear system slightly, but due to the
+ // cost of applying the preconditioner it is
+ // no win in terms of run-time. It is not
+ // much of a loss either, but let's keep it
+ // simple and just do without:
+ template <int dim>
+ void WaveEquation<dim>::solve_u ()
+ {
+ SolverControl solver_control (1000, 1e-8*system_rhs.l2_norm());
+ SolverCG<> cg (solver_control);
+
+ cg.solve (matrix_u, solution_u, system_rhs,
+ PreconditionIdentity());
+
+ std::cout << " u-equation: " << solver_control.last_step()
+ << " CG iterations."
+ << std::endl;
+ }
+
+
+ template <int dim>
+ void WaveEquation<dim>::solve_v ()
+ {
+ SolverControl solver_control (1000, 1e-8*system_rhs.l2_norm());
+ SolverCG<> cg (solver_control);
+
+ cg.solve (matrix_v, solution_v, system_rhs,
+ PreconditionIdentity());
+
+ std::cout << " v-equation: " << solver_control.last_step()
+ << " CG iterations."
+ << std::endl;
+ }
+
+
+
+ // @sect4{WaveEquation::output_results}
+
+ // Likewise, the following function is pretty
+ // much what we've done before. The only
+ // thing worth mentioning is how here we
+ // generate a string representation of the
+ // time step number padded with leading zeros
+ // to 3 character length using the
+ // Utilities::int_to_string function's second
+ // argument.
+ template <int dim>
+ void WaveEquation<dim>::output_results () const
+ {
+ DataOut<dim> data_out;
+
+ data_out.attach_dof_handler (dof_handler);
+ data_out.add_data_vector (solution_u, "U");
+ data_out.add_data_vector (solution_v, "V");
+
+ data_out.build_patches ();
+
+ const std::string filename = "solution-" +
+ Utilities::int_to_string (timestep_number, 3) +
+ ".gnuplot";
+ std::ofstream output (filename.c_str());
+ data_out.write_gnuplot (output);
+ }
+
+
+
+
+ // @sect4{WaveEquation::run}
+
+ // The following is really the only
+ // interesting function of the program. It
+ // contains the loop over all time steps, but
+ // before we get to that we have to set up
+ // the grid, DoFHandler, and matrices. In
+ // addition, we have to somehow get started
+ // with initial values. To this end, we use
+ // the VectorTools::project function that
+ // takes an object that describes a
+ // continuous function and computes the $L^2$
+ // projection of this function onto the
+ // finite element space described by the
+ // DoFHandler object. Can't be any simpler
+ // than that:
+ template <int dim>
+ void WaveEquation<dim>::run ()
+ {
+ setup_system();
+
+ VectorTools::project (dof_handler, constraints, QGauss<dim>(3),
+ InitialValuesU<dim>(),
+ old_solution_u);
+ VectorTools::project (dof_handler, constraints, QGauss<dim>(3),
+ InitialValuesV<dim>(),
+ old_solution_v);
+
+ // The next thing is to loop over all the
+ // time steps until we reach the end time
+ // ($T=5$ in this case). In each time step,
+ // we first have to solve for $U^n$, using
+ // the equation $(M^n + k^2\theta^2 A^n)U^n
+ // =$ $(M^{n,n-1} - k^2\theta(1-\theta)
+ // A^{n,n-1})U^{n-1} + kM^{n,n-1}V^{n-1} +$
+ // $k\theta \left[k \theta F^n + k(1-\theta)
+ // F^{n-1} \right]$. Note that we use the
+ // same mesh for all time steps, so that
+ // $M^n=M^{n,n-1}=M$ and
+ // $A^n=A^{n,n-1}=A$. What we therefore
+ // have to do first is to add up $MU^{n-1}
+ // - k^2\theta(1-\theta) AU^{n-1} + kMV^{n-1}$ and
+ // the forcing terms, and put the result
+ // into the <code>system_rhs</code>
+ // vector. (For these additions, we need a
+ // temporary vector that we declare before
+ // the loop to avoid repeated memory
+ // allocations in each time step.)
+ //
+ // The one thing to realize here is how we
+ // communicate the time variable to the
+ // object describing the right hand side:
+ // each object derived from the Function
+ // class has a time field that can be set
+ // using the Function::set_time and read by
+ // Function::get_time. In essence, using
+ // this mechanism, all functions of space
+ // and time are therefore considered
+ // functions of space evaluated at a
+ // particular time. This matches well what
+ // we typically need in finite element
+ // programs, where we almost always work on
+ // a single time step at a time, and where
+ // it never happens that, for example, one
+ // would like to evaluate a space-time
+ // function for all times at any given
+ // spatial location.
+ Vector<double> tmp (solution_u.size());
+ Vector<double> forcing_terms (solution_u.size());
+
+ for (timestep_number=1, time=time_step;
+ time<=5;
+ time+=time_step, ++timestep_number)
{
- BoundaryValuesV<dim> boundary_values_v_function;
- boundary_values_v_function.set_time (time);
-
- std::map<unsigned int,double> boundary_values;
- VectorTools::interpolate_boundary_values (dof_handler,
- 0,
- boundary_values_v_function,
- boundary_values);
- matrix_v.copy_from (mass_matrix);
- MatrixTools::apply_boundary_values (boundary_values,
- matrix_v,
- solution_v,
- system_rhs);
+ std::cout << "Time step " << timestep_number
+ << " at t=" << time
+ << std::endl;
+
+ mass_matrix.vmult (system_rhs, old_solution_u);
+
+ mass_matrix.vmult (tmp, old_solution_v);
+ system_rhs.add (time_step, tmp);
+
+ laplace_matrix.vmult (tmp, old_solution_u);
+ system_rhs.add (-theta * (1-theta) * time_step * time_step, tmp);
+
+ RightHandSide<dim> rhs_function;
+ rhs_function.set_time (time);
+ VectorTools::create_right_hand_side (dof_handler, QGauss<dim>(2),
+ rhs_function, tmp);
+ forcing_terms = tmp;
+ forcing_terms *= theta * time_step;
+
+ rhs_function.set_time (time-time_step);
+ VectorTools::create_right_hand_side (dof_handler, QGauss<dim>(2),
+ rhs_function, tmp);
+
+ forcing_terms.add ((1-theta) * time_step, tmp);
+
+ system_rhs.add (theta * time_step, forcing_terms);
+
+ // After so constructing the right hand
+ // side vector of the first equation,
+ // all we have to do is apply the
+ // correct boundary values. As for the
+ // right hand side, this is a
+ // space-time function evaluated at a
+ // particular time, which we
+ // interpolate at boundary nodes and
+ // then use the result to apply
+ // boundary values as we usually
+ // do. The result is then handed off to
+ // the solve_u() function:
+ {
+ BoundaryValuesU<dim> boundary_values_u_function;
+ boundary_values_u_function.set_time (time);
+
+ std::map<unsigned int,double> boundary_values;
+ VectorTools::interpolate_boundary_values (dof_handler,
+ 0,
+ boundary_values_u_function,
+ boundary_values);
+
+ // The matrix for solve_u() is the same in
+ // every time steps, so one could think
+ // that it is enough to do this only once
+ // at the beginning of the
+ // simulation. However, since we need to
+ // apply boundary values to the linear
+ // system (which eliminate some matrix rows
+ // and columns and give contributions to
+ // the right hand side), we have to refill
+ // the matrix in every time steps before we
+ // actually apply boundary data. The actual
+ // content is very simple: it is the sum of
+ // the mass matrix and a weighted Laplace
+ // matrix:
+ matrix_u.copy_from (mass_matrix);
+ matrix_u.add (theta * theta * time_step * time_step, laplace_matrix);
+ MatrixTools::apply_boundary_values (boundary_values,
+ matrix_u,
+ solution_u,
+ system_rhs);
+ }
+ solve_u ();
+
+
+ // The second step, i.e. solving for
+ // $V^n$, works similarly, except that
+ // this time the matrix on the left is
+ // the mass matrix (which we copy again
+ // in order to be able to apply
+ // boundary conditions, and the right
+ // hand side is $MV^{n-1} - k\left[
+ // \theta A U^n + (1-\theta)
+ // AU^{n-1}\right]$ plus forcing
+ // terms. %Boundary values are applied
+ // in the same way as before, except
+ // that now we have to use the
+ // BoundaryValuesV class:
+ laplace_matrix.vmult (system_rhs, solution_u);
+ system_rhs *= -theta * time_step;
+
+ mass_matrix.vmult (tmp, old_solution_v);
+ system_rhs += tmp;
+
+ laplace_matrix.vmult (tmp, old_solution_u);
+ system_rhs.add (-time_step * (1-theta), tmp);
+
+ system_rhs += forcing_terms;
+
+ {
+ BoundaryValuesV<dim> boundary_values_v_function;
+ boundary_values_v_function.set_time (time);
+
+ std::map<unsigned int,double> boundary_values;
+ VectorTools::interpolate_boundary_values (dof_handler,
+ 0,
+ boundary_values_v_function,
+ boundary_values);
+ matrix_v.copy_from (mass_matrix);
+ MatrixTools::apply_boundary_values (boundary_values,
+ matrix_v,
+ solution_v,
+ system_rhs);
+ }
+ solve_v ();
+
+ // Finally, after both solution
+ // components have been computed, we
+ // output the result, compute the
+ // energy in the solution, and go on to
+ // the next time step after shifting
+ // the present solution into the
+ // vectors that hold the solution at
+ // the previous time step. Note the
+ // function
+ // SparseMatrix::matrix_norm_square
+ // that can compute
+ // $\left<V^n,MV^n\right>$ and
+ // $\left<U^n,AU^n\right>$ in one step,
+ // saving us the expense of a temporary
+ // vector and several lines of code:
+ output_results ();
+
+ std::cout << " Total energy: "
+ << (mass_matrix.matrix_norm_square (solution_v) +
+ laplace_matrix.matrix_norm_square (solution_u)) / 2
+ << std::endl;
+
+ old_solution_u = solution_u;
+ old_solution_v = solution_v;
}
- solve_v ();
-
- // Finally, after both solution
- // components have been computed, we
- // output the result, compute the
- // energy in the solution, and go on to
- // the next time step after shifting
- // the present solution into the
- // vectors that hold the solution at
- // the previous time step. Note the
- // function
- // SparseMatrix::matrix_norm_square
- // that can compute
- // $\left<V^n,MV^n\right>$ and
- // $\left<U^n,AU^n\right>$ in one step,
- // saving us the expense of a temporary
- // vector and several lines of code:
- output_results ();
-
- std::cout << " Total energy: "
- << (mass_matrix.matrix_norm_square (solution_v) +
- laplace_matrix.matrix_norm_square (solution_u)) / 2
- << std::endl;
-
- old_solution_u = solution_u;
- old_solution_v = solution_v;
- }
+ }
}
// program. There is nothing here that hasn't
// been shown in several of the previous
// programs:
-int main ()
+int main ()
{
try
{
+ using namespace dealii;
+ using namespace Step23;
+
deallog.depth_console (0);
+
WaveEquation<2> wave_equation_solver;
wave_equation_solver.run ();
}
return 1;
}
- catch (...)
+ catch (...)
{
std::cerr << std::endl << std::endl
<< "----------------------------------------------------"
<< std::endl;
return 1;
}
-
+
return 0;
}
/* $Id$ */
/* Version: $Name: $ */
/* */
-/* Copyright (C) 2006, 2007, 2008, 2009 by the deal.II authors */
+/* Copyright (C) 2006, 2007, 2008, 2009, 2011 by the deal.II authors */
/* Author: Xing Jin, Wolfgang Bangerth, Texas A&M University, 2006 */
/* */
/* This file is subject to QPL and may not be distributed */
/* without copyright and license information. Please refer */
/* to the file deal.II/doc/license.html for the text and */
/* further information on this license. */
-
+
// @sect3{Include files}
// The last step is as in all
// previous programs:
-using namespace dealii;
-
- // @sect3{The "forward problem" class template}
-
- // The first part of the main class is
- // exactly as in step-23
- // (except for the name):
-template <int dim>
-class TATForwardProblem
+namespace Step24
{
- public:
- TATForwardProblem ();
- void run ();
-
- private:
- void setup_system ();
- void solve_p ();
- void solve_v ();
- void output_results () const;
-
- Triangulation<dim> triangulation;
- FE_Q<dim> fe;
- DoFHandler<dim> dof_handler;
-
- ConstraintMatrix constraints;
-
- SparsityPattern sparsity_pattern;
- SparseMatrix<double> system_matrix;
- SparseMatrix<double> mass_matrix;
- SparseMatrix<double> laplace_matrix;
-
- Vector<double> solution_p, solution_v;
- Vector<double> old_solution_p, old_solution_v;
- Vector<double> system_rhs_p, system_rhs_v;
-
- double time, time_step;
- unsigned int timestep_number;
- const double theta;
-
- // Here's what's new: first, we need
- // that boundary mass matrix $B$ that
- // came out of the absorbing boundary
- // condition. Likewise, since this time
- // we consider a realistic medium, we
- // must have a measure of the wave speed
- // $c_0$ that will enter all the
- // formulas with the Laplace matrix
- // (which we still define as $(\nabla
- // \phi_i,\nabla \phi_j)$):
- SparseMatrix<double> boundary_matrix;
- const double wave_speed;
-
- // The last thing we have to take care of
- // is that we wanted to evaluate the
- // solution at a certain number of
- // detector locations. We need an array
- // to hold these locations, declared here
- // and filled in the constructor:
- std::vector<Point<dim> > detector_locations;
-};
-
-
- // @sect3{Equation data}
-
- // As usual, we have to define our
- // initial values, boundary
- // conditions, and right hand side
- // functions. Except things are a bit
- // simpler this time: we are to
- // consider a problem that is driven
- // by initial conditions, so there is
- // no right hand side function
- // (though you could look up in
- // step-23 to see how this can be
- // done. Secondly, there are no
- // boundary conditions: the entire
- // boundary of the domain consists of
- // absorbing boundary
- // conditions. That only leaves
- // initial conditions, and there
- // things are simple too since for
- // this particular application only
- // nonzero initial conditions for the
- // pressure are prescribed, not for
- // the velocity (which is zero at the
- // initial time).
- //
- // So this is all we need: a class that
- // specifies initial conditions for the
- // pressure. In the physical setting
- // considered in this program, these are
- // small absorbers, which we model as a
- // series of little circles where we assume
- // that the pressure surplus is one, whereas
- // no absorption and therefore no pressure
- // surplus is anywhere else. This is how we
- // do things (note that if we wanted to
- // expand this program to not only compile
- // but also to run, we would have to
- // initialize the sources with
- // three-dimensional source locations):
-template <int dim>
-class InitialValuesP : public Function<dim>
-{
- public:
- InitialValuesP ()
- :
- Function<dim>()
- {}
-
- virtual double value (const Point<dim> &p,
- const unsigned int component = 0) const;
-
- private:
- struct Source
- {
- Source (const Point<dim> &l,
- const double r)
- :
- location (l),
- radius (r)
- {}
-
- const Point<dim> location;
- const double radius;
- };
-};
-
-
-template <int dim>
-double InitialValuesP<dim>::value (const Point<dim> &p,
- const unsigned int /*component*/) const
-{
- static const Source sources[] = {Source (Point<dim> (0, 0), 0.025),
- Source (Point<dim> (-0.135, 0), 0.05),
- Source (Point<dim> (0.17, 0), 0.03),
- Source (Point<dim> (-0.25, 0), 0.02),
- Source (Point<dim> (-0.05, -0.15), 0.015)};
- static const unsigned int n_sources = sizeof(sources)/sizeof(sources[0]);
-
- for (unsigned int i=0; i<n_sources; ++i)
- if (p.distance(sources[i].location) < sources[i].radius)
- return 1;
+ using namespace dealii;
- return 0;
-}
+ // @sect3{The "forward problem" class template}
-
- // @sect3{Implementation of the <code>TATForwardProblem</code> class}
-
- // Let's start again with the
- // constructor. Setting the member variables
- // is straightforward. We use the acoustic
- // wave speed of mineral oil (in millimeters
- // per microsecond, a common unit in
- // experimental biomedical imaging) since
- // this is where many of the experiments we
- // want to compare the output with are made
- // in. The Crank-Nicolson scheme is used
- // again, i.e. theta is set to 0.5. The time
- // step is later selected to satisfy $k =
- // \frac hc$
-template <int dim>
-TATForwardProblem<dim>::TATForwardProblem ()
- :
- fe (1),
- dof_handler (triangulation),
- theta (0.5),
- wave_speed (1.437)
-{
- // The second task in the constructor is to
- // initialize the array that holds the
- // detector locations. The results of this
- // program were compared with experiments
- // in which the step size of the detector
- // spacing is 2.25 degree, corresponding to
- // 160 detector locations. The radius of
- // the scanning circle is selected to be
- // half way between the center and the
- // boundary to avoid that the remaining
- // reflections from the imperfect boundary
- // condition spoils our numerical results.
+ // The first part of the main class is
+ // exactly as in step-23
+ // (except for the name):
+ template <int dim>
+ class TATForwardProblem
+ {
+ public:
+ TATForwardProblem ();
+ void run ();
+
+ private:
+ void setup_system ();
+ void solve_p ();
+ void solve_v ();
+ void output_results () const;
+
+ Triangulation<dim> triangulation;
+ FE_Q<dim> fe;
+ DoFHandler<dim> dof_handler;
+
+ ConstraintMatrix constraints;
+
+ SparsityPattern sparsity_pattern;
+ SparseMatrix<double> system_matrix;
+ SparseMatrix<double> mass_matrix;
+ SparseMatrix<double> laplace_matrix;
+
+ Vector<double> solution_p, solution_v;
+ Vector<double> old_solution_p, old_solution_v;
+ Vector<double> system_rhs_p, system_rhs_v;
+
+ double time, time_step;
+ unsigned int timestep_number;
+ const double theta;
+
+ // Here's what's new: first, we need
+ // that boundary mass matrix $B$ that
+ // came out of the absorbing boundary
+ // condition. Likewise, since this time
+ // we consider a realistic medium, we
+ // must have a measure of the wave speed
+ // $c_0$ that will enter all the
+ // formulas with the Laplace matrix
+ // (which we still define as $(\nabla
+ // \phi_i,\nabla \phi_j)$):
+ SparseMatrix<double> boundary_matrix;
+ const double wave_speed;
+
+ // The last thing we have to take care of
+ // is that we wanted to evaluate the
+ // solution at a certain number of
+ // detector locations. We need an array
+ // to hold these locations, declared here
+ // and filled in the constructor:
+ std::vector<Point<dim> > detector_locations;
+ };
+
+
+ // @sect3{Equation data}
+
+ // As usual, we have to define our
+ // initial values, boundary
+ // conditions, and right hand side
+ // functions. Except things are a bit
+ // simpler this time: we are to
+ // consider a problem that is driven
+ // by initial conditions, so there is
+ // no right hand side function
+ // (though you could look up in
+ // step-23 to see how this can be
+ // done. Secondly, there are no
+ // boundary conditions: the entire
+ // boundary of the domain consists of
+ // absorbing boundary
+ // conditions. That only leaves
+ // initial conditions, and there
+ // things are simple too since for
+ // this particular application only
+ // nonzero initial conditions for the
+ // pressure are prescribed, not for
+ // the velocity (which is zero at the
+ // initial time).
//
- // The locations of the detectors are then
- // calculated in clockwise order. Note that
- // the following of course only works if we
- // are computing in 2d, a condition that we
- // guard with an assertion. If we later
- // wanted to run the same program in 3d, we
- // would have to add code here for the
- // initialization of detector locations in
- // 3d. Due to the assertion, there is no
- // way we can forget to do this.
- Assert (dim == 2, ExcNotImplemented());
-
- const double detector_step_angle = 2.25;
- const double detector_radius = 0.5;
-
- for (double detector_angle = 2*numbers::PI;
- detector_angle >= 0;
- detector_angle -= detector_step_angle/360*2*numbers::PI)
- detector_locations.push_back (Point<dim> (std::cos(detector_angle),
- std::sin(detector_angle)) *
- detector_radius);
-}
+ // So this is all we need: a class that
+ // specifies initial conditions for the
+ // pressure. In the physical setting
+ // considered in this program, these are
+ // small absorbers, which we model as a
+ // series of little circles where we assume
+ // that the pressure surplus is one, whereas
+ // no absorption and therefore no pressure
+ // surplus is anywhere else. This is how we
+ // do things (note that if we wanted to
+ // expand this program to not only compile
+ // but also to run, we would have to
+ // initialize the sources with
+ // three-dimensional source locations):
+ template <int dim>
+ class InitialValuesP : public Function<dim>
+ {
+ public:
+ InitialValuesP ()
+ :
+ Function<dim>()
+ {}
+
+ virtual double value (const Point<dim> &p,
+ const unsigned int component = 0) const;
+
+ private:
+ struct Source
+ {
+ Source (const Point<dim> &l,
+ const double r)
+ :
+ location (l),
+ radius (r)
+ {}
+
+ const Point<dim> location;
+ const double radius;
+ };
+ };
+
+
+ template <int dim>
+ double InitialValuesP<dim>::value (const Point<dim> &p,
+ const unsigned int /*component*/) const
+ {
+ static const Source sources[] = {Source (Point<dim> (0, 0), 0.025),
+ Source (Point<dim> (-0.135, 0), 0.05),
+ Source (Point<dim> (0.17, 0), 0.03),
+ Source (Point<dim> (-0.25, 0), 0.02),
+ Source (Point<dim> (-0.05, -0.15), 0.015)};
+ static const unsigned int n_sources = sizeof(sources)/sizeof(sources[0]);
+
+ for (unsigned int i=0; i<n_sources; ++i)
+ if (p.distance(sources[i].location) < sources[i].radius)
+ return 1;
+
+ return 0;
+ }
+ // @sect3{Implementation of the <code>TATForwardProblem</code> class}
+
+ // Let's start again with the
+ // constructor. Setting the member variables
+ // is straightforward. We use the acoustic
+ // wave speed of mineral oil (in millimeters
+ // per microsecond, a common unit in
+ // experimental biomedical imaging) since
+ // this is where many of the experiments we
+ // want to compare the output with are made
+ // in. The Crank-Nicolson scheme is used
+ // again, i.e. theta is set to 0.5. The time
+ // step is later selected to satisfy $k =
+ // \frac hc$
+ template <int dim>
+ TATForwardProblem<dim>::TATForwardProblem ()
+ :
+ fe (1),
+ dof_handler (triangulation),
+ theta (0.5),
+ wave_speed (1.437)
+ {
+ // The second task in the constructor is to
+ // initialize the array that holds the
+ // detector locations. The results of this
+ // program were compared with experiments
+ // in which the step size of the detector
+ // spacing is 2.25 degree, corresponding to
+ // 160 detector locations. The radius of
+ // the scanning circle is selected to be
+ // half way between the center and the
+ // boundary to avoid that the remaining
+ // reflections from the imperfect boundary
+ // condition spoils our numerical results.
+ //
+ // The locations of the detectors are then
+ // calculated in clockwise order. Note that
+ // the following of course only works if we
+ // are computing in 2d, a condition that we
+ // guard with an assertion. If we later
+ // wanted to run the same program in 3d, we
+ // would have to add code here for the
+ // initialization of detector locations in
+ // 3d. Due to the assertion, there is no
+ // way we can forget to do this.
+ Assert (dim == 2, ExcNotImplemented());
+
+ const double detector_step_angle = 2.25;
+ const double detector_radius = 0.5;
+
+ for (double detector_angle = 2*numbers::PI;
+ detector_angle >= 0;
+ detector_angle -= detector_step_angle/360*2*numbers::PI)
+ detector_locations.push_back (Point<dim> (std::cos(detector_angle),
+ std::sin(detector_angle)) *
+ detector_radius);
+ }
- // @sect4{TATForwardProblem::setup_system}
-
- // The following system is pretty much what
- // we've already done in
- // step-23, but with two important
- // differences. First, we have to create a
- // circular (or spherical) mesh around the
- // origin, with a radius of 1. This nothing
- // new: we've done so before in
- // step-6, step-10, and
- // step-11, where we also explain
- // how to attach a boundary object to a
- // triangulation to be used whenever the
- // triangulation needs to know where new
- // boundary points lie when a cell is
- // refined. Following this, the mesh is
- // refined a number of times.
- //
- // One thing we had to make sure is that the
- // time step satisfies the CFL condition
- // discussed in the introduction of
- // step-23. Back in that program,
- // we ensured this by hand by setting a
- // timestep that matches the mesh width, but
- // that was error prone because if we refined
- // the mesh once more we would also have to
- // make sure the time step is changed. Here,
- // we do that automatically: we ask a library
- // function for the minimal diameter of any
- // cell. Then we set $k=\frac h{c_0}$. The
- // only problem is: what exactly is $h$? The
- // point is that there is really no good
- // theory on this question for the wave
- // equation. It is known that for uniformly
- // refined meshes consisting of rectangles,
- // $h$ is the minimal edge length. But for
- // meshes on general quadrilaterals, the
- // exact relationship appears to be unknown,
- // i.e. it is unknown what properties of
- // cells are relevant for the CFL
- // condition. The problem is that the CFL
- // condition follows from knowledge of the
- // smallest eigenvalue of the Laplace matrix,
- // and that can only be computed analytically
- // for simply structured meshes.
- //
- // The upshot of all this is that we're not
- // quite sure what exactly we should take for
- // $h$. The function
- // GridTools::minimal_cell_diameter computes
- // the minimal diameter of all cells. If the
- // cells were all squares or cubes, then the
- // minimal edge length would be the minimal
- // diameter divided by
- // <code>std::sqrt(dim)</code>. We simply
- // generalize this, without theoretical
- // justification, to the case of non-uniform
- // meshes.
- //
- // The only other significant change is that
- // we need to build the boundary mass
- // matrix. We will comment on this further
- // down below.
-template <int dim>
-void TATForwardProblem<dim>::setup_system ()
-{
- const Point<dim> center;
- GridGenerator::hyper_ball (triangulation, center, 1.);
- static const HyperBallBoundary<dim> boundary_description (center, 1.);
- triangulation.set_boundary (0,boundary_description);
- triangulation.refine_global (7);
-
- time_step = GridTools::minimal_cell_diameter(triangulation) /
- wave_speed /
- std::sqrt (1.*dim);
-
- std::cout << "Number of active cells: "
- << triangulation.n_active_cells()
- << std::endl;
-
- dof_handler.distribute_dofs (fe);
-
- std::cout << "Number of degrees of freedom: "
- << dof_handler.n_dofs()
- << std::endl
- << std::endl;
-
- sparsity_pattern.reinit (dof_handler.n_dofs(),
- dof_handler.n_dofs(),
- dof_handler.max_couplings_between_dofs());
- DoFTools::make_sparsity_pattern (dof_handler, sparsity_pattern);
- sparsity_pattern.compress();
-
- system_matrix.reinit (sparsity_pattern);
- mass_matrix.reinit (sparsity_pattern);
- laplace_matrix.reinit (sparsity_pattern);
-
- MatrixCreator::create_mass_matrix (dof_handler, QGauss<dim>(3),
- mass_matrix);
- MatrixCreator::create_laplace_matrix (dof_handler, QGauss<dim>(3),
- laplace_matrix);
-
- // The second difference, as mentioned, to
- // step-23 is that we need
- // to build the boundary mass matrix that
- // grew out of the absorbing boundary
- // conditions.
+
+
+ // @sect4{TATForwardProblem::setup_system}
+
+ // The following system is pretty much what
+ // we've already done in
+ // step-23, but with two important
+ // differences. First, we have to create a
+ // circular (or spherical) mesh around the
+ // origin, with a radius of 1. This nothing
+ // new: we've done so before in
+ // step-6, step-10, and
+ // step-11, where we also explain
+ // how to attach a boundary object to a
+ // triangulation to be used whenever the
+ // triangulation needs to know where new
+ // boundary points lie when a cell is
+ // refined. Following this, the mesh is
+ // refined a number of times.
//
- // A first observation would be that this
- // matrix is much sparser than the regular
- // mass matrix, since none of the shape
- // functions with purely interior support
- // contributes to this matrix. We could
- // therefore optimize the storage pattern
- // to this situation and build up a second
- // sparsity pattern that only contains the
- // nonzero entries that we need. There is a
- // trade-off to make here: first, we would
- // have to have a second sparsity pattern
- // object, so that costs memory. Secondly,
- // the matrix attached to this sparsity
- // pattern is going to be smaller and
- // therefore requires less memory; it would
- // also be faster to perform matrix-vector
- // multiplications with it. The final
- // argument, however, is the one that tips
- // the scale: we are not primarily
- // interested in performing matrix-vector
- // with the boundary matrix alone (though
- // we need to do that for the right hand
- // side vector once per time step), but
- // mostly wish to add it up to the other
- // matrices used in the first of the two
- // equations since this is the one that is
- // going to be multiplied with once per
- // iteration of the CG method,
- // i.e. significantly more often. It is now
- // the case that the SparseMatrix::add
- // class allows to add one matrix to
- // another, but only if they use the same
- // sparsity pattern (the reason being that
- // we can't add nonzero entries to a matrix
- // after the sparsity pattern has been
- // created, so we simply require that the
- // two matrices have the same sparsity
- // pattern).
+ // One thing we had to make sure is that the
+ // time step satisfies the CFL condition
+ // discussed in the introduction of
+ // step-23. Back in that program,
+ // we ensured this by hand by setting a
+ // timestep that matches the mesh width, but
+ // that was error prone because if we refined
+ // the mesh once more we would also have to
+ // make sure the time step is changed. Here,
+ // we do that automatically: we ask a library
+ // function for the minimal diameter of any
+ // cell. Then we set $k=\frac h{c_0}$. The
+ // only problem is: what exactly is $h$? The
+ // point is that there is really no good
+ // theory on this question for the wave
+ // equation. It is known that for uniformly
+ // refined meshes consisting of rectangles,
+ // $h$ is the minimal edge length. But for
+ // meshes on general quadrilaterals, the
+ // exact relationship appears to be unknown,
+ // i.e. it is unknown what properties of
+ // cells are relevant for the CFL
+ // condition. The problem is that the CFL
+ // condition follows from knowledge of the
+ // smallest eigenvalue of the Laplace matrix,
+ // and that can only be computed analytically
+ // for simply structured meshes.
//
- // So let's go with that:
- boundary_matrix.reinit (sparsity_pattern);
-
- // The second thing to do is to actually
- // build the matrix. Here, we need to
- // integrate over faces of cells, so first
- // we need a quadrature object that works
- // on <code>dim-1</code> dimensional
- // objects. Secondly, the FEFaceValues
- // variant of FEValues that works on faces,
- // as its name suggest. And finally, the
- // other variables that are part of the
- // assembly machinery. All of this we put
- // between curly braces to limit the scope
- // of these variables to where we actually
- // need them.
+ // The upshot of all this is that we're not
+ // quite sure what exactly we should take for
+ // $h$. The function
+ // GridTools::minimal_cell_diameter computes
+ // the minimal diameter of all cells. If the
+ // cells were all squares or cubes, then the
+ // minimal edge length would be the minimal
+ // diameter divided by
+ // <code>std::sqrt(dim)</code>. We simply
+ // generalize this, without theoretical
+ // justification, to the case of non-uniform
+ // meshes.
//
- // The actual act of assembling the matrix
- // is then fairly straightforward: we loop
- // over all cells, over all faces of each
- // of these cells, and then do something
- // only if that particular face is at the
- // boundary of the domain. Like this:
+ // The only other significant change is that
+ // we need to build the boundary mass
+ // matrix. We will comment on this further
+ // down below.
+ template <int dim>
+ void TATForwardProblem<dim>::setup_system ()
{
- const QGauss<dim-1> quadrature_formula(3);
- FEFaceValues<dim> fe_values (fe, quadrature_formula,
- update_values | update_JxW_values);
+ const Point<dim> center;
+ GridGenerator::hyper_ball (triangulation, center, 1.);
+ static const HyperBallBoundary<dim> boundary_description (center, 1.);
+ triangulation.set_boundary (0,boundary_description);
+ triangulation.refine_global (7);
+
+ time_step = GridTools::minimal_cell_diameter(triangulation) /
+ wave_speed /
+ std::sqrt (1.*dim);
+
+ std::cout << "Number of active cells: "
+ << triangulation.n_active_cells()
+ << std::endl;
+
+ dof_handler.distribute_dofs (fe);
+
+ std::cout << "Number of degrees of freedom: "
+ << dof_handler.n_dofs()
+ << std::endl
+ << std::endl;
+
+ sparsity_pattern.reinit (dof_handler.n_dofs(),
+ dof_handler.n_dofs(),
+ dof_handler.max_couplings_between_dofs());
+ DoFTools::make_sparsity_pattern (dof_handler, sparsity_pattern);
+ sparsity_pattern.compress();
+
+ system_matrix.reinit (sparsity_pattern);
+ mass_matrix.reinit (sparsity_pattern);
+ laplace_matrix.reinit (sparsity_pattern);
+
+ MatrixCreator::create_mass_matrix (dof_handler, QGauss<dim>(3),
+ mass_matrix);
+ MatrixCreator::create_laplace_matrix (dof_handler, QGauss<dim>(3),
+ laplace_matrix);
+
+ // The second difference, as mentioned, to
+ // step-23 is that we need
+ // to build the boundary mass matrix that
+ // grew out of the absorbing boundary
+ // conditions.
+ //
+ // A first observation would be that this
+ // matrix is much sparser than the regular
+ // mass matrix, since none of the shape
+ // functions with purely interior support
+ // contributes to this matrix. We could
+ // therefore optimize the storage pattern
+ // to this situation and build up a second
+ // sparsity pattern that only contains the
+ // nonzero entries that we need. There is a
+ // trade-off to make here: first, we would
+ // have to have a second sparsity pattern
+ // object, so that costs memory. Secondly,
+ // the matrix attached to this sparsity
+ // pattern is going to be smaller and
+ // therefore requires less memory; it would
+ // also be faster to perform matrix-vector
+ // multiplications with it. The final
+ // argument, however, is the one that tips
+ // the scale: we are not primarily
+ // interested in performing matrix-vector
+ // with the boundary matrix alone (though
+ // we need to do that for the right hand
+ // side vector once per time step), but
+ // mostly wish to add it up to the other
+ // matrices used in the first of the two
+ // equations since this is the one that is
+ // going to be multiplied with once per
+ // iteration of the CG method,
+ // i.e. significantly more often. It is now
+ // the case that the SparseMatrix::add
+ // class allows to add one matrix to
+ // another, but only if they use the same
+ // sparsity pattern (the reason being that
+ // we can't add nonzero entries to a matrix
+ // after the sparsity pattern has been
+ // created, so we simply require that the
+ // two matrices have the same sparsity
+ // pattern).
+ //
+ // So let's go with that:
+ boundary_matrix.reinit (sparsity_pattern);
+
+ // The second thing to do is to actually
+ // build the matrix. Here, we need to
+ // integrate over faces of cells, so first
+ // we need a quadrature object that works
+ // on <code>dim-1</code> dimensional
+ // objects. Secondly, the FEFaceValues
+ // variant of FEValues that works on faces,
+ // as its name suggest. And finally, the
+ // other variables that are part of the
+ // assembly machinery. All of this we put
+ // between curly braces to limit the scope
+ // of these variables to where we actually
+ // need them.
+ //
+ // The actual act of assembling the matrix
+ // is then fairly straightforward: we loop
+ // over all cells, over all faces of each
+ // of these cells, and then do something
+ // only if that particular face is at the
+ // boundary of the domain. Like this:
+ {
+ const QGauss<dim-1> quadrature_formula(3);
+ FEFaceValues<dim> fe_values (fe, quadrature_formula,
+ update_values | 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_matrix (dofs_per_cell, dofs_per_cell);
+
+ std::vector<unsigned int> local_dof_indices (dofs_per_cell);
- const unsigned int dofs_per_cell = fe.dofs_per_cell;
- const unsigned int n_q_points = quadrature_formula.size();
- FullMatrix<double> cell_matrix (dofs_per_cell, dofs_per_cell);
- std::vector<unsigned int> local_dof_indices (dofs_per_cell);
+ typename DoFHandler<dim>::active_cell_iterator
+ cell = dof_handler.begin_active(),
+ endc = dof_handler.end();
+ for (; cell!=endc; ++cell)
+ for (unsigned int f=0; f<GeometryInfo<dim>::faces_per_cell; ++f)
+ if (cell->at_boundary(f))
+ {
+ cell_matrix = 0;
-
-
- typename DoFHandler<dim>::active_cell_iterator
- cell = dof_handler.begin_active(),
- endc = dof_handler.end();
- for (; cell!=endc; ++cell)
- for (unsigned int f=0; f<GeometryInfo<dim>::faces_per_cell; ++f)
- if (cell->at_boundary(f))
- {
- cell_matrix = 0;
+ fe_values.reinit (cell, f);
- fe_values.reinit (cell, f);
+ 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_matrix(i,j) += (fe_values.shape_value(i,q_point) *
+ fe_values.shape_value(j,q_point) *
+ fe_values.JxW(q_point));
- for (unsigned int q_point=0; q_point<n_q_points; ++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)
- cell_matrix(i,j) += (fe_values.shape_value(i,q_point) *
- fe_values.shape_value(j,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)
- boundary_matrix.add (local_dof_indices[i],
- local_dof_indices[j],
- cell_matrix(i,j));
- }
-
+ boundary_matrix.add (local_dof_indices[i],
+ local_dof_indices[j],
+ cell_matrix(i,j));
+ }
+
+ }
+
+ system_matrix.copy_from (mass_matrix);
+ system_matrix.add (time_step * time_step * theta * theta *
+ wave_speed * wave_speed,
+ laplace_matrix);
+ system_matrix.add (wave_speed * theta * time_step, boundary_matrix);
+
+
+ solution_p.reinit (dof_handler.n_dofs());
+ old_solution_p.reinit (dof_handler.n_dofs());
+ system_rhs_p.reinit (dof_handler.n_dofs());
+
+ solution_v.reinit (dof_handler.n_dofs());
+ old_solution_v.reinit (dof_handler.n_dofs());
+ system_rhs_v.reinit (dof_handler.n_dofs());
+
+ constraints.close ();
}
- system_matrix.copy_from (mass_matrix);
- system_matrix.add (time_step * time_step * theta * theta *
- wave_speed * wave_speed,
- laplace_matrix);
- system_matrix.add (wave_speed * theta * time_step, boundary_matrix);
-
- solution_p.reinit (dof_handler.n_dofs());
- old_solution_p.reinit (dof_handler.n_dofs());
- system_rhs_p.reinit (dof_handler.n_dofs());
+ // @sect4{TATForwardProblem::solve_p and TATForwardProblem::solve_v}
- solution_v.reinit (dof_handler.n_dofs());
- old_solution_v.reinit (dof_handler.n_dofs());
- system_rhs_v.reinit (dof_handler.n_dofs());
+ // The following two functions, solving the
+ // linear systems for the pressure and the
+ // velocity variable, are taken pretty much
+ // verbatim (with the exception of the change
+ // of name from $u$ to $p$ of the primary
+ // variable) from step-23:
+ template <int dim>
+ void TATForwardProblem<dim>::solve_p ()
+ {
+ SolverControl solver_control (1000, 1e-8*system_rhs_p.l2_norm());
+ SolverCG<> cg (solver_control);
- constraints.close ();
-}
+ cg.solve (system_matrix, solution_p, system_rhs_p,
+ PreconditionIdentity());
+ std::cout << " p-equation: " << solver_control.last_step()
+ << " CG iterations."
+ << std::endl;
+ }
- // @sect4{TATForwardProblem::solve_p and TATForwardProblem::solve_v}
- // The following two functions, solving the
- // linear systems for the pressure and the
- // velocity variable, are taken pretty much
- // verbatim (with the exception of the change
- // of name from $u$ to $p$ of the primary
- // variable) from step-23:
-template <int dim>
-void TATForwardProblem<dim>::solve_p ()
-{
- SolverControl solver_control (1000, 1e-8*system_rhs_p.l2_norm());
- SolverCG<> cg (solver_control);
+ template <int dim>
+ void TATForwardProblem<dim>::solve_v ()
+ {
+ SolverControl solver_control (1000, 1e-8*system_rhs_v.l2_norm());
+ SolverCG<> cg (solver_control);
- cg.solve (system_matrix, solution_p, system_rhs_p,
- PreconditionIdentity());
+ cg.solve (mass_matrix, solution_v, system_rhs_v,
+ PreconditionIdentity());
- std::cout << " p-equation: " << solver_control.last_step()
- << " CG iterations."
- << std::endl;
-}
+ std::cout << " v-equation: " << solver_control.last_step()
+ << " CG iterations."
+ << std::endl;
+ }
-template <int dim>
-void TATForwardProblem<dim>::solve_v ()
-{
- SolverControl solver_control (1000, 1e-8*system_rhs_v.l2_norm());
- SolverCG<> cg (solver_control);
- cg.solve (mass_matrix, solution_v, system_rhs_v,
- PreconditionIdentity());
+ // @sect4{TATForwardProblem::output_results}
- std::cout << " v-equation: " << solver_control.last_step()
- << " CG iterations."
- << std::endl;
-}
+ // The same holds here: the function is from
+ // step-23.
+ template <int dim>
+ void TATForwardProblem<dim>::output_results () const
+ {
+ DataOut<dim> data_out;
+ data_out.attach_dof_handler (dof_handler);
+ data_out.add_data_vector (solution_p, "P");
+ data_out.add_data_vector (solution_v, "V");
+ data_out.build_patches ();
- // @sect4{TATForwardProblem::output_results}
+ const std::string filename = "solution-" +
+ Utilities::int_to_string (timestep_number, 3) +
+ ".gnuplot";
+ std::ofstream output (filename.c_str());
+ data_out.write_gnuplot (output);
+ }
- // The same holds here: the function is from
- // step-23.
-template <int dim>
-void TATForwardProblem<dim>::output_results () const
-{
- DataOut<dim> data_out;
- data_out.attach_dof_handler (dof_handler);
- data_out.add_data_vector (solution_p, "P");
- data_out.add_data_vector (solution_v, "V");
- data_out.build_patches ();
+ // @sect4{TATForwardProblem::run}
- const std::string filename = "solution-" +
- Utilities::int_to_string (timestep_number, 3) +
- ".gnuplot";
- std::ofstream output (filename.c_str());
- data_out.write_gnuplot (output);
-}
+ // This function that does most of the work
+ // is pretty much again like in step-23,
+ // though we make things a bit clearer by
+ // using the vectors G1 and G2 mentioned in
+ // the introduction. Compared to the overall
+ // memory consumption of the program, the
+ // introduction of a few temporary vectors
+ // isn't doing much harm.
+ //
+ // The only changes to this function are:
+ // First, that we do not have to project
+ // initial values for the velocity $v$, since
+ // we know that it is zero. And second that
+ // we evaluate the solution at the detector
+ // locations computed in the
+ // constructor. This is done using the
+ // VectorTools::point_value function. These
+ // values are then written to a file that we
+ // open at the beginning of the function.
+ template <int dim>
+ void TATForwardProblem<dim>::run ()
+ {
+ setup_system();
+ VectorTools::project (dof_handler, constraints,
+ QGauss<dim>(3), InitialValuesP<dim>(),
+ old_solution_p);
+ old_solution_v = 0;
- // @sect4{TATForwardProblem::run}
-
- // This function that does most of the work
- // is pretty much again like in step-23,
- // though we make things a bit clearer by
- // using the vectors G1 and G2 mentioned in
- // the introduction. Compared to the overall
- // memory consumption of the program, the
- // introduction of a few temporary vectors
- // isn't doing much harm.
- //
- // The only changes to this function are:
- // First, that we do not have to project
- // initial values for the velocity $v$, since
- // we know that it is zero. And second that
- // we evaluate the solution at the detector
- // locations computed in the
- // constructor. This is done using the
- // VectorTools::point_value function. These
- // values are then written to a file that we
- // open at the beginning of the function.
-template <int dim>
-void TATForwardProblem<dim>::run ()
-{
- setup_system();
+ std::ofstream detector_data("detectors.dat");
- VectorTools::project (dof_handler, constraints,
- QGauss<dim>(3), InitialValuesP<dim>(),
- old_solution_p);
- old_solution_v = 0;
+ Vector<double> tmp (solution_p.size());
+ Vector<double> G1 (solution_p.size());
+ Vector<double> G2 (solution_v.size());
+ const double end_time = 0.7;
+ for (timestep_number=1, time=time_step;
+ time<=end_time;
+ time+=time_step, ++timestep_number)
+ {
+ std::cout << std::endl;
+ std::cout<< "time_step " << timestep_number << " @ t=" << time << std::endl;
- std::ofstream detector_data("detectors.dat");
+ mass_matrix.vmult (G1, old_solution_p);
+ mass_matrix.vmult (tmp, old_solution_v);
+ G1.add(time_step * (1-theta), tmp);
- Vector<double> tmp (solution_p.size());
- Vector<double> G1 (solution_p.size());
- Vector<double> G2 (solution_v.size());
+ mass_matrix.vmult (G2, old_solution_v);
+ laplace_matrix.vmult (tmp, old_solution_p);
+ G2.add (-wave_speed * wave_speed * time_step * (1-theta), tmp);
- const double end_time = 0.7;
- for (timestep_number=1, time=time_step;
- time<=end_time;
- time+=time_step, ++timestep_number)
- {
- std::cout << std::endl;
- std::cout<< "time_step " << timestep_number << " @ t=" << time << std::endl;
-
- mass_matrix.vmult (G1, old_solution_p);
- mass_matrix.vmult (tmp, old_solution_v);
- G1.add(time_step * (1-theta), tmp);
-
- mass_matrix.vmult (G2, old_solution_v);
- laplace_matrix.vmult (tmp, old_solution_p);
- G2.add (-wave_speed * wave_speed * time_step * (1-theta), tmp);
-
- boundary_matrix.vmult (tmp, old_solution_p);
- G2.add (wave_speed, tmp);
-
- system_rhs_p = G1;
- system_rhs_p.add(time_step * theta , G2);
-
- solve_p ();
-
-
- system_rhs_v = G2;
- laplace_matrix.vmult (tmp, solution_p);
- system_rhs_v.add (-time_step * theta * wave_speed * wave_speed, tmp);
-
- boundary_matrix.vmult (tmp, solution_p);
- system_rhs_v.add (-wave_speed, tmp);
-
- solve_v ();
-
- output_results ();
-
-
- detector_data << time;
- for (unsigned int i=0 ; i<detector_locations.size(); ++i)
- detector_data << " "
- << VectorTools::point_value (dof_handler,
- solution_p,
- detector_locations[i])
- << " ";
- detector_data << std::endl;
-
-
- old_solution_p = solution_p;
- old_solution_v = solution_v;
- }
+ boundary_matrix.vmult (tmp, old_solution_p);
+ G2.add (wave_speed, tmp);
+
+ system_rhs_p = G1;
+ system_rhs_p.add(time_step * theta , G2);
+
+ solve_p ();
+
+
+ system_rhs_v = G2;
+ laplace_matrix.vmult (tmp, solution_p);
+ system_rhs_v.add (-time_step * theta * wave_speed * wave_speed, tmp);
+
+ boundary_matrix.vmult (tmp, solution_p);
+ system_rhs_v.add (-wave_speed, tmp);
+
+ solve_v ();
+
+ output_results ();
+
+
+ detector_data << time;
+ for (unsigned int i=0 ; i<detector_locations.size(); ++i)
+ detector_data << " "
+ << VectorTools::point_value (dof_handler,
+ solution_p,
+ detector_locations[i])
+ << " ";
+ detector_data << std::endl;
+
+
+ old_solution_p = solution_p;
+ old_solution_v = solution_v;
+ }
+ }
}
// program. There is nothing here that hasn't
// been shown in several of the previous
// programs:
-int main ()
+int main ()
{
try
{
+ using namespace dealii;
+ using namespace Step24;
+
deallog.depth_console (0);
+
TATForwardProblem<2> forward_problem_solver;
forward_problem_solver.run ();
}
return 1;
}
- catch (...)
+ catch (...)
{
std::cerr << std::endl << std::endl
<< "----------------------------------------------------"
<< std::endl;
return 1;
}
-
+
return 0;
}
/* $Id$ */
-/* Copyright (C) 2006, 2007, 2008, 2009 by the deal.II authors */
+/* Copyright (C) 2006, 2007, 2008, 2009, 2011 by the deal.II authors */
/* Author: Ivan Christov, Wolfgang Bangerth, Texas A&M University, 2006 */
/* */
/* This file is subject to QPL and may not be distributed */
// The last step is as in all
// previous programs:
-using namespace dealii;
-
-
- // @sect3{The <code>SineGordonProblem</code> class template}
-
- // The entire algorithm for solving the
- // problem is encapsulated in this class. As
- // in previous example programs, the class is
- // declared with a template parameter, which
- // is the spatial dimension, so that we can
- // solve the sine-Gordon equation in one, two
- // or three spatial dimensions. For more on
- // the dimension-independent
- // class-encapsulation of the problem, the
- // reader should consult step-3 and step-4.
- //
- // Compared to step-23 and step-24, there
- // isn't anything newsworthy in the general
- // structure of the program (though there is
- // of course in the inner workings of the
- // various functions!). The most notable
- // difference is the presence of the two new
- // functions <code>compute_nl_term</code> and
- // <code>compute_nl_matrix</code> that
- // compute the nonlinear contributions to the
- // system matrix and right-hand side of the first
- // equation, as discussed in the
- // Introduction. In addition, we have to have
- // a vector <code>solution_update</code> that
- // contains the nonlinear update to the
- // solution vector in each Newton step.
- //
- // As also mentioned in the introduction, we
- // do not store the velocity variable in this
- // program, but the mass matrix times the
- // velocity. This is done in the
- // <code>M_x_velocity</code> variable (the
- // "x" is intended to stand for
- // "times").
- //
- // Finally, the
- // <code>output_timestep_skip</code>
- // variable stores the number of time
- // steps to be taken each time before
- // graphical output is to be
- // generated. This is of importance
- // when using fine meshes (and
- // consequently small time steps)
- // where we would run lots of time
- // steps and create lots of output
- // files of solutions that look
- // almost the same in subsequent
- // files. This only clogs up our
- // visualization procedures and we
- // should avoid creating more output
- // than we are really interested
- // in. Therefore, if this variable is
- // set to a value $n$ bigger than one,
- // output is generated only every
- // $n$th time step.
-template <int dim>
-class SineGordonProblem
+namespace Step25
{
- public:
- SineGordonProblem ();
- void run ();
-
- private:
- void make_grid_and_dofs ();
- void assemble_system ();
- void compute_nl_term (const Vector<double> &old_data,
- const Vector<double> &new_data,
- Vector<double> &nl_term) const;
- void compute_nl_matrix (const Vector<double> &old_data,
- const Vector<double> &new_data,
- SparseMatrix<double> &nl_matrix) const;
- unsigned int solve ();
- void output_results (const unsigned int timestep_number) const;
-
- Triangulation<dim> triangulation;
- FE_Q<dim> fe;
- DoFHandler<dim> dof_handler;
-
- SparsityPattern sparsity_pattern;
- SparseMatrix<double> system_matrix;
- SparseMatrix<double> mass_matrix;
- SparseMatrix<double> laplace_matrix;
-
- const unsigned int n_global_refinements;
-
- double time;
- const double final_time, time_step;
- const double theta;
-
- Vector<double> solution, solution_update, old_solution;
- Vector<double> M_x_velocity;
- Vector<double> system_rhs;
-
- const unsigned int output_timestep_skip;
-};
-
-
- // @sect3{Initial conditions}
-
- // In the following two classes, we first
- // implement the exact solution for 1D, 2D,
- // and 3D mentioned in the introduction to
- // this program. This space-time solution may
- // be of independent interest if one wanted
- // to test the accuracy of the program by
- // comparing the numerical against the
- // analytic solution (note however that the
- // program uses a finite domain, whereas
- // these are analytic solutions for an
- // unbounded domain). This may, for example,
- // be done using the
- // VectorTools::integrate_difference
- // function. Note, again (as was already
- // discussed in step-23), how we describe
- // space-time functions as spatial functions
- // that depend on a time variable that can be
- // set and queried using the
- // FunctionTime::set_time() and
- // FunctionTime::get_time() member functions
- // of the FunctionTime base class of the
- // Function class.
-template <int dim>
-class ExactSolution : public Function<dim>
-{
- public:
- ExactSolution (const unsigned int n_components = 1,
- const double time = 0.) : Function<dim>(n_components, time) {}
- virtual double value (const Point<dim> &p,
- const unsigned int component = 0) const;
-};
-
-template <int dim>
-double ExactSolution<dim>::value (const Point<dim> &p,
- const unsigned int /*component*/) const
-{
- double t = this->get_time ();
+ using namespace dealii;
- switch (dim)
- {
- case 1:
+
+ // @sect3{The <code>SineGordonProblem</code> class template}
+
+ // The entire algorithm for solving the
+ // problem is encapsulated in this class. As
+ // in previous example programs, the class is
+ // declared with a template parameter, which
+ // is the spatial dimension, so that we can
+ // solve the sine-Gordon equation in one, two
+ // or three spatial dimensions. For more on
+ // the dimension-independent
+ // class-encapsulation of the problem, the
+ // reader should consult step-3 and step-4.
+ //
+ // Compared to step-23 and step-24, there
+ // isn't anything newsworthy in the general
+ // structure of the program (though there is
+ // of course in the inner workings of the
+ // various functions!). The most notable
+ // difference is the presence of the two new
+ // functions <code>compute_nl_term</code> and
+ // <code>compute_nl_matrix</code> that
+ // compute the nonlinear contributions to the
+ // system matrix and right-hand side of the first
+ // equation, as discussed in the
+ // Introduction. In addition, we have to have
+ // a vector <code>solution_update</code> that
+ // contains the nonlinear update to the
+ // solution vector in each Newton step.
+ //
+ // As also mentioned in the introduction, we
+ // do not store the velocity variable in this
+ // program, but the mass matrix times the
+ // velocity. This is done in the
+ // <code>M_x_velocity</code> variable (the
+ // "x" is intended to stand for
+ // "times").
+ //
+ // Finally, the
+ // <code>output_timestep_skip</code>
+ // variable stores the number of time
+ // steps to be taken each time before
+ // graphical output is to be
+ // generated. This is of importance
+ // when using fine meshes (and
+ // consequently small time steps)
+ // where we would run lots of time
+ // steps and create lots of output
+ // files of solutions that look
+ // almost the same in subsequent
+ // files. This only clogs up our
+ // visualization procedures and we
+ // should avoid creating more output
+ // than we are really interested
+ // in. Therefore, if this variable is
+ // set to a value $n$ bigger than one,
+ // output is generated only every
+ // $n$th time step.
+ template <int dim>
+ class SineGordonProblem
+ {
+ public:
+ SineGordonProblem ();
+ void run ();
+
+ private:
+ void make_grid_and_dofs ();
+ void assemble_system ();
+ void compute_nl_term (const Vector<double> &old_data,
+ const Vector<double> &new_data,
+ Vector<double> &nl_term) const;
+ void compute_nl_matrix (const Vector<double> &old_data,
+ const Vector<double> &new_data,
+ SparseMatrix<double> &nl_matrix) const;
+ unsigned int solve ();
+ void output_results (const unsigned int timestep_number) const;
+
+ Triangulation<dim> triangulation;
+ FE_Q<dim> fe;
+ DoFHandler<dim> dof_handler;
+
+ SparsityPattern sparsity_pattern;
+ SparseMatrix<double> system_matrix;
+ SparseMatrix<double> mass_matrix;
+ SparseMatrix<double> laplace_matrix;
+
+ const unsigned int n_global_refinements;
+
+ double time;
+ const double final_time, time_step;
+ const double theta;
+
+ Vector<double> solution, solution_update, old_solution;
+ Vector<double> M_x_velocity;
+ Vector<double> system_rhs;
+
+ const unsigned int output_timestep_skip;
+ };
+
+
+ // @sect3{Initial conditions}
+
+ // In the following two classes, we first
+ // implement the exact solution for 1D, 2D,
+ // and 3D mentioned in the introduction to
+ // this program. This space-time solution may
+ // be of independent interest if one wanted
+ // to test the accuracy of the program by
+ // comparing the numerical against the
+ // analytic solution (note however that the
+ // program uses a finite domain, whereas
+ // these are analytic solutions for an
+ // unbounded domain). This may, for example,
+ // be done using the
+ // VectorTools::integrate_difference
+ // function. Note, again (as was already
+ // discussed in step-23), how we describe
+ // space-time functions as spatial functions
+ // that depend on a time variable that can be
+ // set and queried using the
+ // FunctionTime::set_time() and
+ // FunctionTime::get_time() member functions
+ // of the FunctionTime base class of the
+ // Function class.
+ template <int dim>
+ class ExactSolution : public Function<dim>
+ {
+ public:
+ ExactSolution (const unsigned int n_components = 1,
+ const double time = 0.) : Function<dim>(n_components, time) {}
+ virtual double value (const Point<dim> &p,
+ const unsigned int component = 0) const;
+ };
+
+ template <int dim>
+ double ExactSolution<dim>::value (const Point<dim> &p,
+ const unsigned int /*component*/) const
+ {
+ double t = this->get_time ();
+
+ switch (dim)
{
- const double m = 0.5;
- const double c1 = 0.;
- const double c2 = 0.;
- return -4.*std::atan (m /
- std::sqrt(1.-m*m) *
- std::sin(std::sqrt(1.-m*m)*t+c2) /
- std::cosh(m*p[0]+c1));
+ case 1:
+ {
+ const double m = 0.5;
+ const double c1 = 0.;
+ const double c2 = 0.;
+ return -4.*std::atan (m /
+ std::sqrt(1.-m*m) *
+ std::sin(std::sqrt(1.-m*m)*t+c2) /
+ std::cosh(m*p[0]+c1));
+ }
+
+ case 2:
+ {
+ const double theta = numbers::PI/4.;
+ const double lambda = 1.;
+ const double a0 = 1.;
+ const double s = 1.;
+ const double arg = p[0] * std::cos(theta) +
+ std::sin(theta) *
+ (p[1] * std::cosh(lambda) +
+ t * std::sinh(lambda));
+ return 4.*std::atan(a0*std::exp(s*arg));
+ }
+
+ case 3:
+ {
+ double theta = numbers::PI/4;
+ double phi = numbers::PI/4;
+ double tau = 1.;
+ double c0 = 1.;
+ double s = 1.;
+ double arg = p[0]*std::cos(theta) +
+ p[1]*std::sin(theta) * std::cos(phi) +
+ std::sin(theta) * std::sin(phi) *
+ (p[2]*std::cosh(tau)+t*std::sinh(tau));
+ return 4.*std::atan(c0*std::exp(s*arg));
+ }
+
+ default:
+ Assert (false, ExcNotImplemented());
+ return -1e8;
}
-
- case 2:
+ }
+
+ // In the second part of this section, we
+ // provide the initial conditions. We are lazy
+ // (and cautious) and don't want to implement
+ // the same functions as above a second
+ // time. Rather, if we are queried for
+ // initial conditions, we create an object
+ // <code>ExactSolution</code>, set it to the
+ // correct time, and let it compute whatever
+ // values the exact solution has at that
+ // time:
+ template <int dim>
+ class InitialValues : public Function<dim>
+ {
+ public:
+ InitialValues (const unsigned int n_components = 1,
+ const double time = 0.)
+ :
+ Function<dim>(n_components, time)
+ {}
+
+ virtual double value (const Point<dim> &p,
+ const unsigned int component = 0) const;
+ };
+
+ template <int dim>
+ double InitialValues<dim>::value (const Point<dim> &p,
+ const unsigned int component) const
+ {
+ return ExactSolution<dim>(1, this->get_time()).value (p, component);
+ }
+
+
+
+ // @sect3{Implementation of the <code>SineGordonProblem</code> class}
+
+ // Let's move on to the implementation of the
+ // main class, as it implements the algorithm
+ // outlined in the introduction.
+
+ // @sect4{SineGordonProblem::SineGordonProblem}
+
+ // This is the constructor of the
+ // <code>SineGordonProblem</code> class. It
+ // specifies the desired polynomial degree of
+ // the finite elements, associates a
+ // <code>DoFHandler</code> to the
+ // <code>triangulation</code> object (just as
+ // in the example programs step-3 and
+ // step-4), initializes the current or
+ // initial time, the final time, the time
+ // step size, and the value of $\theta$ for
+ // the time stepping scheme. Since the
+ // solutions we compute here are
+ // time-periodic, the actual value of the
+ // start-time doesn't matter, and we choose
+ // it so that we start at an interesting
+ // time.
+ //
+ // Note that if we were to chose the explicit
+ // Euler time stepping scheme ($\theta = 0$),
+ // then we must pick a time step $k \le h$,
+ // otherwise the scheme is not stable and
+ // oscillations might arise in the
+ // solution. The Crank-Nicolson scheme
+ // ($\theta = \frac{1}{2}$) and the implicit
+ // Euler scheme ($\theta=1$) do not suffer
+ // from this deficiency, since they are
+ // unconditionally stable. However, even then
+ // the time step should be chosen to be on
+ // the order of $h$ in order to obtain a good
+ // solution. Since we know that our mesh
+ // results from the uniform subdivision of a
+ // rectangle, we can compute that time step
+ // easily; if we had a different domain, the
+ // technique in step-24 using
+ // GridTools::minimal_cell_diameter would
+ // work as well.
+ template <int dim>
+ SineGordonProblem<dim>::SineGordonProblem ()
+ :
+ fe (1),
+ dof_handler (triangulation),
+ n_global_refinements (6),
+ time (-5.4414),
+ final_time (2.7207),
+ time_step (10*1./std::pow(2.,1.*n_global_refinements)),
+ theta (0.5),
+ output_timestep_skip (1)
+ {}
+
+ // @sect4{SineGordonProblem::make_grid_and_dofs}
+
+ // This function creates a rectangular grid
+ // in <code>dim</code> dimensions and refines
+ // it several times. Also, all matrix and
+ // vector members of the
+ // <code>SineGordonProblem</code> class are
+ // initialized to their appropriate sizes
+ // once the degrees of freedom have been
+ // assembled. Like step-24, we use the
+ // <code>MatrixCreator</code> class to
+ // generate a mass matrix $M$ and a Laplace
+ // matrix $A$ and store them in the
+ // appropriate variables for the remainder of
+ // the program's life.
+ template <int dim>
+ void SineGordonProblem<dim>::make_grid_and_dofs ()
+ {
+ GridGenerator::hyper_cube (triangulation, -10, 10);
+ triangulation.refine_global (n_global_refinements);
+
+ std::cout << " Number of active cells: "
+ << triangulation.n_active_cells()
+ << std::endl
+ << " Total number of cells: "
+ << triangulation.n_cells()
+ << std::endl;
+
+ dof_handler.distribute_dofs (fe);
+
+ std::cout << " Number of degrees of freedom: "
+ << dof_handler.n_dofs()
+ << std::endl;
+
+ sparsity_pattern.reinit (dof_handler.n_dofs(),
+ dof_handler.n_dofs(),
+ dof_handler.max_couplings_between_dofs());
+ DoFTools::make_sparsity_pattern (dof_handler, sparsity_pattern);
+ sparsity_pattern.compress ();
+
+ system_matrix.reinit (sparsity_pattern);
+ mass_matrix.reinit (sparsity_pattern);
+ laplace_matrix.reinit (sparsity_pattern);
+
+ MatrixCreator::create_mass_matrix (dof_handler,
+ QGauss<dim>(3),
+ mass_matrix);
+ MatrixCreator::create_laplace_matrix (dof_handler,
+ QGauss<dim>(3),
+ laplace_matrix);
+
+ solution.reinit (dof_handler.n_dofs());
+ solution_update.reinit (dof_handler.n_dofs());
+ old_solution.reinit (dof_handler.n_dofs());
+ M_x_velocity.reinit (dof_handler.n_dofs());
+ system_rhs.reinit (dof_handler.n_dofs());
+ }
+
+ // @sect4{SineGordonProblem::assemble_system}
+
+ // This functions assembles the system matrix
+ // and right-hand side vector for each
+ // iteration of Newton's method. The reader
+ // should refer to the Introduction for the
+ // explicit formulas for the system matrix
+ // and right-hand side.
+ //
+ // Note that during each time step, we have to
+ // add up the various contributions to the
+ // matrix and right hand sides. In contrast
+ // to step-23 and step-24, this requires
+ // assembling a few more terms, since they
+ // depend on the solution of the previous
+ // time step or previous nonlinear step. We
+ // use the functions
+ // <code>compute_nl_matrix</code> and
+ // <code>compute_nl_term</code> to do this,
+ // while the present function provides the
+ // top-level logic.
+ template <int dim>
+ void SineGordonProblem<dim>::assemble_system ()
+ {
+ // First we assemble the Jacobian
+ // matrix $F'_h(U^{n,l})$, where
+ // $U^{n,l}$ is stored in the vector
+ // <code>solution</code> for
+ // convenience.
+ system_matrix = 0;
+ system_matrix.copy_from (mass_matrix);
+ system_matrix.add (std::pow(time_step*theta,2), laplace_matrix);
+
+ SparseMatrix<double> tmp_matrix (sparsity_pattern);
+ compute_nl_matrix (old_solution, solution, tmp_matrix);
+ system_matrix.add (-std::pow(time_step*theta,2), tmp_matrix);
+
+ // Then, we compute the right-hand
+ // side vector $-F_h(U^{n,l})$.
+ system_rhs = 0;
+
+ tmp_matrix = 0;
+ tmp_matrix.copy_from (mass_matrix);
+ tmp_matrix.add (std::pow(time_step*theta,2), laplace_matrix);
+
+ Vector<double> tmp_vector (solution.size());
+ tmp_matrix.vmult (tmp_vector, solution);
+ system_rhs += tmp_vector;
+
+ tmp_matrix = 0;
+ tmp_matrix.copy_from (mass_matrix);
+ tmp_matrix.add (-std::pow(time_step,2)*theta*(1-theta), laplace_matrix);
+
+ tmp_vector = 0;
+ tmp_matrix.vmult (tmp_vector, old_solution);
+ system_rhs -= tmp_vector;
+
+ system_rhs.add (-time_step, M_x_velocity);
+
+ tmp_vector = 0;
+ compute_nl_term (old_solution, solution, tmp_vector);
+ system_rhs.add (std::pow(time_step,2)*theta, tmp_vector);
+
+ system_rhs *= -1;
+ }
+
+ // @sect4{SineGordonProblem::compute_nl_term}
+
+ // This function computes the vector
+ // $S(\cdot,\cdot)$, which appears in the
+ // nonlinear term in the both equations of
+ // the split formulation. This function not
+ // only simplifies the repeated computation
+ // of this term, but it is also a fundamental
+ // part of the nonlinear iterative solver
+ // that we use when the time stepping is
+ // implicit (i.e. $\theta\ne 0$). Moreover,
+ // we must allow the function to receive as
+ // input an "old" and a "new" solution. These
+ // may not be the actual solutions of the
+ // problem stored in
+ // <code>old_solution</code> and
+ // <code>solution</code>, but are simply the
+ // two functions we linearize about. For the
+ // purposes of this function, let us call the
+ // first two arguments $w_{\mathrm{old}}$ and
+ // $w_{\mathrm{new}}$ in the documentation of
+ // this class below, respectively.
+ //
+ // As a side-note, it is perhaps worth
+ // investigating what order quadrature
+ // formula is best suited for this type of
+ // integration. Since $\sin(\cdot)$ is not a
+ // polynomial, there are probably no
+ // quadrature formulas that can integrate
+ // these terms exactly. It is usually
+ // sufficient to just make sure that the
+ // right hand side is integrated up to the
+ // same order of accuracy as the
+ // discretization scheme is, but it may be
+ // possible to improve on the constant in the
+ // asympotitic statement of convergence by
+ // choosing a more accurate quadrature
+ // formula.
+ template <int dim>
+ void SineGordonProblem<dim>::compute_nl_term (const Vector<double> &old_data,
+ const Vector<double> &new_data,
+ Vector<double> &nl_term) const
+ {
+ const QGauss<dim> quadrature_formula (3);
+ FEValues<dim> fe_values (fe, quadrature_formula,
+ update_values |
+ update_JxW_values |
+ update_quadrature_points);
+
+ const unsigned int dofs_per_cell = fe.dofs_per_cell;
+ const unsigned int n_q_points = quadrature_formula.size();
+
+ Vector<double> local_nl_term (dofs_per_cell);
+ std::vector<unsigned int> local_dof_indices (dofs_per_cell);
+ std::vector<double> old_data_values (n_q_points);
+ std::vector<double> new_data_values (n_q_points);
+
+ typename DoFHandler<dim>::active_cell_iterator
+ cell = dof_handler.begin_active(),
+ endc = dof_handler.end();
+
+ for (; cell!=endc; ++cell)
{
- const double theta = numbers::PI/4.;
- const double lambda = 1.;
- const double a0 = 1.;
- const double s = 1.;
- const double arg = p[0] * std::cos(theta) +
- std::sin(theta) *
- (p[1] * std::cosh(lambda) +
- t * std::sinh(lambda));
- return 4.*std::atan(a0*std::exp(s*arg));
+ // Once we re-initialize our
+ // <code>FEValues</code> instantiation
+ // to the current cell, we make use of
+ // the <code>get_function_values</code>
+ // routine to get the values of the
+ // "old" data (presumably at
+ // $t=t_{n-1}$) and the "new" data
+ // (presumably at $t=t_n$) at the nodes
+ // of the chosen quadrature formula.
+ fe_values.reinit (cell);
+ fe_values.get_function_values (old_data, old_data_values);
+ fe_values.get_function_values (new_data, new_data_values);
+
+ // Now, we can evaluate $\int_K
+ // \sin\left[\theta w_{\mathrm{new}} +
+ // (1-\theta) w_{\mathrm{old}}\right]
+ // \,\varphi_j\,\mathrm{d}x$ using the
+ // desired quadrature formula.
+ for (unsigned int q_point=0; q_point<n_q_points; ++q_point)
+ for (unsigned int i=0; i<dofs_per_cell; ++i)
+ local_nl_term(i) += (std::sin(theta * new_data_values[q_point] +
+ (1-theta) * old_data_values[q_point]) *
+ fe_values.shape_value (i, q_point) *
+ fe_values.JxW (q_point));
+
+ // We conclude by adding up the
+ // contributions of the
+ // integrals over the cells to
+ // the global integral.
+ cell->get_dof_indices (local_dof_indices);
+
+ for (unsigned int i=0; i<dofs_per_cell; ++i)
+ nl_term(local_dof_indices[i]) += local_nl_term(i);
+
+ local_nl_term = 0;
}
-
- case 3:
+ }
+
+ // @sect4{SineGordonProblem::compute_nl_matrix}
+
+ // This is the second function dealing with the
+ // nonlinear scheme. It computes the matrix
+ // $N(\cdot,\cdot)$, whicih appears in the
+ // nonlinear term in the Jacobian of
+ // $F(\cdot)$. Just as
+ // <code>compute_nl_term</code>, we must
+ // allow this function to receive as input an
+ // "old" and a "new" solution, which we again
+ // call $w_{\mathrm{old}}$ and
+ // $w_{\mathrm{new}}$ below, respectively.
+ template <int dim>
+ void SineGordonProblem<dim>::compute_nl_matrix (const Vector<double> &old_data,
+ const Vector<double> &new_data,
+ SparseMatrix<double> &nl_matrix) const
+ {
+ QGauss<dim> quadrature_formula (3);
+ FEValues<dim> fe_values (fe, quadrature_formula,
+ update_values | update_JxW_values | update_quadrature_points);
+
+ const unsigned int dofs_per_cell = fe.dofs_per_cell;
+ const unsigned int n_q_points = quadrature_formula.size();
+
+ FullMatrix<double> local_nl_matrix (dofs_per_cell, dofs_per_cell);
+ std::vector<unsigned int> local_dof_indices (dofs_per_cell);
+ std::vector<double> old_data_values (n_q_points);
+ std::vector<double> new_data_values (n_q_points);
+
+ typename DoFHandler<dim>::active_cell_iterator
+ cell = dof_handler.begin_active(),
+ endc = dof_handler.end();
+
+ for (; cell!=endc; ++cell)
{
- double theta = numbers::PI/4;
- double phi = numbers::PI/4;
- double tau = 1.;
- double c0 = 1.;
- double s = 1.;
- double arg = p[0]*std::cos(theta) +
- p[1]*std::sin(theta) * std::cos(phi) +
- std::sin(theta) * std::sin(phi) *
- (p[2]*std::cosh(tau)+t*std::sinh(tau));
- return 4.*std::atan(c0*std::exp(s*arg));
+ // Again, first we
+ // re-initialize our
+ // <code>FEValues</code>
+ // instantiation to the current
+ // cell.
+ fe_values.reinit (cell);
+ fe_values.get_function_values (old_data, old_data_values);
+ fe_values.get_function_values (new_data, new_data_values);
+
+ // Then, we evaluate $\int_K
+ // \cos\left[\theta
+ // w_{\mathrm{new}} +
+ // (1-\theta)
+ // w_{\mathrm{old}}\right]\,
+ // \varphi_i\,
+ // \varphi_j\,\mathrm{d}x$
+ // using the desired quadrature
+ // formula.
+ 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)
+ local_nl_matrix(i,j) += (std::cos(theta * new_data_values[q_point] +
+ (1-theta) * old_data_values[q_point]) *
+ fe_values.shape_value (i, q_point) *
+ fe_values.shape_value (j, q_point) *
+ fe_values.JxW (q_point));
+
+ // Finally, we add up the
+ // contributions of the
+ // integrals over the cells to
+ // the global integral.
+ 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)
+ nl_matrix.add(local_dof_indices[i], local_dof_indices[j],
+ local_nl_matrix(i,j));
+
+ local_nl_matrix = 0;
}
+ }
- default:
- Assert (false, ExcNotImplemented());
- return -1e8;
- }
-}
-
- // In the second part of this section, we
- // provide the initial conditions. We are lazy
- // (and cautious) and don't want to implement
- // the same functions as above a second
- // time. Rather, if we are queried for
- // initial conditions, we create an object
- // <code>ExactSolution</code>, set it to the
- // correct time, and let it compute whatever
- // values the exact solution has at that
- // time:
-template <int dim>
-class InitialValues : public Function<dim>
-{
- public:
- InitialValues (const unsigned int n_components = 1,
- const double time = 0.)
- :
- Function<dim>(n_components, time)
- {}
-
- virtual double value (const Point<dim> &p,
- const unsigned int component = 0) const;
-};
-
-template <int dim>
-double InitialValues<dim>::value (const Point<dim> &p,
- const unsigned int component) const
-{
- return ExactSolution<dim>(1, this->get_time()).value (p, component);
-}
-
-
-
- // @sect3{Implementation of the <code>SineGordonProblem</code> class}
-
- // Let's move on to the implementation of the
- // main class, as it implements the algorithm
- // outlined in the introduction.
-
- // @sect4{SineGordonProblem::SineGordonProblem}
-
- // This is the constructor of the
- // <code>SineGordonProblem</code> class. It
- // specifies the desired polynomial degree of
- // the finite elements, associates a
- // <code>DoFHandler</code> to the
- // <code>triangulation</code> object (just as
- // in the example programs step-3 and
- // step-4), initializes the current or
- // initial time, the final time, the time
- // step size, and the value of $\theta$ for
- // the time stepping scheme. Since the
- // solutions we compute here are
- // time-periodic, the actual value of the
- // start-time doesn't matter, and we choose
- // it so that we start at an interesting
- // time.
- //
- // Note that if we were to chose the explicit
- // Euler time stepping scheme ($\theta = 0$),
- // then we must pick a time step $k \le h$,
- // otherwise the scheme is not stable and
- // oscillations might arise in the
- // solution. The Crank-Nicolson scheme
- // ($\theta = \frac{1}{2}$) and the implicit
- // Euler scheme ($\theta=1$) do not suffer
- // from this deficiency, since they are
- // unconditionally stable. However, even then
- // the time step should be chosen to be on
- // the order of $h$ in order to obtain a good
- // solution. Since we know that our mesh
- // results from the uniform subdivision of a
- // rectangle, we can compute that time step
- // easily; if we had a different domain, the
- // technique in step-24 using
- // GridTools::minimal_cell_diameter would
- // work as well.
-template <int dim>
-SineGordonProblem<dim>::SineGordonProblem ()
- :
- fe (1),
- dof_handler (triangulation),
- n_global_refinements (6),
- time (-5.4414),
- final_time (2.7207),
- time_step (10*1./std::pow(2.,1.*n_global_refinements)),
- theta (0.5),
- output_timestep_skip (1)
-{}
-
- // @sect4{SineGordonProblem::make_grid_and_dofs}
-
- // This function creates a rectangular grid
- // in <code>dim</code> dimensions and refines
- // it several times. Also, all matrix and
- // vector members of the
- // <code>SineGordonProblem</code> class are
- // initialized to their appropriate sizes
- // once the degrees of freedom have been
- // assembled. Like step-24, we use the
- // <code>MatrixCreator</code> class to
- // generate a mass matrix $M$ and a Laplace
- // matrix $A$ and store them in the
- // appropriate variables for the remainder of
- // the program's life.
-template <int dim>
-void SineGordonProblem<dim>::make_grid_and_dofs ()
-{
- GridGenerator::hyper_cube (triangulation, -10, 10);
- triangulation.refine_global (n_global_refinements);
-
- std::cout << " Number of active cells: "
- << triangulation.n_active_cells()
- << std::endl
- << " Total number of cells: "
- << triangulation.n_cells()
- << std::endl;
-
- dof_handler.distribute_dofs (fe);
-
- std::cout << " Number of degrees of freedom: "
- << dof_handler.n_dofs()
- << std::endl;
-
- sparsity_pattern.reinit (dof_handler.n_dofs(),
- dof_handler.n_dofs(),
- dof_handler.max_couplings_between_dofs());
- DoFTools::make_sparsity_pattern (dof_handler, sparsity_pattern);
- sparsity_pattern.compress ();
-
- system_matrix.reinit (sparsity_pattern);
- mass_matrix.reinit (sparsity_pattern);
- laplace_matrix.reinit (sparsity_pattern);
-
- MatrixCreator::create_mass_matrix (dof_handler,
- QGauss<dim>(3),
- mass_matrix);
- MatrixCreator::create_laplace_matrix (dof_handler,
- QGauss<dim>(3),
- laplace_matrix);
-
- solution.reinit (dof_handler.n_dofs());
- solution_update.reinit (dof_handler.n_dofs());
- old_solution.reinit (dof_handler.n_dofs());
- M_x_velocity.reinit (dof_handler.n_dofs());
- system_rhs.reinit (dof_handler.n_dofs());
-}
-
- // @sect4{SineGordonProblem::assemble_system}
-
- // This functions assembles the system matrix
- // and right-hand side vector for each
- // iteration of Newton's method. The reader
- // should refer to the Introduction for the
- // explicit formulas for the system matrix
- // and right-hand side.
- //
- // Note that during each time step, we have to
- // add up the various contributions to the
- // matrix and right hand sides. In contrast
- // to step-23 and step-24, this requires
- // assembling a few more terms, since they
- // depend on the solution of the previous
- // time step or previous nonlinear step. We
- // use the functions
- // <code>compute_nl_matrix</code> and
- // <code>compute_nl_term</code> to do this,
- // while the present function provides the
- // top-level logic.
-template <int dim>
-void SineGordonProblem<dim>::assemble_system ()
-{
- // First we assemble the Jacobian
- // matrix $F'_h(U^{n,l})$, where
- // $U^{n,l}$ is stored in the vector
- // <code>solution</code> for
- // convenience.
- system_matrix = 0;
- system_matrix.copy_from (mass_matrix);
- system_matrix.add (std::pow(time_step*theta,2), laplace_matrix);
-
- SparseMatrix<double> tmp_matrix (sparsity_pattern);
- compute_nl_matrix (old_solution, solution, tmp_matrix);
- system_matrix.add (-std::pow(time_step*theta,2), tmp_matrix);
-
- // Then, we compute the right-hand
- // side vector $-F_h(U^{n,l})$.
- system_rhs = 0;
-
- tmp_matrix = 0;
- tmp_matrix.copy_from (mass_matrix);
- tmp_matrix.add (std::pow(time_step*theta,2), laplace_matrix);
-
- Vector<double> tmp_vector (solution.size());
- tmp_matrix.vmult (tmp_vector, solution);
- system_rhs += tmp_vector;
-
- tmp_matrix = 0;
- tmp_matrix.copy_from (mass_matrix);
- tmp_matrix.add (-std::pow(time_step,2)*theta*(1-theta), laplace_matrix);
-
- tmp_vector = 0;
- tmp_matrix.vmult (tmp_vector, old_solution);
- system_rhs -= tmp_vector;
-
- system_rhs.add (-time_step, M_x_velocity);
-
- tmp_vector = 0;
- compute_nl_term (old_solution, solution, tmp_vector);
- system_rhs.add (std::pow(time_step,2)*theta, tmp_vector);
-
- system_rhs *= -1;
-}
-
- // @sect4{SineGordonProblem::compute_nl_term}
-
- // This function computes the vector
- // $S(\cdot,\cdot)$, which appears in the
- // nonlinear term in the both equations of
- // the split formulation. This function not
- // only simplifies the repeated computation
- // of this term, but it is also a fundamental
- // part of the nonlinear iterative solver
- // that we use when the time stepping is
- // implicit (i.e. $\theta\ne 0$). Moreover,
- // we must allow the function to receive as
- // input an "old" and a "new" solution. These
- // may not be the actual solutions of the
- // problem stored in
- // <code>old_solution</code> and
- // <code>solution</code>, but are simply the
- // two functions we linearize about. For the
- // purposes of this function, let us call the
- // first two arguments $w_{\mathrm{old}}$ and
- // $w_{\mathrm{new}}$ in the documentation of
- // this class below, respectively.
- //
- // As a side-note, it is perhaps worth
- // investigating what order quadrature
- // formula is best suited for this type of
- // integration. Since $\sin(\cdot)$ is not a
- // polynomial, there are probably no
- // quadrature formulas that can integrate
- // these terms exactly. It is usually
- // sufficient to just make sure that the
- // right hand side is integrated up to the
- // same order of accuracy as the
- // discretization scheme is, but it may be
- // possible to improve on the constant in the
- // asympotitic statement of convergence by
- // choosing a more accurate quadrature
- // formula.
-template <int dim>
-void SineGordonProblem<dim>::compute_nl_term (const Vector<double> &old_data,
- const Vector<double> &new_data,
- Vector<double> &nl_term) const
-{
- const QGauss<dim> quadrature_formula (3);
- FEValues<dim> fe_values (fe, quadrature_formula,
- update_values |
- update_JxW_values |
- update_quadrature_points);
-
- const unsigned int dofs_per_cell = fe.dofs_per_cell;
- const unsigned int n_q_points = quadrature_formula.size();
-
- Vector<double> local_nl_term (dofs_per_cell);
- std::vector<unsigned int> local_dof_indices (dofs_per_cell);
- std::vector<double> old_data_values (n_q_points);
- std::vector<double> new_data_values (n_q_points);
-
- typename DoFHandler<dim>::active_cell_iterator
- cell = dof_handler.begin_active(),
- endc = dof_handler.end();
-
- for (; cell!=endc; ++cell)
- {
- // Once we re-initialize our
- // <code>FEValues</code> instantiation
- // to the current cell, we make use of
- // the <code>get_function_values</code>
- // routine to get the values of the
- // "old" data (presumably at
- // $t=t_{n-1}$) and the "new" data
- // (presumably at $t=t_n$) at the nodes
- // of the chosen quadrature formula.
- fe_values.reinit (cell);
- fe_values.get_function_values (old_data, old_data_values);
- fe_values.get_function_values (new_data, new_data_values);
-
- // Now, we can evaluate $\int_K
- // \sin\left[\theta w_{\mathrm{new}} +
- // (1-\theta) w_{\mathrm{old}}\right]
- // \,\varphi_j\,\mathrm{d}x$ using the
- // desired quadrature formula.
- for (unsigned int q_point=0; q_point<n_q_points; ++q_point)
- for (unsigned int i=0; i<dofs_per_cell; ++i)
- local_nl_term(i) += (std::sin(theta * new_data_values[q_point] +
- (1-theta) * old_data_values[q_point]) *
- fe_values.shape_value (i, q_point) *
- fe_values.JxW (q_point));
-
- // We conclude by adding up the
- // contributions of the
- // integrals over the cells to
- // the global integral.
- cell->get_dof_indices (local_dof_indices);
-
- for (unsigned int i=0; i<dofs_per_cell; ++i)
- nl_term(local_dof_indices[i]) += local_nl_term(i);
-
- local_nl_term = 0;
- }
-}
-
- // @sect4{SineGordonProblem::compute_nl_matrix}
-
- // This is the second function dealing with the
- // nonlinear scheme. It computes the matrix
- // $N(\cdot,\cdot)$, whicih appears in the
- // nonlinear term in the Jacobian of
- // $F(\cdot)$. Just as
- // <code>compute_nl_term</code>, we must
- // allow this function to receive as input an
- // "old" and a "new" solution, which we again
- // call $w_{\mathrm{old}}$ and
- // $w_{\mathrm{new}}$ below, respectively.
-template <int dim>
-void SineGordonProblem<dim>::compute_nl_matrix (const Vector<double> &old_data,
- const Vector<double> &new_data,
- SparseMatrix<double> &nl_matrix) const
-{
- QGauss<dim> quadrature_formula (3);
- FEValues<dim> fe_values (fe, quadrature_formula,
- update_values | update_JxW_values | update_quadrature_points);
-
- const unsigned int dofs_per_cell = fe.dofs_per_cell;
- const unsigned int n_q_points = quadrature_formula.size();
-
- FullMatrix<double> local_nl_matrix (dofs_per_cell, dofs_per_cell);
- std::vector<unsigned int> local_dof_indices (dofs_per_cell);
- std::vector<double> old_data_values (n_q_points);
- std::vector<double> new_data_values (n_q_points);
-
- typename DoFHandler<dim>::active_cell_iterator
- cell = dof_handler.begin_active(),
- endc = dof_handler.end();
-
- for (; cell!=endc; ++cell)
- {
- // Again, first we
- // re-initialize our
- // <code>FEValues</code>
- // instantiation to the current
- // cell.
- fe_values.reinit (cell);
- fe_values.get_function_values (old_data, old_data_values);
- fe_values.get_function_values (new_data, new_data_values);
-
- // Then, we evaluate $\int_K
- // \cos\left[\theta
- // w_{\mathrm{new}} +
- // (1-\theta)
- // w_{\mathrm{old}}\right]\,
- // \varphi_i\,
- // \varphi_j\,\mathrm{d}x$
- // using the desired quadrature
- // formula.
- 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)
- local_nl_matrix(i,j) += (std::cos(theta * new_data_values[q_point] +
- (1-theta) * old_data_values[q_point]) *
- fe_values.shape_value (i, q_point) *
- fe_values.shape_value (j, q_point) *
- fe_values.JxW (q_point));
-
- // Finally, we add up the
- // contributions of the
- // integrals over the cells to
- // the global integral.
- 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)
- nl_matrix.add(local_dof_indices[i], local_dof_indices[j],
- local_nl_matrix(i,j));
-
- local_nl_matrix = 0;
- }
-}
-
-
-
- // @sect4{SineGordonProblem::solve}
-
- // As discussed in the Introduction, this
- // function uses the CG iterative solver on
- // the linear system of equations resulting
- // from the finite element spatial
- // discretization of each iteration of
- // Newton's method for the (nonlinear) first
- // equation of the split formulation. The
- // solution to the system is, in fact,
- // $\delta U^{n,l}$ so it is stored in
- // <code>solution_update</code> and used to update
- // <code>solution</code> in the
- // <code>run</code> function.
- //
- // Note that we re-set the solution update to
- // zero before solving for it. This is not
- // necessary: iterative solvers can start
- // from any point and converge to the correct
- // solution. If one has a good estimate about
- // the solution of a linear system, it may be
- // worthwhile to start from that vector, but
- // as a general observation it is a fact that
- // the starting point doesn't matter very
- // much: it has to be a very, very good guess
- // to reduce the number of iterations by more
- // than a few. It turns out that for this problem,
- // using the previous nonlinear update as a
- // starting point actually hurts convergence and
- // increases the number of iterations needed,
- // so we simply set it to zero.
- //
- // The function returns the number of
- // iterations it took to converge to a
- // solution. This number will later be used
- // to generate output on the screen showing
- // how many iterations were needed in each
- // nonlinear iteration.
-template <int dim>
-unsigned int
-SineGordonProblem<dim>::solve ()
-{
- SolverControl solver_control (1000, 1e-12*system_rhs.l2_norm());
- SolverCG<> cg (solver_control);
-
- PreconditionSSOR<> preconditioner;
- preconditioner.initialize(system_matrix, 1.2);
-
- solution_update = 0;
- cg.solve (system_matrix, solution_update,
- system_rhs,
- preconditioner);
-
- return solver_control.last_step();
-}
-
- // @sect4{SineGordonProblem::output_results}
- // This function outputs the results to a
- // file. It is pretty much identical to the
- // respective functions in step-23 and
- // step-24:
-template <int dim>
-void
-SineGordonProblem<dim>::output_results (const unsigned int timestep_number) const
-{
- DataOut<dim> data_out;
- data_out.attach_dof_handler (dof_handler);
- data_out.add_data_vector (solution, "u");
- data_out.build_patches ();
+ // @sect4{SineGordonProblem::solve}
- const std::string filename = "solution-" +
- Utilities::int_to_string (timestep_number, 3) +
- ".vtk";
-
- std::ofstream output (filename.c_str());
- data_out.write_vtk (output);
-}
-
- // @sect4{SineGordonProblem::run}
-
- // This function has the top-level
- // control over everything: it runs
- // the (outer) time-stepping loop,
- // the (inner) nonlinear-solver loop,
- // and outputs the solution after each
- // time step.
-template <int dim>
-void SineGordonProblem<dim>::run ()
-{
- make_grid_and_dofs ();
-
- // To aknowledge the initial
- // condition, we must use the
- // function $u_0(x)$ to compute
- // $U^0$. To this end, below we
- // will create an object of type
- // <code>InitialValues</code>; note
- // that when we create this object
- // (which is derived from the
- // <code>Function</code> class), we
- // set its internal time variable
- // to $t_0$, to indicate that the
- // initial condition is a function
- // of space and time evaluated at
- // $t=t_0$.
+ // As discussed in the Introduction, this
+ // function uses the CG iterative solver on
+ // the linear system of equations resulting
+ // from the finite element spatial
+ // discretization of each iteration of
+ // Newton's method for the (nonlinear) first
+ // equation of the split formulation. The
+ // solution to the system is, in fact,
+ // $\delta U^{n,l}$ so it is stored in
+ // <code>solution_update</code> and used to update
+ // <code>solution</code> in the
+ // <code>run</code> function.
+ //
+ // Note that we re-set the solution update to
+ // zero before solving for it. This is not
+ // necessary: iterative solvers can start
+ // from any point and converge to the correct
+ // solution. If one has a good estimate about
+ // the solution of a linear system, it may be
+ // worthwhile to start from that vector, but
+ // as a general observation it is a fact that
+ // the starting point doesn't matter very
+ // much: it has to be a very, very good guess
+ // to reduce the number of iterations by more
+ // than a few. It turns out that for this problem,
+ // using the previous nonlinear update as a
+ // starting point actually hurts convergence and
+ // increases the number of iterations needed,
+ // so we simply set it to zero.
//
- // Then we produce $U^0$ by projecting
- // $u_0(x)$ onto the grid using
- // <code>VectorTools::project</code>. We
- // have to use the same construct using
- // hanging node constraints as in step-21:
- // the VectorTools::project function
- // requires a hanging node constraints
- // object, but to be used we first need to
- // close it:
+ // The function returns the number of
+ // iterations it took to converge to a
+ // solution. This number will later be used
+ // to generate output on the screen showing
+ // how many iterations were needed in each
+ // nonlinear iteration.
+ template <int dim>
+ unsigned int
+ SineGordonProblem<dim>::solve ()
+ {
+ SolverControl solver_control (1000, 1e-12*system_rhs.l2_norm());
+ SolverCG<> cg (solver_control);
+
+ PreconditionSSOR<> preconditioner;
+ preconditioner.initialize(system_matrix, 1.2);
+
+ solution_update = 0;
+ cg.solve (system_matrix, solution_update,
+ system_rhs,
+ preconditioner);
+
+ return solver_control.last_step();
+ }
+
+ // @sect4{SineGordonProblem::output_results}
+
+ // This function outputs the results to a
+ // file. It is pretty much identical to the
+ // respective functions in step-23 and
+ // step-24:
+ template <int dim>
+ void
+ SineGordonProblem<dim>::output_results (const unsigned int timestep_number) const
+ {
+ DataOut<dim> data_out;
+
+ data_out.attach_dof_handler (dof_handler);
+ data_out.add_data_vector (solution, "u");
+ data_out.build_patches ();
+
+ const std::string filename = "solution-" +
+ Utilities::int_to_string (timestep_number, 3) +
+ ".vtk";
+
+ std::ofstream output (filename.c_str());
+ data_out.write_vtk (output);
+ }
+
+ // @sect4{SineGordonProblem::run}
+
+ // This function has the top-level
+ // control over everything: it runs
+ // the (outer) time-stepping loop,
+ // the (inner) nonlinear-solver loop,
+ // and outputs the solution after each
+ // time step.
+ template <int dim>
+ void SineGordonProblem<dim>::run ()
+ {
+ make_grid_and_dofs ();
+
+ // To aknowledge the initial
+ // condition, we must use the
+ // function $u_0(x)$ to compute
+ // $U^0$. To this end, below we
+ // will create an object of type
+ // <code>InitialValues</code>; note
+ // that when we create this object
+ // (which is derived from the
+ // <code>Function</code> class), we
+ // set its internal time variable
+ // to $t_0$, to indicate that the
+ // initial condition is a function
+ // of space and time evaluated at
+ // $t=t_0$.
+ //
+ // Then we produce $U^0$ by projecting
+ // $u_0(x)$ onto the grid using
+ // <code>VectorTools::project</code>. We
+ // have to use the same construct using
+ // hanging node constraints as in step-21:
+ // the VectorTools::project function
+ // requires a hanging node constraints
+ // object, but to be used we first need to
+ // close it:
{
ConstraintMatrix constraints;
constraints.close();
solution);
}
- // For completeness, we output the
- // zeroth time step to a file just
- // like any other other time step.
- output_results (0);
-
- // Now we perform the time
- // stepping: at every time step we
- // solve the matrix equation(s)
- // corresponding to the finite
- // element discretization of the
- // problem, and then advance our
- // solution according to the time
- // stepping formulas we discussed
- // in the Introduction.
- unsigned int timestep_number = 1;
- for (time+=time_step; time<=final_time; time+=time_step, ++timestep_number)
- {
- old_solution = solution;
-
- std::cout << std::endl
- << "Time step #" << timestep_number << "; "
- << "advancing to t = " << time << "."
- << std::endl;
-
- // At the beginning of each
- // time step we must solve the
- // nonlinear equation in the
- // split formulation via
- // Newton's method ---
- // i.e. solve for $\delta
- // U^{n,l}$ then compute
- // $U^{n,l+1}$ and so on. The
- // stopping criterion for this
- // nonlinear iteration is that
- // $\|F_h(U^{n,l})\|_2 \le
- // 10^{-6}
- // \|F_h(U^{n,0})\|_2$. Consequently,
- // we need to record the norm
- // of the residual in the first
- // iteration.
- //
- // At the end of each iteration, we
- // output to the console how many
- // linear solver iterations it took
- // us. When the loop below is done, we
- // have (an approximation of) $U^n$.
- double initial_rhs_norm = 0.;
- bool first_iteration = true;
- do
- {
- assemble_system ();
-
- if (first_iteration == true)
- initial_rhs_norm = system_rhs.l2_norm();
-
- const unsigned int n_iterations
- = solve ();
-
- solution += solution_update;
-
- if (first_iteration == true)
- std::cout << " " << n_iterations;
- else
- std::cout << '+' << n_iterations;
- first_iteration = false;
- }
- while (system_rhs.l2_norm() > 1e-6 * initial_rhs_norm);
-
- std::cout << " CG iterations per nonlinear step."
- << std::endl;
-
- // Upon obtaining the solution to the
- // first equation of the problem at
- // $t=t_n$, we must update the
- // auxiliary velocity variable
- // $V^n$. However, we do not compute
- // and store $V^n$ since it is not a
- // quantity we use directly in the
- // problem. Hence, for simplicity, we
- // update $MV^n$ directly:
- Vector<double> tmp_vector (solution.size());
- laplace_matrix.vmult (tmp_vector, solution);
- M_x_velocity.add (-time_step*theta, tmp_vector);
-
- tmp_vector = 0;
- laplace_matrix.vmult (tmp_vector, old_solution);
- M_x_velocity.add (-time_step*(1-theta), tmp_vector);
-
- tmp_vector = 0;
- compute_nl_term (old_solution, solution, tmp_vector);
- M_x_velocity.add (-time_step, tmp_vector);
-
- // Oftentimes, in particular
- // for fine meshes, we must
- // pick the time step to be
- // quite small in order for the
- // scheme to be
- // stable. Therefore, there are
- // a lot of time steps during
- // which "nothing interesting
- // happens" in the solution. To
- // improve overall efficiency
- // -- in particular, speed up
- // the program and save disk
- // space -- we only output the
- // solution every
- // <code>output_timestep_skip</code>
- // time steps:
- if (timestep_number % output_timestep_skip == 0)
- output_results (timestep_number);
- }
+ // For completeness, we output the
+ // zeroth time step to a file just
+ // like any other other time step.
+ output_results (0);
+
+ // Now we perform the time
+ // stepping: at every time step we
+ // solve the matrix equation(s)
+ // corresponding to the finite
+ // element discretization of the
+ // problem, and then advance our
+ // solution according to the time
+ // stepping formulas we discussed
+ // in the Introduction.
+ unsigned int timestep_number = 1;
+ for (time+=time_step; time<=final_time; time+=time_step, ++timestep_number)
+ {
+ old_solution = solution;
+
+ std::cout << std::endl
+ << "Time step #" << timestep_number << "; "
+ << "advancing to t = " << time << "."
+ << std::endl;
+
+ // At the beginning of each
+ // time step we must solve the
+ // nonlinear equation in the
+ // split formulation via
+ // Newton's method ---
+ // i.e. solve for $\delta
+ // U^{n,l}$ then compute
+ // $U^{n,l+1}$ and so on. The
+ // stopping criterion for this
+ // nonlinear iteration is that
+ // $\|F_h(U^{n,l})\|_2 \le
+ // 10^{-6}
+ // \|F_h(U^{n,0})\|_2$. Consequently,
+ // we need to record the norm
+ // of the residual in the first
+ // iteration.
+ //
+ // At the end of each iteration, we
+ // output to the console how many
+ // linear solver iterations it took
+ // us. When the loop below is done, we
+ // have (an approximation of) $U^n$.
+ double initial_rhs_norm = 0.;
+ bool first_iteration = true;
+ do
+ {
+ assemble_system ();
+
+ if (first_iteration == true)
+ initial_rhs_norm = system_rhs.l2_norm();
+
+ const unsigned int n_iterations
+ = solve ();
+
+ solution += solution_update;
+
+ if (first_iteration == true)
+ std::cout << " " << n_iterations;
+ else
+ std::cout << '+' << n_iterations;
+ first_iteration = false;
+ }
+ while (system_rhs.l2_norm() > 1e-6 * initial_rhs_norm);
+
+ std::cout << " CG iterations per nonlinear step."
+ << std::endl;
+
+ // Upon obtaining the solution to the
+ // first equation of the problem at
+ // $t=t_n$, we must update the
+ // auxiliary velocity variable
+ // $V^n$. However, we do not compute
+ // and store $V^n$ since it is not a
+ // quantity we use directly in the
+ // problem. Hence, for simplicity, we
+ // update $MV^n$ directly:
+ Vector<double> tmp_vector (solution.size());
+ laplace_matrix.vmult (tmp_vector, solution);
+ M_x_velocity.add (-time_step*theta, tmp_vector);
+
+ tmp_vector = 0;
+ laplace_matrix.vmult (tmp_vector, old_solution);
+ M_x_velocity.add (-time_step*(1-theta), tmp_vector);
+
+ tmp_vector = 0;
+ compute_nl_term (old_solution, solution, tmp_vector);
+ M_x_velocity.add (-time_step, tmp_vector);
+
+ // Oftentimes, in particular
+ // for fine meshes, we must
+ // pick the time step to be
+ // quite small in order for the
+ // scheme to be
+ // stable. Therefore, there are
+ // a lot of time steps during
+ // which "nothing interesting
+ // happens" in the solution. To
+ // improve overall efficiency
+ // -- in particular, speed up
+ // the program and save disk
+ // space -- we only output the
+ // solution every
+ // <code>output_timestep_skip</code>
+ // time steps:
+ if (timestep_number % output_timestep_skip == 0)
+ output_results (timestep_number);
+ }
+ }
}
// @sect3{The <code>main</code> function}
// here. For more information about
// exceptions the reader should
// consult step-6.
-int main ()
+int main ()
{
try
{
+ using namespace dealii;
+ using namespace Step25;
+
deallog.depth_console (0);
SineGordonProblem<1> sg_problem;
<< "Aborting!" << std::endl
<< "----------------------------------------------------"
<< std::endl;
-
+
return 1;
}
- catch (...)
+ catch (...)
{
std::cerr << std::endl << std::endl
<< "----------------------------------------------------"
<< std::endl;
return 1;
}
-
+
return 0;
}
// The last step is as in all
// previous programs:
-using namespace dealii;
-
-class PointCloudSurface : public StraightBoundary<3>
-{
- public:
- /**
- * Constructor.
- */
- PointCloudSurface (const std::string &filename);
-
- /**
- * Let the new point be the
- * arithmetic mean of the two
- * vertices of the line.
- *
- * Refer to the general
- * documentation of this class
- * and the documentation of the
- * base class for more
- * information.
- */
- virtual Point<3>
- get_new_point_on_line (const Triangulation<3>::line_iterator &line) const;
-
- /**
- * Let the new point be the
- * arithmetic mean of the four
- * vertices of this quad and the
- * four midpoints of the lines,
- * which are already created at
- * the time of calling this
- * function.
- *
- * Refer to the general
- * documentation of this class
- * and the documentation of the
- * base class for more
- * information.
- */
- virtual Point<3>
- get_new_point_on_quad (const Triangulation<3>::quad_iterator &quad) const;
-
- /**
- * Gives <tt>n=points.size()</tt>
- * points that splits the
- * StraightBoundary line into
- * $n+1$ partitions of equal
- * lengths.
- *
- * Refer to the general
- * documentation of this class
- * and the documentation of the
- * base class.
- */
- virtual void
- get_intermediate_points_on_line (const Triangulation<3>::line_iterator &line,
- std::vector<Point<3> > &points) const;
-
- /**
- * Gives <tt>n=points.size()=m*m</tt>
- * points that splits the
- * p{StraightBoundary} quad into
- * <tt>(m+1)(m+1)</tt> subquads of equal
- * size.
- *
- * Refer to the general
- * documentation of this class
- * and the documentation of the
- * base class.
- */
- virtual void
- get_intermediate_points_on_quad (const Triangulation<3>::quad_iterator &quad,
- std::vector<Point<3> > &points) const;
-
- /**
- * A function that, given a point
- * <code>p</code>, returns the closest
- * point on the surface defined by the
- * input file. For the time being, we
- * simply return the closest point in the
- * point cloud, rather than doing any
- * sort of interpolation.
- */
- Point<3> closest_point (const Point<3> &p) const;
- private:
- std::vector<Point<3> > point_list;
-};
-
-
-PointCloudSurface::PointCloudSurface (const std::string &filename)
+namespace Step26
{
- // first read in all the points
+ using namespace dealii;
+
+ class PointCloudSurface : public StraightBoundary<3>
+ {
+ public:
+ /**
+ * Constructor.
+ */
+ PointCloudSurface (const std::string &filename);
+
+ /**
+ * Let the new point be the
+ * arithmetic mean of the two
+ * vertices of the line.
+ *
+ * Refer to the general
+ * documentation of this class
+ * and the documentation of the
+ * base class for more
+ * information.
+ */
+ virtual Point<3>
+ get_new_point_on_line (const Triangulation<3>::line_iterator &line) const;
+
+ /**
+ * Let the new point be the
+ * arithmetic mean of the four
+ * vertices of this quad and the
+ * four midpoints of the lines,
+ * which are already created at
+ * the time of calling this
+ * function.
+ *
+ * Refer to the general
+ * documentation of this class
+ * and the documentation of the
+ * base class for more
+ * information.
+ */
+ virtual Point<3>
+ get_new_point_on_quad (const Triangulation<3>::quad_iterator &quad) const;
+
+ /**
+ * Gives <tt>n=points.size()</tt>
+ * points that splits the
+ * StraightBoundary line into
+ * $n+1$ partitions of equal
+ * lengths.
+ *
+ * Refer to the general
+ * documentation of this class
+ * and the documentation of the
+ * base class.
+ */
+ virtual void
+ get_intermediate_points_on_line (const Triangulation<3>::line_iterator &line,
+ std::vector<Point<3> > &points) const;
+
+ /**
+ * Gives <tt>n=points.size()=m*m</tt>
+ * points that splits the
+ * p{StraightBoundary} quad into
+ * <tt>(m+1)(m+1)</tt> subquads of equal
+ * size.
+ *
+ * Refer to the general
+ * documentation of this class
+ * and the documentation of the
+ * base class.
+ */
+ virtual void
+ get_intermediate_points_on_quad (const Triangulation<3>::quad_iterator &quad,
+ std::vector<Point<3> > &points) const;
+
+ /**
+ * A function that, given a point
+ * <code>p</code>, returns the closest
+ * point on the surface defined by the
+ * input file. For the time being, we
+ * simply return the closest point in the
+ * point cloud, rather than doing any
+ * sort of interpolation.
+ */
+ Point<3> closest_point (const Point<3> &p) const;
+ private:
+ std::vector<Point<3> > point_list;
+ };
+
+
+ PointCloudSurface::PointCloudSurface (const std::string &filename)
{
- std::ifstream in (filename.c_str());
- AssertThrow (in, ExcIO());
+ // first read in all the points
+ {
+ std::ifstream in (filename.c_str());
+ AssertThrow (in, ExcIO());
- while (in)
- {
- Point<3> p;
- in >> p;
- point_list.push_back (p);
- }
+ while (in)
+ {
+ Point<3> p;
+ in >> p;
+ point_list.push_back (p);
+ }
- AssertThrow (point_list.size() > 1, ExcIO());
- }
+ AssertThrow (point_list.size() > 1, ExcIO());
+ }
- // next fit a linear model through the data
- // cloud to rectify it in a local
- // coordinate system
- //
- // the first step is to move the center of
- // mass of the points to the origin
- {
- const Point<3> c_o_m = std::accumulate (point_list.begin(),
- point_list.end(),
- Point<3>()) /
+ // next fit a linear model through the data
+ // cloud to rectify it in a local
+ // coordinate system
+ //
+ // the first step is to move the center of
+ // mass of the points to the origin
+ {
+ const Point<3> c_o_m = std::accumulate (point_list.begin(),
+ point_list.end(),
+ Point<3>()) /
point_list.size();
- for (unsigned int i=0; i<point_list.size(); ++i)
- point_list[i] -= c_o_m;
- }
-
- // next do a least squares fit to the
- // function ax+by. this leads to the
- // following equations:
+ for (unsigned int i=0; i<point_list.size(); ++i)
+ point_list[i] -= c_o_m;
+ }
- // min f(a,b) = sum_i (zi-a xi - b yi)^2 / 2
- //
- // f_a = sum_i (zi - a xi - b yi) xi = 0
- // f_b = sum_i (zi - a xi - b yi) yi = 0
- //
- // f_a = (sum_i zi xi) - (sum xi^2) a - (sum xi yi) b = 0
- // f_a = (sum_i zi yi) - (sum xi yi) a - (sum yi^2) b = 0
- {
- double A[2][2] = {{0,0},{0,0}};
- double B[2] = {0,0};
+ // next do a least squares fit to the
+ // function ax+by. this leads to the
+ // following equations:
+
+ // min f(a,b) = sum_i (zi-a xi - b yi)^2 / 2
+ //
+ // f_a = sum_i (zi - a xi - b yi) xi = 0
+ // f_b = sum_i (zi - a xi - b yi) yi = 0
+ //
+ // f_a = (sum_i zi xi) - (sum xi^2) a - (sum xi yi) b = 0
+ // f_a = (sum_i zi yi) - (sum xi yi) a - (sum yi^2) b = 0
+ {
+ double A[2][2] = {{0,0},{0,0}};
+ double B[2] = {0,0};
- for (unsigned int i=0; i<point_list.size(); ++i)
- {
- A[0][0] += point_list[i][0] * point_list[i][0];
- A[0][1] += point_list[i][0] * point_list[i][1];
- A[1][1] += point_list[i][1] * point_list[i][1];
+ for (unsigned int i=0; i<point_list.size(); ++i)
+ {
+ A[0][0] += point_list[i][0] * point_list[i][0];
+ A[0][1] += point_list[i][0] * point_list[i][1];
+ A[1][1] += point_list[i][1] * point_list[i][1];
- B[0] += point_list[i][0] * point_list[i][2];
- B[1] += point_list[i][1] * point_list[i][2];
- }
+ B[0] += point_list[i][0] * point_list[i][2];
+ B[1] += point_list[i][1] * point_list[i][2];
+ }
- const double det = A[0][0]*A[1][1]-2*A[0][1];
- const double a = (A[1][1] * B[0] - A[0][1] * B[1]) / det;
- const double b = (A[0][0] * B[1] - A[0][1] * B[0]) / det;
+ const double det = A[0][0]*A[1][1]-2*A[0][1];
+ const double a = (A[1][1] * B[0] - A[0][1] * B[1]) / det;
+ const double b = (A[0][0] * B[1] - A[0][1] * B[0]) / det;
- // with this information, we can rotate
- // the points so that the corresponding
- // least-squares fit would be the x-y
- // plane
- const Point<2> gradient_direction
- = Point<2>(a,b) / std::sqrt(a*a+b*b);
- const Point<2> orthogonal_direction
- = Point<2>(-b,a) / std::sqrt(a*a+b*b);
+ // with this information, we can rotate
+ // the points so that the corresponding
+ // least-squares fit would be the x-y
+ // plane
+ const Point<2> gradient_direction
+ = Point<2>(a,b) / std::sqrt(a*a+b*b);
+ const Point<2> orthogonal_direction
+ = Point<2>(-b,a) / std::sqrt(a*a+b*b);
- const double stretch_factor = std::sqrt(1.+a*a+b*b);
+ const double stretch_factor = std::sqrt(1.+a*a+b*b);
- for (unsigned int i=0; i<point_list.size(); ++i)
- {
- // we can do that by, for each point,
- // first subtract the points in the
- // plane:
- point_list[i][2] -= a*point_list[i][0] + b*point_list[i][1];
-
- // we made a mistake here, though:
- // we've shrunk the plan in the
- // direction parallel to the
- // gradient. we will have to correct
- // for this:
- const Point<2> xy (point_list[i][0],
- point_list[i][1]);
- const double grad_distance = xy * gradient_direction;
- const double orth_distance = xy * orthogonal_direction;
-
- // we then have to stretch the points
- // in the gradient direction. the
- // stretch factor is defined above
- // (zero if the original plane was
- // already the xy plane, infinity if
- // it was vertical)
- const Point<2> new_xy
- = (grad_distance * stretch_factor * gradient_direction +
- orth_distance * orthogonal_direction);
- point_list[i][0] = new_xy[0];
- point_list[i][1] = new_xy[1];
- }
+ for (unsigned int i=0; i<point_list.size(); ++i)
+ {
+ // we can do that by, for each point,
+ // first subtract the points in the
+ // plane:
+ point_list[i][2] -= a*point_list[i][0] + b*point_list[i][1];
+
+ // we made a mistake here, though:
+ // we've shrunk the plan in the
+ // direction parallel to the
+ // gradient. we will have to correct
+ // for this:
+ const Point<2> xy (point_list[i][0],
+ point_list[i][1]);
+ const double grad_distance = xy * gradient_direction;
+ const double orth_distance = xy * orthogonal_direction;
+
+ // we then have to stretch the points
+ // in the gradient direction. the
+ // stretch factor is defined above
+ // (zero if the original plane was
+ // already the xy plane, infinity if
+ // it was vertical)
+ const Point<2> new_xy
+ = (grad_distance * stretch_factor * gradient_direction +
+ orth_distance * orthogonal_direction);
+ point_list[i][0] = new_xy[0];
+ point_list[i][1] = new_xy[1];
+ }
+ }
}
-}
-Point<3>
-PointCloudSurface::closest_point (const Point<3> &p) const
-{
- double distance = p.distance (point_list[0]);
- Point<3> point = point_list[0];
+ Point<3>
+ PointCloudSurface::closest_point (const Point<3> &p) const
+ {
+ double distance = p.distance (point_list[0]);
+ Point<3> point = point_list[0];
- for (std::vector<Point<3> >::const_iterator i=point_list.begin();
- i != point_list.end(); ++i)
- {
- const double d = p.distance (*i);
- if (d < distance)
- {
- distance = d;
- point = *i;
- }
- }
+ for (std::vector<Point<3> >::const_iterator i=point_list.begin();
+ i != point_list.end(); ++i)
+ {
+ const double d = p.distance (*i);
+ if (d < distance)
+ {
+ distance = d;
+ point = *i;
+ }
+ }
- return point;
-}
+ return point;
+ }
-Point<3>
-PointCloudSurface::
-get_new_point_on_line (const Triangulation<3>::line_iterator &line) const
-{
- return closest_point (StraightBoundary<3>::get_new_point_on_line (line));
-}
+ Point<3>
+ PointCloudSurface::
+ get_new_point_on_line (const Triangulation<3>::line_iterator &line) const
+ {
+ return closest_point (StraightBoundary<3>::get_new_point_on_line (line));
+ }
-Point<3>
-PointCloudSurface::
-get_new_point_on_quad (const Triangulation<3>::quad_iterator &quad) const
-{
- return closest_point (StraightBoundary<3>::get_new_point_on_quad (quad));
-}
+ Point<3>
+ PointCloudSurface::
+ get_new_point_on_quad (const Triangulation<3>::quad_iterator &quad) const
+ {
+ return closest_point (StraightBoundary<3>::get_new_point_on_quad (quad));
+ }
-void
-PointCloudSurface::
-get_intermediate_points_on_line (const Triangulation<3>::line_iterator &line,
- std::vector<Point<3> > &points) const
-{
- StraightBoundary<3>::get_intermediate_points_on_line (line,
- points);
- for (unsigned int i=0; i<points.size(); ++i)
- points[i] = closest_point(points[i]);
-}
+ void
+ PointCloudSurface::
+ get_intermediate_points_on_line (const Triangulation<3>::line_iterator &line,
+ std::vector<Point<3> > &points) const
+ {
+ StraightBoundary<3>::get_intermediate_points_on_line (line,
+ points);
+ for (unsigned int i=0; i<points.size(); ++i)
+ points[i] = closest_point(points[i]);
+ }
-void
-PointCloudSurface::
-get_intermediate_points_on_quad (const Triangulation<3>::quad_iterator &quad,
- std::vector<Point<3> > &points) const
-{
- StraightBoundary<3>::get_intermediate_points_on_quad (quad,
- points);
- for (unsigned int i=0; i<points.size(); ++i)
- points[i] = closest_point(points[i]);
-}
+ void
+ PointCloudSurface::
+ get_intermediate_points_on_quad (const Triangulation<3>::quad_iterator &quad,
+ std::vector<Point<3> > &points) const
+ {
+ StraightBoundary<3>::get_intermediate_points_on_quad (quad,
+ points);
+ for (unsigned int i=0; i<points.size(); ++i)
+ points[i] = closest_point(points[i]);
+ }
-PointCloudSurface pds("surface-points");
+ PointCloudSurface pds("surface-points");
- // @sect3{The <code>LaplaceProblem</code> class template}
+ // @sect3{The <code>LaplaceProblem</code> class template}
- // This is again the same
- // <code>LaplaceProblem</code> class as in the
- // previous example. The only
- // difference is that we have now
- // declared it as a class with a
- // template parameter, and the
- // template parameter is of course
- // the spatial dimension in which we
- // would like to solve the Laplace
- // equation. Of course, several of
- // the member variables depend on
- // this dimension as well, in
- // particular the Triangulation
- // class, which has to represent
- // quadrilaterals or hexahedra,
- // respectively. Apart from this,
- // everything is as before.
-template <int dim>
-class LaplaceProblem
-{
- public:
- LaplaceProblem ();
- void run ();
+ // This is again the same
+ // <code>LaplaceProblem</code> class as in the
+ // previous example. The only
+ // difference is that we have now
+ // declared it as a class with a
+ // template parameter, and the
+ // template parameter is of course
+ // the spatial dimension in which we
+ // would like to solve the Laplace
+ // equation. Of course, several of
+ // the member variables depend on
+ // this dimension as well, in
+ // particular the Triangulation
+ // class, which has to represent
+ // quadrilaterals or hexahedra,
+ // respectively. Apart from this,
+ // everything is as before.
+ template <int dim>
+ class LaplaceProblem
+ {
+ public:
+ LaplaceProblem ();
+ void run ();
- private:
- void make_grid_and_dofs ();
- void assemble_system ();
- void solve ();
- void output_results () const;
+ 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;
+ Triangulation<dim> triangulation;
+ FE_Q<dim> fe;
+ DoFHandler<dim> dof_handler;
- SparsityPattern sparsity_pattern;
- SparseMatrix<double> system_matrix;
+ SparsityPattern sparsity_pattern;
+ SparseMatrix<double> system_matrix;
- Vector<double> solution;
- Vector<double> system_rhs;
-};
+ Vector<double> solution;
+ Vector<double> system_rhs;
+ };
- // @sect3{Right hand side and boundary values}
+ // @sect3{Right hand side and boundary values}
-template <int dim>
-class BoundaryValues : public Function<dim>
-{
- public:
- BoundaryValues () : Function<dim>() {}
+ template <int dim>
+ class BoundaryValues : public Function<dim>
+ {
+ public:
+ BoundaryValues () : Function<dim>() {}
- 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;
+ };
-template <int dim>
-double BoundaryValues<dim>::value (const Point<dim> &p,
- const unsigned int /*component*/) const
-{
- return std::max(p[dim-1], -5.);
-}
+ template <int dim>
+ double BoundaryValues<dim>::value (const Point<dim> &p,
+ const unsigned int /*component*/) const
+ {
+ return std::max(p[dim-1], -5.);
+ }
- // @sect3{Implementation of the <code>LaplaceProblem</code> class}
-
- // Next for the implementation of the class
- // template that makes use of the functions
- // above. As before, we will write everything
- // as templates that have a formal parameter
- // <code>dim</code> that we assume unknown at the time
- // we define the template functions. Only
- // later, the compiler will find a
- // declaration of <code>LaplaceProblem@<2@></code> (in
- // the <code>main</code> function, actually) and
- // compile the entire class with <code>dim</code>
- // replaced by 2, a process referred to as
- // `instantiation of a template'. When doing
- // so, it will also replace instances of
- // <code>RightHandSide@<dim@></code> by
- // <code>RightHandSide@<2@></code> and instantiate the
- // latter class from the class template.
- //
- // In fact, the compiler will also find a
- // declaration <code>LaplaceProblem@<3@></code> in
- // <code>main()</code>. This will cause it to again go
- // back to the general
- // <code>LaplaceProblem@<dim@></code> template, replace
- // all occurrences of <code>dim</code>, this time by
- // 3, and compile the class a second
- // time. Note that the two instantiations
- // <code>LaplaceProblem@<2@></code> and
- // <code>LaplaceProblem@<3@></code> are completely
- // independent classes; their only common
- // feature is that they are both instantiated
- // from the same general template, but they
- // are not convertible into each other, for
- // example, and share no code (both
- // instantiations are compiled completely
- // independently).
-
-
- // @sect4{LaplaceProblem::LaplaceProblem}
-
- // After this introduction, here is the
- // constructor of the <code>LaplaceProblem</code>
- // class. It specifies the desired polynomial
- // degree of the finite elements and
- // associates the DoFHandler to the
- // triangulation just as in the previous
- // example program, step-3:
-template <int dim>
-LaplaceProblem<dim>::LaplaceProblem () :
- fe (1),
- dof_handler (triangulation)
-{}
-
-
- // @sect4{LaplaceProblem::make_grid_and_dofs}
-
- // Grid creation is something
- // inherently dimension
- // dependent. However, as long as the
- // domains are sufficiently similar
- // in 2D or 3D, the library can
- // abstract for you. In our case, we
- // would like to again solve on the
- // square [-1,1]x[-1,1] in 2D, or on
- // the cube [-1,1]x[-1,1]x[-1,1] in
- // 3D; both can be termed
- // <code>hyper_cube</code>, so we may use the
- // same function in whatever
- // dimension we are. Of course, the
- // functions that create a hypercube
- // in two and three dimensions are
- // very much different, but that is
- // something you need not care
- // about. Let the library handle the
- // difficult things.
- //
- // Likewise, associating a degree of freedom
- // with each vertex is something which
- // certainly looks different in 2D and 3D,
- // but that does not need to bother you
- // either. This function therefore looks
- // exactly like in the previous example,
- // although it performs actions that in their
- // details are quite different if <code>dim</code>
- // happens to be 3. The only significant
- // difference from a user's perspective is
- // the number of cells resulting, which is
- // much higher in three than in two space
- // dimensions!
-template <int dim>
-void LaplaceProblem<dim>::make_grid_and_dofs ()
-{
- GridGenerator::hyper_cube (triangulation, -30, 30);
+ // @sect3{Implementation of the <code>LaplaceProblem</code> class}
+
+ // Next for the implementation of the class
+ // template that makes use of the functions
+ // above. As before, we will write everything
+ // as templates that have a formal parameter
+ // <code>dim</code> that we assume unknown at the time
+ // we define the template functions. Only
+ // later, the compiler will find a
+ // declaration of <code>LaplaceProblem@<2@></code> (in
+ // the <code>main</code> function, actually) and
+ // compile the entire class with <code>dim</code>
+ // replaced by 2, a process referred to as
+ // `instantiation of a template'. When doing
+ // so, it will also replace instances of
+ // <code>RightHandSide@<dim@></code> by
+ // <code>RightHandSide@<2@></code> and instantiate the
+ // latter class from the class template.
+ //
+ // In fact, the compiler will also find a
+ // declaration <code>LaplaceProblem@<3@></code> in
+ // <code>main()</code>. This will cause it to again go
+ // back to the general
+ // <code>LaplaceProblem@<dim@></code> template, replace
+ // all occurrences of <code>dim</code>, this time by
+ // 3, and compile the class a second
+ // time. Note that the two instantiations
+ // <code>LaplaceProblem@<2@></code> and
+ // <code>LaplaceProblem@<3@></code> are completely
+ // independent classes; their only common
+ // feature is that they are both instantiated
+ // from the same general template, but they
+ // are not convertible into each other, for
+ // example, and share no code (both
+ // instantiations are compiled completely
+ // independently).
+
+
+ // @sect4{LaplaceProblem::LaplaceProblem}
+
+ // After this introduction, here is the
+ // constructor of the <code>LaplaceProblem</code>
+ // class. It specifies the desired polynomial
+ // degree of the finite elements and
+ // associates the DoFHandler to the
+ // triangulation just as in the previous
+ // example program, step-3:
+ template <int dim>
+ LaplaceProblem<dim>::LaplaceProblem () :
+ fe (1),
+ dof_handler (triangulation)
+ {}
+
+
+ // @sect4{LaplaceProblem::make_grid_and_dofs}
+
+ // Grid creation is something
+ // inherently dimension
+ // dependent. However, as long as the
+ // domains are sufficiently similar
+ // in 2D or 3D, the library can
+ // abstract for you. In our case, we
+ // would like to again solve on the
+ // square [-1,1]x[-1,1] in 2D, or on
+ // the cube [-1,1]x[-1,1]x[-1,1] in
+ // 3D; both can be termed
+ // <code>hyper_cube</code>, so we may use the
+ // same function in whatever
+ // dimension we are. Of course, the
+ // functions that create a hypercube
+ // in two and three dimensions are
+ // very much different, but that is
+ // something you need not care
+ // about. Let the library handle the
+ // difficult things.
+ //
+ // Likewise, associating a degree of freedom
+ // with each vertex is something which
+ // certainly looks different in 2D and 3D,
+ // but that does not need to bother you
+ // either. This function therefore looks
+ // exactly like in the previous example,
+ // although it performs actions that in their
+ // details are quite different if <code>dim</code>
+ // happens to be 3. The only significant
+ // difference from a user's perspective is
+ // the number of cells resulting, which is
+ // much higher in three than in two space
+ // dimensions!
+ template <int dim>
+ void LaplaceProblem<dim>::make_grid_and_dofs ()
+ {
+ GridGenerator::hyper_cube (triangulation, -30, 30);
- for (unsigned int f=0; f<GeometryInfo<dim>::faces_per_cell; ++f)
- if (triangulation.begin()->face(f)->center()[2] > 15)
- {
- triangulation.begin()->face(f)->set_boundary_indicator (1);
- for (unsigned int i=0; i<GeometryInfo<dim>::lines_per_face; ++i)
- triangulation.begin()->face(f)->line(i)->set_boundary_indicator (1);
- break;
- }
- triangulation.set_boundary (1, pds);
+ for (unsigned int f=0; f<GeometryInfo<dim>::faces_per_cell; ++f)
+ if (triangulation.begin()->face(f)->center()[2] > 15)
+ {
+ triangulation.begin()->face(f)->set_boundary_indicator (1);
+ for (unsigned int i=0; i<GeometryInfo<dim>::lines_per_face; ++i)
+ triangulation.begin()->face(f)->line(i)->set_boundary_indicator (1);
+ break;
+ }
+ triangulation.set_boundary (1, pds);
- for (unsigned int v=0; v<GeometryInfo<dim>::vertices_per_cell; ++v)
- if (triangulation.begin()->vertex(v)[2] > 0)
- triangulation.begin()->vertex(v)
- = pds.closest_point (Point<3>(triangulation.begin()->vertex(v)[0],
- triangulation.begin()->vertex(v)[1],
- 0));
+ for (unsigned int v=0; v<GeometryInfo<dim>::vertices_per_cell; ++v)
+ if (triangulation.begin()->vertex(v)[2] > 0)
+ triangulation.begin()->vertex(v)
+ = pds.closest_point (Point<3>(triangulation.begin()->vertex(v)[0],
+ triangulation.begin()->vertex(v)[1],
+ 0));
- for (unsigned int i=0; i<4; ++i)
- {
- for (typename Triangulation<dim>::active_cell_iterator
- cell = triangulation.begin_active();
- cell != triangulation.end(); ++cell)
- for (unsigned int f=0; f<GeometryInfo<dim>::faces_per_cell; ++f)
- if (cell->face(f)->boundary_indicator() == 1)
- cell->set_refine_flag ();
-
- triangulation.execute_coarsening_and_refinement ();
-
- std::cout << "Refinement cycle " << i << std::endl
- << " Number of active cells: "
- << triangulation.n_active_cells()
- << std::endl
- << " Total number of cells: "
- << triangulation.n_cells()
- << std::endl;
+ for (unsigned int i=0; i<4; ++i)
+ {
+ for (typename Triangulation<dim>::active_cell_iterator
+ cell = triangulation.begin_active();
+ cell != triangulation.end(); ++cell)
+ for (unsigned int f=0; f<GeometryInfo<dim>::faces_per_cell; ++f)
+ if (cell->face(f)->boundary_indicator() == 1)
+ cell->set_refine_flag ();
+
+ triangulation.execute_coarsening_and_refinement ();
+
+ std::cout << "Refinement cycle " << i << std::endl
+ << " Number of active cells: "
+ << triangulation.n_active_cells()
+ << std::endl
+ << " Total number of cells: "
+ << triangulation.n_cells()
+ << std::endl;
- }
+ }
- dof_handler.distribute_dofs (fe);
+ dof_handler.distribute_dofs (fe);
- std::cout << " Number of degrees of freedom: "
- << dof_handler.n_dofs()
- << std::endl;
+ std::cout << " Number of degrees of freedom: "
+ << dof_handler.n_dofs()
+ << std::endl;
- sparsity_pattern.reinit (dof_handler.n_dofs(),
- dof_handler.n_dofs(),
- dof_handler.max_couplings_between_dofs());
- DoFTools::make_sparsity_pattern (dof_handler, sparsity_pattern);
- sparsity_pattern.compress();
+ sparsity_pattern.reinit (dof_handler.n_dofs(),
+ dof_handler.n_dofs(),
+ dof_handler.max_couplings_between_dofs());
+ DoFTools::make_sparsity_pattern (dof_handler, sparsity_pattern);
+ sparsity_pattern.compress();
- system_matrix.reinit (sparsity_pattern);
+ system_matrix.reinit (sparsity_pattern);
- solution.reinit (dof_handler.n_dofs());
- system_rhs.reinit (dof_handler.n_dofs());
-}
+ solution.reinit (dof_handler.n_dofs());
+ system_rhs.reinit (dof_handler.n_dofs());
+ }
- // @sect4{LaplaceProblem::assemble_system}
+ // @sect4{LaplaceProblem::assemble_system}
- // Unlike in the previous example, we
- // would now like to use a
- // non-constant right hand side
- // function and non-zero boundary
- // values. Both are tasks that are
- // readily achieved with a only a few
- // new lines of code in the
- // assemblage of the matrix and right
- // hand side.
- //
- // More interesting, though, is the
- // way we assemble matrix and right
- // hand side vector dimension
- // independently: there is simply no
- // difference to the
- // two-dimensional case. Since the
- // important objects used in this
- // function (quadrature formula,
- // FEValues) depend on the dimension
- // by way of a template parameter as
- // well, they can take care of
- // setting up properly everything for
- // the dimension for which this
- // function is compiled. By declaring
- // all classes which might depend on
- // the dimension using a template
- // parameter, the library can make
- // nearly all work for you and you
- // don't have to care about most
- // things.
-template <int dim>
-void LaplaceProblem<dim>::assemble_system ()
-{
- MatrixTools::create_laplace_matrix (dof_handler,
- QGauss<dim>(2),
- system_matrix);
- system_rhs = 0;
-
- std::map<unsigned int,double> boundary_values;
- VectorTools::interpolate_boundary_values (dof_handler,
- 0,
- BoundaryValues<dim>(),
- boundary_values);
- MatrixTools::apply_boundary_values (boundary_values,
- system_matrix,
- solution,
- system_rhs);
-}
+ // Unlike in the previous example, we
+ // would now like to use a
+ // non-constant right hand side
+ // function and non-zero boundary
+ // values. Both are tasks that are
+ // readily achieved with a only a few
+ // new lines of code in the
+ // assemblage of the matrix and right
+ // hand side.
+ //
+ // More interesting, though, is the
+ // way we assemble matrix and right
+ // hand side vector dimension
+ // independently: there is simply no
+ // difference to the
+ // two-dimensional case. Since the
+ // important objects used in this
+ // function (quadrature formula,
+ // FEValues) depend on the dimension
+ // by way of a template parameter as
+ // well, they can take care of
+ // setting up properly everything for
+ // the dimension for which this
+ // function is compiled. By declaring
+ // all classes which might depend on
+ // the dimension using a template
+ // parameter, the library can make
+ // nearly all work for you and you
+ // don't have to care about most
+ // things.
+ template <int dim>
+ void LaplaceProblem<dim>::assemble_system ()
+ {
+ MatrixTools::create_laplace_matrix (dof_handler,
+ QGauss<dim>(2),
+ system_matrix);
+ system_rhs = 0;
+
+ std::map<unsigned int,double> boundary_values;
+ VectorTools::interpolate_boundary_values (dof_handler,
+ 0,
+ BoundaryValues<dim>(),
+ boundary_values);
+ MatrixTools::apply_boundary_values (boundary_values,
+ system_matrix,
+ solution,
+ system_rhs);
+ }
- // @sect4{LaplaceProblem::solve}
+ // @sect4{LaplaceProblem::solve}
- // Solving the linear system of
- // equations is something that looks
- // almost identical in most
- // programs. In particular, it is
- // dimension independent, so this
- // function is copied verbatim from the
- // previous example.
-template <int dim>
-void LaplaceProblem<dim>::solve ()
-{
- // NEW
- SolverControl solver_control (dof_handler.n_dofs(),
- 1e-12*system_rhs.l2_norm());
- SolverCG<> cg (solver_control);
+ // Solving the linear system of
+ // equations is something that looks
+ // almost identical in most
+ // programs. In particular, it is
+ // dimension independent, so this
+ // function is copied verbatim from the
+ // previous example.
+ template <int dim>
+ void LaplaceProblem<dim>::solve ()
+ {
+ // NEW
+ SolverControl solver_control (dof_handler.n_dofs(),
+ 1e-12*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{LaplaceProblem::output_results}
+ // @sect4{LaplaceProblem::output_results}
- // This function also does what the
- // respective one did in step-3. No changes
- // here for dimension independence either.
- //
- // The only difference to the previous
- // example is that we want to write output in
- // GMV format, rather than for gnuplot (GMV
- // is another graphics program that, contrary
- // to gnuplot, shows data in nice colors,
- // allows rotation of geometries with the
- // mouse, and generates reasonable
- // representations of 3d data; for ways to
- // obtain it see the ReadMe file of
- // deal.II). To write data in this format, we
- // simply replace the
- // <code>data_out.write_gnuplot</code> call by
- // <code>data_out.write_gmv</code>.
- //
- // Since the program will run both 2d and 3d
- // versions of the laplace solver, we use the
- // dimension in the filename to generate
- // distinct filenames for each run (in a
- // better program, one would check whether
- // `dim' can have other values than 2 or 3,
- // but we neglect this here for the sake of
- // brevity).
-template <int dim>
-void LaplaceProblem<dim>::output_results () const
-{
- DataOut<dim> data_out;
+ // This function also does what the
+ // respective one did in step-3. No changes
+ // here for dimension independence either.
+ //
+ // The only difference to the previous
+ // example is that we want to write output in
+ // GMV format, rather than for gnuplot (GMV
+ // is another graphics program that, contrary
+ // to gnuplot, shows data in nice colors,
+ // allows rotation of geometries with the
+ // mouse, and generates reasonable
+ // representations of 3d data; for ways to
+ // obtain it see the ReadMe file of
+ // deal.II). To write data in this format, we
+ // simply replace the
+ // <code>data_out.write_gnuplot</code> call by
+ // <code>data_out.write_gmv</code>.
+ //
+ // Since the program will run both 2d and 3d
+ // versions of the laplace solver, we use the
+ // dimension in the filename to generate
+ // distinct filenames for each run (in a
+ // better program, one would check whether
+ // `dim' can have other values than 2 or 3,
+ // but we neglect this here for the sake of
+ // brevity).
+ template <int dim>
+ void LaplaceProblem<dim>::output_results () const
+ {
+ DataOut<dim> data_out;
- data_out.attach_dof_handler (dof_handler);
- data_out.add_data_vector (solution, "solution");
+ data_out.attach_dof_handler (dof_handler);
+ data_out.add_data_vector (solution, "solution");
- data_out.build_patches ();
+ data_out.build_patches ();
- std::ofstream output (dim == 2 ?
- "solution-2d.gmv" :
- "solution-3d.gmv");
- data_out.write_gmv (output);
-}
+ std::ofstream output (dim == 2 ?
+ "solution-2d.gmv" :
+ "solution-3d.gmv");
+ data_out.write_gmv (output);
+ }
- // @sect4{LaplaceProblem::run}
+ // @sect4{LaplaceProblem::run}
- // This is the function which has the
- // top-level control over
- // everything. Apart from one line of
- // additional output, it is the same
- // as for the previous example.
-template <int dim>
-void LaplaceProblem<dim>::run ()
-{
- std::cout << "Solving problem in " << dim << " space dimensions." << std::endl;
+ // This is the function which has the
+ // top-level control over
+ // everything. Apart from one line of
+ // additional output, it is the same
+ // as for the previous example.
+ template <int dim>
+ void LaplaceProblem<dim>::run ()
+ {
+ std::cout << "Solving problem in " << dim << " space dimensions." << std::endl;
- make_grid_and_dofs();
- assemble_system ();
- solve ();
- output_results ();
+ make_grid_and_dofs();
+ assemble_system ();
+ solve ();
+ output_results ();
+ }
}
// library.
int main ()
{
+ using namespace dealii;
+ using namespace Step26;
+
deallog.depth_console (0);
+
{
LaplaceProblem<3> laplace_problem_3d;
laplace_problem_3d.run ();
/* $Id$ */
/* */
-/* Copyright (C) 2006, 2007, 2008, 2009, 2010 by the deal.II authors */
+/* Copyright (C) 2006, 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 */
// Finally, this is as in previous
// programs:
-using namespace dealii;
-
-
- // @sect3{The main class}
-
- // The main class of this program looks very
- // much like the one already used in the
- // first few tutorial programs, for example
- // the one in step-6. The main difference is
- // that we have merged the refine_grid and
- // output_results functions into one since we
- // will also want to output some of the
- // quantities used in deciding how to refine
- // the mesh (in particular the estimated
- // smoothness of the solution). There is also
- // a function that computes this estimated
- // smoothness, as discussed in the
- // introduction.
- //
- // As far as member variables are concerned,
- // we use the same structure as already used
- // in step-6, but instead of a regular
- // DoFHandler we use an object of type
- // hp::DoFHandler, and we need collections
- // instead of individual finite element,
- // quadrature, and face quadrature
- // objects. We will fill these collections in
- // the constructor of the class. The last
- // variable, <code>max_degree</code>,
- // indicates the maximal polynomial degree of
- // shape functions used.
-template <int dim>
-class LaplaceProblem
+namespace Step27
{
- public:
- LaplaceProblem ();
- ~LaplaceProblem ();
+ using namespace dealii;
+
+
+ // @sect3{The main class}
+
+ // The main class of this program looks very
+ // much like the one already used in the
+ // first few tutorial programs, for example
+ // the one in step-6. The main difference is
+ // that we have merged the refine_grid and
+ // output_results functions into one since we
+ // will also want to output some of the
+ // quantities used in deciding how to refine
+ // the mesh (in particular the estimated
+ // smoothness of the solution). There is also
+ // a function that computes this estimated
+ // smoothness, as discussed in the
+ // introduction.
+ //
+ // As far as member variables are concerned,
+ // we use the same structure as already used
+ // in step-6, but instead of a regular
+ // DoFHandler we use an object of type
+ // hp::DoFHandler, and we need collections
+ // instead of individual finite element,
+ // quadrature, and face quadrature
+ // objects. We will fill these collections in
+ // the constructor of the class. The last
+ // variable, <code>max_degree</code>,
+ // indicates the maximal polynomial degree of
+ // shape functions used.
+ template <int dim>
+ class LaplaceProblem
+ {
+ public:
+ LaplaceProblem ();
+ ~LaplaceProblem ();
- void run ();
-
- private:
- void setup_system ();
- void assemble_system ();
- void solve ();
- void create_coarse_grid ();
- void estimate_smoothness (Vector<float> &smoothness_indicators) const;
- void postprocess (const unsigned int cycle);
+ void run ();
- Triangulation<dim> triangulation;
+ private:
+ void setup_system ();
+ void assemble_system ();
+ void solve ();
+ void create_coarse_grid ();
+ void estimate_smoothness (Vector<float> &smoothness_indicators) const;
+ void postprocess (const unsigned int cycle);
- hp::DoFHandler<dim> dof_handler;
- hp::FECollection<dim> fe_collection;
- hp::QCollection<dim> quadrature_collection;
- hp::QCollection<dim-1> face_quadrature_collection;
+ Triangulation<dim> triangulation;
- ConstraintMatrix constraints;
+ hp::DoFHandler<dim> dof_handler;
+ hp::FECollection<dim> fe_collection;
+ hp::QCollection<dim> quadrature_collection;
+ hp::QCollection<dim-1> face_quadrature_collection;
- SparsityPattern sparsity_pattern;
- SparseMatrix<double> system_matrix;
+ ConstraintMatrix constraints;
- Vector<double> solution;
- Vector<double> system_rhs;
+ SparsityPattern sparsity_pattern;
+ SparseMatrix<double> system_matrix;
- const unsigned int max_degree;
-};
+ Vector<double> solution;
+ Vector<double> system_rhs;
+ const unsigned int max_degree;
+ };
- // @sect3{Equation data}
- //
- // Next, let us define the right hand side
- // function for this problem. It is $x+1$ in
- // 1d, $(x+1)(y+1)$ in 2d, and so on.
-template <int dim>
-class RightHandSide : public Function<dim>
-{
- public:
- RightHandSide () : Function<dim> () {}
-
- virtual double value (const Point<dim> &p,
- const unsigned int component) const;
-};
-
-
-template <int dim>
-double
-RightHandSide<dim>::value (const Point<dim> &p,
- const unsigned int /*component*/) const
-{
- double product = 1;
- for (unsigned int d=0; d<dim; ++d)
- product *= (p[d]+1);
- return product;
-}
+ // @sect3{Equation data}
+ //
+ // Next, let us define the right hand side
+ // function for this problem. It is $x+1$ in
+ // 1d, $(x+1)(y+1)$ in 2d, and so on.
+ template <int dim>
+ class RightHandSide : public Function<dim>
+ {
+ public:
+ RightHandSide () : Function<dim> () {}
+ virtual double value (const Point<dim> &p,
+ const unsigned int component) const;
+ };
- // @sect3{Implementation of the main class}
-
- // @sect4{LaplaceProblem::LaplaceProblem}
-
- // The constructor of this class is fairly
- // straightforward. It associates the
- // hp::DoFHandler object with the
- // triangulation, and then sets the maximal
- // polynomial degree to 7 (in 1d and 2d) or 5
- // (in 3d and higher). We do so because using
- // higher order polynomial degrees becomes
- // prohibitively expensive, especially in
- // higher space dimensions.
- //
- // Following this, we fill the collections of
- // finite element, and cell and face
- // quadrature objects. We start with
- // quadratic elements, and each quadrature
- // formula is chosen so that it is
- // appropriate for the matching finite
- // element in the hp::FECollection object.
-template <int dim>
-LaplaceProblem<dim>::LaplaceProblem ()
- :
- dof_handler (triangulation),
- max_degree (dim <= 2 ? 7 : 5)
-{
- for (unsigned int degree=2; degree<=max_degree; ++degree)
- {
- fe_collection.push_back (FE_Q<dim>(degree));
- quadrature_collection.push_back (QGauss<dim>(degree+1));
- face_quadrature_collection.push_back (QGauss<dim-1>(degree+1));
- }
-}
+ template <int dim>
+ double
+ RightHandSide<dim>::value (const Point<dim> &p,
+ const unsigned int /*component*/) const
+ {
+ double product = 1;
+ for (unsigned int d=0; d<dim; ++d)
+ product *= (p[d]+1);
+ return product;
+ }
- // @sect4{LaplaceProblem::~LaplaceProblem}
- // The destructor is unchanged from what we
- // already did in step-6:
-template <int dim>
-LaplaceProblem<dim>::~LaplaceProblem ()
-{
- dof_handler.clear ();
-}
+ // @sect3{Implementation of the main class}
- // @sect4{LaplaceProblem::setup_system}
- //
- // This function is again an almost
- // verbatim copy of what we already did in
- // step-6. The first change is that we
- // append the Dirichlet boundary conditions
- // to the ConstraintMatrix object, which we
- // consequently call just
- // <code>constraints</code> instead of
- // <code>hanging_node_constraints</code>. The
- // second difference is that we don't
- // directly build the sparsity pattern, but
- // first create an intermediate object that
- // we later copy into the usual
- // SparsityPattern data structure, since
- // this is more efficient for the problem
- // with many entries per row (and different
- // number of entries in different rows). In
- // another slight deviation, we do not
- // first build the sparsity pattern and
- // then condense away constrained degrees
- // of freedom, but pass the constraint
- // matrix object directly to the function
- // that builds the sparsity pattern. We
- // disable the insertion of constrained
- // entries with <tt>false</tt> as fourth
- // argument in the
- // DoFTools::make_sparsity_pattern
- // function. All of these changes are
- // explained in the introduction of this
- // program.
- //
- // The last change, maybe hidden in plain
- // sight, is that the dof_handler variable
- // here is an hp object -- nevertheless all
- // the function calls we had before still
- // work in exactly the same way as they
- // always did.
-template <int dim>
-void LaplaceProblem<dim>::setup_system ()
-{
- dof_handler.distribute_dofs (fe_collection);
-
- solution.reinit (dof_handler.n_dofs());
- system_rhs.reinit (dof_handler.n_dofs());
-
- constraints.clear ();
- DoFTools::make_hanging_node_constraints (dof_handler,
- constraints);
- VectorTools::interpolate_boundary_values (dof_handler,
- 0,
- ZeroFunction<dim>(),
- constraints);
- constraints.close ();
-
- CompressedSetSparsityPattern csp (dof_handler.n_dofs(),
- dof_handler.n_dofs());
- DoFTools::make_sparsity_pattern (dof_handler, csp, constraints, false);
- sparsity_pattern.copy_from (csp);
-
- system_matrix.reinit (sparsity_pattern);
-}
+ // @sect4{LaplaceProblem::LaplaceProblem}
+ // The constructor of this class is fairly
+ // straightforward. It associates the
+ // hp::DoFHandler object with the
+ // triangulation, and then sets the maximal
+ // polynomial degree to 7 (in 1d and 2d) or 5
+ // (in 3d and higher). We do so because using
+ // higher order polynomial degrees becomes
+ // prohibitively expensive, especially in
+ // higher space dimensions.
+ //
+ // Following this, we fill the collections of
+ // finite element, and cell and face
+ // quadrature objects. We start with
+ // quadratic elements, and each quadrature
+ // formula is chosen so that it is
+ // appropriate for the matching finite
+ // element in the hp::FECollection object.
+ template <int dim>
+ LaplaceProblem<dim>::LaplaceProblem ()
+ :
+ dof_handler (triangulation),
+ max_degree (dim <= 2 ? 7 : 5)
+ {
+ for (unsigned int degree=2; degree<=max_degree; ++degree)
+ {
+ fe_collection.push_back (FE_Q<dim>(degree));
+ quadrature_collection.push_back (QGauss<dim>(degree+1));
+ face_quadrature_collection.push_back (QGauss<dim-1>(degree+1));
+ }
+ }
- // @sect4{LaplaceProblem::assemble_system}
-
- // This is the function that assembles the
- // global matrix and right hand side vector
- // from the local contributions of each
- // cell. Its main working is as has been
- // described in many of the tutorial programs
- // before. The significant deviations are the
- // ones necessary for <i>hp</i> finite element
- // methods. In particular, that we need to
- // use a collection of FEValues object
- // (implemented through the hp::FEValues
- // class), and that we have to eliminate
- // constrained degrees of freedom already
- // when copying local contributions into
- // global objects. Both of these are
- // explained in detail in the introduction of
- // this program.
- //
- // One other slight complication is the fact
- // that because we use different polynomial
- // degrees on different cells, the matrices
- // and vectors holding local contributions do
- // not have the same size on all cells. At
- // the beginning of the loop over all cells,
- // we therefore each time have to resize them
- // to the correct size (given by
- // <code>dofs_per_cell</code>). Because these
- // classes are implement in such a way that
- // reducing the size of a matrix or vector
- // does not release the currently allocated
- // memory (unless the new size is zero), the
- // process of resizing at the beginning of
- // the loop will only require re-allocation
- // of memory during the first few
- // iterations. Once we have found in a cell
- // with the maximal finite element degree, no
- // more re-allocations will happen because
- // all subsequent <code>reinit</code> calls
- // will only set the size to something that
- // fits the currently allocated memory. This
- // is important since allocating memory is
- // expensive, and doing so every time we
- // visit a new cell would take significant
- // compute time.
-template <int dim>
-void LaplaceProblem<dim>::assemble_system ()
-{
- hp::FEValues<dim> hp_fe_values (fe_collection,
- quadrature_collection,
- update_values | update_gradients |
- update_quadrature_points | update_JxW_values);
-
- const RightHandSide<dim> rhs_function;
-
- FullMatrix<double> cell_matrix;
- Vector<double> cell_rhs;
-
- std::vector<unsigned int> local_dof_indices;
-
- typename hp::DoFHandler<dim>::active_cell_iterator
- cell = dof_handler.begin_active(),
- endc = dof_handler.end();
- for (; cell!=endc; ++cell)
- {
- const unsigned int dofs_per_cell = cell->get_fe().dofs_per_cell;
-
- cell_matrix.reinit (dofs_per_cell, dofs_per_cell);
- cell_matrix = 0;
-
- cell_rhs.reinit (dofs_per_cell);
- cell_rhs = 0;
-
- hp_fe_values.reinit (cell);
-
- const FEValues<dim> &fe_values = hp_fe_values.get_present_fe_values ();
-
- std::vector<double> rhs_values (fe_values.n_quadrature_points);
- rhs_function.value_list (fe_values.get_quadrature_points(),
- rhs_values);
-
- for (unsigned int q_point=0;
- q_point<fe_values.n_quadrature_points;
- ++q_point)
- for (unsigned int i=0; i<dofs_per_cell; ++i)
- {
- for (unsigned int j=0; j<dofs_per_cell; ++j)
- cell_matrix(i,j) += (fe_values.shape_grad(i,q_point) *
- fe_values.shape_grad(j,q_point) *
- fe_values.JxW(q_point));
-
- cell_rhs(i) += (fe_values.shape_value(i,q_point) *
- rhs_values[q_point] *
- fe_values.JxW(q_point));
- }
+ // @sect4{LaplaceProblem::~LaplaceProblem}
- local_dof_indices.resize (dofs_per_cell);
- cell->get_dof_indices (local_dof_indices);
+ // The destructor is unchanged from what we
+ // already did in step-6:
+ template <int dim>
+ LaplaceProblem<dim>::~LaplaceProblem ()
+ {
+ dof_handler.clear ();
+ }
- constraints.distribute_local_to_global (cell_matrix, cell_rhs,
- local_dof_indices,
- system_matrix, system_rhs);
- }
- // Now with the loop over all cells
- // finished, we are done for this
- // function. The steps we still had to do
- // at this point in earlier tutorial
- // programs, namely condensing hanging
- // node constraints and applying
- // Dirichlet boundary conditions, have
- // been taken care of by the
- // ConstraintMatrix object
- // <code>constraints</code> on the fly.
-}
+ // @sect4{LaplaceProblem::setup_system}
+ //
+ // This function is again an almost
+ // verbatim copy of what we already did in
+ // step-6. The first change is that we
+ // append the Dirichlet boundary conditions
+ // to the ConstraintMatrix object, which we
+ // consequently call just
+ // <code>constraints</code> instead of
+ // <code>hanging_node_constraints</code>. The
+ // second difference is that we don't
+ // directly build the sparsity pattern, but
+ // first create an intermediate object that
+ // we later copy into the usual
+ // SparsityPattern data structure, since
+ // this is more efficient for the problem
+ // with many entries per row (and different
+ // number of entries in different rows). In
+ // another slight deviation, we do not
+ // first build the sparsity pattern and
+ // then condense away constrained degrees
+ // of freedom, but pass the constraint
+ // matrix object directly to the function
+ // that builds the sparsity pattern. We
+ // disable the insertion of constrained
+ // entries with <tt>false</tt> as fourth
+ // argument in the
+ // DoFTools::make_sparsity_pattern
+ // function. All of these changes are
+ // explained in the introduction of this
+ // program.
+ //
+ // The last change, maybe hidden in plain
+ // sight, is that the dof_handler variable
+ // here is an hp object -- nevertheless all
+ // the function calls we had before still
+ // work in exactly the same way as they
+ // always did.
+ template <int dim>
+ void LaplaceProblem<dim>::setup_system ()
+ {
+ dof_handler.distribute_dofs (fe_collection);
+
+ solution.reinit (dof_handler.n_dofs());
+ system_rhs.reinit (dof_handler.n_dofs());
+
+ constraints.clear ();
+ DoFTools::make_hanging_node_constraints (dof_handler,
+ constraints);
+ VectorTools::interpolate_boundary_values (dof_handler,
+ 0,
+ ZeroFunction<dim>(),
+ constraints);
+ constraints.close ();
+
+ CompressedSetSparsityPattern csp (dof_handler.n_dofs(),
+ dof_handler.n_dofs());
+ DoFTools::make_sparsity_pattern (dof_handler, csp, constraints, false);
+ sparsity_pattern.copy_from (csp);
+
+ system_matrix.reinit (sparsity_pattern);
+ }
- // @sect4{LaplaceProblem::solve}
+ // @sect4{LaplaceProblem::assemble_system}
+
+ // This is the function that assembles the
+ // global matrix and right hand side vector
+ // from the local contributions of each
+ // cell. Its main working is as has been
+ // described in many of the tutorial programs
+ // before. The significant deviations are the
+ // ones necessary for <i>hp</i> finite element
+ // methods. In particular, that we need to
+ // use a collection of FEValues object
+ // (implemented through the hp::FEValues
+ // class), and that we have to eliminate
+ // constrained degrees of freedom already
+ // when copying local contributions into
+ // global objects. Both of these are
+ // explained in detail in the introduction of
+ // this program.
+ //
+ // One other slight complication is the fact
+ // that because we use different polynomial
+ // degrees on different cells, the matrices
+ // and vectors holding local contributions do
+ // not have the same size on all cells. At
+ // the beginning of the loop over all cells,
+ // we therefore each time have to resize them
+ // to the correct size (given by
+ // <code>dofs_per_cell</code>). Because these
+ // classes are implement in such a way that
+ // reducing the size of a matrix or vector
+ // does not release the currently allocated
+ // memory (unless the new size is zero), the
+ // process of resizing at the beginning of
+ // the loop will only require re-allocation
+ // of memory during the first few
+ // iterations. Once we have found in a cell
+ // with the maximal finite element degree, no
+ // more re-allocations will happen because
+ // all subsequent <code>reinit</code> calls
+ // will only set the size to something that
+ // fits the currently allocated memory. This
+ // is important since allocating memory is
+ // expensive, and doing so every time we
+ // visit a new cell would take significant
+ // compute time.
+ template <int dim>
+ void LaplaceProblem<dim>::assemble_system ()
+ {
+ hp::FEValues<dim> hp_fe_values (fe_collection,
+ quadrature_collection,
+ update_values | update_gradients |
+ update_quadrature_points | update_JxW_values);
- // The function solving the linear system is
- // entirely unchanged from previous
- // examples. We simply try to reduce the
- // initial residual (which equals the $l_2$
- // norm of the right hand side) by a certain
- // factor:
-template <int dim>
-void LaplaceProblem<dim>::solve ()
-{
- SolverControl solver_control (system_rhs.size(),
- 1e-8*system_rhs.l2_norm());
- SolverCG<> cg (solver_control);
+ const RightHandSide<dim> rhs_function;
- PreconditionSSOR<> preconditioner;
- preconditioner.initialize(system_matrix, 1.2);
+ FullMatrix<double> cell_matrix;
+ Vector<double> cell_rhs;
- cg.solve (system_matrix, solution, system_rhs,
- preconditioner);
+ std::vector<unsigned int> local_dof_indices;
- constraints.distribute (solution);
-}
+ typename hp::DoFHandler<dim>::active_cell_iterator
+ cell = dof_handler.begin_active(),
+ endc = dof_handler.end();
+ for (; cell!=endc; ++cell)
+ {
+ const unsigned int dofs_per_cell = cell->get_fe().dofs_per_cell;
+ cell_matrix.reinit (dofs_per_cell, dofs_per_cell);
+ cell_matrix = 0;
+ cell_rhs.reinit (dofs_per_cell);
+ cell_rhs = 0;
- // @sect4{LaplaceProblem::postprocess}
-
- // After solving the linear system, we will
- // want to postprocess the solution. Here,
- // all we do is to estimate the error,
- // estimate the local smoothness of the
- // solution as described in the introduction,
- // then write graphical output, and finally
- // refine the mesh in both $h$ and $p$
- // according to the indicators computed
- // before. We do all this in the same
- // function because we want the estimated
- // error and smoothness indicators not only
- // for refinement, but also include them in
- // the graphical output.
-template <int dim>
-void LaplaceProblem<dim>::postprocess (const unsigned int cycle)
-{
- // Let us start with computing estimated
- // error and smoothness indicators, which
- // each are one number for each active cell
- // of our triangulation. For the error
- // indicator, we use the
- // KellyErrorEstimator class as
- // always. Estimating the smoothness is
- // done in the respective function of this
- // class; that function is discussed
- // further down below:
- Vector<float> estimated_error_per_cell (triangulation.n_active_cells());
- KellyErrorEstimator<dim>::estimate (dof_handler,
- face_quadrature_collection,
- typename FunctionMap<dim>::type(),
- solution,
- estimated_error_per_cell);
-
- Vector<float> smoothness_indicators (triangulation.n_active_cells());
- estimate_smoothness (smoothness_indicators);
-
- // Next we want to generate graphical
- // output. In addition to the two estimated
- // quantities derived above, we would also
- // like to output the polynomial degree of
- // the finite elements used on each of the
- // elements on the mesh.
- //
- // The way to do that requires that we loop
- // over all cells and poll the active
- // finite element index of them using
- // <code>cell-@>active_fe_index()</code>. We
- // then use the result of this operation
- // and query the finite element collection
- // for the finite element with that index,
- // and finally determine the polynomial
- // degree of that element. The result we
- // put into a vector with one element per
- // cell. The DataOut class requires this to
- // be a vector of <code>float</code> or
- // <code>double</code>, even though our
- // values are all integers, so that it what
- // we use:
- {
- Vector<float> fe_degrees (triangulation.n_active_cells());
- {
- typename hp::DoFHandler<dim>::active_cell_iterator
- cell = dof_handler.begin_active(),
- endc = dof_handler.end();
- for (unsigned int index=0; cell!=endc; ++cell, ++index)
- fe_degrees(index)
- = fe_collection[cell->active_fe_index()].degree;
- }
+ hp_fe_values.reinit (cell);
- // With now all data vectors available --
- // solution, estimated errors and
- // smoothness indicators, and finite
- // element degrees --, we create a
- // DataOut object for graphical output
- // and attach all data. Note that the
- // DataOut class has a second template
- // argument (which defaults to
- // DoFHandler@<dim@>, which is why we
- // have never seen it in previous
- // tutorial programs) that indicates the
- // type of DoF handler to be used. Here,
- // we have to use the hp::DoFHandler
- // class:
- DataOut<dim,hp::DoFHandler<dim> > data_out;
-
- data_out.attach_dof_handler (dof_handler);
- data_out.add_data_vector (solution, "solution");
- data_out.add_data_vector (estimated_error_per_cell, "error");
- data_out.add_data_vector (smoothness_indicators, "smoothness");
- data_out.add_data_vector (fe_degrees, "fe_degree");
- data_out.build_patches ();
-
- // The final step in generating
- // output is to determine a file
- // name, open the file, and write
- // the data into it (here, we use
- // VTK format):
- const std::string filename = "solution-" +
- Utilities::int_to_string (cycle, 2) +
- ".vtk";
- std::ofstream output (filename.c_str());
- data_out.write_vtk (output);
+ const FEValues<dim> &fe_values = hp_fe_values.get_present_fe_values ();
+
+ std::vector<double> rhs_values (fe_values.n_quadrature_points);
+ rhs_function.value_list (fe_values.get_quadrature_points(),
+ rhs_values);
+
+ for (unsigned int q_point=0;
+ q_point<fe_values.n_quadrature_points;
+ ++q_point)
+ for (unsigned int i=0; i<dofs_per_cell; ++i)
+ {
+ for (unsigned int j=0; j<dofs_per_cell; ++j)
+ cell_matrix(i,j) += (fe_values.shape_grad(i,q_point) *
+ fe_values.shape_grad(j,q_point) *
+ fe_values.JxW(q_point));
+
+ cell_rhs(i) += (fe_values.shape_value(i,q_point) *
+ rhs_values[q_point] *
+ fe_values.JxW(q_point));
+ }
+
+ local_dof_indices.resize (dofs_per_cell);
+ cell->get_dof_indices (local_dof_indices);
+
+ constraints.distribute_local_to_global (cell_matrix, cell_rhs,
+ local_dof_indices,
+ system_matrix, system_rhs);
+ }
+
+ // Now with the loop over all cells
+ // finished, we are done for this
+ // function. The steps we still had to do
+ // at this point in earlier tutorial
+ // programs, namely condensing hanging
+ // node constraints and applying
+ // Dirichlet boundary conditions, have
+ // been taken care of by the
+ // ConstraintMatrix object
+ // <code>constraints</code> on the fly.
}
- // After this, we would like to actually
- // refine the mesh, in both $h$ and
- // $p$. The way we are going to do this is
- // as follows: first, we use the estimated
- // error to flag those cells for refinement
- // that have the largest error. This is
- // what we have always done:
+
+
+ // @sect4{LaplaceProblem::solve}
+
+ // The function solving the linear system is
+ // entirely unchanged from previous
+ // examples. We simply try to reduce the
+ // initial residual (which equals the $l_2$
+ // norm of the right hand side) by a certain
+ // factor:
+ template <int dim>
+ void LaplaceProblem<dim>::solve ()
{
- GridRefinement::refine_and_coarsen_fixed_number (triangulation,
- estimated_error_per_cell,
- 0.3, 0.03);
-
- // Next we would like to figure out which
- // of the cells that have been flagged
- // for refinement should actually have
- // $p$ increased instead of $h$
- // decreased. The strategy we choose here
- // is that we look at the smoothness
- // indicators of those cells that are
- // flagged for refinement, and increase
- // $p$ for those with a smoothness larger
- // than a certain threshold. For this, we
- // first have to determine the maximal
- // and minimal values of the smoothness
- // indicators of all flagged cells, which
- // we do using a loop over all cells and
- // comparing current minimal and maximal
- // values. (We start with the minimal and
- // maximal values of <i>all</i> cells, a
- // range within which the minimal and
- // maximal values on cells flagged for
- // refinement must surely lie.) Absent
- // any better strategies, we will then
- // set the threshold above which will
- // increase $p$ instead of reducing $h$
- // as the mean value between minimal and
- // maximal smoothness indicators on cells
- // flagged for refinement:
- float max_smoothness = *std::min_element (smoothness_indicators.begin(),
- smoothness_indicators.end()),
- min_smoothness = *std::max_element (smoothness_indicators.begin(),
- smoothness_indicators.end());
- {
- typename hp::DoFHandler<dim>::active_cell_iterator
- cell = dof_handler.begin_active(),
- endc = dof_handler.end();
- for (unsigned int index=0; cell!=endc; ++cell, ++index)
- if (cell->refine_flag_set())
- {
- max_smoothness = std::max (max_smoothness,
- smoothness_indicators(index));
- min_smoothness = std::min (min_smoothness,
- smoothness_indicators(index));
- }
- }
- const float threshold_smoothness = (max_smoothness + min_smoothness) / 2;
-
- // With this, we can go back, loop over
- // all cells again, and for those cells
- // for which (i) the refinement flag is
- // set, (ii) the smoothness indicator is
- // larger than the threshold, and (iii)
- // we still have a finite element with a
- // polynomial degree higher than the
- // current one in the finite element
- // collection, we then increase the
- // polynomial degree and in return remove
- // the flag indicating that the cell
- // should undergo bisection. For all
- // other cells, the refinement flags
- // remain untouched:
- {
- typename hp::DoFHandler<dim>::active_cell_iterator
- cell = dof_handler.begin_active(),
- endc = dof_handler.end();
- for (unsigned int index=0; cell!=endc; ++cell, ++index)
- if (cell->refine_flag_set()
- &&
- (smoothness_indicators(index) > threshold_smoothness)
- &&
- (cell->active_fe_index()+1 < fe_collection.size()))
- {
- cell->clear_refine_flag();
- cell->set_active_fe_index (cell->active_fe_index() + 1);
- }
- }
-
- // At the end of this procedure, we then
- // refine the mesh. During this process,
- // children of cells undergoing bisection
- // inherit their mother cell's finite
- // element index:
- triangulation.execute_coarsening_and_refinement ();
- }
-}
+ SolverControl solver_control (system_rhs.size(),
+ 1e-8*system_rhs.l2_norm());
+ SolverCG<> cg (solver_control);
+ PreconditionSSOR<> preconditioner;
+ preconditioner.initialize(system_matrix, 1.2);
- // @sect4{LaplaceProblem::create_coarse_grid}
-
- // The following function is used when
- // creating the initial grid. It is a
- // specialization for the 2d case, i.e. a
- // corresponding function needs to be
- // implemented if the program is run in
- // anything other then 2d. The function is
- // actually stolen from step-14 and generates
- // the same mesh used already there, i.e. the
- // square domain with the square hole in the
- // middle. The meaning of the different parts
- // of this function are explained in the
- // documentation of step-14:
-template <>
-void LaplaceProblem<2>::create_coarse_grid ()
-{
- const unsigned int dim = 2;
-
- static const Point<2> vertices_1[]
- = { Point<2> (-1., -1.),
- Point<2> (-1./2, -1.),
- Point<2> (0., -1.),
- Point<2> (+1./2, -1.),
- Point<2> (+1, -1.),
-
- Point<2> (-1., -1./2.),
- Point<2> (-1./2, -1./2.),
- Point<2> (0., -1./2.),
- Point<2> (+1./2, -1./2.),
- Point<2> (+1, -1./2.),
-
- Point<2> (-1., 0.),
- Point<2> (-1./2, 0.),
- Point<2> (+1./2, 0.),
- Point<2> (+1, 0.),
-
- Point<2> (-1., 1./2.),
- Point<2> (-1./2, 1./2.),
- Point<2> (0., 1./2.),
- Point<2> (+1./2, 1./2.),
- Point<2> (+1, 1./2.),
-
- Point<2> (-1., 1.),
- Point<2> (-1./2, 1.),
- Point<2> (0., 1.),
- Point<2> (+1./2, 1.),
- Point<2> (+1, 1.) };
- const unsigned int
- n_vertices = sizeof(vertices_1) / sizeof(vertices_1[0]);
- const std::vector<Point<dim> > vertices (&vertices_1[0],
- &vertices_1[n_vertices]);
- static const int cell_vertices[][GeometryInfo<dim>::vertices_per_cell]
- = {{0, 1, 5, 6},
- {1, 2, 6, 7},
- {2, 3, 7, 8},
- {3, 4, 8, 9},
- {5, 6, 10, 11},
- {8, 9, 12, 13},
- {10, 11, 14, 15},
- {12, 13, 17, 18},
- {14, 15, 19, 20},
- {15, 16, 20, 21},
- {16, 17, 21, 22},
- {17, 18, 22, 23}};
- const unsigned int
- n_cells = sizeof(cell_vertices) / sizeof(cell_vertices[0]);
-
- std::vector<CellData<dim> > cells (n_cells, CellData<dim>());
- for (unsigned int i=0; i<n_cells; ++i)
- {
- for (unsigned int j=0;
- j<GeometryInfo<dim>::vertices_per_cell;
- ++j)
- cells[i].vertices[j] = cell_vertices[i][j];
- cells[i].material_id = 0;
- }
+ cg.solve (system_matrix, solution, system_rhs,
+ preconditioner);
- triangulation.create_triangulation (vertices,
- cells,
- SubCellData());
- triangulation.refine_global (3);
-}
+ constraints.distribute (solution);
+ }
+ // @sect4{LaplaceProblem::postprocess}
+
+ // After solving the linear system, we will
+ // want to postprocess the solution. Here,
+ // all we do is to estimate the error,
+ // estimate the local smoothness of the
+ // solution as described in the introduction,
+ // then write graphical output, and finally
+ // refine the mesh in both $h$ and $p$
+ // according to the indicators computed
+ // before. We do all this in the same
+ // function because we want the estimated
+ // error and smoothness indicators not only
+ // for refinement, but also include them in
+ // the graphical output.
+ template <int dim>
+ void LaplaceProblem<dim>::postprocess (const unsigned int cycle)
+ {
+ // Let us start with computing estimated
+ // error and smoothness indicators, which
+ // each are one number for each active cell
+ // of our triangulation. For the error
+ // indicator, we use the
+ // KellyErrorEstimator class as
+ // always. Estimating the smoothness is
+ // done in the respective function of this
+ // class; that function is discussed
+ // further down below:
+ Vector<float> estimated_error_per_cell (triangulation.n_active_cells());
+ KellyErrorEstimator<dim>::estimate (dof_handler,
+ face_quadrature_collection,
+ typename FunctionMap<dim>::type(),
+ solution,
+ estimated_error_per_cell);
+
+ Vector<float> smoothness_indicators (triangulation.n_active_cells());
+ estimate_smoothness (smoothness_indicators);
+
+ // Next we want to generate graphical
+ // output. In addition to the two estimated
+ // quantities derived above, we would also
+ // like to output the polynomial degree of
+ // the finite elements used on each of the
+ // elements on the mesh.
+ //
+ // The way to do that requires that we loop
+ // over all cells and poll the active
+ // finite element index of them using
+ // <code>cell-@>active_fe_index()</code>. We
+ // then use the result of this operation
+ // and query the finite element collection
+ // for the finite element with that index,
+ // and finally determine the polynomial
+ // degree of that element. The result we
+ // put into a vector with one element per
+ // cell. The DataOut class requires this to
+ // be a vector of <code>float</code> or
+ // <code>double</code>, even though our
+ // values are all integers, so that it what
+ // we use:
+ {
+ Vector<float> fe_degrees (triangulation.n_active_cells());
+ {
+ typename hp::DoFHandler<dim>::active_cell_iterator
+ cell = dof_handler.begin_active(),
+ endc = dof_handler.end();
+ for (unsigned int index=0; cell!=endc; ++cell, ++index)
+ fe_degrees(index)
+ = fe_collection[cell->active_fe_index()].degree;
+ }
- // @sect4{LaplaceProblem::run}
+ // With now all data vectors available --
+ // solution, estimated errors and
+ // smoothness indicators, and finite
+ // element degrees --, we create a
+ // DataOut object for graphical output
+ // and attach all data. Note that the
+ // DataOut class has a second template
+ // argument (which defaults to
+ // DoFHandler@<dim@>, which is why we
+ // have never seen it in previous
+ // tutorial programs) that indicates the
+ // type of DoF handler to be used. Here,
+ // we have to use the hp::DoFHandler
+ // class:
+ DataOut<dim,hp::DoFHandler<dim> > data_out;
+
+ data_out.attach_dof_handler (dof_handler);
+ data_out.add_data_vector (solution, "solution");
+ data_out.add_data_vector (estimated_error_per_cell, "error");
+ data_out.add_data_vector (smoothness_indicators, "smoothness");
+ data_out.add_data_vector (fe_degrees, "fe_degree");
+ data_out.build_patches ();
+
+ // The final step in generating
+ // output is to determine a file
+ // name, open the file, and write
+ // the data into it (here, we use
+ // VTK format):
+ const std::string filename = "solution-" +
+ Utilities::int_to_string (cycle, 2) +
+ ".vtk";
+ std::ofstream output (filename.c_str());
+ data_out.write_vtk (output);
+ }
- // This function implements the logic of the
- // program, as did the respective function in
- // most of the previous programs already, see
- // for example step-6.
- //
- // Basically, it contains the adaptive loop:
- // in the first iteration create a coarse
- // grid, and then set up the linear system,
- // assemble it, solve, and postprocess the
- // solution including mesh refinement. Then
- // start over again. In the meantime, also
- // output some information for those staring
- // at the screen trying to figure out what
- // the program does:
-template <int dim>
-void LaplaceProblem<dim>::run ()
-{
- for (unsigned int cycle=0; cycle<6; ++cycle)
+ // After this, we would like to actually
+ // refine the mesh, in both $h$ and
+ // $p$. The way we are going to do this is
+ // as follows: first, we use the estimated
+ // error to flag those cells for refinement
+ // that have the largest error. This is
+ // what we have always done:
{
- std::cout << "Cycle " << cycle << ':' << std::endl;
+ GridRefinement::refine_and_coarsen_fixed_number (triangulation,
+ estimated_error_per_cell,
+ 0.3, 0.03);
+
+ // Next we would like to figure out which
+ // of the cells that have been flagged
+ // for refinement should actually have
+ // $p$ increased instead of $h$
+ // decreased. The strategy we choose here
+ // is that we look at the smoothness
+ // indicators of those cells that are
+ // flagged for refinement, and increase
+ // $p$ for those with a smoothness larger
+ // than a certain threshold. For this, we
+ // first have to determine the maximal
+ // and minimal values of the smoothness
+ // indicators of all flagged cells, which
+ // we do using a loop over all cells and
+ // comparing current minimal and maximal
+ // values. (We start with the minimal and
+ // maximal values of <i>all</i> cells, a
+ // range within which the minimal and
+ // maximal values on cells flagged for
+ // refinement must surely lie.) Absent
+ // any better strategies, we will then
+ // set the threshold above which will
+ // increase $p$ instead of reducing $h$
+ // as the mean value between minimal and
+ // maximal smoothness indicators on cells
+ // flagged for refinement:
+ float max_smoothness = *std::min_element (smoothness_indicators.begin(),
+ smoothness_indicators.end()),
+ min_smoothness = *std::max_element (smoothness_indicators.begin(),
+ smoothness_indicators.end());
+ {
+ typename hp::DoFHandler<dim>::active_cell_iterator
+ cell = dof_handler.begin_active(),
+ endc = dof_handler.end();
+ for (unsigned int index=0; cell!=endc; ++cell, ++index)
+ if (cell->refine_flag_set())
+ {
+ max_smoothness = std::max (max_smoothness,
+ smoothness_indicators(index));
+ min_smoothness = std::min (min_smoothness,
+ smoothness_indicators(index));
+ }
+ }
+ const float threshold_smoothness = (max_smoothness + min_smoothness) / 2;
+
+ // With this, we can go back, loop over
+ // all cells again, and for those cells
+ // for which (i) the refinement flag is
+ // set, (ii) the smoothness indicator is
+ // larger than the threshold, and (iii)
+ // we still have a finite element with a
+ // polynomial degree higher than the
+ // current one in the finite element
+ // collection, we then increase the
+ // polynomial degree and in return remove
+ // the flag indicating that the cell
+ // should undergo bisection. For all
+ // other cells, the refinement flags
+ // remain untouched:
+ {
+ typename hp::DoFHandler<dim>::active_cell_iterator
+ cell = dof_handler.begin_active(),
+ endc = dof_handler.end();
+ for (unsigned int index=0; cell!=endc; ++cell, ++index)
+ if (cell->refine_flag_set()
+ &&
+ (smoothness_indicators(index) > threshold_smoothness)
+ &&
+ (cell->active_fe_index()+1 < fe_collection.size()))
+ {
+ cell->clear_refine_flag();
+ cell->set_active_fe_index (cell->active_fe_index() + 1);
+ }
+ }
- if (cycle == 0)
- create_coarse_grid ();
+ // At the end of this procedure, we then
+ // refine the mesh. During this process,
+ // children of cells undergoing bisection
+ // inherit their mother cell's finite
+ // element index:
+ triangulation.execute_coarsening_and_refinement ();
+ }
+ }
- setup_system ();
- std::cout << " Number of active cells: "
- << triangulation.n_active_cells()
- << std::endl
- << " Number of degrees of freedom: "
- << dof_handler.n_dofs()
- << std::endl
- << " Number of constraints : "
- << constraints.n_constraints()
- << std::endl;
+ // @sect4{LaplaceProblem::create_coarse_grid}
+
+ // The following function is used when
+ // creating the initial grid. It is a
+ // specialization for the 2d case, i.e. a
+ // corresponding function needs to be
+ // implemented if the program is run in
+ // anything other then 2d. The function is
+ // actually stolen from step-14 and generates
+ // the same mesh used already there, i.e. the
+ // square domain with the square hole in the
+ // middle. The meaning of the different parts
+ // of this function are explained in the
+ // documentation of step-14:
+ template <>
+ void LaplaceProblem<2>::create_coarse_grid ()
+ {
+ const unsigned int dim = 2;
+
+ static const Point<2> vertices_1[]
+ = { Point<2> (-1., -1.),
+ Point<2> (-1./2, -1.),
+ Point<2> (0., -1.),
+ Point<2> (+1./2, -1.),
+ Point<2> (+1, -1.),
+
+ Point<2> (-1., -1./2.),
+ Point<2> (-1./2, -1./2.),
+ Point<2> (0., -1./2.),
+ Point<2> (+1./2, -1./2.),
+ Point<2> (+1, -1./2.),
+
+ Point<2> (-1., 0.),
+ Point<2> (-1./2, 0.),
+ Point<2> (+1./2, 0.),
+ Point<2> (+1, 0.),
+
+ Point<2> (-1., 1./2.),
+ Point<2> (-1./2, 1./2.),
+ Point<2> (0., 1./2.),
+ Point<2> (+1./2, 1./2.),
+ Point<2> (+1, 1./2.),
+
+ Point<2> (-1., 1.),
+ Point<2> (-1./2, 1.),
+ Point<2> (0., 1.),
+ Point<2> (+1./2, 1.),
+ Point<2> (+1, 1.) };
+ const unsigned int
+ n_vertices = sizeof(vertices_1) / sizeof(vertices_1[0]);
+ const std::vector<Point<dim> > vertices (&vertices_1[0],
+ &vertices_1[n_vertices]);
+ static const int cell_vertices[][GeometryInfo<dim>::vertices_per_cell]
+ = {{0, 1, 5, 6},
+ {1, 2, 6, 7},
+ {2, 3, 7, 8},
+ {3, 4, 8, 9},
+ {5, 6, 10, 11},
+ {8, 9, 12, 13},
+ {10, 11, 14, 15},
+ {12, 13, 17, 18},
+ {14, 15, 19, 20},
+ {15, 16, 20, 21},
+ {16, 17, 21, 22},
+ {17, 18, 22, 23}};
+ const unsigned int
+ n_cells = sizeof(cell_vertices) / sizeof(cell_vertices[0]);
+
+ std::vector<CellData<dim> > cells (n_cells, CellData<dim>());
+ for (unsigned int i=0; i<n_cells; ++i)
+ {
+ for (unsigned int j=0;
+ j<GeometryInfo<dim>::vertices_per_cell;
+ ++j)
+ cells[i].vertices[j] = cell_vertices[i][j];
+ cells[i].material_id = 0;
+ }
- assemble_system ();
- solve ();
- postprocess (cycle);
- }
-}
+ triangulation.create_triangulation (vertices,
+ cells,
+ SubCellData());
+ triangulation.refine_global (3);
+ }
- // @sect4{LaplaceProblem::estimate_smoothness}
- // This last function of significance
- // implements the algorithm to estimate the
- // smoothness exponent using the algorithms
- // explained in detail in the
- // introduction. We will therefore only
- // comment on those points that are of
- // implementational importance.
-template <int dim>
-void
-LaplaceProblem<dim>::
-estimate_smoothness (Vector<float> &smoothness_indicators) const
-{
- // The first thing we need to do is
- // to define the Fourier vectors
- // ${\bf k}$ for which we want to
- // compute Fourier coefficients of
- // the solution on each cell. In
- // 2d, we pick those vectors ${\bf
- // k}=(\pi i, \pi j)^T$ for which
- // $\sqrt{i^2+j^2}\le N$, with
- // $i,j$ integers and $N$ being the
- // maximal polynomial degree we use
- // for the finite elements in this
- // program. The 3d case is handled
- // analogously. 1d and dimensions
- // higher than 3 are not
- // implemented, and we guard our
- // implementation by making sure
- // that we receive an exception in
- // case someone tries to compile
- // the program for any of these
- // dimensions.
+
+ // @sect4{LaplaceProblem::run}
+
+ // This function implements the logic of the
+ // program, as did the respective function in
+ // most of the previous programs already, see
+ // for example step-6.
//
- // We exclude ${\bf k}=0$ to avoid problems
- // computing $|{\bf k}|^{-mu}$ and $\ln
- // |{\bf k}|$. The other vectors are stored
- // in the field <code>k_vectors</code>. In
- // addition, we store the square of the
- // magnitude of each of these vectors (up
- // to a factor $\pi^2$) in the
- // <code>k_vectors_magnitude</code> array
- // -- we will need that when we attempt to
- // find out which of those Fourier
- // coefficients corresponding to Fourier
- // vectors of the same magnitude is the
- // largest:
- const unsigned int N = max_degree;
-
- std::vector<Tensor<1,dim> > k_vectors;
- std::vector<unsigned int> k_vectors_magnitude;
- switch (dim)
- {
- case 2:
+ // Basically, it contains the adaptive loop:
+ // in the first iteration create a coarse
+ // grid, and then set up the linear system,
+ // assemble it, solve, and postprocess the
+ // solution including mesh refinement. Then
+ // start over again. In the meantime, also
+ // output some information for those staring
+ // at the screen trying to figure out what
+ // the program does:
+ template <int dim>
+ void LaplaceProblem<dim>::run ()
+ {
+ for (unsigned int cycle=0; cycle<6; ++cycle)
{
- for (unsigned int i=0; i<N; ++i)
- for (unsigned int j=0; j<N; ++j)
- if (!((i==0) && (j==0))
- &&
- (i*i + j*j < N*N))
- {
- k_vectors.push_back (Point<dim>(numbers::PI * i,
- numbers::PI * j));
- k_vectors_magnitude.push_back (i*i+j*j);
- }
-
- break;
+ std::cout << "Cycle " << cycle << ':' << std::endl;
+
+ if (cycle == 0)
+ create_coarse_grid ();
+
+ setup_system ();
+
+ std::cout << " Number of active cells: "
+ << triangulation.n_active_cells()
+ << std::endl
+ << " Number of degrees of freedom: "
+ << dof_handler.n_dofs()
+ << std::endl
+ << " Number of constraints : "
+ << constraints.n_constraints()
+ << std::endl;
+
+ assemble_system ();
+ solve ();
+ postprocess (cycle);
}
+ }
- case 3:
+
+ // @sect4{LaplaceProblem::estimate_smoothness}
+
+ // This last function of significance
+ // implements the algorithm to estimate the
+ // smoothness exponent using the algorithms
+ // explained in detail in the
+ // introduction. We will therefore only
+ // comment on those points that are of
+ // implementational importance.
+ template <int dim>
+ void
+ LaplaceProblem<dim>::
+ estimate_smoothness (Vector<float> &smoothness_indicators) const
+ {
+ // The first thing we need to do is
+ // to define the Fourier vectors
+ // ${\bf k}$ for which we want to
+ // compute Fourier coefficients of
+ // the solution on each cell. In
+ // 2d, we pick those vectors ${\bf
+ // k}=(\pi i, \pi j)^T$ for which
+ // $\sqrt{i^2+j^2}\le N$, with
+ // $i,j$ integers and $N$ being the
+ // maximal polynomial degree we use
+ // for the finite elements in this
+ // program. The 3d case is handled
+ // analogously. 1d and dimensions
+ // higher than 3 are not
+ // implemented, and we guard our
+ // implementation by making sure
+ // that we receive an exception in
+ // case someone tries to compile
+ // the program for any of these
+ // dimensions.
+ //
+ // We exclude ${\bf k}=0$ to avoid problems
+ // computing $|{\bf k}|^{-mu}$ and $\ln
+ // |{\bf k}|$. The other vectors are stored
+ // in the field <code>k_vectors</code>. In
+ // addition, we store the square of the
+ // magnitude of each of these vectors (up
+ // to a factor $\pi^2$) in the
+ // <code>k_vectors_magnitude</code> array
+ // -- we will need that when we attempt to
+ // find out which of those Fourier
+ // coefficients corresponding to Fourier
+ // vectors of the same magnitude is the
+ // largest:
+ const unsigned int N = max_degree;
+
+ std::vector<Tensor<1,dim> > k_vectors;
+ std::vector<unsigned int> k_vectors_magnitude;
+ switch (dim)
{
- for (unsigned int i=0; i<N; ++i)
- for (unsigned int j=0; j<N; ++j)
- for (unsigned int k=0; k<N; ++k)
- if (!((i==0) && (j==0) && (k==0))
+ case 2:
+ {
+ for (unsigned int i=0; i<N; ++i)
+ for (unsigned int j=0; j<N; ++j)
+ if (!((i==0) && (j==0))
&&
- (i*i + j*j + k*k < N*N))
+ (i*i + j*j < N*N))
{
k_vectors.push_back (Point<dim>(numbers::PI * i,
- numbers::PI * j,
- numbers::PI * k));
- k_vectors_magnitude.push_back (i*i+j*j+k*k);
- }
-
- break;
- }
-
- default:
- Assert (false, ExcNotImplemented());
- }
-
- // After we have set up the Fourier
- // vectors, we also store their total
- // number for simplicity, and compute the
- // logarithm of the magnitude of each of
- // these vectors since we will need it many
- // times over further down below:
- const unsigned n_fourier_modes = k_vectors.size();
- std::vector<double> ln_k (n_fourier_modes);
- for (unsigned int i=0; i<n_fourier_modes; ++i)
- ln_k[i] = std::log (k_vectors[i].norm());
-
-
- // Next, we need to assemble the matrices
- // that do the Fourier transforms for each
- // of the finite elements we deal with,
- // i.e. the matrices ${\cal F}_{{\bf k},j}$
- // defined in the introduction. We have to
- // do that for each of the finite elements
- // in use. Note that these matrices are
- // complex-valued, so we can't use the
- // FullMatrix class. Instead, we use the
- // Table class template.
- std::vector<Table<2,std::complex<double> > >
- fourier_transform_matrices (fe_collection.size());
-
- // In order to compute them, we of
- // course can't perform the Fourier
- // transform analytically, but have
- // to approximate it using
- // quadrature. To this end, we use
- // a quadrature formula that is
- // obtained by iterating a 2-point
- // Gauss formula as many times as
- // the maximal exponent we use for
- // the term $e^{i{\bf k}\cdot{\bf
- // x}}$:
- QGauss<1> base_quadrature (2);
- QIterated<dim> quadrature (base_quadrature, N);
-
- // With this, we then loop over all finite
- // elements in use, reinitialize the
- // respective matrix ${\cal F}$ to the
- // right size, and integrate each entry of
- // the matrix numerically as ${\cal
- // F}_{{\bf k},j}=\sum_q e^{i{\bf k}\cdot
- // {\bf x}}\varphi_j({\bf x}_q)
- // w_q$, where $x_q$
- // are the quadrature points and $w_q$ are
- // the quadrature weights. Note that the
- // imaginary unit $i=\sqrt{-1}$ is obtained
- // from the standard C++ classes using
- // <code>std::complex@<double@>(0,1)</code>.
-
- // Because we work on the unit cell, we can
- // do all this work without a mapping from
- // reference to real cell and consequently
- // do not need the FEValues class.
- for (unsigned int fe=0; fe<fe_collection.size(); ++fe)
- {
- fourier_transform_matrices[fe].reinit (n_fourier_modes,
- fe_collection[fe].dofs_per_cell);
+ numbers::PI * j));
+ k_vectors_magnitude.push_back (i*i+j*j);
+ }
- for (unsigned int k=0; k<n_fourier_modes; ++k)
- for (unsigned int j=0; j<fe_collection[fe].dofs_per_cell; ++j)
- {
- std::complex<double> sum = 0;
- for (unsigned int q=0; q<quadrature.size(); ++q)
- {
- const Point<dim> x_q = quadrature.point(q);
- sum += std::exp(std::complex<double>(0,1) *
- (k_vectors[k] * x_q)) *
- fe_collection[fe].shape_value(j,x_q) *
- quadrature.weight(q);
- }
- fourier_transform_matrices[fe](k,j)
- = sum / std::pow(2*numbers::PI, 1.*dim/2);
- }
- }
+ break;
+ }
- // The next thing is to loop over all cells
- // and do our work there, i.e. to locally
- // do the Fourier transform and estimate
- // the decay coefficient. We will use the
- // following two arrays as scratch arrays
- // in the loop and allocate them here to
- // avoid repeated memory allocations:
- std::vector<std::complex<double> > fourier_coefficients (n_fourier_modes);
- Vector<double> local_dof_values;
-
- // Then here is the loop:
- typename hp::DoFHandler<dim>::active_cell_iterator
- cell = dof_handler.begin_active(),
- endc = dof_handler.end();
- for (unsigned int index=0; cell!=endc; ++cell, ++index)
- {
- // Inside the loop, we first need to
- // get the values of the local degrees
- // of freedom (which we put into the
- // <code>local_dof_values</code> array
- // after setting it to the right size)
- // and then need to compute the Fourier
- // transform by multiplying this vector
- // with the matrix ${\cal F}$
- // corresponding to this finite
- // element. We need to write out the
- // multiplication by hand because the
- // objects holding the data do not have
- // <code>vmult</code>-like functions
- // declared:
- local_dof_values.reinit (cell->get_fe().dofs_per_cell);
- cell->get_dof_values (solution, local_dof_values);
-
- for (unsigned int f=0; f<n_fourier_modes; ++f)
+ case 3:
{
- fourier_coefficients[f] = 0;
-
- for (unsigned int i=0; i<cell->get_fe().dofs_per_cell; ++i)
- fourier_coefficients[f] +=
- fourier_transform_matrices[cell->active_fe_index()](f,i)
- *
- local_dof_values(i);
+ for (unsigned int i=0; i<N; ++i)
+ for (unsigned int j=0; j<N; ++j)
+ for (unsigned int k=0; k<N; ++k)
+ if (!((i==0) && (j==0) && (k==0))
+ &&
+ (i*i + j*j + k*k < N*N))
+ {
+ k_vectors.push_back (Point<dim>(numbers::PI * i,
+ numbers::PI * j,
+ numbers::PI * k));
+ k_vectors_magnitude.push_back (i*i+j*j+k*k);
+ }
+
+ break;
}
- // The next thing, as explained in the
- // introduction, is that we wanted to
- // only fit our exponential decay of
- // Fourier coefficients to the largest
- // coefficients for each possible value
- // of $|{\bf k}|$. To this end, we
- // create a map that for each magnitude
- // $|{\bf k}|$ stores the largest $|\hat
- // U_{{\bf k}}|$ found so far, i.e. we
- // overwrite the existing value (or add
- // it to the map) if no value for the
- // current $|{\bf k}|$ exists yet, or if
- // the current value is larger than the
- // previously stored one:
- std::map<unsigned int, double> k_to_max_U_map;
- for (unsigned int f=0; f<n_fourier_modes; ++f)
- if ((k_to_max_U_map.find (k_vectors_magnitude[f]) ==
- k_to_max_U_map.end())
- ||
- (k_to_max_U_map[k_vectors_magnitude[f]] <
- std::abs (fourier_coefficients[f])))
- k_to_max_U_map[k_vectors_magnitude[f]]
- = std::abs (fourier_coefficients[f]);
- // Note that it comes in handy here
- // that we have stored the magnitudes
- // of vectors as integers, since this
- // way we do not have to deal with
- // round-off-sized differences between
- // different values of $|{\bf k}|$.
-
- // As the final task, we have to
- // calculate the various contributions
- // to the formula for $\mu$. We'll only
- // take those Fourier coefficients with
- // the largest magnitude for a given
- // value of $|{\bf k}|$ as explained
- // above:
- double sum_1 = 0,
- sum_ln_k = 0,
- sum_ln_k_square = 0,
- sum_ln_U = 0,
- sum_ln_U_ln_k = 0;
- for (unsigned int f=0; f<n_fourier_modes; ++f)
- if (k_to_max_U_map[k_vectors_magnitude[f]] ==
- std::abs (fourier_coefficients[f]))
+ default:
+ Assert (false, ExcNotImplemented());
+ }
+
+ // After we have set up the Fourier
+ // vectors, we also store their total
+ // number for simplicity, and compute the
+ // logarithm of the magnitude of each of
+ // these vectors since we will need it many
+ // times over further down below:
+ const unsigned n_fourier_modes = k_vectors.size();
+ std::vector<double> ln_k (n_fourier_modes);
+ for (unsigned int i=0; i<n_fourier_modes; ++i)
+ ln_k[i] = std::log (k_vectors[i].norm());
+
+
+ // Next, we need to assemble the matrices
+ // that do the Fourier transforms for each
+ // of the finite elements we deal with,
+ // i.e. the matrices ${\cal F}_{{\bf k},j}$
+ // defined in the introduction. We have to
+ // do that for each of the finite elements
+ // in use. Note that these matrices are
+ // complex-valued, so we can't use the
+ // FullMatrix class. Instead, we use the
+ // Table class template.
+ std::vector<Table<2,std::complex<double> > >
+ fourier_transform_matrices (fe_collection.size());
+
+ // In order to compute them, we of
+ // course can't perform the Fourier
+ // transform analytically, but have
+ // to approximate it using
+ // quadrature. To this end, we use
+ // a quadrature formula that is
+ // obtained by iterating a 2-point
+ // Gauss formula as many times as
+ // the maximal exponent we use for
+ // the term $e^{i{\bf k}\cdot{\bf
+ // x}}$:
+ QGauss<1> base_quadrature (2);
+ QIterated<dim> quadrature (base_quadrature, N);
+
+ // With this, we then loop over all finite
+ // elements in use, reinitialize the
+ // respective matrix ${\cal F}$ to the
+ // right size, and integrate each entry of
+ // the matrix numerically as ${\cal
+ // F}_{{\bf k},j}=\sum_q e^{i{\bf k}\cdot
+ // {\bf x}}\varphi_j({\bf x}_q)
+ // w_q$, where $x_q$
+ // are the quadrature points and $w_q$ are
+ // the quadrature weights. Note that the
+ // imaginary unit $i=\sqrt{-1}$ is obtained
+ // from the standard C++ classes using
+ // <code>std::complex@<double@>(0,1)</code>.
+
+ // Because we work on the unit cell, we can
+ // do all this work without a mapping from
+ // reference to real cell and consequently
+ // do not need the FEValues class.
+ for (unsigned int fe=0; fe<fe_collection.size(); ++fe)
+ {
+ fourier_transform_matrices[fe].reinit (n_fourier_modes,
+ fe_collection[fe].dofs_per_cell);
+
+ for (unsigned int k=0; k<n_fourier_modes; ++k)
+ for (unsigned int j=0; j<fe_collection[fe].dofs_per_cell; ++j)
+ {
+ std::complex<double> sum = 0;
+ for (unsigned int q=0; q<quadrature.size(); ++q)
+ {
+ const Point<dim> x_q = quadrature.point(q);
+ sum += std::exp(std::complex<double>(0,1) *
+ (k_vectors[k] * x_q)) *
+ fe_collection[fe].shape_value(j,x_q) *
+ quadrature.weight(q);
+ }
+ fourier_transform_matrices[fe](k,j)
+ = sum / std::pow(2*numbers::PI, 1.*dim/2);
+ }
+ }
+
+ // The next thing is to loop over all cells
+ // and do our work there, i.e. to locally
+ // do the Fourier transform and estimate
+ // the decay coefficient. We will use the
+ // following two arrays as scratch arrays
+ // in the loop and allocate them here to
+ // avoid repeated memory allocations:
+ std::vector<std::complex<double> > fourier_coefficients (n_fourier_modes);
+ Vector<double> local_dof_values;
+
+ // Then here is the loop:
+ typename hp::DoFHandler<dim>::active_cell_iterator
+ cell = dof_handler.begin_active(),
+ endc = dof_handler.end();
+ for (unsigned int index=0; cell!=endc; ++cell, ++index)
+ {
+ // Inside the loop, we first need to
+ // get the values of the local degrees
+ // of freedom (which we put into the
+ // <code>local_dof_values</code> array
+ // after setting it to the right size)
+ // and then need to compute the Fourier
+ // transform by multiplying this vector
+ // with the matrix ${\cal F}$
+ // corresponding to this finite
+ // element. We need to write out the
+ // multiplication by hand because the
+ // objects holding the data do not have
+ // <code>vmult</code>-like functions
+ // declared:
+ local_dof_values.reinit (cell->get_fe().dofs_per_cell);
+ cell->get_dof_values (solution, local_dof_values);
+
+ for (unsigned int f=0; f<n_fourier_modes; ++f)
{
- sum_1 += 1;
- sum_ln_k += ln_k[f];
- sum_ln_k_square += ln_k[f]*ln_k[f];
- sum_ln_U += std::log (std::abs (fourier_coefficients[f]));
- sum_ln_U_ln_k += std::log (std::abs (fourier_coefficients[f])) *
- ln_k[f];
+ fourier_coefficients[f] = 0;
+
+ for (unsigned int i=0; i<cell->get_fe().dofs_per_cell; ++i)
+ fourier_coefficients[f] +=
+ fourier_transform_matrices[cell->active_fe_index()](f,i)
+ *
+ local_dof_values(i);
}
- // With these so-computed sums, we can
- // now evaluate the formula for $\mu$
- // derived in the introduction:
- const double mu
- = (1./(sum_1*sum_ln_k_square - sum_ln_k*sum_ln_k)
- *
- (sum_ln_k*sum_ln_U - sum_1*sum_ln_U_ln_k));
-
- // The final step is to compute the
- // Sobolev index $s=\mu-\frac d2$ and
- // store it in the vector of estimated
- // values for each cell:
- smoothness_indicators(index) = mu - 1.*dim/2;
- }
+ // The next thing, as explained in the
+ // introduction, is that we wanted to
+ // only fit our exponential decay of
+ // Fourier coefficients to the largest
+ // coefficients for each possible value
+ // of $|{\bf k}|$. To this end, we
+ // create a map that for each magnitude
+ // $|{\bf k}|$ stores the largest $|\hat
+ // U_{{\bf k}}|$ found so far, i.e. we
+ // overwrite the existing value (or add
+ // it to the map) if no value for the
+ // current $|{\bf k}|$ exists yet, or if
+ // the current value is larger than the
+ // previously stored one:
+ std::map<unsigned int, double> k_to_max_U_map;
+ for (unsigned int f=0; f<n_fourier_modes; ++f)
+ if ((k_to_max_U_map.find (k_vectors_magnitude[f]) ==
+ k_to_max_U_map.end())
+ ||
+ (k_to_max_U_map[k_vectors_magnitude[f]] <
+ std::abs (fourier_coefficients[f])))
+ k_to_max_U_map[k_vectors_magnitude[f]]
+ = std::abs (fourier_coefficients[f]);
+ // Note that it comes in handy here
+ // that we have stored the magnitudes
+ // of vectors as integers, since this
+ // way we do not have to deal with
+ // round-off-sized differences between
+ // different values of $|{\bf k}|$.
+
+ // As the final task, we have to
+ // calculate the various contributions
+ // to the formula for $\mu$. We'll only
+ // take those Fourier coefficients with
+ // the largest magnitude for a given
+ // value of $|{\bf k}|$ as explained
+ // above:
+ double sum_1 = 0,
+ sum_ln_k = 0,
+ sum_ln_k_square = 0,
+ sum_ln_U = 0,
+ sum_ln_U_ln_k = 0;
+ for (unsigned int f=0; f<n_fourier_modes; ++f)
+ if (k_to_max_U_map[k_vectors_magnitude[f]] ==
+ std::abs (fourier_coefficients[f]))
+ {
+ sum_1 += 1;
+ sum_ln_k += ln_k[f];
+ sum_ln_k_square += ln_k[f]*ln_k[f];
+ sum_ln_U += std::log (std::abs (fourier_coefficients[f]));
+ sum_ln_U_ln_k += std::log (std::abs (fourier_coefficients[f])) *
+ ln_k[f];
+ }
+
+ // With these so-computed sums, we can
+ // now evaluate the formula for $\mu$
+ // derived in the introduction:
+ const double mu
+ = (1./(sum_1*sum_ln_k_square - sum_ln_k*sum_ln_k)
+ *
+ (sum_ln_k*sum_ln_U - sum_1*sum_ln_U_ln_k));
+
+ // The final step is to compute the
+ // Sobolev index $s=\mu-\frac d2$ and
+ // store it in the vector of estimated
+ // values for each cell:
+ smoothness_indicators(index) = mu - 1.*dim/2;
+ }
+ }
}
// exceptions are thrown, thereby producing
// meaningful output if anything should go
// wrong:
-int main ()
+int main ()
{
try
{
+ using namespace dealii;
+ using namespace Step27;
+
deallog.depth_console (0);
LaplaceProblem<2> laplace_problem;
return 1;
}
- catch (...)
+ catch (...)
{
std::cerr << std::endl << std::endl
<< "----------------------------------------------------"
/* $Id$ */
/* Version: $Name: $ */
/* */
-/* Copyright (C) 2006, 2007, 2008, 2009, 2010 by the deal.II authors and Yaqi Wang */
+/* Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 by the deal.II authors and Yaqi Wang */
/* */
/* 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;
+namespace Step28
+{
+ using namespace dealii;
- // @sect3{Material data}
+ // @sect3{Material data}
- // First up, we need to define a
- // class that provides material data
- // (including diffusion coefficients,
- // removal cross sections, scattering
- // cross sections, fission cross
- // sections and fission spectra) to
- // the main class.
- //
- // The parameter to the constructor
- // determines for how many energy
- // groups we set up the relevant
- // tables. At present, this program
- // only includes data for 2 energy
- // groups, but a more sophisticated
- // program may be able to initialize
- // the data structures for more
- // groups as well, depending on how
- // many energy groups are selected in
- // the parameter file.
- //
- // For each of the different
- // coefficient types, there is one
- // function that returns the value of
- // this coefficient for a particular
- // energy group (or combination of
- // energy groups, as for the
- // distribution cross section
- // $\chi_g\nu\Sigma_{f,g'}$ or
- // scattering cross section
- // $\Sigma_{s,g'\to g}$). In addition
- // to the energy group or groups,
- // these coefficients depend on the
- // type of fuel or control rod, as
- // explained in the introduction. The
- // functions therefore take an
- // additional parameter, @p
- // material_id, that identifies the
- // particular kind of rod. Within
- // this program, we use
- // <code>n_materials=8</code>
- // different kinds of rods.
- //
- // Except for the scattering cross
- // section, each of the coefficients
- // therefore can be represented as an
- // entry in a two-dimensional array
- // of floating point values indexed
- // by the energy group number as well
- // as the material ID. The Table
- // class template is the ideal way to
- // store such data. Finally, the
- // scattering coefficient depends on
- // both two energy group indices and
- // therefore needs to be stored in a
- // three-dimensional array, for which
- // we again use the Table class,
- // where this time the first template
- // argument (denoting the
- // dimensionality of the array) of
- // course needs to be three:
-class MaterialData
-{
- public:
- MaterialData (const unsigned int n_groups);
-
- double get_diffusion_coefficient (const unsigned int group,
- const unsigned int material_id) const;
- double get_removal_XS (const unsigned int group,
- const unsigned int material_id) const;
- double get_fission_XS (const unsigned int group,
- const unsigned int material_id) const;
- double get_fission_dist_XS (const unsigned int group_1,
+ // First up, we need to define a
+ // class that provides material data
+ // (including diffusion coefficients,
+ // removal cross sections, scattering
+ // cross sections, fission cross
+ // sections and fission spectra) to
+ // the main class.
+ //
+ // The parameter to the constructor
+ // determines for how many energy
+ // groups we set up the relevant
+ // tables. At present, this program
+ // only includes data for 2 energy
+ // groups, but a more sophisticated
+ // program may be able to initialize
+ // the data structures for more
+ // groups as well, depending on how
+ // many energy groups are selected in
+ // the parameter file.
+ //
+ // For each of the different
+ // coefficient types, there is one
+ // function that returns the value of
+ // this coefficient for a particular
+ // energy group (or combination of
+ // energy groups, as for the
+ // distribution cross section
+ // $\chi_g\nu\Sigma_{f,g'}$ or
+ // scattering cross section
+ // $\Sigma_{s,g'\to g}$). In addition
+ // to the energy group or groups,
+ // these coefficients depend on the
+ // type of fuel or control rod, as
+ // explained in the introduction. The
+ // functions therefore take an
+ // additional parameter, @p
+ // material_id, that identifies the
+ // particular kind of rod. Within
+ // this program, we use
+ // <code>n_materials=8</code>
+ // different kinds of rods.
+ //
+ // Except for the scattering cross
+ // section, each of the coefficients
+ // therefore can be represented as an
+ // entry in a two-dimensional array
+ // of floating point values indexed
+ // by the energy group number as well
+ // as the material ID. The Table
+ // class template is the ideal way to
+ // store such data. Finally, the
+ // scattering coefficient depends on
+ // both two energy group indices and
+ // therefore needs to be stored in a
+ // three-dimensional array, for which
+ // we again use the Table class,
+ // where this time the first template
+ // argument (denoting the
+ // dimensionality of the array) of
+ // course needs to be three:
+ class MaterialData
+ {
+ public:
+ MaterialData (const unsigned int n_groups);
+
+ double get_diffusion_coefficient (const unsigned int group,
+ const unsigned int material_id) const;
+ double get_removal_XS (const unsigned int group,
+ const unsigned int material_id) const;
+ double get_fission_XS (const unsigned int group,
+ const unsigned int material_id) const;
+ double get_fission_dist_XS (const unsigned int group_1,
+ const unsigned int group_2,
+ const unsigned int material_id) const;
+ double get_scattering_XS (const unsigned int group_1,
const unsigned int group_2,
const unsigned int material_id) const;
- double get_scattering_XS (const unsigned int group_1,
- const unsigned int group_2,
- const unsigned int material_id) const;
- double get_fission_spectrum (const unsigned int group,
- const unsigned int material_id) const;
-
- private:
- const unsigned int n_groups;
- const unsigned int n_materials;
-
- Table<2,double> diffusion;
- Table<2,double> sigma_r;
- Table<2,double> nu_sigma_f;
- Table<3,double> sigma_s;
- Table<2,double> chi;
-};
-
- // The constructor of the class is
- // used to initialize all the
- // material data arrays. It takes the
- // number of energy groups as an
- // argument (an throws an error if
- // that value is not equal to two,
- // since at presently only data for
- // two energy groups is implemented;
- // however, using this, the function
- // remains flexible and extendible
- // into the future). In the member
- // initialization part at the
- // beginning, it also resizes the
- // arrays to their correct sizes.
- //
- // At present, material data is
- // stored for 8 different types of
- // material. This, as well, may
- // easily be extended in the future.
-MaterialData::MaterialData (const unsigned int n_groups)
- :
- n_groups (n_groups),
- n_materials (8),
- diffusion (n_materials, n_groups),
- sigma_r (n_materials, n_groups),
- nu_sigma_f (n_materials, n_groups),
- sigma_s (n_materials, n_groups, n_groups),
- chi (n_materials, n_groups)
-{
- switch (n_groups)
- {
- case 2:
+ double get_fission_spectrum (const unsigned int group,
+ const unsigned int material_id) const;
+
+ private:
+ const unsigned int n_groups;
+ const unsigned int n_materials;
+
+ Table<2,double> diffusion;
+ Table<2,double> sigma_r;
+ Table<2,double> nu_sigma_f;
+ Table<3,double> sigma_s;
+ Table<2,double> chi;
+ };
+
+ // The constructor of the class is
+ // used to initialize all the
+ // material data arrays. It takes the
+ // number of energy groups as an
+ // argument (an throws an error if
+ // that value is not equal to two,
+ // since at presently only data for
+ // two energy groups is implemented;
+ // however, using this, the function
+ // remains flexible and extendible
+ // into the future). In the member
+ // initialization part at the
+ // beginning, it also resizes the
+ // arrays to their correct sizes.
+ //
+ // At present, material data is
+ // stored for 8 different types of
+ // material. This, as well, may
+ // easily be extended in the future.
+ MaterialData::MaterialData (const unsigned int n_groups)
+ :
+ n_groups (n_groups),
+ n_materials (8),
+ diffusion (n_materials, n_groups),
+ sigma_r (n_materials, n_groups),
+ nu_sigma_f (n_materials, n_groups),
+ sigma_s (n_materials, n_groups, n_groups),
+ chi (n_materials, n_groups)
+ {
+ switch (n_groups)
{
- for (unsigned int m=0; m<n_materials; ++m)
- {
- diffusion[m][0] = 1.2;
- diffusion[m][1] = 0.4;
- chi[m][0] = 1.0;
- chi[m][1] = 0.0;
- sigma_r[m][0] = 0.03;
- for (unsigned int group_1=0; group_1<n_groups; ++group_1)
- for (unsigned int group_2=0; group_2<n_groups; ++ group_2)
- sigma_s[m][group_1][group_2] = 0.0;
- }
-
-
- diffusion[5][1] = 0.2;
-
- sigma_r[4][0] = 0.026;
- sigma_r[5][0] = 0.051;
- sigma_r[6][0] = 0.026;
- sigma_r[7][0] = 0.050;
-
- sigma_r[0][1] = 0.100;
- sigma_r[1][1] = 0.200;
- sigma_r[2][1] = 0.250;
- sigma_r[3][1] = 0.300;
- sigma_r[4][1] = 0.020;
- sigma_r[5][1] = 0.040;
- sigma_r[6][1] = 0.020;
- sigma_r[7][1] = 0.800;
-
- nu_sigma_f[0][0] = 0.0050;
- nu_sigma_f[1][0] = 0.0075;
- nu_sigma_f[2][0] = 0.0075;
- nu_sigma_f[3][0] = 0.0075;
- nu_sigma_f[4][0] = 0.000;
- nu_sigma_f[5][0] = 0.000;
- nu_sigma_f[6][0] = 1e-7;
- nu_sigma_f[7][0] = 0.00;
-
- nu_sigma_f[0][1] = 0.125;
- nu_sigma_f[1][1] = 0.300;
- nu_sigma_f[2][1] = 0.375;
- nu_sigma_f[3][1] = 0.450;
- nu_sigma_f[4][1] = 0.000;
- nu_sigma_f[5][1] = 0.000;
- nu_sigma_f[6][1] = 3e-6;
- nu_sigma_f[7][1] = 0.00;
-
- sigma_s[0][0][1] = 0.020;
- sigma_s[1][0][1] = 0.015;
- sigma_s[2][0][1] = 0.015;
- sigma_s[3][0][1] = 0.015;
- sigma_s[4][0][1] = 0.025;
- sigma_s[5][0][1] = 0.050;
- sigma_s[6][0][1] = 0.025;
- sigma_s[7][0][1] = 0.010;
-
- break;
- }
-
+ case 2:
+ {
+ for (unsigned int m=0; m<n_materials; ++m)
+ {
+ diffusion[m][0] = 1.2;
+ diffusion[m][1] = 0.4;
+ chi[m][0] = 1.0;
+ chi[m][1] = 0.0;
+ sigma_r[m][0] = 0.03;
+ for (unsigned int group_1=0; group_1<n_groups; ++group_1)
+ for (unsigned int group_2=0; group_2<n_groups; ++ group_2)
+ sigma_s[m][group_1][group_2] = 0.0;
+ }
- default:
- Assert (false,
- ExcMessage ("Presently, only data for 2 groups is implemented"));
- }
-}
+ diffusion[5][1] = 0.2;
+
+ sigma_r[4][0] = 0.026;
+ sigma_r[5][0] = 0.051;
+ sigma_r[6][0] = 0.026;
+ sigma_r[7][0] = 0.050;
+
+ sigma_r[0][1] = 0.100;
+ sigma_r[1][1] = 0.200;
+ sigma_r[2][1] = 0.250;
+ sigma_r[3][1] = 0.300;
+ sigma_r[4][1] = 0.020;
+ sigma_r[5][1] = 0.040;
+ sigma_r[6][1] = 0.020;
+ sigma_r[7][1] = 0.800;
+
+ nu_sigma_f[0][0] = 0.0050;
+ nu_sigma_f[1][0] = 0.0075;
+ nu_sigma_f[2][0] = 0.0075;
+ nu_sigma_f[3][0] = 0.0075;
+ nu_sigma_f[4][0] = 0.000;
+ nu_sigma_f[5][0] = 0.000;
+ nu_sigma_f[6][0] = 1e-7;
+ nu_sigma_f[7][0] = 0.00;
+
+ nu_sigma_f[0][1] = 0.125;
+ nu_sigma_f[1][1] = 0.300;
+ nu_sigma_f[2][1] = 0.375;
+ nu_sigma_f[3][1] = 0.450;
+ nu_sigma_f[4][1] = 0.000;
+ nu_sigma_f[5][1] = 0.000;
+ nu_sigma_f[6][1] = 3e-6;
+ nu_sigma_f[7][1] = 0.00;
+
+ sigma_s[0][0][1] = 0.020;
+ sigma_s[1][0][1] = 0.015;
+ sigma_s[2][0][1] = 0.015;
+ sigma_s[3][0][1] = 0.015;
+ sigma_s[4][0][1] = 0.025;
+ sigma_s[5][0][1] = 0.050;
+ sigma_s[6][0][1] = 0.025;
+ sigma_s[7][0][1] = 0.010;
+
+ break;
+ }
- // Next are the functions that return
- // the coefficient values for given
- // materials and energy groups. All
- // they do is to make sure that the
- // given arguments are within the
- // allowed ranges, and then look the
- // respective value up in the
- // corresponding tables:
-double
-MaterialData::get_diffusion_coefficient (const unsigned int group,
- const unsigned int material_id) const
-{
- Assert (group < n_groups,
- ExcIndexRange (group, 0, n_groups));
- Assert (material_id < n_materials,
- ExcIndexRange (material_id, 0, n_materials));
- return diffusion[material_id][group];
-}
+ default:
+ Assert (false,
+ ExcMessage ("Presently, only data for 2 groups is implemented"));
+ }
+ }
+ // Next are the functions that return
+ // the coefficient values for given
+ // materials and energy groups. All
+ // they do is to make sure that the
+ // given arguments are within the
+ // allowed ranges, and then look the
+ // respective value up in the
+ // corresponding tables:
+ double
+ MaterialData::get_diffusion_coefficient (const unsigned int group,
+ const unsigned int material_id) const
+ {
+ Assert (group < n_groups,
+ ExcIndexRange (group, 0, n_groups));
+ Assert (material_id < n_materials,
+ ExcIndexRange (material_id, 0, n_materials));
-double
-MaterialData::get_removal_XS (const unsigned int group,
- const unsigned int material_id) const
-{
- Assert (group < n_groups,
- ExcIndexRange (group, 0, n_groups));
- Assert (material_id < n_materials,
- ExcIndexRange (material_id, 0, n_materials));
+ return diffusion[material_id][group];
+ }
- return sigma_r[material_id][group];
-}
-double
-MaterialData::get_fission_XS (const unsigned int group,
- const unsigned int material_id) const
-{
- Assert (group < n_groups,
- ExcIndexRange (group, 0, n_groups));
- Assert (material_id < n_materials,
- ExcIndexRange (material_id, 0, n_materials));
+ double
+ MaterialData::get_removal_XS (const unsigned int group,
+ const unsigned int material_id) const
+ {
+ Assert (group < n_groups,
+ ExcIndexRange (group, 0, n_groups));
+ Assert (material_id < n_materials,
+ ExcIndexRange (material_id, 0, n_materials));
- return nu_sigma_f[material_id][group];
-}
+ return sigma_r[material_id][group];
+ }
+ double
+ MaterialData::get_fission_XS (const unsigned int group,
+ const unsigned int material_id) const
+ {
+ Assert (group < n_groups,
+ ExcIndexRange (group, 0, n_groups));
+ Assert (material_id < n_materials,
+ ExcIndexRange (material_id, 0, n_materials));
-double
-MaterialData::get_scattering_XS (const unsigned int group_1,
- const unsigned int group_2,
- const unsigned int material_id) const
-{
- Assert (group_1 < n_groups,
- ExcIndexRange (group_1, 0, n_groups));
- Assert (group_2 < n_groups,
- ExcIndexRange (group_2, 0, n_groups));
- Assert (material_id < n_materials,
- ExcIndexRange (material_id, 0, n_materials));
-
- return sigma_s[material_id][group_1][group_2];
-}
+ return nu_sigma_f[material_id][group];
+ }
-double
-MaterialData::get_fission_spectrum (const unsigned int group,
- const unsigned int material_id) const
-{
- Assert (group < n_groups,
- ExcIndexRange (group, 0, n_groups));
- Assert (material_id < n_materials,
- ExcIndexRange (material_id, 0, n_materials));
+ double
+ MaterialData::get_scattering_XS (const unsigned int group_1,
+ const unsigned int group_2,
+ const unsigned int material_id) const
+ {
+ Assert (group_1 < n_groups,
+ ExcIndexRange (group_1, 0, n_groups));
+ Assert (group_2 < n_groups,
+ ExcIndexRange (group_2, 0, n_groups));
+ Assert (material_id < n_materials,
+ ExcIndexRange (material_id, 0, n_materials));
+
+ return sigma_s[material_id][group_1][group_2];
+ }
- return chi[material_id][group];
-}
- // The function computing the fission
- // distribution cross section is
- // slightly different, since it
- // computes its value as the product
- // of two other coefficients. We
- // don't need to check arguments
- // here, since this already happens
- // when we call the two other
- // functions involved, even though it
- // would probably not hurt either:
-double
-MaterialData::get_fission_dist_XS (const unsigned int group_1,
- const unsigned int group_2,
- const unsigned int material_id) const
-{
- return (get_fission_spectrum(group_1, material_id) *
- get_fission_XS(group_2, material_id));
-}
+ double
+ MaterialData::get_fission_spectrum (const unsigned int group,
+ const unsigned int material_id) const
+ {
+ Assert (group < n_groups,
+ ExcIndexRange (group, 0, n_groups));
+ Assert (material_id < n_materials,
+ ExcIndexRange (material_id, 0, n_materials));
+ return chi[material_id][group];
+ }
- // @sect3{The <code>EnergyGroup</code> class}
+ // The function computing the fission
+ // distribution cross section is
+ // slightly different, since it
+ // computes its value as the product
+ // of two other coefficients. We
+ // don't need to check arguments
+ // here, since this already happens
+ // when we call the two other
+ // functions involved, even though it
+ // would probably not hurt either:
+ double
+ MaterialData::get_fission_dist_XS (const unsigned int group_1,
+ const unsigned int group_2,
+ const unsigned int material_id) const
+ {
+ return (get_fission_spectrum(group_1, material_id) *
+ get_fission_XS(group_2, material_id));
+ }
- // The first interesting class is the
- // one that contains everything that
- // is specific to a single energy
- // group. To group things that belong
- // together into individual objects,
- // we declare a structure that holds
- // the Triangulation and DoFHandler
- // objects for the mesh used for a
- // single energy group, and a number
- // of other objects and member
- // functions that we will discuss in
- // the following sections.
- //
- // The main reason for this class is
- // as follows: for both the forward
- // problem (with a specified right
- // hand side) as well as for the
- // eigenvalue problem, one typically
- // solves a sequence of problems for
- // a single energy group each, rather
- // than the fully coupled
- // problem. This becomes
- // understandable once one realizes
- // that the system matrix for a
- // single energy group is symmetric
- // and positive definite (it is
- // simply a diffusion operator),
- // whereas the matrix for the fully
- // coupled problem is generally
- // nonsymmetric and not definite. It
- // is also very large and quite full
- // if more than a few energy groups
- // are involved.
- //
- // Let us first look at the equation
- // to solve in the case of an
- // external right hand side (for the time
- // independent case):
- // @f{eqnarray*}
- // -\nabla \cdot(D_g(x) \nabla \phi_g(x))
- // +
- // \Sigma_{r,g}(x)\phi_g(x)
- // =
- // \chi_g\sum_{g'=1}^G\nu\Sigma_{f,g'}(x)\phi_{g'}(x)
- // +
- // \sum_{g'\ne g}\Sigma_{s,g'\to g}(x)\phi_{g'}(x)
- // +
- // s_{\mathrm{ext},g}(x)
- // @f}
- //
- // We would typically solve this
- // equation by moving all the terms
- // on the right hand side with $g'=g$
- // to the left hand side, and solving
- // for $\phi_g$. Of course, we don't
- // know $\phi_{g'}$ yet, since the
- // equations for those variables
- // include right hand side terms
- // involving $\phi_g$. What one
- // typically does in such situations
- // is to iterate: compute
- // @f{eqnarray*}
- // -\nabla \cdot(D_g(x) \nabla \phi^{(n)}_g(x))
- // &+&
- // \Sigma_{r,g}(x)\phi^{(n)}_g(x)
- // \\ &=&
- // \chi_g\sum_{g'=1}^{g-1}\nu\Sigma_{f,g'}(x)\phi^{(n)}_{g'}(x)
- // +
- // \chi_g\sum_{g'=g}^G\nu\Sigma_{f,g'}(x)\phi^{(n-1)}_{g'}(x)
- // +
- // \sum_{g'\ne g, g'<g}\Sigma_{s,g'\to g}(x)\phi^{(n)}_{g'}(x)
- // +
- // \sum_{g'\ne g, g'>g}\Sigma_{s,g'\to g}(x)\phi^{(n-1)}_{g'}(x)
- // +
- // s_{\mathrm{ext},g}(x)
- // @f}
- //
- // In other words, we solve the
- // equation one by one, using values
- // for $\phi_{g'}$ from the previous
- // iteration $n-1$ if $g'\ge g$ and
- // already computed values for
- // $\phi_{g'}$ from the present
- // iteration if $g'<g$.
- //
- // When computing the eigenvalue, we
- // do a very similar iteration,
- // except that we have no external
- // right hand side and that the
- // solution is scaled after each
- // iteration as explained in the
- // introduction.
- //
- // In either case, these two cases
- // can be treated jointly if all we
- // do is to equip the following class
- // with these abilities: (i) form the
- // left hand side matrix, (ii) form
- // the in-group right hand side
- // contribution, i.e. involving the
- // extraneous source, and (iii) form
- // that contribution to the right
- // hand side that stems from group
- // $g'$. This class does exactly
- // these tasks (as well as some
- // book-keeping, such as mesh
- // refinement, setting up matrices
- // and vectors, etc). On the other
- // hand, the class itself has no idea
- // how many energy groups there are,
- // and in particular how they
- // interact, i.e. the decision of how
- // the outer iteration looks (and
- // consequently whether we solve an
- // eigenvalue or a direct problem) is
- // left to the
- // NeutronDiffusionProblem class
- // further down below in this
- // program.
- //
- // So let us go through the class and
- // its interface:
-template <int dim>
-class EnergyGroup
-{
- public:
- // @sect5{Public member functions}
- //
- // The class has a good number of
- // public member functions, since
- // its the way it operates is
- // controlled from the outside,
- // and therefore all functions
- // that do something significant
- // need to be called from another
- // class. Let's start off with
- // book-keeping: the class
- // obviously needs to know which
- // energy group it represents,
- // which material data to use,
- // and from what coarse grid to
- // start. The constructor takes
- // this information and
- // initializes the relevant
- // member variables with that
- // (see below).
- //
- // Then we also need functions
- // that set up the linear system,
- // i.e. correctly size the matrix
- // and its sparsity pattern, etc,
- // given a finite element object
- // to use. The
- // <code>setup_linear_system</code>
- // function does that. Finally,
- // for this initial block, there
- // are two functions that return
- // the number of active cells and
- // degrees of freedom used in
- // this object -- using this, we
- // can make the triangulation and
- // DoF handler member variables
- // private, and do not have to
- // grant external use to it,
- // enhancing encapsulation:
- EnergyGroup (const unsigned int group,
- const MaterialData &material_data,
- const Triangulation<dim> &coarse_grid,
- const FiniteElement<dim> &fe);
-
- void setup_linear_system ();
-
- unsigned int n_active_cells () const;
- unsigned int n_dofs () const;
-
- // Then there are functions that
- // assemble the linear system for
- // each iteration and the present
- // energy group. Note that the
- // matrix is independent of the
- // iteration number, so only has
- // to be computed once for each
- // refinement cycle. The
- // situation is a bit more
- // involved for the right hand
- // side that has to be updated in
- // each inverse power iteration,
- // and that is further
- // complicated by the fact that
- // computing it may involve
- // several different meshes as
- // explained in the
- // introduction. To make things
- // more flexible with regard to
- // solving the forward or the
- // eigenvalue problem, we split
- // the computation of the right
- // hand side into a function that
- // assembles the extraneous
- // source and in-group
- // contributions (which we will
- // call with a zero function as
- // source terms for the
- // eigenvalue problem) and one
- // that computes contributions to
- // the right hand side from
- // another energy group:
- void assemble_system_matrix ();
- void assemble_ingroup_rhs (const Function<dim> &extraneous_source);
- void assemble_cross_group_rhs (const EnergyGroup<dim> &g_prime);
-
- // Next we need a set of
- // functions that actually
- // compute the solution of a
- // linear system, and do
- // something with it (such as
- // computing the fission source
- // contribution mentioned in the
- // introduction, writing
- // graphical information to an
- // output file, computing error
- // indicators, or actually
- // refining the grid based on
- // these criteria and thresholds
- // for refinement and
- // coarsening). All these
- // functions will later be called
- // from the driver class
- // <code>NeutronDiffusionProblem</code>,
- // or any other class you may
- // want to implement to solve a
- // problem involving the neutron
- // flux equations:
- void solve ();
-
- double get_fission_source () const;
-
- void output_results (const unsigned int cycle) const;
-
- void estimate_errors (Vector<float> &error_indicators) const;
-
- void refine_grid (const Vector<float> &error_indicators,
- const double refine_threshold,
- const double coarsen_threshold);
-
- // @sect5{Public data members}
- //
- // As is good practice in object
- // oriented programming, we hide
- // most data members by making
- // them private. However, we have
- // to grant the class that drives
- // the process access to the
- // solution vector as well as the
- // solution of the previous
- // iteration, since in the power
- // iteration, the solution vector
- // is scaled in every iteration
- // by the present guess of the
- // eigenvalue we are looking for:
- public:
-
- Vector<double> solution;
- Vector<double> solution_old;
-
-
- // @sect5{Private data members}
- //
- // The rest of the data members
- // are private. Compared to all
- // the previous tutorial
- // programs, the only new data
- // members are an integer storing
- // which energy group this object
- // represents, and a reference to
- // the material data object that
- // this object's constructor gets
- // passed from the driver
- // class. Likewise, the
- // constructor gets a reference
- // to the finite element object
- // we are to use.
- //
- // Finally, we have to apply
- // boundary values to the linear
- // system in each iteration,
- // i.e. quite frequently. Rather
- // than interpolating them every
- // time, we interpolate them once
- // on each new mesh and then
- // store them along with all the
- // other data of this class:
- private:
-
- const unsigned int group;
- const MaterialData &material_data;
-
- Triangulation<dim> triangulation;
- const FiniteElement<dim> &fe;
- DoFHandler<dim> dof_handler;
-
- SparsityPattern sparsity_pattern;
- SparseMatrix<double> system_matrix;
-
- Vector<double> system_rhs;
-
- std::map<unsigned int,double> boundary_values;
- ConstraintMatrix hanging_node_constraints;
-
-
- // @sect5{Private member functionss}
- //
- // There is one private member
- // function in this class. It
- // recursively walks over cells
- // of two meshes to compute the
- // cross-group right hand side
- // terms. The algorithm for this
- // is explained in the
- // introduction to this
- // program. The arguments to this
- // function are a reference to an
- // object representing the energy
- // group against which we want to
- // integrate a right hand side
- // term, an iterator to a cell of
- // the mesh used for the present
- // energy group, an iterator to a
- // corresponding cell on the
- // other mesh, and the matrix
- // that interpolates the degrees
- // of freedom from the coarser of
- // the two cells to the finer
- // one:
- private:
-
- void
- assemble_cross_group_rhs_recursive (const EnergyGroup<dim> &g_prime,
- const typename DoFHandler<dim>::cell_iterator &cell_g,
- const typename DoFHandler<dim>::cell_iterator &cell_g_prime,
- const FullMatrix<double> prolongation_matrix);
-};
-
-
- // @sect4{Implementation of the <code>EnergyGroup</code> class}
-
- // The first few functions of this
- // class are mostly
- // self-explanatory. The constructor
- // only sets a few data members and
- // creates a copy of the given
- // triangulation as the base for the
- // triangulation used for this energy
- // group. The next two functions
- // simply return data from private
- // data members, thereby enabling us
- // to make these data members
- // private.
-template <int dim>
-EnergyGroup<dim>::EnergyGroup (const unsigned int group,
- const MaterialData &material_data,
- const Triangulation<dim> &coarse_grid,
- const FiniteElement<dim> &fe)
- :
- group (group),
- material_data (material_data),
- fe (fe),
- dof_handler (triangulation)
-{
- triangulation.copy_triangulation (coarse_grid);
- dof_handler.distribute_dofs (fe);
-}
+ // @sect3{The <code>EnergyGroup</code> class}
+ // The first interesting class is the
+ // one that contains everything that
+ // is specific to a single energy
+ // group. To group things that belong
+ // together into individual objects,
+ // we declare a structure that holds
+ // the Triangulation and DoFHandler
+ // objects for the mesh used for a
+ // single energy group, and a number
+ // of other objects and member
+ // functions that we will discuss in
+ // the following sections.
+ //
+ // The main reason for this class is
+ // as follows: for both the forward
+ // problem (with a specified right
+ // hand side) as well as for the
+ // eigenvalue problem, one typically
+ // solves a sequence of problems for
+ // a single energy group each, rather
+ // than the fully coupled
+ // problem. This becomes
+ // understandable once one realizes
+ // that the system matrix for a
+ // single energy group is symmetric
+ // and positive definite (it is
+ // simply a diffusion operator),
+ // whereas the matrix for the fully
+ // coupled problem is generally
+ // nonsymmetric and not definite. It
+ // is also very large and quite full
+ // if more than a few energy groups
+ // are involved.
+ //
+ // Let us first look at the equation
+ // to solve in the case of an
+ // external right hand side (for the time
+ // independent case):
+ // @f{eqnarray*}
+ // -\nabla \cdot(D_g(x) \nabla \phi_g(x))
+ // +
+ // \Sigma_{r,g}(x)\phi_g(x)
+ // =
+ // \chi_g\sum_{g'=1}^G\nu\Sigma_{f,g'}(x)\phi_{g'}(x)
+ // +
+ // \sum_{g'\ne g}\Sigma_{s,g'\to g}(x)\phi_{g'}(x)
+ // +
+ // s_{\mathrm{ext},g}(x)
+ // @f}
+ //
+ // We would typically solve this
+ // equation by moving all the terms
+ // on the right hand side with $g'=g$
+ // to the left hand side, and solving
+ // for $\phi_g$. Of course, we don't
+ // know $\phi_{g'}$ yet, since the
+ // equations for those variables
+ // include right hand side terms
+ // involving $\phi_g$. What one
+ // typically does in such situations
+ // is to iterate: compute
+ // @f{eqnarray*}
+ // -\nabla \cdot(D_g(x) \nabla \phi^{(n)}_g(x))
+ // &+&
+ // \Sigma_{r,g}(x)\phi^{(n)}_g(x)
+ // \\ &=&
+ // \chi_g\sum_{g'=1}^{g-1}\nu\Sigma_{f,g'}(x)\phi^{(n)}_{g'}(x)
+ // +
+ // \chi_g\sum_{g'=g}^G\nu\Sigma_{f,g'}(x)\phi^{(n-1)}_{g'}(x)
+ // +
+ // \sum_{g'\ne g, g'<g}\Sigma_{s,g'\to g}(x)\phi^{(n)}_{g'}(x)
+ // +
+ // \sum_{g'\ne g, g'>g}\Sigma_{s,g'\to g}(x)\phi^{(n-1)}_{g'}(x)
+ // +
+ // s_{\mathrm{ext},g}(x)
+ // @f}
+ //
+ // In other words, we solve the
+ // equation one by one, using values
+ // for $\phi_{g'}$ from the previous
+ // iteration $n-1$ if $g'\ge g$ and
+ // already computed values for
+ // $\phi_{g'}$ from the present
+ // iteration if $g'<g$.
+ //
+ // When computing the eigenvalue, we
+ // do a very similar iteration,
+ // except that we have no external
+ // right hand side and that the
+ // solution is scaled after each
+ // iteration as explained in the
+ // introduction.
+ //
+ // In either case, these two cases
+ // can be treated jointly if all we
+ // do is to equip the following class
+ // with these abilities: (i) form the
+ // left hand side matrix, (ii) form
+ // the in-group right hand side
+ // contribution, i.e. involving the
+ // extraneous source, and (iii) form
+ // that contribution to the right
+ // hand side that stems from group
+ // $g'$. This class does exactly
+ // these tasks (as well as some
+ // book-keeping, such as mesh
+ // refinement, setting up matrices
+ // and vectors, etc). On the other
+ // hand, the class itself has no idea
+ // how many energy groups there are,
+ // and in particular how they
+ // interact, i.e. the decision of how
+ // the outer iteration looks (and
+ // consequently whether we solve an
+ // eigenvalue or a direct problem) is
+ // left to the
+ // NeutronDiffusionProblem class
+ // further down below in this
+ // program.
+ //
+ // So let us go through the class and
+ // its interface:
+ template <int dim>
+ class EnergyGroup
+ {
+ public:
+
+ // @sect5{Public member functions}
+ //
+ // The class has a good number of
+ // public member functions, since
+ // its the way it operates is
+ // controlled from the outside,
+ // and therefore all functions
+ // that do something significant
+ // need to be called from another
+ // class. Let's start off with
+ // book-keeping: the class
+ // obviously needs to know which
+ // energy group it represents,
+ // which material data to use,
+ // and from what coarse grid to
+ // start. The constructor takes
+ // this information and
+ // initializes the relevant
+ // member variables with that
+ // (see below).
+ //
+ // Then we also need functions
+ // that set up the linear system,
+ // i.e. correctly size the matrix
+ // and its sparsity pattern, etc,
+ // given a finite element object
+ // to use. The
+ // <code>setup_linear_system</code>
+ // function does that. Finally,
+ // for this initial block, there
+ // are two functions that return
+ // the number of active cells and
+ // degrees of freedom used in
+ // this object -- using this, we
+ // can make the triangulation and
+ // DoF handler member variables
+ // private, and do not have to
+ // grant external use to it,
+ // enhancing encapsulation:
+ EnergyGroup (const unsigned int group,
+ const MaterialData &material_data,
+ const Triangulation<dim> &coarse_grid,
+ const FiniteElement<dim> &fe);
+
+ void setup_linear_system ();
+
+ unsigned int n_active_cells () const;
+ unsigned int n_dofs () const;
+
+ // Then there are functions that
+ // assemble the linear system for
+ // each iteration and the present
+ // energy group. Note that the
+ // matrix is independent of the
+ // iteration number, so only has
+ // to be computed once for each
+ // refinement cycle. The
+ // situation is a bit more
+ // involved for the right hand
+ // side that has to be updated in
+ // each inverse power iteration,
+ // and that is further
+ // complicated by the fact that
+ // computing it may involve
+ // several different meshes as
+ // explained in the
+ // introduction. To make things
+ // more flexible with regard to
+ // solving the forward or the
+ // eigenvalue problem, we split
+ // the computation of the right
+ // hand side into a function that
+ // assembles the extraneous
+ // source and in-group
+ // contributions (which we will
+ // call with a zero function as
+ // source terms for the
+ // eigenvalue problem) and one
+ // that computes contributions to
+ // the right hand side from
+ // another energy group:
+ void assemble_system_matrix ();
+ void assemble_ingroup_rhs (const Function<dim> &extraneous_source);
+ void assemble_cross_group_rhs (const EnergyGroup<dim> &g_prime);
+
+ // Next we need a set of
+ // functions that actually
+ // compute the solution of a
+ // linear system, and do
+ // something with it (such as
+ // computing the fission source
+ // contribution mentioned in the
+ // introduction, writing
+ // graphical information to an
+ // output file, computing error
+ // indicators, or actually
+ // refining the grid based on
+ // these criteria and thresholds
+ // for refinement and
+ // coarsening). All these
+ // functions will later be called
+ // from the driver class
+ // <code>NeutronDiffusionProblem</code>,
+ // or any other class you may
+ // want to implement to solve a
+ // problem involving the neutron
+ // flux equations:
+ void solve ();
+
+ double get_fission_source () const;
+
+ void output_results (const unsigned int cycle) const;
+
+ void estimate_errors (Vector<float> &error_indicators) const;
+
+ void refine_grid (const Vector<float> &error_indicators,
+ const double refine_threshold,
+ const double coarsen_threshold);
+
+ // @sect5{Public data members}
+ //
+ // As is good practice in object
+ // oriented programming, we hide
+ // most data members by making
+ // them private. However, we have
+ // to grant the class that drives
+ // the process access to the
+ // solution vector as well as the
+ // solution of the previous
+ // iteration, since in the power
+ // iteration, the solution vector
+ // is scaled in every iteration
+ // by the present guess of the
+ // eigenvalue we are looking for:
+ public:
+
+ Vector<double> solution;
+ Vector<double> solution_old;
+
+
+ // @sect5{Private data members}
+ //
+ // The rest of the data members
+ // are private. Compared to all
+ // the previous tutorial
+ // programs, the only new data
+ // members are an integer storing
+ // which energy group this object
+ // represents, and a reference to
+ // the material data object that
+ // this object's constructor gets
+ // passed from the driver
+ // class. Likewise, the
+ // constructor gets a reference
+ // to the finite element object
+ // we are to use.
+ //
+ // Finally, we have to apply
+ // boundary values to the linear
+ // system in each iteration,
+ // i.e. quite frequently. Rather
+ // than interpolating them every
+ // time, we interpolate them once
+ // on each new mesh and then
+ // store them along with all the
+ // other data of this class:
+ private:
+
+ const unsigned int group;
+ const MaterialData &material_data;
+
+ Triangulation<dim> triangulation;
+ const FiniteElement<dim> &fe;
+ DoFHandler<dim> dof_handler;
+
+ SparsityPattern sparsity_pattern;
+ SparseMatrix<double> system_matrix;
+
+ Vector<double> system_rhs;
+
+ std::map<unsigned int,double> boundary_values;
+ ConstraintMatrix hanging_node_constraints;
+
+
+ // @sect5{Private member functionss}
+ //
+ // There is one private member
+ // function in this class. It
+ // recursively walks over cells
+ // of two meshes to compute the
+ // cross-group right hand side
+ // terms. The algorithm for this
+ // is explained in the
+ // introduction to this
+ // program. The arguments to this
+ // function are a reference to an
+ // object representing the energy
+ // group against which we want to
+ // integrate a right hand side
+ // term, an iterator to a cell of
+ // the mesh used for the present
+ // energy group, an iterator to a
+ // corresponding cell on the
+ // other mesh, and the matrix
+ // that interpolates the degrees
+ // of freedom from the coarser of
+ // the two cells to the finer
+ // one:
+ private:
+
+ void
+ assemble_cross_group_rhs_recursive (const EnergyGroup<dim> &g_prime,
+ const typename DoFHandler<dim>::cell_iterator &cell_g,
+ const typename DoFHandler<dim>::cell_iterator &cell_g_prime,
+ const FullMatrix<double> prolongation_matrix);
+ };
+
+
+ // @sect4{Implementation of the <code>EnergyGroup</code> class}
+
+ // The first few functions of this
+ // class are mostly
+ // self-explanatory. The constructor
+ // only sets a few data members and
+ // creates a copy of the given
+ // triangulation as the base for the
+ // triangulation used for this energy
+ // group. The next two functions
+ // simply return data from private
+ // data members, thereby enabling us
+ // to make these data members
+ // private.
+ template <int dim>
+ EnergyGroup<dim>::EnergyGroup (const unsigned int group,
+ const MaterialData &material_data,
+ const Triangulation<dim> &coarse_grid,
+ const FiniteElement<dim> &fe)
+ :
+ group (group),
+ material_data (material_data),
+ fe (fe),
+ dof_handler (triangulation)
+ {
+ triangulation.copy_triangulation (coarse_grid);
+ dof_handler.distribute_dofs (fe);
+ }
-template <int dim>
-unsigned int
-EnergyGroup<dim>::n_active_cells () const
-{
- return triangulation.n_active_cells ();
-}
+ template <int dim>
+ unsigned int
+ EnergyGroup<dim>::n_active_cells () const
+ {
+ return triangulation.n_active_cells ();
+ }
-template <int dim>
-unsigned int
-EnergyGroup<dim>::n_dofs () const
-{
- return dof_handler.n_dofs ();
-}
+ template <int dim>
+ unsigned int
+ EnergyGroup<dim>::n_dofs () const
+ {
+ return dof_handler.n_dofs ();
+ }
- // @sect5{<code>EnergyGroup::setup_linear_system</code>}
- //
- // The first "real" function is the
- // one that sets up the mesh,
- // matrices, etc, on the new mesh or
- // after mesh refinement. We use this
- // function to initialize sparse
- // system matrices, and the right
- // hand side vector. If the solution
- // vector has never been set before
- // (as indicated by a zero size), we
- // also initialize it and set it to a
- // default value. We don't do that if
- // it already has a non-zero size
- // (i.e. this function is called
- // after mesh refinement) since in
- // that case we want to preserve the
- // solution across mesh refinement
- // (something we do in the
- // <code>EnergyGroup::refine_grid</code>
- // function).
-template <int dim>
-void
-EnergyGroup<dim>::setup_linear_system ()
-{
- const unsigned int n_dofs = dof_handler.n_dofs();
- hanging_node_constraints.clear ();
- DoFTools::make_hanging_node_constraints (dof_handler,
- hanging_node_constraints);
- hanging_node_constraints.close ();
- system_matrix.clear ();
+ // @sect5{<code>EnergyGroup::setup_linear_system</code>}
+ //
+ // The first "real" function is the
+ // one that sets up the mesh,
+ // matrices, etc, on the new mesh or
+ // after mesh refinement. We use this
+ // function to initialize sparse
+ // system matrices, and the right
+ // hand side vector. If the solution
+ // vector has never been set before
+ // (as indicated by a zero size), we
+ // also initialize it and set it to a
+ // default value. We don't do that if
+ // it already has a non-zero size
+ // (i.e. this function is called
+ // after mesh refinement) since in
+ // that case we want to preserve the
+ // solution across mesh refinement
+ // (something we do in the
+ // <code>EnergyGroup::refine_grid</code>
+ // function).
+ template <int dim>
+ void
+ EnergyGroup<dim>::setup_linear_system ()
+ {
+ const unsigned int n_dofs = dof_handler.n_dofs();
- sparsity_pattern.reinit (n_dofs, n_dofs,
- dof_handler.max_couplings_between_dofs());
- DoFTools::make_sparsity_pattern (dof_handler, sparsity_pattern);
- hanging_node_constraints.condense (sparsity_pattern);
- sparsity_pattern.compress ();
+ hanging_node_constraints.clear ();
+ DoFTools::make_hanging_node_constraints (dof_handler,
+ hanging_node_constraints);
+ hanging_node_constraints.close ();
- system_matrix.reinit (sparsity_pattern);
+ system_matrix.clear ();
- system_rhs.reinit (n_dofs);
+ sparsity_pattern.reinit (n_dofs, n_dofs,
+ dof_handler.max_couplings_between_dofs());
+ DoFTools::make_sparsity_pattern (dof_handler, sparsity_pattern);
+ hanging_node_constraints.condense (sparsity_pattern);
+ sparsity_pattern.compress ();
- if (solution.size() == 0)
- {
- solution.reinit (n_dofs);
- solution_old.reinit(n_dofs);
- solution_old = 1.0;
- solution = solution_old;
- }
+ system_matrix.reinit (sparsity_pattern);
+ system_rhs.reinit (n_dofs);
- // At the end of this function, we
- // update the list of boundary
- // nodes and their values, by first
- // clearing this list and the
- // re-interpolating boundary values
- // (remember that this function is
- // called after first setting up
- // the mesh, and each time after
- // mesh refinement).
- //
- // To understand the code, it is
- // necessary to realize that we
- // create the mesh using the
- // <code>GridGenerator::subdivided_hyper_rectangle</code>
- // function (in
- // <code>NeutronDiffusionProblem::initialize_problem</code>)
- // where we set the last parameter
- // to <code>true</code>. This means that
- // boundaries of the domain are
- // "colored", i.e. the four (or
- // six, in 3d) sides of the domain
- // are assigned different boundary
- // indicators. As it turns out, the
- // bottom boundary gets indicator
- // zero, the top one boundary
- // indicator one, and left and
- // right boundaries get indicators
- // two and three, respectively.
- //
- // In this program, we simulate
- // only one, namely the top right,
- // quarter of a reactor. That is,
- // we want to interpolate boundary
- // conditions only on the top and
- // right boundaries, while do
- // nothing on the bottom and left
- // boundaries (i.e. impose natural,
- // no-flux Neumann boundary
- // conditions). This is most easily
- // generalized to arbitrary
- // dimension by saying that we want
- // to interpolate on those
- // boundaries with indicators 1, 3,
- // ..., which we do in the
- // following loop (note that calls
- // to
- // <code>VectorTools::interpolate_boundary_values</code>
- // are additive, i.e. they do not
- // first clear the boundary value
- // map):
- boundary_values.clear();
-
- for (unsigned int i=0; i<dim; ++i)
- VectorTools::interpolate_boundary_values (dof_handler,
- 2*i+1,
- ZeroFunction<dim>(),
- boundary_values);
-}
+ if (solution.size() == 0)
+ {
+ solution.reinit (n_dofs);
+ solution_old.reinit(n_dofs);
+ solution_old = 1.0;
+ solution = solution_old;
+ }
+ // At the end of this function, we
+ // update the list of boundary
+ // nodes and their values, by first
+ // clearing this list and the
+ // re-interpolating boundary values
+ // (remember that this function is
+ // called after first setting up
+ // the mesh, and each time after
+ // mesh refinement).
+ //
+ // To understand the code, it is
+ // necessary to realize that we
+ // create the mesh using the
+ // <code>GridGenerator::subdivided_hyper_rectangle</code>
+ // function (in
+ // <code>NeutronDiffusionProblem::initialize_problem</code>)
+ // where we set the last parameter
+ // to <code>true</code>. This means that
+ // boundaries of the domain are
+ // "colored", i.e. the four (or
+ // six, in 3d) sides of the domain
+ // are assigned different boundary
+ // indicators. As it turns out, the
+ // bottom boundary gets indicator
+ // zero, the top one boundary
+ // indicator one, and left and
+ // right boundaries get indicators
+ // two and three, respectively.
+ //
+ // In this program, we simulate
+ // only one, namely the top right,
+ // quarter of a reactor. That is,
+ // we want to interpolate boundary
+ // conditions only on the top and
+ // right boundaries, while do
+ // nothing on the bottom and left
+ // boundaries (i.e. impose natural,
+ // no-flux Neumann boundary
+ // conditions). This is most easily
+ // generalized to arbitrary
+ // dimension by saying that we want
+ // to interpolate on those
+ // boundaries with indicators 1, 3,
+ // ..., which we do in the
+ // following loop (note that calls
+ // to
+ // <code>VectorTools::interpolate_boundary_values</code>
+ // are additive, i.e. they do not
+ // first clear the boundary value
+ // map):
+ boundary_values.clear();
+
+ for (unsigned int i=0; i<dim; ++i)
+ VectorTools::interpolate_boundary_values (dof_handler,
+ 2*i+1,
+ ZeroFunction<dim>(),
+ boundary_values);
+ }
- // @sect5{<code>EnergyGroup::assemble_system_matrix</code>}
- //
- // Next we need functions assembling
- // the system matrix and right hand
- // sides. Assembling the matrix is
- // straightforward given the
- // equations outlined in the
- // introduction as well as what we've
- // seen in previous example
- // programs. Note the use of
- // <code>cell->material_id()</code> to get at
- // the kind of material from which a
- // cell is made up of. Note also how
- // we set the order of the quadrature
- // formula so that it is always
- // appropriate for the finite element
- // in use.
- //
- // Finally, note that since we only
- // assemble the system matrix here,
- // we can't yet eliminate boundary
- // values (we need the right hand
- // side vector for this). We defer
- // this to the <code>EnergyGroup::solve</code>
- // function, at which point all the
- // information is available.
-template <int dim>
-void
-EnergyGroup<dim>::assemble_system_matrix ()
-{
- const QGauss<dim> quadrature_formula(fe.degree + 1);
- FEValues<dim> fe_values (fe, quadrature_formula,
- update_values | update_gradients |
- update_JxW_values);
- const unsigned int dofs_per_cell = fe.dofs_per_cell;
- const unsigned int n_q_points = quadrature_formula.size();
+ // @sect5{<code>EnergyGroup::assemble_system_matrix</code>}
+ //
+ // Next we need functions assembling
+ // the system matrix and right hand
+ // sides. Assembling the matrix is
+ // straightforward given the
+ // equations outlined in the
+ // introduction as well as what we've
+ // seen in previous example
+ // programs. Note the use of
+ // <code>cell->material_id()</code> to get at
+ // the kind of material from which a
+ // cell is made up of. Note also how
+ // we set the order of the quadrature
+ // formula so that it is always
+ // appropriate for the finite element
+ // in use.
+ //
+ // Finally, note that since we only
+ // assemble the system matrix here,
+ // we can't yet eliminate boundary
+ // values (we need the right hand
+ // side vector for this). We defer
+ // this to the <code>EnergyGroup::solve</code>
+ // function, at which point all the
+ // information is available.
+ template <int dim>
+ void
+ EnergyGroup<dim>::assemble_system_matrix ()
+ {
+ const QGauss<dim> quadrature_formula(fe.degree + 1);
- FullMatrix<double> cell_matrix (dofs_per_cell, dofs_per_cell);
- Vector<double> cell_rhs (dofs_per_cell);
+ FEValues<dim> fe_values (fe, quadrature_formula,
+ update_values | update_gradients |
+ update_JxW_values);
- std::vector<unsigned int> local_dof_indices (dofs_per_cell);
+ const unsigned int dofs_per_cell = fe.dofs_per_cell;
+ const unsigned int n_q_points = quadrature_formula.size();
- typename DoFHandler<dim>::active_cell_iterator
- cell = dof_handler.begin_active(),
- endc = dof_handler.end();
+ FullMatrix<double> cell_matrix (dofs_per_cell, dofs_per_cell);
+ Vector<double> cell_rhs (dofs_per_cell);
- for (; cell!=endc; ++cell)
- {
- cell_matrix = 0;
+ std::vector<unsigned int> local_dof_indices (dofs_per_cell);
- fe_values.reinit (cell);
+ typename DoFHandler<dim>::active_cell_iterator
+ cell = dof_handler.begin_active(),
+ endc = dof_handler.end();
- const double diffusion_coefficient
- = material_data.get_diffusion_coefficient (group, cell->material_id());
- const double removal_XS
- = material_data.get_removal_XS (group,cell->material_id());
+ for (; cell!=endc; ++cell)
+ {
+ cell_matrix = 0;
+
+ fe_values.reinit (cell);
+
+ const double diffusion_coefficient
+ = material_data.get_diffusion_coefficient (group, cell->material_id());
+ const double removal_XS
+ = material_data.get_removal_XS (group,cell->material_id());
+
+ 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_matrix(i,j) += ((diffusion_coefficient *
+ fe_values.shape_grad(i,q_point) *
+ fe_values.shape_grad(j,q_point)
+ +
+ removal_XS *
+ fe_values.shape_value(i,q_point) *
+ fe_values.shape_value(j,q_point))
+ *
+ fe_values.JxW(q_point));
+
+ cell->get_dof_indices (local_dof_indices);
- 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_matrix(i,j) += ((diffusion_coefficient *
- fe_values.shape_grad(i,q_point) *
- fe_values.shape_grad(j,q_point)
- +
- removal_XS *
- fe_values.shape_value(i,q_point) *
- fe_values.shape_value(j,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_matrix.add (local_dof_indices[i],
+ local_dof_indices[j],
+ cell_matrix(i,j));
+ }
- hanging_node_constraints.condense (system_matrix);
-}
+ hanging_node_constraints.condense (system_matrix);
+ }
- // @sect5{<code>EnergyGroup::assemble_ingroup_rhs</code>}
- //
- // As explained in the documentation
- // of the <code>EnergyGroup</code> class, we
- // split assembling the right hand
- // side into two parts: the ingroup
- // and the cross-group
- // couplings. First, we need a
- // function to assemble the right
- // hand side of one specific group
- // here, i.e. including an extraneous
- // source (that we will set to zero
- // for the eigenvalue problem) as
- // well as the ingroup fission
- // contributions. (In-group
- // scattering has already been
- // accounted for with the definition
- // of removal cross section.) The
- // function's workings are pretty
- // standard as far as assembling
- // right hand sides go, and therefore
- // does not require more comments
- // except that we mention that the
- // right hand side vector is set to
- // zero at the beginning of the
- // function -- something we are not
- // going to do for the cross-group
- // terms that simply add to the right
- // hand side vector.
-template <int dim>
-void EnergyGroup<dim>::assemble_ingroup_rhs (const Function<dim> &extraneous_source)
-{
- system_rhs.reinit (dof_handler.n_dofs());
+ // @sect5{<code>EnergyGroup::assemble_ingroup_rhs</code>}
+ //
+ // As explained in the documentation
+ // of the <code>EnergyGroup</code> class, we
+ // split assembling the right hand
+ // side into two parts: the ingroup
+ // and the cross-group
+ // couplings. First, we need a
+ // function to assemble the right
+ // hand side of one specific group
+ // here, i.e. including an extraneous
+ // source (that we will set to zero
+ // for the eigenvalue problem) as
+ // well as the ingroup fission
+ // contributions. (In-group
+ // scattering has already been
+ // accounted for with the definition
+ // of removal cross section.) The
+ // function's workings are pretty
+ // standard as far as assembling
+ // right hand sides go, and therefore
+ // does not require more comments
+ // except that we mention that the
+ // right hand side vector is set to
+ // zero at the beginning of the
+ // function -- something we are not
+ // going to do for the cross-group
+ // terms that simply add to the right
+ // hand side vector.
+ template <int dim>
+ void EnergyGroup<dim>::assemble_ingroup_rhs (const Function<dim> &extraneous_source)
+ {
+ system_rhs.reinit (dof_handler.n_dofs());
- const QGauss<dim> quadrature_formula (fe.degree + 1);
+ const QGauss<dim> quadrature_formula (fe.degree + 1);
- const unsigned int dofs_per_cell = fe.dofs_per_cell;
- const unsigned int n_q_points = quadrature_formula.size();
+ const unsigned int dofs_per_cell = fe.dofs_per_cell;
+ const unsigned int n_q_points = quadrature_formula.size();
- FEValues<dim> fe_values (fe, quadrature_formula,
- update_values | update_quadrature_points |
- update_JxW_values);
+ FEValues<dim> fe_values (fe, quadrature_formula,
+ update_values | update_quadrature_points |
+ update_JxW_values);
- Vector<double> cell_rhs (dofs_per_cell);
- std::vector<double> extraneous_source_values (n_q_points);
- std::vector<double> solution_old_values (n_q_points);
+ Vector<double> cell_rhs (dofs_per_cell);
+ std::vector<double> extraneous_source_values (n_q_points);
+ std::vector<double> solution_old_values (n_q_points);
- std::vector<unsigned int> local_dof_indices (dofs_per_cell);
+ std::vector<unsigned int> local_dof_indices (dofs_per_cell);
- typename DoFHandler<dim>::active_cell_iterator
- cell = dof_handler.begin_active(),
- endc = dof_handler.end();
+ typename DoFHandler<dim>::active_cell_iterator
+ cell = dof_handler.begin_active(),
+ endc = dof_handler.end();
- for (; cell!=endc; ++cell)
- {
- cell_rhs = 0;
+ for (; cell!=endc; ++cell)
+ {
+ cell_rhs = 0;
+
+ fe_values.reinit (cell);
- fe_values.reinit (cell);
+ const double fission_dist_XS
+ = material_data.get_fission_dist_XS (group, group, cell->material_id());
- const double fission_dist_XS
- = material_data.get_fission_dist_XS (group, group, cell->material_id());
+ extraneous_source.value_list (fe_values.get_quadrature_points(),
+ extraneous_source_values);
- extraneous_source.value_list (fe_values.get_quadrature_points(),
- extraneous_source_values);
+ fe_values.get_function_values (solution_old, solution_old_values);
- fe_values.get_function_values (solution_old, solution_old_values);
+ cell->get_dof_indices (local_dof_indices);
- cell->get_dof_indices (local_dof_indices);
+ for (unsigned int q_point=0; q_point<n_q_points; ++q_point)
+ for (unsigned int i=0; i<dofs_per_cell; ++i)
+ cell_rhs(i) += ((extraneous_source_values[q_point]
+ +
+ fission_dist_XS *
+ solution_old_values[q_point]) *
+ fe_values.shape_value(i,q_point) *
+ fe_values.JxW(q_point));
- for (unsigned int q_point=0; q_point<n_q_points; ++q_point)
for (unsigned int i=0; i<dofs_per_cell; ++i)
- cell_rhs(i) += ((extraneous_source_values[q_point]
- +
- fission_dist_XS *
- solution_old_values[q_point]) *
- fe_values.shape_value(i,q_point) *
- fe_values.JxW(q_point));
-
- for (unsigned int i=0; i<dofs_per_cell; ++i)
- system_rhs(local_dof_indices[i]) += cell_rhs(i);
- }
-}
+ system_rhs(local_dof_indices[i]) += cell_rhs(i);
+ }
+ }
- // @sect5{<code>EnergyGroup::assemble_cross_group_rhs</code>}
- //
- // The more interesting function for
- // assembling the right hand side
- // vector for the equation of a
- // single energy group is the one
- // that couples energy group $g$ and
- // $g'$. As explained in the
- // introduction, we first have to
- // find the set of cells common to
- // the meshes of the two energy
- // groups. First we call
- // <code>get_finest_common_cells</code> to
- // obtain this list of pairs of
- // common cells from both
- // meshes. Both cells in a pair may
- // not be active but at least one of
- // them is. We then hand each of
- // these cell pairs off to a function
- // tha computes the right hand side
- // terms recursively.
- //
- // Note that ingroup coupling is
- // handled already before, so we exit
- // the function early if $g=g'$.
-template <int dim>
-void EnergyGroup<dim>::assemble_cross_group_rhs (const EnergyGroup<dim> &g_prime)
-{
- if (group == g_prime.group)
- return;
+ // @sect5{<code>EnergyGroup::assemble_cross_group_rhs</code>}
+ //
+ // The more interesting function for
+ // assembling the right hand side
+ // vector for the equation of a
+ // single energy group is the one
+ // that couples energy group $g$ and
+ // $g'$. As explained in the
+ // introduction, we first have to
+ // find the set of cells common to
+ // the meshes of the two energy
+ // groups. First we call
+ // <code>get_finest_common_cells</code> to
+ // obtain this list of pairs of
+ // common cells from both
+ // meshes. Both cells in a pair may
+ // not be active but at least one of
+ // them is. We then hand each of
+ // these cell pairs off to a function
+ // tha computes the right hand side
+ // terms recursively.
+ //
+ // Note that ingroup coupling is
+ // handled already before, so we exit
+ // the function early if $g=g'$.
+ template <int dim>
+ void EnergyGroup<dim>::assemble_cross_group_rhs (const EnergyGroup<dim> &g_prime)
+ {
+ if (group == g_prime.group)
+ return;
- const std::list<std::pair<typename DoFHandler<dim>::cell_iterator,
- typename DoFHandler<dim>::cell_iterator> >
- cell_list
- = GridTools::get_finest_common_cells (dof_handler,
- g_prime.dof_handler);
+ const std::list<std::pair<typename DoFHandler<dim>::cell_iterator,
+ typename DoFHandler<dim>::cell_iterator> >
+ cell_list
+ = GridTools::get_finest_common_cells (dof_handler,
+ g_prime.dof_handler);
- typename std::list<std::pair<typename DoFHandler<dim>::cell_iterator,
- typename DoFHandler<dim>::cell_iterator> >
- ::const_iterator
- cell_iter = cell_list.begin();
+ typename std::list<std::pair<typename DoFHandler<dim>::cell_iterator,
+ typename DoFHandler<dim>::cell_iterator> >
+ ::const_iterator
+ cell_iter = cell_list.begin();
- for (; cell_iter!=cell_list.end(); ++cell_iter)
- {
- FullMatrix<double> unit_matrix (fe.dofs_per_cell);
- for (unsigned int i=0; i<unit_matrix.m(); ++i)
- unit_matrix(i,i) = 1;
- assemble_cross_group_rhs_recursive (g_prime,
- cell_iter->first,
- cell_iter->second,
- unit_matrix);
- }
-}
+ for (; cell_iter!=cell_list.end(); ++cell_iter)
+ {
+ FullMatrix<double> unit_matrix (fe.dofs_per_cell);
+ for (unsigned int i=0; i<unit_matrix.m(); ++i)
+ unit_matrix(i,i) = 1;
+ assemble_cross_group_rhs_recursive (g_prime,
+ cell_iter->first,
+ cell_iter->second,
+ unit_matrix);
+ }
+ }
- // @sect5{<code>EnergyGroup::assemble_cross_group_rhs_recursive</code>}
- //
- // This is finally the function that
- // handles assembling right hand side
- // terms on potentially different
- // meshes recursively, using the
- // algorithm described in the
- // introduction. The function takes a
- // reference to the object
- // representing energy group $g'$, as
- // well as iterators to corresponding
- // cells in the meshes for energy
- // groups $g$ and $g'$. At first,
- // i.e. when this function is called
- // from the one above, these two
- // cells will be matching cells on
- // two meshes; however, one of the
- // two may be further refined, and we
- // will call the function recursively
- // with one of the two iterators
- // replaced by one of the children of
- // the original cell.
- //
- // The last argument is the matrix
- // product matrix $B_{c^{(k)}}^T
- // \cdots B_{c'}^T B_c^T$ from the
- // introduction that interpolates
- // from the coarser of the two cells
- // to the finer one. If the two cells
- // match, then this is the identity
- // matrix -- exactly what we pass to
- // this function initially.
- //
- // The function has to consider two
- // cases: that both of the two cells
- // are not further refined, i.e. have
- // no children, in which case we can
- // finally assemble the right hand
- // side contributions of this pair of
- // cells; and that one of the two
- // cells is further refined, in which
- // case we have to keep recursing by
- // looping over the children of the
- // one cell that is not active. These
- // two cases will be discussed below:
-template <int dim>
-void
-EnergyGroup<dim>::
-assemble_cross_group_rhs_recursive (const EnergyGroup<dim> &g_prime,
- const typename DoFHandler<dim>::cell_iterator &cell_g,
- const typename DoFHandler<dim>::cell_iterator &cell_g_prime,
- const FullMatrix<double> prolongation_matrix)
-{
- // The first case is that both
- // cells are no further refined. In
- // that case, we can assemble the
- // relevant terms (see the
- // introduction). This involves
- // assembling the mass matrix on
- // the finer of the two cells (in
- // fact there are two mass matrices
- // with different coefficients, one
- // for the fission distribution
- // cross section
- // $\chi_g\nu\Sigma_{f,g'}$ and one
- // for the scattering cross section
- // $\Sigma_{s,g'\to g}$). This is
- // straight forward, but note how
- // we determine which of the two
- // cells is ther finer one by
- // looking at the refinement level
- // of the two cells:
- if (!cell_g->has_children() && !cell_g_prime->has_children())
- {
- const QGauss<dim> quadrature_formula (fe.degree+1);
- const unsigned int n_q_points = quadrature_formula.size();
+ // @sect5{<code>EnergyGroup::assemble_cross_group_rhs_recursive</code>}
+ //
+ // This is finally the function that
+ // handles assembling right hand side
+ // terms on potentially different
+ // meshes recursively, using the
+ // algorithm described in the
+ // introduction. The function takes a
+ // reference to the object
+ // representing energy group $g'$, as
+ // well as iterators to corresponding
+ // cells in the meshes for energy
+ // groups $g$ and $g'$. At first,
+ // i.e. when this function is called
+ // from the one above, these two
+ // cells will be matching cells on
+ // two meshes; however, one of the
+ // two may be further refined, and we
+ // will call the function recursively
+ // with one of the two iterators
+ // replaced by one of the children of
+ // the original cell.
+ //
+ // The last argument is the matrix
+ // product matrix $B_{c^{(k)}}^T
+ // \cdots B_{c'}^T B_c^T$ from the
+ // introduction that interpolates
+ // from the coarser of the two cells
+ // to the finer one. If the two cells
+ // match, then this is the identity
+ // matrix -- exactly what we pass to
+ // this function initially.
+ //
+ // The function has to consider two
+ // cases: that both of the two cells
+ // are not further refined, i.e. have
+ // no children, in which case we can
+ // finally assemble the right hand
+ // side contributions of this pair of
+ // cells; and that one of the two
+ // cells is further refined, in which
+ // case we have to keep recursing by
+ // looping over the children of the
+ // one cell that is not active. These
+ // two cases will be discussed below:
+ template <int dim>
+ void
+ EnergyGroup<dim>::
+ assemble_cross_group_rhs_recursive (const EnergyGroup<dim> &g_prime,
+ const typename DoFHandler<dim>::cell_iterator &cell_g,
+ const typename DoFHandler<dim>::cell_iterator &cell_g_prime,
+ const FullMatrix<double> prolongation_matrix)
+ {
+ // The first case is that both
+ // cells are no further refined. In
+ // that case, we can assemble the
+ // relevant terms (see the
+ // introduction). This involves
+ // assembling the mass matrix on
+ // the finer of the two cells (in
+ // fact there are two mass matrices
+ // with different coefficients, one
+ // for the fission distribution
+ // cross section
+ // $\chi_g\nu\Sigma_{f,g'}$ and one
+ // for the scattering cross section
+ // $\Sigma_{s,g'\to g}$). This is
+ // straight forward, but note how
+ // we determine which of the two
+ // cells is ther finer one by
+ // looking at the refinement level
+ // of the two cells:
+ if (!cell_g->has_children() && !cell_g_prime->has_children())
+ {
+ const QGauss<dim> quadrature_formula (fe.degree+1);
+ const unsigned int n_q_points = quadrature_formula.size();
- FEValues<dim> fe_values (fe, quadrature_formula,
- update_values | update_JxW_values);
+ FEValues<dim> fe_values (fe, quadrature_formula,
+ update_values | update_JxW_values);
- if (cell_g->level() > cell_g_prime->level())
- fe_values.reinit (cell_g);
- else
- fe_values.reinit (cell_g_prime);
+ if (cell_g->level() > cell_g_prime->level())
+ fe_values.reinit (cell_g);
+ else
+ fe_values.reinit (cell_g_prime);
+
+ const double fission_dist_XS
+ = material_data.get_fission_dist_XS (group, g_prime.group,
+ cell_g_prime->material_id());
+
+ const double scattering_XS
+ = material_data.get_scattering_XS (g_prime.group, group,
+ cell_g_prime->material_id());
+
+ FullMatrix<double> local_mass_matrix_f (fe.dofs_per_cell,
+ fe.dofs_per_cell);
+ FullMatrix<double> local_mass_matrix_g (fe.dofs_per_cell,
+ fe.dofs_per_cell);
+
+ for (unsigned int q_point=0; q_point<n_q_points; ++q_point)
+ for (unsigned int i=0; i<fe.dofs_per_cell; ++i)
+ for (unsigned int j=0; j<fe.dofs_per_cell; ++j)
+ {
+ local_mass_matrix_f(i,j) += (fission_dist_XS *
+ fe_values.shape_value(i,q_point) *
+ fe_values.shape_value(j,q_point) *
+ fe_values.JxW(q_point));
+ local_mass_matrix_g(i,j) += (scattering_XS *
+ fe_values.shape_value(i,q_point) *
+ fe_values.shape_value(j,q_point) *
+ fe_values.JxW(q_point));
+ }
+
+ // Now we have all the
+ // interpolation (prolongation)
+ // matrices as well as local
+ // mass matrices, so we only
+ // have to form the product
+ // @f[
+ // F_i|_{K_{cc'\cdots
+ // c^{(k)}}} = [B_c B_{c'}
+ // \cdots B_{c^{(k)}}
+ // M_{K_{cc'\cdots
+ // c^{(k)}}}]^{ij}
+ // \phi_{g'}^j,
+ // @f]
+ // or
+ // @f[
+ // F_i|_{K_{cc'\cdots
+ // c^{(k)}}} = [(B_c B_{c'}
+ // \cdots B_{c^{(k)}}
+ // M_{K_{cc'\cdots
+ // c^{(k)}}})^T]^{ij}
+ // \phi_{g'}^j,
+ // @f]
+ // depending on which of the two
+ // cells is the finer. We do this
+ // using either the matrix-vector
+ // product provided by the <code>vmult</code>
+ // function, or the product with the
+ // transpose matrix using <code>Tvmult</code>.
+ // After doing so, we transfer the
+ // result into the global right hand
+ // side vector of energy group $g$.
+ Vector<double> g_prime_new_values (fe.dofs_per_cell);
+ Vector<double> g_prime_old_values (fe.dofs_per_cell);
+ cell_g_prime->get_dof_values (g_prime.solution_old, g_prime_old_values);
+ cell_g_prime->get_dof_values (g_prime.solution, g_prime_new_values);
+
+ Vector<double> cell_rhs (fe.dofs_per_cell);
+ Vector<double> tmp (fe.dofs_per_cell);
+
+ if (cell_g->level() > cell_g_prime->level())
+ {
+ prolongation_matrix.vmult (tmp, g_prime_old_values);
+ local_mass_matrix_f.vmult (cell_rhs, tmp);
- const double fission_dist_XS
- = material_data.get_fission_dist_XS (group, g_prime.group,
- cell_g_prime->material_id());
+ prolongation_matrix.vmult (tmp, g_prime_new_values);
+ local_mass_matrix_g.vmult_add (cell_rhs, tmp);
+ }
+ else
+ {
+ local_mass_matrix_f.vmult (tmp, g_prime_old_values);
+ prolongation_matrix.Tvmult (cell_rhs, tmp);
- const double scattering_XS
- = material_data.get_scattering_XS (g_prime.group, group,
- cell_g_prime->material_id());
+ local_mass_matrix_g.vmult (tmp, g_prime_new_values);
+ prolongation_matrix.Tvmult_add (cell_rhs, tmp);
+ }
- FullMatrix<double> local_mass_matrix_f (fe.dofs_per_cell,
- fe.dofs_per_cell);
- FullMatrix<double> local_mass_matrix_g (fe.dofs_per_cell,
- fe.dofs_per_cell);
+ std::vector<unsigned int> local_dof_indices (fe.dofs_per_cell);
+ cell_g->get_dof_indices (local_dof_indices);
- for (unsigned int q_point=0; q_point<n_q_points; ++q_point)
for (unsigned int i=0; i<fe.dofs_per_cell; ++i)
- for (unsigned int j=0; j<fe.dofs_per_cell; ++j)
- {
- local_mass_matrix_f(i,j) += (fission_dist_XS *
- fe_values.shape_value(i,q_point) *
- fe_values.shape_value(j,q_point) *
- fe_values.JxW(q_point));
- local_mass_matrix_g(i,j) += (scattering_XS *
- fe_values.shape_value(i,q_point) *
- fe_values.shape_value(j,q_point) *
- fe_values.JxW(q_point));
- }
-
- // Now we have all the
- // interpolation (prolongation)
- // matrices as well as local
- // mass matrices, so we only
- // have to form the product
- // @f[
- // F_i|_{K_{cc'\cdots
- // c^{(k)}}} = [B_c B_{c'}
- // \cdots B_{c^{(k)}}
- // M_{K_{cc'\cdots
- // c^{(k)}}}]^{ij}
- // \phi_{g'}^j,
- // @f]
- // or
- // @f[
- // F_i|_{K_{cc'\cdots
- // c^{(k)}}} = [(B_c B_{c'}
- // \cdots B_{c^{(k)}}
- // M_{K_{cc'\cdots
- // c^{(k)}}})^T]^{ij}
- // \phi_{g'}^j,
- // @f]
- // depending on which of the two
- // cells is the finer. We do this
- // using either the matrix-vector
- // product provided by the <code>vmult</code>
- // function, or the product with the
- // transpose matrix using <code>Tvmult</code>.
- // After doing so, we transfer the
- // result into the global right hand
- // side vector of energy group $g$.
- Vector<double> g_prime_new_values (fe.dofs_per_cell);
- Vector<double> g_prime_old_values (fe.dofs_per_cell);
- cell_g_prime->get_dof_values (g_prime.solution_old, g_prime_old_values);
- cell_g_prime->get_dof_values (g_prime.solution, g_prime_new_values);
-
- Vector<double> cell_rhs (fe.dofs_per_cell);
- Vector<double> tmp (fe.dofs_per_cell);
-
- if (cell_g->level() > cell_g_prime->level())
- {
- prolongation_matrix.vmult (tmp, g_prime_old_values);
- local_mass_matrix_f.vmult (cell_rhs, tmp);
+ system_rhs(local_dof_indices[i]) += cell_rhs(i);
+ }
- prolongation_matrix.vmult (tmp, g_prime_new_values);
- local_mass_matrix_g.vmult_add (cell_rhs, tmp);
- }
- else
+ // The alternative is that one of
+ // the two cells is further
+ // refined. In that case, we have
+ // to loop over all the children,
+ // multiply the existing
+ // interpolation (prolongation)
+ // product of matrices from the
+ // left with the interpolation from
+ // the present cell to its child
+ // (using the matrix-matrix
+ // multiplication function
+ // <code>mmult</code>), and then hand the
+ // result off to this very same
+ // function again, but with the
+ // cell that has children replaced
+ // by one of its children:
+ else
+ for (unsigned int child=0; child<GeometryInfo<dim>::max_children_per_cell;++child)
{
- local_mass_matrix_f.vmult (tmp, g_prime_old_values);
- prolongation_matrix.Tvmult (cell_rhs, tmp);
-
- local_mass_matrix_g.vmult (tmp, g_prime_new_values);
- prolongation_matrix.Tvmult_add (cell_rhs, tmp);
+ FullMatrix<double> new_matrix (fe.dofs_per_cell, fe.dofs_per_cell);
+ fe.get_prolongation_matrix(child).mmult (new_matrix,
+ prolongation_matrix);
+
+ if (cell_g->has_children())
+ assemble_cross_group_rhs_recursive (g_prime,
+ cell_g->child(child), cell_g_prime,
+ new_matrix);
+ else
+ assemble_cross_group_rhs_recursive (g_prime,
+ cell_g, cell_g_prime->child(child),
+ new_matrix);
}
+ }
- std::vector<unsigned int> local_dof_indices (fe.dofs_per_cell);
- cell_g->get_dof_indices (local_dof_indices);
-
- for (unsigned int i=0; i<fe.dofs_per_cell; ++i)
- system_rhs(local_dof_indices[i]) += cell_rhs(i);
- }
-
- // The alternative is that one of
- // the two cells is further
- // refined. In that case, we have
- // to loop over all the children,
- // multiply the existing
- // interpolation (prolongation)
- // product of matrices from the
- // left with the interpolation from
- // the present cell to its child
- // (using the matrix-matrix
- // multiplication function
- // <code>mmult</code>), and then hand the
- // result off to this very same
- // function again, but with the
- // cell that has children replaced
- // by one of its children:
- else
- for (unsigned int child=0; child<GeometryInfo<dim>::max_children_per_cell;++child)
- {
- FullMatrix<double> new_matrix (fe.dofs_per_cell, fe.dofs_per_cell);
- fe.get_prolongation_matrix(child).mmult (new_matrix,
- prolongation_matrix);
-
- if (cell_g->has_children())
- assemble_cross_group_rhs_recursive (g_prime,
- cell_g->child(child), cell_g_prime,
- new_matrix);
- else
- assemble_cross_group_rhs_recursive (g_prime,
- cell_g, cell_g_prime->child(child),
- new_matrix);
- }
-}
-
-
- // @sect5{<code>EnergyGroup::get_fission_source</code>}
- //
- // In the (inverse) power iteration,
- // we use the integrated fission
- // source to update the
- // $k$-eigenvalue. Given its
- // definition, the following function
- // is essentially self-explanatory:
-template <int dim>
-double EnergyGroup<dim>::get_fission_source () const
-{
- const QGauss<dim> quadrature_formula (fe.degree + 1);
- const unsigned int n_q_points = quadrature_formula.size();
-
- FEValues<dim> fe_values (fe, quadrature_formula,
- update_values | update_JxW_values);
-
- std::vector<double> solution_values (n_q_points);
-
- double fission_source = 0;
-
- typename DoFHandler<dim>::active_cell_iterator
- cell = dof_handler.begin_active(),
- endc = dof_handler.end();
- for (; cell!=endc; ++cell)
- {
- fe_values.reinit (cell);
-
- const double fission_XS
- = material_data.get_fission_XS(group, cell->material_id());
- fe_values.get_function_values (solution, solution_values);
+ // @sect5{<code>EnergyGroup::get_fission_source</code>}
+ //
+ // In the (inverse) power iteration,
+ // we use the integrated fission
+ // source to update the
+ // $k$-eigenvalue. Given its
+ // definition, the following function
+ // is essentially self-explanatory:
+ template <int dim>
+ double EnergyGroup<dim>::get_fission_source () const
+ {
+ const QGauss<dim> quadrature_formula (fe.degree + 1);
+ const unsigned int n_q_points = quadrature_formula.size();
- for (unsigned int q_point=0; q_point<n_q_points; ++q_point)
- fission_source += (fission_XS *
- solution_values[q_point] *
- fe_values.JxW(q_point));
- }
+ FEValues<dim> fe_values (fe, quadrature_formula,
+ update_values | update_JxW_values);
- return fission_source;
-}
+ std::vector<double> solution_values (n_q_points);
+ double fission_source = 0;
- // @sect5{<code>EnergyGroup::solve</code>}
- //
- // Next a function that solves the
- // linear system assembled
- // before. Things are pretty much
- // standard, except that we delayed
- // applying boundary values until we
- // get here, since in all the
- // previous functions we were still
- // adding up contributions the right
- // hand side vector.
-template <int dim>
-void
-EnergyGroup<dim>::solve ()
-{
- hanging_node_constraints.condense (system_rhs);
- MatrixTools::apply_boundary_values (boundary_values,
- system_matrix,
- solution,
- system_rhs);
+ typename DoFHandler<dim>::active_cell_iterator
+ cell = dof_handler.begin_active(),
+ endc = dof_handler.end();
+ for (; cell!=endc; ++cell)
+ {
+ fe_values.reinit (cell);
- SolverControl solver_control (system_matrix.m(),
- 1e-12*system_rhs.l2_norm());
- SolverCG<> cg (solver_control);
+ const double fission_XS
+ = material_data.get_fission_XS(group, cell->material_id());
- PreconditionSSOR<> preconditioner;
- preconditioner.initialize(system_matrix, 1.2);
+ fe_values.get_function_values (solution, solution_values);
- cg.solve (system_matrix, solution, system_rhs, preconditioner);
+ for (unsigned int q_point=0; q_point<n_q_points; ++q_point)
+ fission_source += (fission_XS *
+ solution_values[q_point] *
+ fe_values.JxW(q_point));
+ }
- hanging_node_constraints.distribute (solution);
-}
+ return fission_source;
+ }
+ // @sect5{<code>EnergyGroup::solve</code>}
+ //
+ // Next a function that solves the
+ // linear system assembled
+ // before. Things are pretty much
+ // standard, except that we delayed
+ // applying boundary values until we
+ // get here, since in all the
+ // previous functions we were still
+ // adding up contributions the right
+ // hand side vector.
+ template <int dim>
+ void
+ EnergyGroup<dim>::solve ()
+ {
+ hanging_node_constraints.condense (system_rhs);
+ MatrixTools::apply_boundary_values (boundary_values,
+ system_matrix,
+ solution,
+ system_rhs);
- // @sect5{<code>EnergyGroup::estimate_errors</code>}
- //
- // Mesh refinement is split into two
- // functions. The first estimates the
- // error for each cell, normalizes it
- // by the magnitude of the solution,
- // and returns it in the vector given
- // as an argument. The calling
- // function collects all error
- // indicators from all energy groups,
- // and computes thresholds for
- // refining and coarsening cells.
-template <int dim>
-void EnergyGroup<dim>::estimate_errors (Vector<float> &error_indicators) const
-{
- KellyErrorEstimator<dim>::estimate (dof_handler,
- QGauss<dim-1> (fe.degree + 1),
- typename FunctionMap<dim>::type(),
- solution,
- error_indicators);
- error_indicators /= solution.linfty_norm();
-}
+ SolverControl solver_control (system_matrix.m(),
+ 1e-12*system_rhs.l2_norm());
+ SolverCG<> cg (solver_control);
+ PreconditionSSOR<> preconditioner;
+ preconditioner.initialize(system_matrix, 1.2);
+ cg.solve (system_matrix, solution, system_rhs, preconditioner);
- // @sect5{<code>EnergyGroup::refine_grid</code>}
- //
- // The second part is to refine the
- // grid given the error indicators
- // compute in the previous function
- // and error thresholds above which
- // cells shall be refined or below
- // which cells shall be
- // coarsened. Note that we do not use
- // any of the functions in
- // <code>GridRefinement</code> here,
- // but rather set refinement flags
- // ourselves.
- //
- // After setting these flags, we use
- // the SolutionTransfer class to move
- // the solution vector from the old
- // to the new mesh. The procedure
- // used here is described in detail
- // in the documentation of that
- // class:
-template <int dim>
-void EnergyGroup<dim>::refine_grid (const Vector<float> &error_indicators,
- const double refine_threshold,
- const double coarsen_threshold)
-{
- typename Triangulation<dim>::active_cell_iterator
- cell = triangulation.begin_active(),
- endc = triangulation.end();
+ hanging_node_constraints.distribute (solution);
+ }
- for (unsigned int cell_index=0; cell!=endc; ++cell, ++cell_index)
- if (error_indicators(cell_index) > refine_threshold)
- cell->set_refine_flag ();
- else if (error_indicators(cell_index) < coarsen_threshold)
- cell->set_coarsen_flag ();
- SolutionTransfer<dim> soltrans(dof_handler);
- triangulation.prepare_coarsening_and_refinement();
- soltrans.prepare_for_coarsening_and_refinement(solution);
+ // @sect5{<code>EnergyGroup::estimate_errors</code>}
+ //
+ // Mesh refinement is split into two
+ // functions. The first estimates the
+ // error for each cell, normalizes it
+ // by the magnitude of the solution,
+ // and returns it in the vector given
+ // as an argument. The calling
+ // function collects all error
+ // indicators from all energy groups,
+ // and computes thresholds for
+ // refining and coarsening cells.
+ template <int dim>
+ void EnergyGroup<dim>::estimate_errors (Vector<float> &error_indicators) const
+ {
+ KellyErrorEstimator<dim>::estimate (dof_handler,
+ QGauss<dim-1> (fe.degree + 1),
+ typename FunctionMap<dim>::type(),
+ solution,
+ error_indicators);
+ error_indicators /= solution.linfty_norm();
+ }
- triangulation.execute_coarsening_and_refinement ();
- dof_handler.distribute_dofs (fe);
- solution.reinit (dof_handler.n_dofs());
- soltrans.interpolate(solution_old, solution);
- solution_old.reinit (dof_handler.n_dofs());
- solution_old = solution;
-}
+ // @sect5{<code>EnergyGroup::refine_grid</code>}
+ //
+ // The second part is to refine the
+ // grid given the error indicators
+ // compute in the previous function
+ // and error thresholds above which
+ // cells shall be refined or below
+ // which cells shall be
+ // coarsened. Note that we do not use
+ // any of the functions in
+ // <code>GridRefinement</code> here,
+ // but rather set refinement flags
+ // ourselves.
+ //
+ // After setting these flags, we use
+ // the SolutionTransfer class to move
+ // the solution vector from the old
+ // to the new mesh. The procedure
+ // used here is described in detail
+ // in the documentation of that
+ // class:
+ template <int dim>
+ void EnergyGroup<dim>::refine_grid (const Vector<float> &error_indicators,
+ const double refine_threshold,
+ const double coarsen_threshold)
+ {
+ typename Triangulation<dim>::active_cell_iterator
+ cell = triangulation.begin_active(),
+ endc = triangulation.end();
+ for (unsigned int cell_index=0; cell!=endc; ++cell, ++cell_index)
+ if (error_indicators(cell_index) > refine_threshold)
+ cell->set_refine_flag ();
+ else if (error_indicators(cell_index) < coarsen_threshold)
+ cell->set_coarsen_flag ();
- // @sect5{<code>EnergyGroup::output_results</code>}
- //
- // The last function of this class
- // outputs meshes and solutions after
- // each mesh iteration. This has been
- // shown many times before. The only
- // thing worth pointing out is the
- // use of the
- // <code>Utilities::int_to_string</code>
- // function to convert an integer
- // into its string
- // representation. The second
- // argument of that function denotes
- // how many digits we shall use -- if
- // this value was larger than one,
- // then the number would be padded by
- // leading zeros.
-template <int dim>
-void
-EnergyGroup<dim>::output_results (const unsigned int cycle) const
-{
- {
- const std::string filename = std::string("grid-") +
- Utilities::int_to_string(group,1) +
- "." +
- Utilities::int_to_string(cycle,1) +
- ".eps";
- std::ofstream output (filename.c_str());
-
- GridOut grid_out;
- grid_out.write_eps (triangulation, output);
- }
+ SolutionTransfer<dim> soltrans(dof_handler);
- {
- const std::string filename = std::string("solution-") +
- Utilities::int_to_string(group,1) +
- "." +
- Utilities::int_to_string(cycle,1) +
- ".gmv";
+ triangulation.prepare_coarsening_and_refinement();
+ soltrans.prepare_for_coarsening_and_refinement(solution);
- DataOut<dim> data_out;
+ triangulation.execute_coarsening_and_refinement ();
+ dof_handler.distribute_dofs (fe);
- data_out.attach_dof_handler (dof_handler);
- data_out.add_data_vector (solution, "solution");
- data_out.build_patches ();
+ solution.reinit (dof_handler.n_dofs());
+ soltrans.interpolate(solution_old, solution);
- std::ofstream output (filename.c_str());
- data_out.write_gmv (output);
+ solution_old.reinit (dof_handler.n_dofs());
+ solution_old = solution;
}
-}
-
- // @sect3{The <code>NeutronDiffusionProblem</code> class template}
-
- // This is the main class of the
- // program, not because it implements
- // all the functionality (in fact,
- // most of it is implemented in the
- // <code>EnergyGroup</code> class)
- // but because it contains the
- // driving algorithm that determines
- // what to compute and when. It is
- // mostly as shown in many of the
- // other tutorial programs in that it
- // has a public <code>run</code>
- // function and private functions
- // doing all the rest. In several
- // places, we have to do something
- // for all energy groups, in which
- // case we will start threads for
- // each group to let these things run
- // in parallel if deal.II was
- // configured for multithreading.
- // For strategies of parallelization,
- // take a look at the @ref threads module.
- //
- // The biggest difference to previous
- // example programs is that we also
- // declare a nested class that has
- // member variables for all the
- // run-time parameters that can be
- // passed to the program in an input
- // file. Right now, these are the
- // number of energy groups, the
- // number of refinement cycles, the
- // polynomial degree of the finite
- // element to be used, and the
- // tolerance used to determine when
- // convergence of the inverse power
- // iteration has occurred. In
- // addition, we have a constructor of
- // this class that sets all these
- // values to their default values, a
- // function
- // <code>declare_parameters</code>
- // that described to the
- // ParameterHandler class already
- // used in step-19
- // what parameters are accepted in
- // the input file, and a function
- // <code>get_parameters</code> that
- // can extract the values of these
- // parameters from a ParameterHandler
- // object.
-template <int dim>
-class NeutronDiffusionProblem
-{
- public:
- class Parameters
+ // @sect5{<code>EnergyGroup::output_results</code>}
+ //
+ // The last function of this class
+ // outputs meshes and solutions after
+ // each mesh iteration. This has been
+ // shown many times before. The only
+ // thing worth pointing out is the
+ // use of the
+ // <code>Utilities::int_to_string</code>
+ // function to convert an integer
+ // into its string
+ // representation. The second
+ // argument of that function denotes
+ // how many digits we shall use -- if
+ // this value was larger than one,
+ // then the number would be padded by
+ // leading zeros.
+ template <int dim>
+ void
+ EnergyGroup<dim>::output_results (const unsigned int cycle) const
+ {
{
- public:
- Parameters ();
+ const std::string filename = std::string("grid-") +
+ Utilities::int_to_string(group,1) +
+ "." +
+ Utilities::int_to_string(cycle,1) +
+ ".eps";
+ std::ofstream output (filename.c_str());
+
+ GridOut grid_out;
+ grid_out.write_eps (triangulation, output);
+ }
- static void declare_parameters (ParameterHandler &prm);
- void get_parameters (ParameterHandler &prm);
+ {
+ const std::string filename = std::string("solution-") +
+ Utilities::int_to_string(group,1) +
+ "." +
+ Utilities::int_to_string(cycle,1) +
+ ".gmv";
- unsigned int n_groups;
- unsigned int n_refinement_cycles;
+ DataOut<dim> data_out;
- unsigned int fe_degree;
+ data_out.attach_dof_handler (dof_handler);
+ data_out.add_data_vector (solution, "solution");
+ data_out.build_patches ();
- double convergence_tolerance;
- };
+ std::ofstream output (filename.c_str());
+ data_out.write_gmv (output);
+ }
+ }
- NeutronDiffusionProblem (const Parameters ¶meters);
- ~NeutronDiffusionProblem ();
-
- void run ();
-
- private:
- // @sect5{Private member functions}
-
- // There are not that many member
- // functions in this class since
- // most of the functionality has
- // been moved into the
- // <code>EnergyGroup</code> class
- // and is simply called from the
- // <code>run()</code> member
- // function of this class. The
- // ones that remain have
- // self-explanatory names:
- void initialize_problem();
-
- void refine_grid ();
-
- double get_total_fission_source () const;
-
-
- // @sect5{Private member variables}
-
- // Next, we have a few member
- // variables. In particular,
- // these are (i) a reference to
- // the parameter object (owned by
- // the main function of this
- // program, and passed to the
- // constructor of this class),
- // (ii) an object describing the
- // material parameters for the
- // number of energy groups
- // requested in the input file,
- // and (iii) the finite element
- // to be used by all energy
- // groups:
- const Parameters ¶meters;
- const MaterialData material_data;
- FE_Q<dim> fe;
-
- // Furthermore, we have (iv) the
- // value of the computed
- // eigenvalue at the present
- // iteration. This is, in fact,
- // the only part of the solution
- // that is shared between all
- // energy groups -- all other
- // parts of the solution, such as
- // neutron fluxes are particular
- // to one or the other energy
- // group, and are therefore
- // stored in objects that
- // describe a single energy
- // group:
- double k_eff;
-
- // Finally, (v), we have an array
- // of pointers to the energy
- // group objects. The length of
- // this array is, of course,
- // equal to the number of energy
- // groups specified in the
- // parameter file.
- std::vector<EnergyGroup<dim>*> energy_groups;
-};
-
-
- // @sect4{Implementation of the <code>NeutronDiffusionProblem::Parameters</code> class}
-
- // Before going on to the
- // implementation of the outer class,
- // we have to implement the functions
- // of the parameters structure. This
- // is pretty straightforward and, in
- // fact, looks pretty much the same
- // for all such parameters classes
- // using the ParameterHandler
- // capabilities. We will therefore
- // not comment further on this:
-template <int dim>
-NeutronDiffusionProblem<dim>::Parameters::Parameters ()
- :
- n_groups (2),
- n_refinement_cycles (5),
- fe_degree (2),
- convergence_tolerance (1e-12)
-{}
-
-
-
-template <int dim>
-void
-NeutronDiffusionProblem<dim>::Parameters::
-declare_parameters (ParameterHandler &prm)
-{
- prm.declare_entry ("Number of energy groups", "2",
- Patterns::Integer (),
- "The number of energy different groups considered");
- prm.declare_entry ("Refinement cycles", "5",
- Patterns::Integer (),
- "Number of refinement cycles to be performed");
- prm.declare_entry ("Finite element degree", "2",
- Patterns::Integer (),
- "Polynomial degree of the finite element to be used");
- prm.declare_entry ("Power iteration tolerance", "1e-12",
- Patterns::Double (),
- "Inner power iterations are stopped when the change in k_eff falls "
- "below this tolerance");
-}
+ // @sect3{The <code>NeutronDiffusionProblem</code> class template}
+
+ // This is the main class of the
+ // program, not because it implements
+ // all the functionality (in fact,
+ // most of it is implemented in the
+ // <code>EnergyGroup</code> class)
+ // but because it contains the
+ // driving algorithm that determines
+ // what to compute and when. It is
+ // mostly as shown in many of the
+ // other tutorial programs in that it
+ // has a public <code>run</code>
+ // function and private functions
+ // doing all the rest. In several
+ // places, we have to do something
+ // for all energy groups, in which
+ // case we will start threads for
+ // each group to let these things run
+ // in parallel if deal.II was
+ // configured for multithreading.
+ // For strategies of parallelization,
+ // take a look at the @ref threads module.
+ //
+ // The biggest difference to previous
+ // example programs is that we also
+ // declare a nested class that has
+ // member variables for all the
+ // run-time parameters that can be
+ // passed to the program in an input
+ // file. Right now, these are the
+ // number of energy groups, the
+ // number of refinement cycles, the
+ // polynomial degree of the finite
+ // element to be used, and the
+ // tolerance used to determine when
+ // convergence of the inverse power
+ // iteration has occurred. In
+ // addition, we have a constructor of
+ // this class that sets all these
+ // values to their default values, a
+ // function
+ // <code>declare_parameters</code>
+ // that described to the
+ // ParameterHandler class already
+ // used in step-19
+ // what parameters are accepted in
+ // the input file, and a function
+ // <code>get_parameters</code> that
+ // can extract the values of these
+ // parameters from a ParameterHandler
+ // object.
+ template <int dim>
+ class NeutronDiffusionProblem
+ {
+ public:
+ class Parameters
+ {
+ public:
+ Parameters ();
+
+ static void declare_parameters (ParameterHandler &prm);
+ void get_parameters (ParameterHandler &prm);
+
+ unsigned int n_groups;
+ unsigned int n_refinement_cycles;
+
+ unsigned int fe_degree;
+
+ double convergence_tolerance;
+ };
+
+
+
+ NeutronDiffusionProblem (const Parameters ¶meters);
+ ~NeutronDiffusionProblem ();
+
+ void run ();
+
+ private:
+ // @sect5{Private member functions}
+
+ // There are not that many member
+ // functions in this class since
+ // most of the functionality has
+ // been moved into the
+ // <code>EnergyGroup</code> class
+ // and is simply called from the
+ // <code>run()</code> member
+ // function of this class. The
+ // ones that remain have
+ // self-explanatory names:
+ void initialize_problem();
+
+ void refine_grid ();
+
+ double get_total_fission_source () const;
+
+
+ // @sect5{Private member variables}
+
+ // Next, we have a few member
+ // variables. In particular,
+ // these are (i) a reference to
+ // the parameter object (owned by
+ // the main function of this
+ // program, and passed to the
+ // constructor of this class),
+ // (ii) an object describing the
+ // material parameters for the
+ // number of energy groups
+ // requested in the input file,
+ // and (iii) the finite element
+ // to be used by all energy
+ // groups:
+ const Parameters ¶meters;
+ const MaterialData material_data;
+ FE_Q<dim> fe;
+
+ // Furthermore, we have (iv) the
+ // value of the computed
+ // eigenvalue at the present
+ // iteration. This is, in fact,
+ // the only part of the solution
+ // that is shared between all
+ // energy groups -- all other
+ // parts of the solution, such as
+ // neutron fluxes are particular
+ // to one or the other energy
+ // group, and are therefore
+ // stored in objects that
+ // describe a single energy
+ // group:
+ double k_eff;
+
+ // Finally, (v), we have an array
+ // of pointers to the energy
+ // group objects. The length of
+ // this array is, of course,
+ // equal to the number of energy
+ // groups specified in the
+ // parameter file.
+ std::vector<EnergyGroup<dim>*> energy_groups;
+ };
+
+
+ // @sect4{Implementation of the <code>NeutronDiffusionProblem::Parameters</code> class}
+
+ // Before going on to the
+ // implementation of the outer class,
+ // we have to implement the functions
+ // of the parameters structure. This
+ // is pretty straightforward and, in
+ // fact, looks pretty much the same
+ // for all such parameters classes
+ // using the ParameterHandler
+ // capabilities. We will therefore
+ // not comment further on this:
+ template <int dim>
+ NeutronDiffusionProblem<dim>::Parameters::Parameters ()
+ :
+ n_groups (2),
+ n_refinement_cycles (5),
+ fe_degree (2),
+ convergence_tolerance (1e-12)
+ {}
+
+
+
+ template <int dim>
+ void
+ NeutronDiffusionProblem<dim>::Parameters::
+ declare_parameters (ParameterHandler &prm)
+ {
+ prm.declare_entry ("Number of energy groups", "2",
+ Patterns::Integer (),
+ "The number of energy different groups considered");
+ prm.declare_entry ("Refinement cycles", "5",
+ Patterns::Integer (),
+ "Number of refinement cycles to be performed");
+ prm.declare_entry ("Finite element degree", "2",
+ Patterns::Integer (),
+ "Polynomial degree of the finite element to be used");
+ prm.declare_entry ("Power iteration tolerance", "1e-12",
+ Patterns::Double (),
+ "Inner power iterations are stopped when the change in k_eff falls "
+ "below this tolerance");
+ }
-template <int dim>
-void
-NeutronDiffusionProblem<dim>::Parameters::
-get_parameters (ParameterHandler &prm)
-{
- n_groups = prm.get_integer ("Number of energy groups");
- n_refinement_cycles = prm.get_integer ("Refinement cycles");
- fe_degree = prm.get_integer ("Finite element degree");
- convergence_tolerance = prm.get_double ("Power iteration tolerance");
-}
+ template <int dim>
+ void
+ NeutronDiffusionProblem<dim>::Parameters::
+ get_parameters (ParameterHandler &prm)
+ {
+ n_groups = prm.get_integer ("Number of energy groups");
+ n_refinement_cycles = prm.get_integer ("Refinement cycles");
+ fe_degree = prm.get_integer ("Finite element degree");
+ convergence_tolerance = prm.get_double ("Power iteration tolerance");
+ }
- // @sect4{Implementation of the <code>NeutronDiffusionProblem</code> class}
+ // @sect4{Implementation of the <code>NeutronDiffusionProblem</code> class}
- // Now for the
- // <code>NeutronDiffusionProblem</code>
- // class. The constructor and
- // destructor have nothing of much
- // interest:
-template <int dim>
-NeutronDiffusionProblem<dim>::
-NeutronDiffusionProblem (const Parameters ¶meters)
- :
- parameters (parameters),
- material_data (parameters.n_groups),
- fe (parameters.fe_degree)
-{}
+ // Now for the
+ // <code>NeutronDiffusionProblem</code>
+ // class. The constructor and
+ // destructor have nothing of much
+ // interest:
+ template <int dim>
+ NeutronDiffusionProblem<dim>::
+ NeutronDiffusionProblem (const Parameters ¶meters)
+ :
+ parameters (parameters),
+ material_data (parameters.n_groups),
+ fe (parameters.fe_degree)
+ {}
-template <int dim>
-NeutronDiffusionProblem<dim>::~NeutronDiffusionProblem ()
-{
- for (unsigned int group=0; group<energy_groups.size(); ++group)
- delete energy_groups[group];
+ template <int dim>
+ NeutronDiffusionProblem<dim>::~NeutronDiffusionProblem ()
+ {
+ for (unsigned int group=0; group<energy_groups.size(); ++group)
+ delete energy_groups[group];
- energy_groups.resize (0);
-}
+ energy_groups.resize (0);
+ }
- // @sect5{<code>NeutronDiffusionProblem::initialize_problem</code>}
- //
- // The first function of interest is
- // the one that sets up the geometry
- // of the reactor core. This is
- // described in more detail in the
- // introduction.
- //
- // The first part of the function
- // defines geometry data, and then
- // creates a coarse mesh that has as
- // many cells as there are fuel rods
- // (or pin cells, for that matter) in
- // that part of the reactor core that
- // we simulate. As mentioned when
- // interpolating boundary values
- // above, the last parameter to the
- // <code>GridGenerator::subdivided_hyper_rectangle</code>
- // function specifies that sides of
- // the domain shall have unique
- // boundary indicators that will
- // later allow us to determine in a
- // simple way which of the boundaries
- // have Neumann and which have
- // Dirichlet conditions attached to
- // them.
-template <int dim>
-void NeutronDiffusionProblem<dim>::initialize_problem()
-{
- const unsigned int rods_per_assembly_x = 17,
- rods_per_assembly_y = 17;
- const double pin_pitch_x = 1.26,
- pin_pitch_y = 1.26;
- const double assembly_height = 200;
-
- const unsigned int assemblies_x = 2,
- assemblies_y = 2,
- assemblies_z = 1;
-
- const Point<dim> bottom_left = Point<dim>();
- const Point<dim> upper_right = (dim == 2
- ?
- Point<dim> (assemblies_x*rods_per_assembly_x*pin_pitch_x,
- assemblies_y*rods_per_assembly_y*pin_pitch_y)
- :
- Point<dim> (assemblies_x*rods_per_assembly_x*pin_pitch_x,
- assemblies_y*rods_per_assembly_y*pin_pitch_y,
- assemblies_z*assembly_height));
-
- std::vector<unsigned int> n_subdivisions;
- n_subdivisions.push_back (assemblies_x*rods_per_assembly_x);
- if (dim >= 2)
- n_subdivisions.push_back (assemblies_y*rods_per_assembly_y);
- if (dim >= 3)
- n_subdivisions.push_back (assemblies_z);
-
- Triangulation<dim> coarse_grid;
- GridGenerator::subdivided_hyper_rectangle (coarse_grid,
- n_subdivisions,
- bottom_left,
- upper_right,
- true);
-
-
- // The second part of the function
- // deals with material numbers of
- // pin cells of each type of
- // assembly. Here, we define four
- // different types of assembly, for
- // which we describe the
- // arrangement of fuel rods in the
- // following tables.
+ // @sect5{<code>NeutronDiffusionProblem::initialize_problem</code>}
//
- // The assemblies described here
- // are taken from the benchmark
- // mentioned in the introduction
- // and are (in this order):
- // <ol>
- // <li>'UX' Assembly: UO2 fuel assembly
- // with 24 guide tubes and a central
- // Moveable Fission Chamber
- // <li>'UA' Assembly: UO2 fuel assembly
- // with 24 AIC and a central
- // Moveable Fission Chamber
- // <li>'PX' Assembly: MOX fuel assembly
- // with 24 guide tubes and a central
- // Moveable Fission Chamber
- // <li>'R' Assembly: a reflector.
- // </ol>
+ // The first function of interest is
+ // the one that sets up the geometry
+ // of the reactor core. This is
+ // described in more detail in the
+ // introduction.
//
- // Note that the numbers listed
- // here and taken from the
- // benchmark description are, in
- // good old Fortran fashion,
- // one-based. We will later
- // subtract one from each number
- // when assigning materials to
- // individual cells to convert
- // things into the C-style
- // zero-based indexing.
- const unsigned int n_assemblies=4;
- const unsigned int
- assembly_materials[n_assemblies][rods_per_assembly_x][rods_per_assembly_y]
- = {
+ // The first part of the function
+ // defines geometry data, and then
+ // creates a coarse mesh that has as
+ // many cells as there are fuel rods
+ // (or pin cells, for that matter) in
+ // that part of the reactor core that
+ // we simulate. As mentioned when
+ // interpolating boundary values
+ // above, the last parameter to the
+ // <code>GridGenerator::subdivided_hyper_rectangle</code>
+ // function specifies that sides of
+ // the domain shall have unique
+ // boundary indicators that will
+ // later allow us to determine in a
+ // simple way which of the boundaries
+ // have Neumann and which have
+ // Dirichlet conditions attached to
+ // them.
+ template <int dim>
+ void NeutronDiffusionProblem<dim>::initialize_problem()
+ {
+ const unsigned int rods_per_assembly_x = 17,
+ rods_per_assembly_y = 17;
+ const double pin_pitch_x = 1.26,
+ pin_pitch_y = 1.26;
+ const double assembly_height = 200;
+
+ const unsigned int assemblies_x = 2,
+ assemblies_y = 2,
+ assemblies_z = 1;
+
+ const Point<dim> bottom_left = Point<dim>();
+ const Point<dim> upper_right = (dim == 2
+ ?
+ Point<dim> (assemblies_x*rods_per_assembly_x*pin_pitch_x,
+ assemblies_y*rods_per_assembly_y*pin_pitch_y)
+ :
+ Point<dim> (assemblies_x*rods_per_assembly_x*pin_pitch_x,
+ assemblies_y*rods_per_assembly_y*pin_pitch_y,
+ assemblies_z*assembly_height));
+
+ std::vector<unsigned int> n_subdivisions;
+ n_subdivisions.push_back (assemblies_x*rods_per_assembly_x);
+ if (dim >= 2)
+ n_subdivisions.push_back (assemblies_y*rods_per_assembly_y);
+ if (dim >= 3)
+ n_subdivisions.push_back (assemblies_z);
+
+ Triangulation<dim> coarse_grid;
+ GridGenerator::subdivided_hyper_rectangle (coarse_grid,
+ n_subdivisions,
+ bottom_left,
+ upper_right,
+ true);
+
+
+ // The second part of the function
+ // deals with material numbers of
+ // pin cells of each type of
+ // assembly. Here, we define four
+ // different types of assembly, for
+ // which we describe the
+ // arrangement of fuel rods in the
+ // following tables.
+ //
+ // The assemblies described here
+ // are taken from the benchmark
+ // mentioned in the introduction
+ // and are (in this order):
+ // <ol>
+ // <li>'UX' Assembly: UO2 fuel assembly
+ // with 24 guide tubes and a central
+ // Moveable Fission Chamber
+ // <li>'UA' Assembly: UO2 fuel assembly
+ // with 24 AIC and a central
+ // Moveable Fission Chamber
+ // <li>'PX' Assembly: MOX fuel assembly
+ // with 24 guide tubes and a central
+ // Moveable Fission Chamber
+ // <li>'R' Assembly: a reflector.
+ // </ol>
+ //
+ // Note that the numbers listed
+ // here and taken from the
+ // benchmark description are, in
+ // good old Fortran fashion,
+ // one-based. We will later
+ // subtract one from each number
+ // when assigning materials to
+ // individual cells to convert
+ // things into the C-style
+ // zero-based indexing.
+ const unsigned int n_assemblies=4;
+ const unsigned int
+ assembly_materials[n_assemblies][rods_per_assembly_x][rods_per_assembly_y]
+ = {
{
{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
}
};
- // After the description of the
- // materials that make up an
- // assembly, we have to specify the
- // arrangement of assemblies within
- // the core. We use a symmetric
- // pattern that in fact only uses
- // the 'UX' and 'PX' assemblies:
- const unsigned int core[assemblies_x][assemblies_y][assemblies_z]
- = {{{0}, {2}}, {{2}, {0}}};
-
- // We are now in a position to
- // actually set material IDs for
- // each cell. To this end, we loop
- // over all cells, look at the
- // location of the cell's center,
- // and determine which assembly and
- // fuel rod this would be in. (We
- // add a few checks to see that the
- // locations we compute are within
- // the bounds of the arrays in
- // which we have to look up
- // materials.) At the end of the
- // loop, we set material
- // identifiers accordingly:
- for (typename Triangulation<dim>::active_cell_iterator
- cell = coarse_grid.begin_active();
- cell!=coarse_grid.end();
- ++cell)
- {
- const Point<dim> cell_center = cell->center();
-
- const unsigned int tmp_x = int(cell_center[0]/pin_pitch_x);
- const unsigned int ax = tmp_x/rods_per_assembly_x;
- const unsigned int cx = tmp_x - ax * rods_per_assembly_x;
-
- const unsigned tmp_y = int(cell_center[1]/pin_pitch_y);
- const unsigned int ay = tmp_y/rods_per_assembly_y;
- const unsigned int cy = tmp_y - ay * rods_per_assembly_y;
-
- const unsigned int az = (dim == 2
- ?
- 0
- :
- int (cell_center[dim-1]/assembly_height));
-
- Assert (ax < assemblies_x, ExcInternalError());
- Assert (ay < assemblies_y, ExcInternalError());
- Assert (az < assemblies_z, ExcInternalError());
-
- Assert (core[ax][ay][az] < n_assemblies, ExcInternalError());
-
- Assert (cx < rods_per_assembly_x, ExcInternalError());
- Assert (cy < rods_per_assembly_y, ExcInternalError());
-
- cell->set_material_id(assembly_materials[core[ax][ay][az]][cx][cy] - 1);
- }
-
- // With the coarse mesh so
- // initialized, we create the
- // appropriate number of energy
- // group objects and let them
- // initialize their individual
- // meshes with the coarse mesh
- // generated above:
- energy_groups.resize (parameters.n_groups);
- for (unsigned int group=0; group<parameters.n_groups; ++group)
- energy_groups[group] = new EnergyGroup<dim> (group, material_data,
- coarse_grid, fe);
-}
-
-
- // @sect5{<code>NeutronDiffusionProblem::get_total_fission_source</code>}
- //
- // In the eigenvalue computation, we
- // need to calculate total fission
- // neutron source after each power
- // iteration. The total power then is
- // used to renew k-effective.
- //
- // Since the total fission source is a sum
- // over all the energy groups, and since each
- // of these sums can be computed
- // independently, we actually do this in
- // parallel. One of the problems is that the
- // function in the <code>EnergyGroup</code>
- // class that computes the fission source
- // returns a value. If we now simply spin off
- // a new thread, we have to later capture the
- // return value of the function run on that
- // thread. The way this can be done is to use
- // the return value of the
- // Threads::new_thread function, which
- // returns an object of type
- // Threads::Thread@<double@> if the function
- // spawned returns a double. We can then later
- // ask this object for the returned value
- // (when doing so, the
- // Threads::Thread::return_value
- // function first waits for the thread to
- // finish if it hasn't done so already).
- //
- // The way this function then works
- // is to first spawn one thread for
- // each energy group we work with,
- // then one-by-one collecting the
- // returned values of each thread and
- // return the sum.
-template <int dim>
-double NeutronDiffusionProblem<dim>::get_total_fission_source () const
-{
- std::vector<Threads::Thread<double> > threads;
- for (unsigned int group=0; group<parameters.n_groups; ++group)
- threads.push_back (Threads::new_thread (&EnergyGroup<dim>::get_fission_source,
- *energy_groups[group]));
+ // After the description of the
+ // materials that make up an
+ // assembly, we have to specify the
+ // arrangement of assemblies within
+ // the core. We use a symmetric
+ // pattern that in fact only uses
+ // the 'UX' and 'PX' assemblies:
+ const unsigned int core[assemblies_x][assemblies_y][assemblies_z]
+ = {{{0}, {2}}, {{2}, {0}}};
+
+ // We are now in a position to
+ // actually set material IDs for
+ // each cell. To this end, we loop
+ // over all cells, look at the
+ // location of the cell's center,
+ // and determine which assembly and
+ // fuel rod this would be in. (We
+ // add a few checks to see that the
+ // locations we compute are within
+ // the bounds of the arrays in
+ // which we have to look up
+ // materials.) At the end of the
+ // loop, we set material
+ // identifiers accordingly:
+ for (typename Triangulation<dim>::active_cell_iterator
+ cell = coarse_grid.begin_active();
+ cell!=coarse_grid.end();
+ ++cell)
+ {
+ const Point<dim> cell_center = cell->center();
- double fission_source = 0;
- for (unsigned int group=0; group<parameters.n_groups; ++group)
- fission_source += threads[group].return_value ();
+ const unsigned int tmp_x = int(cell_center[0]/pin_pitch_x);
+ const unsigned int ax = tmp_x/rods_per_assembly_x;
+ const unsigned int cx = tmp_x - ax * rods_per_assembly_x;
- return fission_source;
-}
+ const unsigned tmp_y = int(cell_center[1]/pin_pitch_y);
+ const unsigned int ay = tmp_y/rods_per_assembly_y;
+ const unsigned int cy = tmp_y - ay * rods_per_assembly_y;
+ const unsigned int az = (dim == 2
+ ?
+ 0
+ :
+ int (cell_center[dim-1]/assembly_height));
+ Assert (ax < assemblies_x, ExcInternalError());
+ Assert (ay < assemblies_y, ExcInternalError());
+ Assert (az < assemblies_z, ExcInternalError());
+ Assert (core[ax][ay][az] < n_assemblies, ExcInternalError());
- // @sect5{<code>NeutronDiffusionProblem::refine_grid</code>}
- //
- // The next function lets the
- // individual energy group objects
- // refine their meshes. Much of this,
- // again, is a task that can be done
- // independently in parallel: first,
- // let all the energy group objects
- // calculate their error indicators
- // in parallel, then compute the
- // maximum error indicator over all
- // energy groups and determine
- // thresholds for refinement and
- // coarsening of cells, and then ask
- // all the energy groups to refine
- // their meshes accordingly, again in
- // parallel.
-template <int dim>
-void NeutronDiffusionProblem<dim>::refine_grid ()
-{
- std::vector<unsigned int> n_cells (parameters.n_groups);
- for (unsigned int group=0; group<parameters.n_groups; ++group)
- n_cells[group] = energy_groups[group]->n_active_cells();
+ Assert (cx < rods_per_assembly_x, ExcInternalError());
+ Assert (cy < rods_per_assembly_y, ExcInternalError());
- BlockVector<float> group_error_indicators(n_cells);
+ cell->set_material_id(assembly_materials[core[ax][ay][az]][cx][cy] - 1);
+ }
- {
- Threads::ThreadGroup<> threads;
+ // With the coarse mesh so
+ // initialized, we create the
+ // appropriate number of energy
+ // group objects and let them
+ // initialize their individual
+ // meshes with the coarse mesh
+ // generated above:
+ energy_groups.resize (parameters.n_groups);
for (unsigned int group=0; group<parameters.n_groups; ++group)
- threads += Threads::new_thread (&EnergyGroup<dim>::estimate_errors,
- *energy_groups[group],
- group_error_indicators.block(group));
- threads.join_all ();
+ energy_groups[group] = new EnergyGroup<dim> (group, material_data,
+ coarse_grid, fe);
}
- const float max_error = group_error_indicators.linfty_norm();
- const float refine_threshold = 0.3*max_error;
- const float coarsen_threshold = 0.01*max_error;
+ // @sect5{<code>NeutronDiffusionProblem::get_total_fission_source</code>}
+ //
+ // In the eigenvalue computation, we
+ // need to calculate total fission
+ // neutron source after each power
+ // iteration. The total power then is
+ // used to renew k-effective.
+ //
+ // Since the total fission source is a sum
+ // over all the energy groups, and since each
+ // of these sums can be computed
+ // independently, we actually do this in
+ // parallel. One of the problems is that the
+ // function in the <code>EnergyGroup</code>
+ // class that computes the fission source
+ // returns a value. If we now simply spin off
+ // a new thread, we have to later capture the
+ // return value of the function run on that
+ // thread. The way this can be done is to use
+ // the return value of the
+ // Threads::new_thread function, which
+ // returns an object of type
+ // Threads::Thread@<double@> if the function
+ // spawned returns a double. We can then later
+ // ask this object for the returned value
+ // (when doing so, the
+ // Threads::Thread::return_value
+ // function first waits for the thread to
+ // finish if it hasn't done so already).
+ //
+ // The way this function then works
+ // is to first spawn one thread for
+ // each energy group we work with,
+ // then one-by-one collecting the
+ // returned values of each thread and
+ // return the sum.
+ template <int dim>
+ double NeutronDiffusionProblem<dim>::get_total_fission_source () const
{
- Threads::ThreadGroup<> threads;
+ std::vector<Threads::Thread<double> > threads;
for (unsigned int group=0; group<parameters.n_groups; ++group)
- threads += Threads::new_thread (&EnergyGroup<dim>::refine_grid,
- *energy_groups[group],
- group_error_indicators.block(group),
- refine_threshold,
- coarsen_threshold);
- threads.join_all ();
- }
-}
+ threads.push_back (Threads::new_thread (&EnergyGroup<dim>::get_fission_source,
+ *energy_groups[group]));
+ double fission_source = 0;
+ for (unsigned int group=0; group<parameters.n_groups; ++group)
+ fission_source += threads[group].return_value ();
- // @sect5{<code>NeutronDiffusionProblem::run</code>}
- //
- // Finally, this is the function
- // where the meat is: iterate on a
- // sequence of meshes, and on each of
- // them do a power iteration to
- // compute the eigenvalue.
- //
- // Given the description of the
- // algorithm in the introduction,
- // there is actually not much to
- // comment on:
-template <int dim>
-void NeutronDiffusionProblem<dim>::run ()
-{
- std::cout << std::setprecision (12) << std::fixed;
+ return fission_source;
+ }
- double k_eff_old = k_eff;
- Timer timer;
- timer.start ();
- for (unsigned int cycle=0; cycle<parameters.n_refinement_cycles; ++cycle)
- {
- std::cout << "Cycle " << cycle << ':' << std::endl;
- if (cycle == 0)
- initialize_problem();
- else
- {
- refine_grid ();
- for (unsigned int group=0; group<parameters.n_groups; ++group)
- energy_groups[group]->solution *= k_eff;
- }
+ // @sect5{<code>NeutronDiffusionProblem::refine_grid</code>}
+ //
+ // The next function lets the
+ // individual energy group objects
+ // refine their meshes. Much of this,
+ // again, is a task that can be done
+ // independently in parallel: first,
+ // let all the energy group objects
+ // calculate their error indicators
+ // in parallel, then compute the
+ // maximum error indicator over all
+ // energy groups and determine
+ // thresholds for refinement and
+ // coarsening of cells, and then ask
+ // all the energy groups to refine
+ // their meshes accordingly, again in
+ // parallel.
+ template <int dim>
+ void NeutronDiffusionProblem<dim>::refine_grid ()
+ {
+ std::vector<unsigned int> n_cells (parameters.n_groups);
+ for (unsigned int group=0; group<parameters.n_groups; ++group)
+ n_cells[group] = energy_groups[group]->n_active_cells();
- for (unsigned int group=0; group<parameters.n_groups; ++group)
- energy_groups[group]->setup_linear_system ();
+ BlockVector<float> group_error_indicators(n_cells);
- std::cout << " Numbers of active cells: ";
- for (unsigned int group=0; group<parameters.n_groups; ++group)
- std::cout << energy_groups[group]->n_active_cells()
- << ' ';
- std::cout << std::endl;
- std::cout << " Numbers of degrees of freedom: ";
+ {
+ Threads::ThreadGroup<> threads;
for (unsigned int group=0; group<parameters.n_groups; ++group)
- std::cout << energy_groups[group]->n_dofs()
- << ' ';
- std::cout << std::endl << std::endl;
+ threads += Threads::new_thread (&EnergyGroup<dim>::estimate_errors,
+ *energy_groups[group],
+ group_error_indicators.block(group));
+ threads.join_all ();
+ }
+ const float max_error = group_error_indicators.linfty_norm();
+ const float refine_threshold = 0.3*max_error;
+ const float coarsen_threshold = 0.01*max_error;
+ {
Threads::ThreadGroup<> threads;
for (unsigned int group=0; group<parameters.n_groups; ++group)
- threads += Threads::new_thread
- (&EnergyGroup<dim>::assemble_system_matrix,
- *energy_groups[group]);
+ threads += Threads::new_thread (&EnergyGroup<dim>::refine_grid,
+ *energy_groups[group],
+ group_error_indicators.block(group),
+ refine_threshold,
+ coarsen_threshold);
threads.join_all ();
+ }
+ }
- double error;
- unsigned int iteration = 1;
- do
- {
- for (unsigned int group=0; group<parameters.n_groups; ++group)
- {
- energy_groups[group]->assemble_ingroup_rhs (ZeroFunction<dim>());
- for (unsigned int bgroup=0; bgroup<parameters.n_groups; ++bgroup)
- energy_groups[group]->assemble_cross_group_rhs (*energy_groups[bgroup]);
+ // @sect5{<code>NeutronDiffusionProblem::run</code>}
+ //
+ // Finally, this is the function
+ // where the meat is: iterate on a
+ // sequence of meshes, and on each of
+ // them do a power iteration to
+ // compute the eigenvalue.
+ //
+ // Given the description of the
+ // algorithm in the introduction,
+ // there is actually not much to
+ // comment on:
+ template <int dim>
+ void NeutronDiffusionProblem<dim>::run ()
+ {
+ std::cout << std::setprecision (12) << std::fixed;
+
+ double k_eff_old = k_eff;
- energy_groups[group]->solve ();
- }
+ Timer timer;
+ timer.start ();
- k_eff = get_total_fission_source();
- error = fabs(k_eff-k_eff_old)/fabs(k_eff);
- std::cout << " Iteration " << iteration
- << ": k_eff=" << k_eff
- << std::endl;
- k_eff_old=k_eff;
+ for (unsigned int cycle=0; cycle<parameters.n_refinement_cycles; ++cycle)
+ {
+ std::cout << "Cycle " << cycle << ':' << std::endl;
- for (unsigned int group=0; group<parameters.n_groups; ++group)
- {
- energy_groups[group]->solution_old = energy_groups[group]->solution;
- energy_groups[group]->solution_old /= k_eff;
- }
+ if (cycle == 0)
+ initialize_problem();
+ else
+ {
+ refine_grid ();
+ for (unsigned int group=0; group<parameters.n_groups; ++group)
+ energy_groups[group]->solution *= k_eff;
+ }
- ++iteration;
- }
- while((error > parameters.convergence_tolerance)
- &&
- (iteration < 500));
+ for (unsigned int group=0; group<parameters.n_groups; ++group)
+ energy_groups[group]->setup_linear_system ();
+
+ std::cout << " Numbers of active cells: ";
+ for (unsigned int group=0; group<parameters.n_groups; ++group)
+ std::cout << energy_groups[group]->n_active_cells()
+ << ' ';
+ std::cout << std::endl;
+ std::cout << " Numbers of degrees of freedom: ";
+ for (unsigned int group=0; group<parameters.n_groups; ++group)
+ std::cout << energy_groups[group]->n_dofs()
+ << ' ';
+ std::cout << std::endl << std::endl;
+
+
+ Threads::ThreadGroup<> threads;
+ for (unsigned int group=0; group<parameters.n_groups; ++group)
+ threads += Threads::new_thread
+ (&EnergyGroup<dim>::assemble_system_matrix,
+ *energy_groups[group]);
+ threads.join_all ();
+
+ double error;
+ unsigned int iteration = 1;
+ do
+ {
+ for (unsigned int group=0; group<parameters.n_groups; ++group)
+ {
+ energy_groups[group]->assemble_ingroup_rhs (ZeroFunction<dim>());
+
+ for (unsigned int bgroup=0; bgroup<parameters.n_groups; ++bgroup)
+ energy_groups[group]->assemble_cross_group_rhs (*energy_groups[bgroup]);
+
+ energy_groups[group]->solve ();
+ }
+
+ k_eff = get_total_fission_source();
+ error = fabs(k_eff-k_eff_old)/fabs(k_eff);
+ std::cout << " Iteration " << iteration
+ << ": k_eff=" << k_eff
+ << std::endl;
+ k_eff_old=k_eff;
+
+ for (unsigned int group=0; group<parameters.n_groups; ++group)
+ {
+ energy_groups[group]->solution_old = energy_groups[group]->solution;
+ energy_groups[group]->solution_old /= k_eff;
+ }
+
+ ++iteration;
+ }
+ while((error > parameters.convergence_tolerance)
+ &&
+ (iteration < 500));
- for (unsigned int group=0; group<parameters.n_groups; ++group)
- energy_groups[group]->output_results (cycle);
+ for (unsigned int group=0; group<parameters.n_groups; ++group)
+ energy_groups[group]->output_results (cycle);
- std::cout << std::endl;
- std::cout << " Cycle=" << cycle
- << ", n_dofs=" << energy_groups[0]->n_dofs() + energy_groups[1]->n_dofs()
- << ", k_eff=" << k_eff
- << ", time=" << timer()
- << std::endl;
+ std::cout << std::endl;
+ std::cout << " Cycle=" << cycle
+ << ", n_dofs=" << energy_groups[0]->n_dofs() + energy_groups[1]->n_dofs()
+ << ", k_eff=" << k_eff
+ << ", time=" << timer()
+ << std::endl;
- std::cout << std::endl << std::endl;
- }
+ std::cout << std::endl << std::endl;
+ }
+ }
}
// for computation of the eigenvalue:
int main (int argc, char ** argv)
{
- const unsigned int dim = 2;
-
try
{
+ using namespace dealii;
+ using namespace Step28;
+
deallog.depth_console (0);
std::string filename;
filename = argv[1];
+ const unsigned int dim = 2;
+
ParameterHandler parameter_handler;
NeutronDiffusionProblem<dim>::Parameters parameters;
/* Author: Moritz Allmaras, Texas A&M University, 2007 */
/* */
-/* Copyright (C) 2007, 2008, 2010 by the deal.II authors and M. Allmaras */
+/* Copyright (C) 2007, 2008, 2010, 2011 by the deal.II authors and M. Allmaras */
/* */
/* This file is subject to QPL and may not be distributed */
/* without copyright and license information. Please refer */
// @sect3{Include files}
- // The following header files are unchanged
+ // The following header files are unchanged
// from step-7 and have been discussed before:
#include <deal.II/base/quadrature_lib.h>
// program takes:
#include <deal.II/base/timer.h>
- // As the last step at the beginning
- // of this program, we make
- // everything that is in the deal.II
- // namespace globally available,
- // without the need to prefix
- // everything with
+ // As the last step at the beginning of this
+ // program, we put everything that is in this
+ // program into its namespace and, within it,
+ // make everything that is in the deal.II
+ // namespace globally available, without the
+ // need to prefix everything with
// <code>dealii</code><code>::</code>:
-using namespace dealii;
-
-
- // @sect3{The <code>DirichletBoundaryValues</code> class}
-
- // First we define a class for the
- // function representing the
- // Dirichlet boundary values. This
- // has been done many times before
- // and therefore does not need much
- // explanation.
- //
- // Since there are two values $v$ and
- // $w$ that need to be prescribed at
- // the boundary, we have to tell the
- // base class that this is a
- // vector-valued function with two
- // components, and the
- // <code>vector_value</code> function
- // and its cousin
- // <code>vector_value_list</code> must
- // return vectors with two entries. In
- // our case the function is very
- // simple, it just returns 1 for the
- // real part $v$ and 0 for the
- // imaginary part $w$ regardless of
- // the point where it is evaluated.
-template <int dim>
-class DirichletBoundaryValues : public Function<dim>
+namespace Step29
{
- public:
- DirichletBoundaryValues() : Function<dim> (2) {};
+ using namespace dealii;
+
+
+ // @sect3{The <code>DirichletBoundaryValues</code> class}
+
+ // First we define a class for the
+ // function representing the
+ // Dirichlet boundary values. This
+ // has been done many times before
+ // and therefore does not need much
+ // explanation.
+ //
+ // Since there are two values $v$ and
+ // $w$ that need to be prescribed at
+ // the boundary, we have to tell the
+ // base class that this is a
+ // vector-valued function with two
+ // components, and the
+ // <code>vector_value</code> function
+ // and its cousin
+ // <code>vector_value_list</code> must
+ // return vectors with two entries. In
+ // our case the function is very
+ // simple, it just returns 1 for the
+ // real part $v$ and 0 for the
+ // imaginary part $w$ regardless of
+ // the point where it is evaluated.
+ template <int dim>
+ class DirichletBoundaryValues : public Function<dim>
+ {
+ public:
+ DirichletBoundaryValues() : Function<dim> (2) {};
- virtual void vector_value (const Point<dim> &p,
- Vector<double> &values) const;
+ virtual void vector_value (const Point<dim> &p,
+ Vector<double> &values) const;
- virtual void vector_value_list (const std::vector<Point<dim> > &points,
- std::vector<Vector<double> > &value_list) const;
-};
+ virtual void vector_value_list (const std::vector<Point<dim> > &points,
+ std::vector<Vector<double> > &value_list) const;
+ };
-template <int dim>
-inline
-void DirichletBoundaryValues<dim>::vector_value (const Point<dim> &/*p*/,
- Vector<double> &values) const
-{
- Assert (values.size() == 2, ExcDimensionMismatch (values.size(), 2));
+ template <int dim>
+ inline
+ void DirichletBoundaryValues<dim>::vector_value (const Point<dim> &/*p*/,
+ Vector<double> &values) const
+ {
+ Assert (values.size() == 2, ExcDimensionMismatch (values.size(), 2));
- values(0) = 1;
- values(1) = 0;
-}
+ values(0) = 1;
+ values(1) = 0;
+ }
-template <int dim>
-void DirichletBoundaryValues<dim>::vector_value_list (const std::vector<Point<dim> > &points,
- std::vector<Vector<double> > &value_list) const
-{
- Assert (value_list.size() == points.size(),
- ExcDimensionMismatch (value_list.size(), points.size()));
+ template <int dim>
+ void DirichletBoundaryValues<dim>::vector_value_list (const std::vector<Point<dim> > &points,
+ std::vector<Vector<double> > &value_list) const
+ {
+ Assert (value_list.size() == points.size(),
+ ExcDimensionMismatch (value_list.size(), points.size()));
- for (unsigned int p=0; p<points.size(); ++p)
- DirichletBoundaryValues<dim>::vector_value (points[p], value_list[p]);
-}
+ for (unsigned int p=0; p<points.size(); ++p)
+ DirichletBoundaryValues<dim>::vector_value (points[p], value_list[p]);
+ }
- // @sect3{The <code>ParameterReader</code> class}
-
- // The next class is responsible for
- // preparing the ParameterHandler
- // object and reading parameters from
- // an input file. It includes a
- // function
- // <code>declare_parameters</code>
- // that declares all the necessary
- // parameters and a
- // <code>read_parameters</code>
- // function that is called from
- // outside to initiate the parameter
- // reading process.
-class ParameterReader : public Subscriptor
-{
- public:
- ParameterReader(ParameterHandler &);
- void read_parameters(const std::string);
-
- private:
- void declare_parameters();
- ParameterHandler &prm;
-};
-
- // The constructor stores a reference to
- // the ParameterHandler object that is passed to it:
-ParameterReader::ParameterReader(ParameterHandler ¶mhandler)
- :
- prm(paramhandler)
-{}
-
- // @sect4{<code>ParameterReader::declare_parameters</code>}
-
- // The <code>declare_parameters</code>
- // function declares all the
- // parameters that our
- // ParameterHandler object will be
- // able to read from input files,
- // along with their types, range
- // conditions and the subsections they
- // appear in. We will wrap all the
- // entries that go into a section in a
- // pair of braces to force the editor
- // to indent them by one level, making
- // it simpler to read which entries
- // together form a section:
-void ParameterReader::declare_parameters()
-{
- // Parameters for mesh and geometry
- // include the number of global
- // refinement steps that are applied
- // to the initial coarse mesh and the
- // focal distance $d$ of the
- // transducer lens. For the number of
- // refinement steps, we allow integer
- // values in the range $[0,\infty)$,
- // where the omitted second argument
- // to the Patterns::Integer object
- // denotes the half-open interval.
- // For the focal distance any number
- // greater than zero is accepted:
- prm.enter_subsection ("Mesh & geometry parameters");
+ // @sect3{The <code>ParameterReader</code> class}
+
+ // The next class is responsible for
+ // preparing the ParameterHandler
+ // object and reading parameters from
+ // an input file. It includes a
+ // function
+ // <code>declare_parameters</code>
+ // that declares all the necessary
+ // parameters and a
+ // <code>read_parameters</code>
+ // function that is called from
+ // outside to initiate the parameter
+ // reading process.
+ class ParameterReader : public Subscriptor
{
- prm.declare_entry("Number of refinements", "6",
- Patterns::Integer(0),
- "Number of global mesh refinement steps "
- "applied to initial coarse grid");
-
- prm.declare_entry("Focal distance", "0.3",
- Patterns::Double(0),
- "Distance of the focal point of the lens "
- "to the x-axis");
- }
- prm.leave_subsection ();
-
- // The next subsection is devoted to
- // the physical parameters appearing
- // in the equation, which are the
- // frequency $\omega$ and wave speed
- // $c$. Again, both need to lie in the
- // half-open interval $[0,\infty)$
- // represented by calling the
- // Patterns::Double class with only
- // the left end-point as argument:
- prm.enter_subsection ("Physical constants");
+ public:
+ ParameterReader(ParameterHandler &);
+ void read_parameters(const std::string);
+
+ private:
+ void declare_parameters();
+ ParameterHandler &prm;
+ };
+
+ // The constructor stores a reference to
+ // the ParameterHandler object that is passed to it:
+ ParameterReader::ParameterReader(ParameterHandler ¶mhandler)
+ :
+ prm(paramhandler)
+ {}
+
+ // @sect4{<code>ParameterReader::declare_parameters</code>}
+
+ // The <code>declare_parameters</code>
+ // function declares all the
+ // parameters that our
+ // ParameterHandler object will be
+ // able to read from input files,
+ // along with their types, range
+ // conditions and the subsections they
+ // appear in. We will wrap all the
+ // entries that go into a section in a
+ // pair of braces to force the editor
+ // to indent them by one level, making
+ // it simpler to read which entries
+ // together form a section:
+ void ParameterReader::declare_parameters()
{
- prm.declare_entry("c", "1.5e5",
- Patterns::Double(0),
- "Wave speed");
+ // Parameters for mesh and geometry
+ // include the number of global
+ // refinement steps that are applied
+ // to the initial coarse mesh and the
+ // focal distance $d$ of the
+ // transducer lens. For the number of
+ // refinement steps, we allow integer
+ // values in the range $[0,\infty)$,
+ // where the omitted second argument
+ // to the Patterns::Integer object
+ // denotes the half-open interval.
+ // For the focal distance any number
+ // greater than zero is accepted:
+ prm.enter_subsection ("Mesh & geometry parameters");
+ {
+ prm.declare_entry("Number of refinements", "6",
+ Patterns::Integer(0),
+ "Number of global mesh refinement steps "
+ "applied to initial coarse grid");
+
+ prm.declare_entry("Focal distance", "0.3",
+ Patterns::Double(0),
+ "Distance of the focal point of the lens "
+ "to the x-axis");
+ }
+ prm.leave_subsection ();
+
+ // The next subsection is devoted to
+ // the physical parameters appearing
+ // in the equation, which are the
+ // frequency $\omega$ and wave speed
+ // $c$. Again, both need to lie in the
+ // half-open interval $[0,\infty)$
+ // represented by calling the
+ // Patterns::Double class with only
+ // the left end-point as argument:
+ prm.enter_subsection ("Physical constants");
+ {
+ prm.declare_entry("c", "1.5e5",
+ Patterns::Double(0),
+ "Wave speed");
- prm.declare_entry("omega", "5.0e7",
- Patterns::Double(0),
- "Frequency");
- }
- prm.leave_subsection ();
+ prm.declare_entry("omega", "5.0e7",
+ Patterns::Double(0),
+ "Frequency");
+ }
+ prm.leave_subsection ();
- // Last but not least we would like
- // to be able to change some
- // properties of the output, like
- // filename and format, through
- // entries in the configuration
- // file, which is the purpose of
- // the last subsection:
- prm.enter_subsection ("Output parameters");
- {
- prm.declare_entry("Output file", "solution",
- Patterns::Anything(),
- "Name of the output file (without extension)");
-
- // Since different output formats
- // may require different
- // parameters for generating
- // output (like for example,
- // postscript output needs
- // viewpoint angles, line widths,
- // colors etc), it would be
- // cumbersome if we had to
- // declare all these parameters
- // by hand for every possible
- // output format supported in the
- // library. Instead, each output
- // format has a
- // <code>FormatFlags::declare_parameters</code>
- // function, which declares all
- // the parameters specific to
- // that format in an own
- // subsection. The following call
- // of
- // DataOutInterface<1>::declare_parameters
- // executes
- // <code>declare_parameters</code>
- // for all available output
- // formats, so that for each
- // format an own subsection will
- // be created with parameters
- // declared for that particular
- // output format. (The actual
- // value of the template
- // parameter in the call,
- // <code>@<1@></code> above, does
- // not matter here: the function
- // does the same work independent
- // of the dimension, but happens
- // to be in a
- // template-parameter-dependent
- // class.) To find out what
- // parameters there are for which
- // output format, you can either
- // consult the documentation of
- // the DataOutBase class, or
- // simply run this program
- // without a parameter file
- // present. It will then create a
- // file with all declared
- // parameters set to their
- // default values, which can
- // conveniently serve as a
- // starting point for setting the
- // parameters to the values you
- // desire.
- DataOutInterface<1>::declare_parameters (prm);
+ // Last but not least we would like
+ // to be able to change some
+ // properties of the output, like
+ // filename and format, through
+ // entries in the configuration
+ // file, which is the purpose of
+ // the last subsection:
+ prm.enter_subsection ("Output parameters");
+ {
+ prm.declare_entry("Output file", "solution",
+ Patterns::Anything(),
+ "Name of the output file (without extension)");
+
+ // Since different output formats
+ // may require different
+ // parameters for generating
+ // output (like for example,
+ // postscript output needs
+ // viewpoint angles, line widths,
+ // colors etc), it would be
+ // cumbersome if we had to
+ // declare all these parameters
+ // by hand for every possible
+ // output format supported in the
+ // library. Instead, each output
+ // format has a
+ // <code>FormatFlags::declare_parameters</code>
+ // function, which declares all
+ // the parameters specific to
+ // that format in an own
+ // subsection. The following call
+ // of
+ // DataOutInterface<1>::declare_parameters
+ // executes
+ // <code>declare_parameters</code>
+ // for all available output
+ // formats, so that for each
+ // format an own subsection will
+ // be created with parameters
+ // declared for that particular
+ // output format. (The actual
+ // value of the template
+ // parameter in the call,
+ // <code>@<1@></code> above, does
+ // not matter here: the function
+ // does the same work independent
+ // of the dimension, but happens
+ // to be in a
+ // template-parameter-dependent
+ // class.) To find out what
+ // parameters there are for which
+ // output format, you can either
+ // consult the documentation of
+ // the DataOutBase class, or
+ // simply run this program
+ // without a parameter file
+ // present. It will then create a
+ // file with all declared
+ // parameters set to their
+ // default values, which can
+ // conveniently serve as a
+ // starting point for setting the
+ // parameters to the values you
+ // desire.
+ DataOutInterface<1>::declare_parameters (prm);
+ }
+ prm.leave_subsection ();
}
- prm.leave_subsection ();
-}
-
- // @sect4{<code>ParameterReader::read_parameters</code>}
-
- // This is the main function in the
- // ParameterReader class. It gets
- // called from outside, first
- // declares all the parameters, and
- // then reads them from the input
- // file whose filename is provided by
- // the caller. After the call to this
- // function is complete, the
- // <code>prm</code> object can be
- // used to retrieve the values of the
- // parameters read in from the file:
-void ParameterReader::read_parameters (const std::string parameter_file)
-{
- declare_parameters();
- prm.read_input (parameter_file);
-}
+ // @sect4{<code>ParameterReader::read_parameters</code>}
+
+ // This is the main function in the
+ // ParameterReader class. It gets
+ // called from outside, first
+ // declares all the parameters, and
+ // then reads them from the input
+ // file whose filename is provided by
+ // the caller. After the call to this
+ // function is complete, the
+ // <code>prm</code> object can be
+ // used to retrieve the values of the
+ // parameters read in from the file:
+ void ParameterReader::read_parameters (const std::string parameter_file)
+ {
+ declare_parameters();
+ prm.read_input (parameter_file);
+ }
- // @sect3{The <code>ComputeIntensity</code> class}
-
- // As mentioned in the introduction,
- // the quantitiy that we are really
- // after is the spatial distribution
- // of the intensity of the ultrasound
- // wave, which corresponds to
- // $|u|=\sqrt{v^2+w^2}$. Now we could
- // just be content with having $v$
- // and $w$ in our output, and use a
- // suitable visualization or
- // postprocessing tool to derive
- // $|u|$ from the solution we
- // computed. However, there is also a
- // way to output data derived from
- // the solution in deal.II, and we
- // are going to make use of this
- // mechanism here.
-
- // So far we have always used the
- // DataOut::add_data_vector function
- // to add vectors containing output
- // data to a DataOut object. There
- // is a special version of this
- // function that in addition to the
- // data vector has an additional
- // argument of type
- // DataPostprocessor. What happens
- // when this function is used for
- // output is that at each point where
- // output data is to be generated,
- // the compute_derived_quantities
- // function of the specified
- // DataPostprocessor object is
- // invoked to compute the output
- // quantities from the values, the
- // gradients and the second
- // derivatives of the finite element
- // function represented by the data
- // vector (in the case of face
- // related data, normal vectors are
- // available as well). Hence, this
- // allows us to output any quantity
- // that can locally be derived from
- // the values of the solution and its
- // derivatives. Of course, the
- // ultrasound intensity $|u|$ is such
- // a quantity and its computation
- // doesn't even involve any
- // derivatives of $v$ or $w$.
-
- // In practice, the DataPostprocessor
- // class only provides an interface
- // to this functionality, and we need
- // to derive our own class from it in
- // order to implement the functions
- // specified by the interface. This
- // is what the
- // <code>ComputeIntensity</code>
- // class is about. Notice that all
- // its member functions are
- // implementations of virtual
- // functions defined by the interface
- // class DataPostprocessor.
-template <int dim>
-class ComputeIntensity : public DataPostprocessor<dim>
-{
- public:
-
- 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 UpdateFlags get_needed_update_flags () const;
- virtual unsigned int n_output_variables () const;
-};
-
- // The <code>get_names</code>
- // function returns a vector of
- // strings representing the names we
- // assign to the individual
- // quantities that our postprocessor
- // outputs. In our case, the
- // postprocessor has only $|u|$ as an
- // output, so we return a vector with
- // a single component named
- // "Intensity":
-template <int dim>
-std::vector<std::string>
-ComputeIntensity<dim>::get_names() const
-{
- return std::vector<std::string> (1, "Intensity");
-}
- // The next function returns a set of
- // flags that indicate which data is
- // needed by the postprocessor in
- // order to compute the output
- // quantities. This can be any
- // subset of update_values,
- // update_gradients and
- // update_hessians (and, in the case
- // of face data, also
- // update_normal_vectors), which are
- // documented in UpdateFlags. Of
- // course, computation of the
- // derivatives requires additional
- // resources, so only the flags for
- // data that is really needed should
- // be given here, just as we do when
- // we use FEValues objects. In our
- // case, only the function values of
- // $v$ and $w$ are needed to compute
- // $|u|$, so we're good with the
- // update_values flag.
-template <int dim>
-UpdateFlags
-ComputeIntensity<dim>::get_needed_update_flags () const
-{
- return update_values;
-}
+ // @sect3{The <code>ComputeIntensity</code> class}
+
+ // As mentioned in the introduction,
+ // the quantitiy that we are really
+ // after is the spatial distribution
+ // of the intensity of the ultrasound
+ // wave, which corresponds to
+ // $|u|=\sqrt{v^2+w^2}$. Now we could
+ // just be content with having $v$
+ // and $w$ in our output, and use a
+ // suitable visualization or
+ // postprocessing tool to derive
+ // $|u|$ from the solution we
+ // computed. However, there is also a
+ // way to output data derived from
+ // the solution in deal.II, and we
+ // are going to make use of this
+ // mechanism here.
+
+ // So far we have always used the
+ // DataOut::add_data_vector function
+ // to add vectors containing output
+ // data to a DataOut object. There
+ // is a special version of this
+ // function that in addition to the
+ // data vector has an additional
+ // argument of type
+ // DataPostprocessor. What happens
+ // when this function is used for
+ // output is that at each point where
+ // output data is to be generated,
+ // the compute_derived_quantities
+ // function of the specified
+ // DataPostprocessor object is
+ // invoked to compute the output
+ // quantities from the values, the
+ // gradients and the second
+ // derivatives of the finite element
+ // function represented by the data
+ // vector (in the case of face
+ // related data, normal vectors are
+ // available as well). Hence, this
+ // allows us to output any quantity
+ // that can locally be derived from
+ // the values of the solution and its
+ // derivatives. Of course, the
+ // ultrasound intensity $|u|$ is such
+ // a quantity and its computation
+ // doesn't even involve any
+ // derivatives of $v$ or $w$.
+
+ // In practice, the DataPostprocessor
+ // class only provides an interface
+ // to this functionality, and we need
+ // to derive our own class from it in
+ // order to implement the functions
+ // specified by the interface. This
+ // is what the
+ // <code>ComputeIntensity</code>
+ // class is about. Notice that all
+ // its member functions are
+ // implementations of virtual
+ // functions defined by the interface
+ // class DataPostprocessor.
+ template <int dim>
+ class ComputeIntensity : public DataPostprocessor<dim>
+ {
+ public:
+
+ 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 UpdateFlags get_needed_update_flags () const;
+ virtual unsigned int n_output_variables () const;
+ };
+
+ // The <code>get_names</code>
+ // function returns a vector of
+ // strings representing the names we
+ // assign to the individual
+ // quantities that our postprocessor
+ // outputs. In our case, the
+ // postprocessor has only $|u|$ as an
+ // output, so we return a vector with
+ // a single component named
+ // "Intensity":
+ template <int dim>
+ std::vector<std::string>
+ ComputeIntensity<dim>::get_names() const
+ {
+ return std::vector<std::string> (1, "Intensity");
+ }
- // To allow the caller to find out
- // how many derived quantities are
- // returned by the postprocessor, the
- // <code>n_output_variables</code>
- // function is used. Since we compute
- // only $|u|$, the correct value to
- // return in our case is just 1:
-template <int dim>
-unsigned int
-ComputeIntensity<dim>::n_output_variables () const
-{
- return 1;
-}
+ // The next function returns a set of
+ // flags that indicate which data is
+ // needed by the postprocessor in
+ // order to compute the output
+ // quantities. This can be any
+ // subset of update_values,
+ // update_gradients and
+ // update_hessians (and, in the case
+ // of face data, also
+ // update_normal_vectors), which are
+ // documented in UpdateFlags. Of
+ // course, computation of the
+ // derivatives requires additional
+ // resources, so only the flags for
+ // data that is really needed should
+ // be given here, just as we do when
+ // we use FEValues objects. In our
+ // case, only the function values of
+ // $v$ and $w$ are needed to compute
+ // $|u|$, so we're good with the
+ // update_values flag.
+ template <int dim>
+ UpdateFlags
+ ComputeIntensity<dim>::get_needed_update_flags () const
+ {
+ return update_values;
+ }
+ // To allow the caller to find out
+ // how many derived quantities are
+ // returned by the postprocessor, the
+ // <code>n_output_variables</code>
+ // function is used. Since we compute
+ // only $|u|$, the correct value to
+ // return in our case is just 1:
+ template <int dim>
+ unsigned int
+ ComputeIntensity<dim>::n_output_variables () const
+ {
+ return 1;
+ }
- // The actual prostprocessing happens
- // in the following function. Its
- // inputs are a vector representing
- // values of the function (which is
- // here vector-valued) representing
- // the data vector given to
- // DataOut::add_data_vector,
- // evaluated at all quadrature points
- // where we generate output, and some
- // tensor objects representing
- // derivatives (that we don't use
- // here since $|u|$ is computed from
- // just $v$ and $w$, and for which we
- // assign no name to the
- // corresponding function argument).
- // The derived quantities are
- // returned in the
- // <code>computed_quantities</code>
- // vector. Remember that this
- // function may only use data for
- // which the respective update flag
- // is specified by
- // <code>get_needed_update_flags</code>. For
- // example, we may not use the
- // derivatives here, since our
- // implementation of
- // <code>get_needed_update_flags</code>
- // requests that only function values
- // are provided.
-template <int dim>
-void
-ComputeIntensity<dim>::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
-{
- Assert(computed_quantities.size() == uh.size(),
- ExcDimensionMismatch (computed_quantities.size(), uh.size()));
-
- // The computation itself is
- // straightforward: We iterate over
- // each entry in the output vector
- // and compute $|u|$ from the
- // corresponding values of $v$ and
- // $w$:
- for (unsigned int i=0; i<computed_quantities.size(); i++)
- {
- Assert(computed_quantities[i].size() == 1,
- ExcDimensionMismatch (computed_quantities[i].size(), 1));
- Assert(uh[i].size() == 2, ExcDimensionMismatch (uh[i].size(), 2));
- computed_quantities[i](0) = sqrt(uh[i](0)*uh[i](0) + uh[i](1)*uh[i](1));
- }
-}
+ // The actual prostprocessing happens
+ // in the following function. Its
+ // inputs are a vector representing
+ // values of the function (which is
+ // here vector-valued) representing
+ // the data vector given to
+ // DataOut::add_data_vector,
+ // evaluated at all quadrature points
+ // where we generate output, and some
+ // tensor objects representing
+ // derivatives (that we don't use
+ // here since $|u|$ is computed from
+ // just $v$ and $w$, and for which we
+ // assign no name to the
+ // corresponding function argument).
+ // The derived quantities are
+ // returned in the
+ // <code>computed_quantities</code>
+ // vector. Remember that this
+ // function may only use data for
+ // which the respective update flag
+ // is specified by
+ // <code>get_needed_update_flags</code>. For
+ // example, we may not use the
+ // derivatives here, since our
+ // implementation of
+ // <code>get_needed_update_flags</code>
+ // requests that only function values
+ // are provided.
+ template <int dim>
+ void
+ ComputeIntensity<dim>::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
+ {
+ Assert(computed_quantities.size() == uh.size(),
+ ExcDimensionMismatch (computed_quantities.size(), uh.size()));
+
+ // The computation itself is
+ // straightforward: We iterate over
+ // each entry in the output vector
+ // and compute $|u|$ from the
+ // corresponding values of $v$ and
+ // $w$:
+ for (unsigned int i=0; i<computed_quantities.size(); i++)
+ {
+ Assert(computed_quantities[i].size() == 1,
+ ExcDimensionMismatch (computed_quantities[i].size(), 1));
+ Assert(uh[i].size() == 2, ExcDimensionMismatch (uh[i].size(), 2));
+
+ computed_quantities[i](0) = sqrt(uh[i](0)*uh[i](0) + uh[i](1)*uh[i](1));
+ }
+ }
- // @sect3{The <code>UltrasoundProblem</code> class}
-
- // Finally here is the main class of
- // this program. It's member
- // functions are very similar to the
- // previous examples, in particular
- // step-4, and the list of member
- // variables does not contain any
- // major surprises either. The
- // ParameterHandler object that is
- // passed to the constructor is
- // stored as a reference to allow
- // easy access to the parameters from
- // all functions of the class. Since
- // we are working with vector valued
- // finite elements, the FE object we
- // are using is of type FESystem.
-template <int dim>
-class UltrasoundProblem
-{
- public:
- UltrasoundProblem (ParameterHandler &);
- ~UltrasoundProblem ();
- void run ();
-
- private:
- void make_grid ();
- void setup_system ();
- void assemble_system ();
- void solve ();
- void output_results () const;
-
- ParameterHandler &prm;
-
- Triangulation<dim> triangulation;
- DoFHandler<dim> dof_handler;
- FESystem<dim> fe;
-
- SparsityPattern sparsity_pattern;
- SparseMatrix<double> system_matrix;
- Vector<double> solution, system_rhs;
-};
-
-
-
- // The constructor takes the
- // ParameterHandler object and stores
- // it in a reference. It also
- // initializes the DoF-Handler and
- // the finite element system, which
- // consists of two copies of the
- // scalar Q1 field, one for $v$ and
- // one for $w$:
-template <int dim>
-UltrasoundProblem<dim>::UltrasoundProblem (ParameterHandler& param)
- :
- prm(param),
- dof_handler(triangulation),
- fe(FE_Q<dim>(1), 2)
-{}
-
-
-template <int dim>
-UltrasoundProblem<dim>::~UltrasoundProblem ()
-{
- dof_handler.clear();
-}
+ // @sect3{The <code>UltrasoundProblem</code> class}
+
+ // Finally here is the main class of
+ // this program. It's member
+ // functions are very similar to the
+ // previous examples, in particular
+ // step-4, and the list of member
+ // variables does not contain any
+ // major surprises either. The
+ // ParameterHandler object that is
+ // passed to the constructor is
+ // stored as a reference to allow
+ // easy access to the parameters from
+ // all functions of the class. Since
+ // we are working with vector valued
+ // finite elements, the FE object we
+ // are using is of type FESystem.
+ template <int dim>
+ class UltrasoundProblem
+ {
+ public:
+ UltrasoundProblem (ParameterHandler &);
+ ~UltrasoundProblem ();
+ void run ();
+
+ private:
+ void make_grid ();
+ void setup_system ();
+ void assemble_system ();
+ void solve ();
+ void output_results () const;
+
+ ParameterHandler &prm;
+
+ Triangulation<dim> triangulation;
+ DoFHandler<dim> dof_handler;
+ FESystem<dim> fe;
+
+ SparsityPattern sparsity_pattern;
+ SparseMatrix<double> system_matrix;
+ Vector<double> solution, system_rhs;
+ };
+
+
+
+ // The constructor takes the
+ // ParameterHandler object and stores
+ // it in a reference. It also
+ // initializes the DoF-Handler and
+ // the finite element system, which
+ // consists of two copies of the
+ // scalar Q1 field, one for $v$ and
+ // one for $w$:
+ template <int dim>
+ UltrasoundProblem<dim>::UltrasoundProblem (ParameterHandler& param)
+ :
+ prm(param),
+ dof_handler(triangulation),
+ fe(FE_Q<dim>(1), 2)
+ {}
+
+
+ template <int dim>
+ UltrasoundProblem<dim>::~UltrasoundProblem ()
+ {
+ dof_handler.clear();
+ }
- // @sect4{<code>UltrasoundProblem::make_grid</code>}
-
- // Here we setup the grid for our
- // domain. As mentioned in the
- // exposition, the geometry is just a
- // unit square (in 2d) with the part
- // of the boundary that represents
- // the transducer lens replaced by a
- // sector of a circle.
-template <int dim>
-void UltrasoundProblem<dim>::make_grid ()
-{
- // First we generate some logging
- // output and start a timer so we
- // can compute execution time when
- // this function is done:
- deallog << "Generating grid... ";
- Timer timer;
- timer.start ();
-
- // Then we query the values for the
- // focal distance of the transducer
- // lens and the number of mesh
- // refinement steps from our
- // ParameterHandler object:
- prm.enter_subsection ("Mesh & geometry parameters");
-
- const double focal_distance = prm.get_double("Focal distance");
- const unsigned int n_refinements = prm.get_integer("Number of refinements");
-
- prm.leave_subsection ();
-
- // Next, two points are defined for
- // position and focal point of the
- // transducer lens, which is the
- // center of the circle whose
- // segment will form the transducer
- // part of the boundary. We compute
- // the radius of this circle in
- // such a way that the segment fits
- // in the interval [0.4,0.6] on the
- // x-axis. Notice that this is the
- // only point in the program where
- // things are slightly different in
- // 2D and 3D. Even though this
- // tutorial only deals with the 2D
- // case, the necessary additions to
- // make this program functional in
- // 3D are so minimal that we opt
- // for including them:
- const Point<dim> transducer = (dim == 2) ?
+ // @sect4{<code>UltrasoundProblem::make_grid</code>}
+
+ // Here we setup the grid for our
+ // domain. As mentioned in the
+ // exposition, the geometry is just a
+ // unit square (in 2d) with the part
+ // of the boundary that represents
+ // the transducer lens replaced by a
+ // sector of a circle.
+ template <int dim>
+ void UltrasoundProblem<dim>::make_grid ()
+ {
+ // First we generate some logging
+ // output and start a timer so we
+ // can compute execution time when
+ // this function is done:
+ deallog << "Generating grid... ";
+ Timer timer;
+ timer.start ();
+
+ // Then we query the values for the
+ // focal distance of the transducer
+ // lens and the number of mesh
+ // refinement steps from our
+ // ParameterHandler object:
+ prm.enter_subsection ("Mesh & geometry parameters");
+
+ const double focal_distance = prm.get_double("Focal distance");
+ const unsigned int n_refinements = prm.get_integer("Number of refinements");
+
+ prm.leave_subsection ();
+
+ // Next, two points are defined for
+ // position and focal point of the
+ // transducer lens, which is the
+ // center of the circle whose
+ // segment will form the transducer
+ // part of the boundary. We compute
+ // the radius of this circle in
+ // such a way that the segment fits
+ // in the interval [0.4,0.6] on the
+ // x-axis. Notice that this is the
+ // only point in the program where
+ // things are slightly different in
+ // 2D and 3D. Even though this
+ // tutorial only deals with the 2D
+ // case, the necessary additions to
+ // make this program functional in
+ // 3D are so minimal that we opt
+ // for including them:
+ const Point<dim> transducer = (dim == 2) ?
Point<dim> (0.5, 0.0) :
- Point<dim> (0.5, 0.5, 0.0),
+ Point<dim> (0.5, 0.5, 0.0),
focal_point = (dim == 2) ?
Point<dim> (0.5, focal_distance) :
Point<dim> (0.5, 0.5, focal_distance);
- const double radius = std::sqrt( (focal_point.distance(transducer) *
- focal_point.distance(transducer)) +
- ((dim==2) ? 0.01 : 0.02));
-
-
- // As initial coarse grid we take a
- // simple unit square with 5
- // subdivisions in each
- // direction. The number of
- // subdivisions is chosen so that
- // the line segment $[0.4,0.6]$
- // that we want to designate as the
- // transducer boundary is spanned
- // by a single face. Then we step
- // through all cells to find the
- // faces where the transducer is to
- // be located, which in fact is
- // just the single edge from 0.4 to
- // 0.6 on the x-axis. This is where
- // we want the refinements to be
- // made according to a circle
- // shaped boundary, so we mark this
- // edge with a different boundary
- // indicator.
- GridGenerator::subdivided_hyper_cube (triangulation, 5, 0, 1);
-
- typename Triangulation<dim>::cell_iterator
- cell = triangulation.begin (),
- endc = triangulation.end();
-
- for (; cell!=endc; ++cell)
- for (unsigned int face=0; face<GeometryInfo<dim>::faces_per_cell; ++face)
- if ( cell->face(face)->at_boundary() &&
- ((cell->face(face)->center() - transducer).square() < 0.01) )
-
- cell->face(face)->set_boundary_indicator (1);
-
- // For the circle part of the
- // transducer lens, a hyper-ball
- // object is used (which, of course,
- // in 2D just represents a circle),
- // with radius and center as computed
- // above. By marking this object as
- // <code>static</code>, we ensure that
- // it lives until the end of the
- // program and thereby longer than the
- // triangulation object we will
- // associated with it. We then assign
- // this boundary-object to the part of
- // the boundary with boundary
- // indicator 1:
- static const HyperBallBoundary<dim> boundary(focal_point, radius);
- triangulation.set_boundary(1, boundary);
-
- // Now global refinement is
- // executed. Cells near the
- // transducer location will be
- // automatically refined according
- // to the circle shaped boundary of
- // the transducer lens:
- triangulation.refine_global (n_refinements);
-
- // Lastly, we generate some more
- // logging output. We stop the
- // timer and query the number of
- // CPU seconds elapsed since the
- // beginning of the function:
- timer.stop ();
- deallog << "done ("
- << timer()
- << "s)"
- << std::endl;
-
- deallog << " Number of active cells: "
- << triangulation.n_active_cells()
- << std::endl;
-}
-
-
- // @sect4{<code>UltrasoundProblem::setup_system</code>}
- //
- // Initialization of the system
- // matrix, sparsity patterns and
- // vectors are the same as in
- // previous examples and therefore do
- // not need further comment. As in
- // the previous function, we also
- // output the run time of what we do
- // here:
-template <int dim>
-void UltrasoundProblem<dim>::setup_system ()
-{
- deallog << "Setting up system... ";
- Timer timer;
- timer.start();
+ const double radius = std::sqrt( (focal_point.distance(transducer) *
+ focal_point.distance(transducer)) +
+ ((dim==2) ? 0.01 : 0.02));
+
+
+ // As initial coarse grid we take a
+ // simple unit square with 5
+ // subdivisions in each
+ // direction. The number of
+ // subdivisions is chosen so that
+ // the line segment $[0.4,0.6]$
+ // that we want to designate as the
+ // transducer boundary is spanned
+ // by a single face. Then we step
+ // through all cells to find the
+ // faces where the transducer is to
+ // be located, which in fact is
+ // just the single edge from 0.4 to
+ // 0.6 on the x-axis. This is where
+ // we want the refinements to be
+ // made according to a circle
+ // shaped boundary, so we mark this
+ // edge with a different boundary
+ // indicator.
+ GridGenerator::subdivided_hyper_cube (triangulation, 5, 0, 1);
+
+ typename Triangulation<dim>::cell_iterator
+ cell = triangulation.begin (),
+ endc = triangulation.end();
+
+ for (; cell!=endc; ++cell)
+ for (unsigned int face=0; face<GeometryInfo<dim>::faces_per_cell; ++face)
+ if ( cell->face(face)->at_boundary() &&
+ ((cell->face(face)->center() - transducer).square() < 0.01) )
+
+ cell->face(face)->set_boundary_indicator (1);
+
+ // For the circle part of the
+ // transducer lens, a hyper-ball
+ // object is used (which, of course,
+ // in 2D just represents a circle),
+ // with radius and center as computed
+ // above. By marking this object as
+ // <code>static</code>, we ensure that
+ // it lives until the end of the
+ // program and thereby longer than the
+ // triangulation object we will
+ // associated with it. We then assign
+ // this boundary-object to the part of
+ // the boundary with boundary
+ // indicator 1:
+ static const HyperBallBoundary<dim> boundary(focal_point, radius);
+ triangulation.set_boundary(1, boundary);
+
+ // Now global refinement is
+ // executed. Cells near the
+ // transducer location will be
+ // automatically refined according
+ // to the circle shaped boundary of
+ // the transducer lens:
+ triangulation.refine_global (n_refinements);
+
+ // Lastly, we generate some more
+ // logging output. We stop the
+ // timer and query the number of
+ // CPU seconds elapsed since the
+ // beginning of the function:
+ timer.stop ();
+ deallog << "done ("
+ << timer()
+ << "s)"
+ << std::endl;
+
+ deallog << " Number of active cells: "
+ << triangulation.n_active_cells()
+ << std::endl;
+ }
- dof_handler.distribute_dofs (fe);
- sparsity_pattern.reinit (dof_handler.n_dofs(),
- dof_handler.n_dofs(),
- dof_handler.max_couplings_between_dofs());
+ // @sect4{<code>UltrasoundProblem::setup_system</code>}
+ //
+ // Initialization of the system
+ // matrix, sparsity patterns and
+ // vectors are the same as in
+ // previous examples and therefore do
+ // not need further comment. As in
+ // the previous function, we also
+ // output the run time of what we do
+ // here:
+ template <int dim>
+ void UltrasoundProblem<dim>::setup_system ()
+ {
+ deallog << "Setting up system... ";
+ Timer timer;
+ timer.start();
- DoFTools::make_sparsity_pattern (dof_handler, sparsity_pattern);
- sparsity_pattern.compress();
+ dof_handler.distribute_dofs (fe);
- system_matrix.reinit (sparsity_pattern);
- system_rhs.reinit (dof_handler.n_dofs());
- solution.reinit (dof_handler.n_dofs());
+ sparsity_pattern.reinit (dof_handler.n_dofs(),
+ dof_handler.n_dofs(),
+ dof_handler.max_couplings_between_dofs());
- timer.stop ();
- deallog << "done ("
- << timer()
- << "s)"
- << std::endl;
+ DoFTools::make_sparsity_pattern (dof_handler, sparsity_pattern);
+ sparsity_pattern.compress();
- deallog << " Number of degrees of freedom: "
- << dof_handler.n_dofs()
- << std::endl;
-}
+ system_matrix.reinit (sparsity_pattern);
+ system_rhs.reinit (dof_handler.n_dofs());
+ solution.reinit (dof_handler.n_dofs());
+ timer.stop ();
+ deallog << "done ("
+ << timer()
+ << "s)"
+ << std::endl;
- // @sect4{<code>UltrasoundProblem::assemble_system</code>}
- // As before, this function takes
- // care of assembling the system
- // matrix and right hand side vector:
-template <int dim>
-void UltrasoundProblem<dim>::assemble_system ()
-{
- deallog << "Assembling system matrix... ";
- Timer timer;
- timer.start ();
-
- // First we query wavespeed and
- // frequency from the
- // ParameterHandler object and
- // store them in local variables,
- // as they will be used frequently
- // throughout this function.
-
- prm.enter_subsection ("Physical constants");
-
- const double omega = prm.get_double("omega"),
- c = prm.get_double("c");
-
- prm.leave_subsection ();
-
- // As usual, for computing
- // integrals ordinary Gauss
- // quadrature rule is used. Since
- // our bilinear form involves
- // boundary integrals on
- // $\Gamma_2$, we also need a
- // quadrature rule for surface
- // integration on the faces, which
- // are $dim-1$ dimensional:
- QGauss<dim> quadrature_formula(2);
- QGauss<dim-1> face_quadrature_formula(2);
-
- const unsigned int n_q_points = quadrature_formula.size(),
- n_face_q_points = face_quadrature_formula.size(),
- dofs_per_cell = fe.dofs_per_cell;
-
- // The FEValues objects will
- // evaluate the shape functions for
- // us. For the part of the
- // bilinear form that involves
- // integration on $\Omega$, we'll
- // need the values and gradients of
- // the shape functions, and of
- // course the quadrature weights.
- // For the terms involving the
- // boundary integrals, only shape
- // function values and the
- // quadrature weights are
- // necessary.
- FEValues<dim> fe_values (fe, quadrature_formula,
- update_values | update_gradients |
- update_JxW_values);
-
- FEFaceValues<dim> fe_face_values (fe, face_quadrature_formula,
- update_values | update_JxW_values);
-
- // As usual, the system matrix is
- // assembled cell by cell, and we
- // need a matrix for storing the
- // local cell contributions as well
- // as an index vector to transfer
- // the cell contributions to the
- // appropriate location in the
- // global system matrix after.
- FullMatrix<double> cell_matrix (dofs_per_cell, dofs_per_cell);
- std::vector<unsigned int> local_dof_indices (dofs_per_cell);
-
- typename DoFHandler<dim>::active_cell_iterator
- cell = dof_handler.begin_active(),
- endc = dof_handler.end();
-
- for (; cell!=endc; ++cell)
- {
+ deallog << " Number of degrees of freedom: "
+ << dof_handler.n_dofs()
+ << std::endl;
+ }
- // On each cell, we first need
- // to reset the local
- // contribution matrix and
- // request the FEValues object
- // to compute the shape
- // functions for the current
- // cell:
- cell_matrix = 0;
- fe_values.reinit (cell);
-
- for (unsigned int i=0; i<dofs_per_cell; ++i)
- {
- for (unsigned int j=0; j<dofs_per_cell; ++j)
- {
- // At this point, it is
- // important to keep in
- // mind that we are
- // dealing with a
- // finite element
- // system with two
- // components. Due to
- // the way we
- // constructed this
- // FESystem, namely as
- // the cartesian
- // product of two
- // scalar finite
- // element fields, each
- // shape function has
- // only a single
- // nonzero component
- // (they are, in
- // deal.II lingo, @ref
- // GlossPrimitive
- // "primitive").
- // Hence, each shape
- // function can be
- // viewed as one of the
- // $\phi$'s or $\psi$'s
- // from the
- // introduction, and
- // similarly the
- // corresponding
- // degrees of freedom
- // can be attributed to
- // either $\alpha$ or
- // $\beta$. As we
- // iterate through all
- // the degrees of
- // freedom on the
- // current cell
- // however, they do not
- // come in any
- // particular order,
- // and so we cannot
- // decide right away
- // whether the DoFs
- // with index $i$ and
- // $j$ belong to the
- // real or imaginary
- // part of our
- // solution. On the
- // other hand, if you
- // look at the form of
- // the system matrix in
- // the introduction,
- // this distinction is
- // crucial since it
- // will determine to
- // which block in the
- // system matrix the
- // contribution of the
- // current pair of DoFs
- // will go and hence
- // which quantity we
- // need to compute from
- // the given two shape
- // functions.
- // Fortunately, the
- // FESystem object can
- // provide us with this
- // information, namely
- // it has a function
- // FESystem::system_to_component_index,
- // that for each local
- // DoF index returns a
- // pair of integers of
- // which the first
- // indicates to which
- // component of the
- // system the DoF
- // belongs. The second
- // integer of the pair
- // indicates which
- // index the DoF has in
- // the scalar base
- // finite element
- // field, but this
- // information is not
- // relevant here. If
- // you want to know
- // more about this
- // function and the
- // underlying scheme
- // behind primitive
- // vector valued
- // elements, take a
- // look at step-8 or
- // the @ref
- // vector_valued
- // module, where these
- // topics are explained
- // in depth.
- if (fe.system_to_component_index(i).first ==
- fe.system_to_component_index(j).first)
- {
-
- // If both DoFs $i$
- // and $j$ belong
- // to same
- // component,
- // i.e. their shape
- // functions are
- // both $\phi$'s or
- // both $\psi$'s,
- // the contribution
- // will end up in
- // one of the
- // diagonal blocks
- // in our system
- // matrix, and
- // since the
- // corresponding
- // entries are
- // computed by the
- // same formula, we
- // do not bother if
- // they actually
- // are $\phi$ or
- // $\psi$ shape
- // functions. We
- // can simply
- // compute the
- // entry by
- // iterating over
- // all quadrature
- // points and
- // adding up their
- // contributions,
- // where values and
- // gradients of the
- // shape functions
- // are supplied by
- // our FEValues
- // object.
-
- for (unsigned int q_point=0; q_point<n_q_points; ++q_point)
- cell_matrix(i,j) += (((fe_values.shape_value(i,q_point) *
- fe_values.shape_value(j,q_point)) *
- (- omega * omega)
- +
- (fe_values.shape_grad(i,q_point) *
- fe_values.shape_grad(j,q_point)) *
- c * c) *
- fe_values.JxW(q_point));
-
- // You might think
- // that we would
- // have to specify
- // which component
- // of the shape
- // function we'd
- // like to evaluate
- // when requesting
- // shape function
- // values or
- // gradients from
- // the FEValues
- // object. However,
- // as the shape
- // functions are
- // primitive, they
- // have only one
- // nonzero
- // component, and
- // the FEValues
- // class is smart
- // enough to figure
- // out that we are
- // definitely
- // interested in
- // this one nonzero
- // component.
- }
- }
- }
-
-
- // We also have to add contributions
- // due to boundary terms. To this end,
- // we loop over all faces of the
- // current cell and see if first it is
- // at the boundary, and second has the
- // correct boundary indicator
- // associated with $\Gamma_2$, the
- // part of the boundary where we have
- // absorbing boundary conditions:
- for (unsigned int face=0; face<GeometryInfo<dim>::faces_per_cell; ++face)
- if (cell->face(face)->at_boundary() &&
- (cell->face(face)->boundary_indicator() == 0) )
+ // @sect4{<code>UltrasoundProblem::assemble_system</code>}
+ // As before, this function takes
+ // care of assembling the system
+ // matrix and right hand side vector:
+ template <int dim>
+ void UltrasoundProblem<dim>::assemble_system ()
+ {
+ deallog << "Assembling system matrix... ";
+ Timer timer;
+ timer.start ();
+
+ // First we query wavespeed and
+ // frequency from the
+ // ParameterHandler object and
+ // store them in local variables,
+ // as they will be used frequently
+ // throughout this function.
+
+ prm.enter_subsection ("Physical constants");
+
+ const double omega = prm.get_double("omega"),
+ c = prm.get_double("c");
+
+ prm.leave_subsection ();
+
+ // As usual, for computing
+ // integrals ordinary Gauss
+ // quadrature rule is used. Since
+ // our bilinear form involves
+ // boundary integrals on
+ // $\Gamma_2$, we also need a
+ // quadrature rule for surface
+ // integration on the faces, which
+ // are $dim-1$ dimensional:
+ QGauss<dim> quadrature_formula(2);
+ QGauss<dim-1> face_quadrature_formula(2);
+
+ const unsigned int n_q_points = quadrature_formula.size(),
+ n_face_q_points = face_quadrature_formula.size(),
+ dofs_per_cell = fe.dofs_per_cell;
+
+ // The FEValues objects will
+ // evaluate the shape functions for
+ // us. For the part of the
+ // bilinear form that involves
+ // integration on $\Omega$, we'll
+ // need the values and gradients of
+ // the shape functions, and of
+ // course the quadrature weights.
+ // For the terms involving the
+ // boundary integrals, only shape
+ // function values and the
+ // quadrature weights are
+ // necessary.
+ FEValues<dim> fe_values (fe, quadrature_formula,
+ update_values | update_gradients |
+ update_JxW_values);
+
+ FEFaceValues<dim> fe_face_values (fe, face_quadrature_formula,
+ update_values | update_JxW_values);
+
+ // As usual, the system matrix is
+ // assembled cell by cell, and we
+ // need a matrix for storing the
+ // local cell contributions as well
+ // as an index vector to transfer
+ // the cell contributions to the
+ // appropriate location in the
+ // global system matrix after.
+ FullMatrix<double> cell_matrix (dofs_per_cell, dofs_per_cell);
+ std::vector<unsigned int> local_dof_indices (dofs_per_cell);
+
+ typename DoFHandler<dim>::active_cell_iterator
+ cell = dof_handler.begin_active(),
+ endc = dof_handler.end();
+
+ for (; cell!=endc; ++cell)
+ {
+
+ // On each cell, we first need
+ // to reset the local
+ // contribution matrix and
+ // request the FEValues object
+ // to compute the shape
+ // functions for the current
+ // cell:
+ cell_matrix = 0;
+ fe_values.reinit (cell);
+
+ for (unsigned int i=0; i<dofs_per_cell; ++i)
{
+ for (unsigned int j=0; j<dofs_per_cell; ++j)
+ {
+
+ // At this point, it is
+ // important to keep in
+ // mind that we are
+ // dealing with a
+ // finite element
+ // system with two
+ // components. Due to
+ // the way we
+ // constructed this
+ // FESystem, namely as
+ // the cartesian
+ // product of two
+ // scalar finite
+ // element fields, each
+ // shape function has
+ // only a single
+ // nonzero component
+ // (they are, in
+ // deal.II lingo, @ref
+ // GlossPrimitive
+ // "primitive").
+ // Hence, each shape
+ // function can be
+ // viewed as one of the
+ // $\phi$'s or $\psi$'s
+ // from the
+ // introduction, and
+ // similarly the
+ // corresponding
+ // degrees of freedom
+ // can be attributed to
+ // either $\alpha$ or
+ // $\beta$. As we
+ // iterate through all
+ // the degrees of
+ // freedom on the
+ // current cell
+ // however, they do not
+ // come in any
+ // particular order,
+ // and so we cannot
+ // decide right away
+ // whether the DoFs
+ // with index $i$ and
+ // $j$ belong to the
+ // real or imaginary
+ // part of our
+ // solution. On the
+ // other hand, if you
+ // look at the form of
+ // the system matrix in
+ // the introduction,
+ // this distinction is
+ // crucial since it
+ // will determine to
+ // which block in the
+ // system matrix the
+ // contribution of the
+ // current pair of DoFs
+ // will go and hence
+ // which quantity we
+ // need to compute from
+ // the given two shape
+ // functions.
+ // Fortunately, the
+ // FESystem object can
+ // provide us with this
+ // information, namely
+ // it has a function
+ // FESystem::system_to_component_index,
+ // that for each local
+ // DoF index returns a
+ // pair of integers of
+ // which the first
+ // indicates to which
+ // component of the
+ // system the DoF
+ // belongs. The second
+ // integer of the pair
+ // indicates which
+ // index the DoF has in
+ // the scalar base
+ // finite element
+ // field, but this
+ // information is not
+ // relevant here. If
+ // you want to know
+ // more about this
+ // function and the
+ // underlying scheme
+ // behind primitive
+ // vector valued
+ // elements, take a
+ // look at step-8 or
+ // the @ref
+ // vector_valued
+ // module, where these
+ // topics are explained
+ // in depth.
+ if (fe.system_to_component_index(i).first ==
+ fe.system_to_component_index(j).first)
+ {
+
+ // If both DoFs $i$
+ // and $j$ belong
+ // to same
+ // component,
+ // i.e. their shape
+ // functions are
+ // both $\phi$'s or
+ // both $\psi$'s,
+ // the contribution
+ // will end up in
+ // one of the
+ // diagonal blocks
+ // in our system
+ // matrix, and
+ // since the
+ // corresponding
+ // entries are
+ // computed by the
+ // same formula, we
+ // do not bother if
+ // they actually
+ // are $\phi$ or
+ // $\psi$ shape
+ // functions. We
+ // can simply
+ // compute the
+ // entry by
+ // iterating over
+ // all quadrature
+ // points and
+ // adding up their
+ // contributions,
+ // where values and
+ // gradients of the
+ // shape functions
+ // are supplied by
+ // our FEValues
+ // object.
+
+ for (unsigned int q_point=0; q_point<n_q_points; ++q_point)
+ cell_matrix(i,j) += (((fe_values.shape_value(i,q_point) *
+ fe_values.shape_value(j,q_point)) *
+ (- omega * omega)
+ +
+ (fe_values.shape_grad(i,q_point) *
+ fe_values.shape_grad(j,q_point)) *
+ c * c) *
+ fe_values.JxW(q_point));
+
+ // You might think
+ // that we would
+ // have to specify
+ // which component
+ // of the shape
+ // function we'd
+ // like to evaluate
+ // when requesting
+ // shape function
+ // values or
+ // gradients from
+ // the FEValues
+ // object. However,
+ // as the shape
+ // functions are
+ // primitive, they
+ // have only one
+ // nonzero
+ // component, and
+ // the FEValues
+ // class is smart
+ // enough to figure
+ // out that we are
+ // definitely
+ // interested in
+ // this one nonzero
+ // component.
+ }
+ }
+ }
- // These faces will
- // certainly contribute
- // to the off-diagonal
- // blocks of the system
- // matrix, so we ask the
- // FEFaceValues object to
- // provide us with the
- // shape function values
- // on this face:
- fe_face_values.reinit (cell, face);
-
-
- // Next, we loop through
- // all DoFs of the
- // current cell to find
- // pairs that belong to
- // different components
- // and both have support
- // on the current face:
- for (unsigned int i=0; i<dofs_per_cell; ++i)
- for (unsigned int j=0; j<dofs_per_cell; ++j)
- if ((fe.system_to_component_index(i).first !=
- fe.system_to_component_index(j).first) &&
- fe.has_support_on_face(i, face) &&
- fe.has_support_on_face(j, face))
- // The check
- // whether shape
- // functions have
- // support on a
- // face is not
- // strictly
- // necessary: if we
- // don't check for
- // it we would
- // simply add up
- // terms to the
- // local cell
- // matrix that
- // happen to be
- // zero because at
- // least one of the
- // shape functions
- // happens to be
- // zero. However,
- // we can save that
- // work by adding
- // the checks
- // above.
-
- // In either case,
- // these DoFs will
- // contribute to
- // the boundary
- // integrals in the
- // off-diagonal
- // blocks of the
- // system
- // matrix. To
- // compute the
- // integral, we
- // loop over all
- // the quadrature
- // points on the
- // face and sum up
- // the contribution
- // weighted with
- // the quadrature
- // weights that the
- // face quadrature
- // rule provides.
- // In contrast to
- // the entries on
- // the diagonal
- // blocks, here it
- // does matter
- // which one of the
- // shape functions
- // is a $\psi$ and
- // which one is a
- // $\phi$, since
- // that will
- // determine the
- // sign of the
- // entry. We
- // account for this
- // by a simple
- // conditional
- // statement that
- // determines the
- // correct
- // sign. Since we
- // already checked
- // that DoF $i$ and
- // $j$ belong to
- // different
- // components, it
- // suffices here to
- // test for one of
- // them to which
- // component it
- // belongs.
- for (unsigned int q_point=0; q_point<n_face_q_points; ++q_point)
- cell_matrix(i,j) += ((fe.system_to_component_index(i).first == 0) ? -1 : 1) *
- fe_face_values.shape_value(i,q_point) *
- fe_face_values.shape_value(j,q_point) *
- c *
- omega *
- fe_face_values.JxW(q_point);
- }
+ // We also have to add contributions
+ // due to boundary terms. To this end,
+ // we loop over all faces of the
+ // current cell and see if first it is
+ // at the boundary, and second has the
+ // correct boundary indicator
+ // associated with $\Gamma_2$, the
+ // part of the boundary where we have
+ // absorbing boundary conditions:
+ for (unsigned int face=0; face<GeometryInfo<dim>::faces_per_cell; ++face)
+ if (cell->face(face)->at_boundary() &&
+ (cell->face(face)->boundary_indicator() == 0) )
+ {
- // Now we are done with this
- // cell and have to transfer
- // its contributions from the
- // local to the global system
- // matrix. To this end, we
- // first get a list of the
- // global indices of the this
- // cells DoFs...
- cell->get_dof_indices (local_dof_indices);
-
-
- // ...and then add the entries to
- // the system matrix one by
- // one:
- 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));
- }
+ // These faces will
+ // certainly contribute
+ // to the off-diagonal
+ // blocks of the system
+ // matrix, so we ask the
+ // FEFaceValues object to
+ // provide us with the
+ // shape function values
+ // on this face:
+ fe_face_values.reinit (cell, face);
+
+
+ // Next, we loop through
+ // all DoFs of the
+ // current cell to find
+ // pairs that belong to
+ // different components
+ // and both have support
+ // on the current face:
+ for (unsigned int i=0; i<dofs_per_cell; ++i)
+ for (unsigned int j=0; j<dofs_per_cell; ++j)
+ if ((fe.system_to_component_index(i).first !=
+ fe.system_to_component_index(j).first) &&
+ fe.has_support_on_face(i, face) &&
+ fe.has_support_on_face(j, face))
+ // The check
+ // whether shape
+ // functions have
+ // support on a
+ // face is not
+ // strictly
+ // necessary: if we
+ // don't check for
+ // it we would
+ // simply add up
+ // terms to the
+ // local cell
+ // matrix that
+ // happen to be
+ // zero because at
+ // least one of the
+ // shape functions
+ // happens to be
+ // zero. However,
+ // we can save that
+ // work by adding
+ // the checks
+ // above.
+
+ // In either case,
+ // these DoFs will
+ // contribute to
+ // the boundary
+ // integrals in the
+ // off-diagonal
+ // blocks of the
+ // system
+ // matrix. To
+ // compute the
+ // integral, we
+ // loop over all
+ // the quadrature
+ // points on the
+ // face and sum up
+ // the contribution
+ // weighted with
+ // the quadrature
+ // weights that the
+ // face quadrature
+ // rule provides.
+ // In contrast to
+ // the entries on
+ // the diagonal
+ // blocks, here it
+ // does matter
+ // which one of the
+ // shape functions
+ // is a $\psi$ and
+ // which one is a
+ // $\phi$, since
+ // that will
+ // determine the
+ // sign of the
+ // entry. We
+ // account for this
+ // by a simple
+ // conditional
+ // statement that
+ // determines the
+ // correct
+ // sign. Since we
+ // already checked
+ // that DoF $i$ and
+ // $j$ belong to
+ // different
+ // components, it
+ // suffices here to
+ // test for one of
+ // them to which
+ // component it
+ // belongs.
+ for (unsigned int q_point=0; q_point<n_face_q_points; ++q_point)
+ cell_matrix(i,j) += ((fe.system_to_component_index(i).first == 0) ? -1 : 1) *
+ fe_face_values.shape_value(i,q_point) *
+ fe_face_values.shape_value(j,q_point) *
+ c *
+ omega *
+ fe_face_values.JxW(q_point);
+ }
- // The only thing left are the
- // Dirichlet boundary values on
- // $\Gamma_1$, which is
- // characterized by the boundary
- // indicator 1. The Dirichlet
- // values are provided by the
- // <code>DirichletBoundaryValues</code>
- // class we defined above:
- std::map<unsigned int,double> boundary_values;
- VectorTools::interpolate_boundary_values (dof_handler,
- 1,
- DirichletBoundaryValues<dim>(),
- boundary_values);
-
- MatrixTools::apply_boundary_values (boundary_values,
- system_matrix,
- solution,
- system_rhs);
-
- timer.stop ();
- deallog << "done ("
- << timer()
- << "s)"
- << std::endl;
-}
+ // Now we are done with this
+ // cell and have to transfer
+ // its contributions from the
+ // local to the global system
+ // matrix. To this end, we
+ // first get a list of the
+ // global indices of the this
+ // cells DoFs...
+ cell->get_dof_indices (local_dof_indices);
+
+
+ // ...and then add the entries to
+ // the system matrix one by
+ // one:
+ 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));
+ }
+
+
+ // The only thing left are the
+ // Dirichlet boundary values on
+ // $\Gamma_1$, which is
+ // characterized by the boundary
+ // indicator 1. The Dirichlet
+ // values are provided by the
+ // <code>DirichletBoundaryValues</code>
+ // class we defined above:
+ std::map<unsigned int,double> boundary_values;
+ VectorTools::interpolate_boundary_values (dof_handler,
+ 1,
+ DirichletBoundaryValues<dim>(),
+ boundary_values);
+
+ MatrixTools::apply_boundary_values (boundary_values,
+ system_matrix,
+ solution,
+ system_rhs);
+
+ timer.stop ();
+ deallog << "done ("
+ << timer()
+ << "s)"
+ << std::endl;
+ }
- // @sect4{<code>UltrasoundProblem::solve</code>}
-
- // As already mentioned in the
- // introduction, the system matrix is
- // neither symmetric nor definite,
- // and so it is not quite obvious how
- // to come up with an iterative
- // solver and a preconditioner that
- // do a good job on this matrix. We
- // chose instead to go a different
- // way and solve the linear system
- // with the sparse LU decomposition
- // provided by UMFPACK. This is often
- // a good first choice for 2D
- // problems and works reasonably well
- // even for a large number of DoFs.
- // The deal.II interface to UMFPACK
- // is given by the
- // SparseDirectUMFPACK class, which
- // is very easy to use and allows us
- // to solve our linear system with
- // just 3 lines of code.
-
- // Note again that for compiling this
- // example program, you need to have
- // the deal.II library built with
- // UMFPACK support, which can be
- // achieved by providing the <code>
- // --with-umfpack</code> switch to
- // the configure script prior to
- // compilation of the library.
-template <int dim>
-void UltrasoundProblem<dim>::solve ()
-{
- deallog << "Solving linear system... ";
- Timer timer;
- timer.start ();
-
- // The code to solve the linear
- // system is short: First, we
- // allocate an object of the right
- // type. The following
- // <code>initialize</code> call
- // provides the matrix that we
- // would like to invert to the
- // SparseDirectUMFPACK object, and
- // at the same time kicks off the
- // LU-decomposition. Hence, this is
- // also the point where most of the
- // computational work in this
- // program happens.
- SparseDirectUMFPACK A_direct;
- A_direct.initialize(system_matrix);
-
- // After the decomposition, we can
- // use <code>A_direct</code> like a
- // matrix representing the inverse
- // of our system matrix, so to
- // compute the solution we just
- // have to multiply with the right
- // hand side vector:
- A_direct.vmult (solution, system_rhs);
-
- timer.stop ();
- deallog << "done ("
- << timer ()
- << "s)"
- << std::endl;
-}
+ // @sect4{<code>UltrasoundProblem::solve</code>}
+
+ // As already mentioned in the
+ // introduction, the system matrix is
+ // neither symmetric nor definite,
+ // and so it is not quite obvious how
+ // to come up with an iterative
+ // solver and a preconditioner that
+ // do a good job on this matrix. We
+ // chose instead to go a different
+ // way and solve the linear system
+ // with the sparse LU decomposition
+ // provided by UMFPACK. This is often
+ // a good first choice for 2D
+ // problems and works reasonably well
+ // even for a large number of DoFs.
+ // The deal.II interface to UMFPACK
+ // is given by the
+ // SparseDirectUMFPACK class, which
+ // is very easy to use and allows us
+ // to solve our linear system with
+ // just 3 lines of code.
+
+ // Note again that for compiling this
+ // example program, you need to have
+ // the deal.II library built with
+ // UMFPACK support, which can be
+ // achieved by providing the <code>
+ // --with-umfpack</code> switch to
+ // the configure script prior to
+ // compilation of the library.
+ template <int dim>
+ void UltrasoundProblem<dim>::solve ()
+ {
+ deallog << "Solving linear system... ";
+ Timer timer;
+ timer.start ();
+
+ // The code to solve the linear
+ // system is short: First, we
+ // allocate an object of the right
+ // type. The following
+ // <code>initialize</code> call
+ // provides the matrix that we
+ // would like to invert to the
+ // SparseDirectUMFPACK object, and
+ // at the same time kicks off the
+ // LU-decomposition. Hence, this is
+ // also the point where most of the
+ // computational work in this
+ // program happens.
+ SparseDirectUMFPACK A_direct;
+ A_direct.initialize(system_matrix);
+
+ // After the decomposition, we can
+ // use <code>A_direct</code> like a
+ // matrix representing the inverse
+ // of our system matrix, so to
+ // compute the solution we just
+ // have to multiply with the right
+ // hand side vector:
+ A_direct.vmult (solution, system_rhs);
+
+ timer.stop ();
+ deallog << "done ("
+ << timer ()
+ << "s)"
+ << std::endl;
+ }
- // @sect4{<code>UltrasoundProblem::output_results</code>}
-
- // Here we output our solution $v$
- // and $w$ as well as the derived
- // quantity $|u|$ in the format
- // specified in the parameter
- // file. Most of the work for
- // deriving $|u|$ from $v$ and $w$
- // was already done in the
- // implementation of the
- // <code>ComputeIntensity</code>
- // class, so that the output routine
- // is rather straightforward and very
- // similar to what is done in the
- // previous tutorials.
-template <int dim>
-void UltrasoundProblem<dim>::output_results () const
-{
- deallog << "Generating output... ";
- Timer timer;
- timer.start ();
+ // @sect4{<code>UltrasoundProblem::output_results</code>}
- // Define objects of our
- // <code>ComputeIntensity</code>
- // class and a DataOut object:
- ComputeIntensity<dim> intensities;
- DataOut<dim> data_out;
-
- data_out.attach_dof_handler (dof_handler);
-
- // Next we query the output-related
- // parameters from the
- // ParameterHandler. The
- // DataOut::parse_parameters call
- // acts as a counterpart to the
- // DataOutInterface<1>::declare_parameters
- // call in
- // <code>ParameterReader::declare_parameters</code>. It
- // collects all the output format
- // related parameters from the
- // ParameterHandler and sets the
- // corresponding properties of the
- // DataOut object accordingly.
- prm.enter_subsection("Output parameters");
-
- const std::string output_file = prm.get("Output file");
- data_out.parse_parameters(prm);
-
- prm.leave_subsection ();
-
- // Now we put together the filename from
- // the base name provided by the
- // ParameterHandler and the suffix which is
- // provided by the DataOut class (the
- // default suffix is set to the right type
- // that matches the one set in the .prm
- // file through parse_parameters()):
- const std::string filename = output_file +
- data_out.default_suffix();
-
- std::ofstream output (filename.c_str());
-
- // The solution vectors $v$ and $w$
- // are added to the DataOut object
- // in the usual way:
- std::vector<std::string> solution_names;
- solution_names.push_back ("Re_u");
- solution_names.push_back ("Im_u");
-
- data_out.add_data_vector (solution, solution_names);
-
- // For the intensity, we just call
- // <code>add_data_vector</code>
- // again, but this with our
+ // Here we output our solution $v$
+ // and $w$ as well as the derived
+ // quantity $|u|$ in the format
+ // specified in the parameter
+ // file. Most of the work for
+ // deriving $|u|$ from $v$ and $w$
+ // was already done in the
+ // implementation of the
// <code>ComputeIntensity</code>
- // object as the second argument,
- // which effectively adds $|u|$ to
- // the output data:
- data_out.add_data_vector (solution, intensities);
-
- // The last steps are as before. Note
- // that the actual output format is
- // now determined by what is stated in
- // the input file, i.e. one can change
- // the output format without having to
- // re-compile this program:
- data_out.build_patches ();
- data_out.write (output);
-
- timer.stop ();
- deallog << "done ("
- << timer()
- << "s)"
- << std::endl;
-}
+ // class, so that the output routine
+ // is rather straightforward and very
+ // similar to what is done in the
+ // previous tutorials.
+ template <int dim>
+ void UltrasoundProblem<dim>::output_results () const
+ {
+ deallog << "Generating output... ";
+ Timer timer;
+ timer.start ();
+
+ // Define objects of our
+ // <code>ComputeIntensity</code>
+ // class and a DataOut object:
+ ComputeIntensity<dim> intensities;
+ DataOut<dim> data_out;
+
+ data_out.attach_dof_handler (dof_handler);
+
+ // Next we query the output-related
+ // parameters from the
+ // ParameterHandler. The
+ // DataOut::parse_parameters call
+ // acts as a counterpart to the
+ // DataOutInterface<1>::declare_parameters
+ // call in
+ // <code>ParameterReader::declare_parameters</code>. It
+ // collects all the output format
+ // related parameters from the
+ // ParameterHandler and sets the
+ // corresponding properties of the
+ // DataOut object accordingly.
+ prm.enter_subsection("Output parameters");
+
+ const std::string output_file = prm.get("Output file");
+ data_out.parse_parameters(prm);
+
+ prm.leave_subsection ();
+
+ // Now we put together the filename from
+ // the base name provided by the
+ // ParameterHandler and the suffix which is
+ // provided by the DataOut class (the
+ // default suffix is set to the right type
+ // that matches the one set in the .prm
+ // file through parse_parameters()):
+ const std::string filename = output_file +
+ data_out.default_suffix();
+
+ std::ofstream output (filename.c_str());
+
+ // The solution vectors $v$ and $w$
+ // are added to the DataOut object
+ // in the usual way:
+ std::vector<std::string> solution_names;
+ solution_names.push_back ("Re_u");
+ solution_names.push_back ("Im_u");
+
+ data_out.add_data_vector (solution, solution_names);
+
+ // For the intensity, we just call
+ // <code>add_data_vector</code>
+ // again, but this with our
+ // <code>ComputeIntensity</code>
+ // object as the second argument,
+ // which effectively adds $|u|$ to
+ // the output data:
+ data_out.add_data_vector (solution, intensities);
+
+ // The last steps are as before. Note
+ // that the actual output format is
+ // now determined by what is stated in
+ // the input file, i.e. one can change
+ // the output format without having to
+ // re-compile this program:
+ data_out.build_patches ();
+ data_out.write (output);
+
+ timer.stop ();
+ deallog << "done ("
+ << timer()
+ << "s)"
+ << std::endl;
+ }
- // @sect4{<code>UltrasoundProblem::run</code>}
- // Here we simply execute our
- // functions one after the other:
-template <int dim>
-void UltrasoundProblem<dim>::run ()
-{
- make_grid ();
- setup_system ();
- assemble_system ();
- solve ();
- output_results ();
+ // @sect4{<code>UltrasoundProblem::run</code>}
+ // Here we simply execute our
+ // functions one after the other:
+ template <int dim>
+ void UltrasoundProblem<dim>::run ()
+ {
+ make_grid ();
+ setup_system ();
+ assemble_system ();
+ solve ();
+ output_results ();
+ }
}
// values so read are then handed over
// to an instance of the
// UltrasoundProblem class:
-int main ()
+int main ()
{
try
{
+ using namespace dealii;
+ using namespace Step29;
+
ParameterHandler prm;
ParameterReader param(prm);
param.read_parameters("step-29.prm");
<< std::endl;
return 1;
}
- catch (...)
+ catch (...)
{
std::cerr << std::endl << std::endl
<< "----------------------------------------------------"