From: bangerth Date: Sat, 20 Aug 2011 05:00:30 +0000 (+0000) Subject: Move everything in these tutorial programs into a namespace StepXX. X-Git-Url: https://gitweb.dealii.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=09889d091bd75618263dbe851d194a04daf85c2a;p=dealii-svn.git Move everything in these tutorial programs into a namespace StepXX. git-svn-id: https://svn.dealii.org/trunk@24119 0785d39b-7218-0410-832d-ea1e28bc413d --- diff --git a/deal.II/examples/step-11/step-11.cc b/deal.II/examples/step-11/step-11.cc index e47b4a1cd5..a237ffc6ec 100644 --- a/deal.II/examples/step-11/step-11.cc +++ b/deal.II/examples/step-11/step-11.cc @@ -3,7 +3,7 @@ /* $Id$ */ /* */ -/* Copyright (C) 2001, 2002, 2003, 2004, 2006, 2009 by the deal.II authors */ +/* Copyright (C) 2001, 2002, 2003, 2004, 2006, 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 */ @@ -56,607 +56,610 @@ // The last step is as in all // previous programs: -using namespace dealii; - - // Then we declare a class which - // represents the solution of a - // Laplace problem. As this example - // program is based on step-5, the - // class looks rather the same, with - // the sole structural difference - // that the functions - // assemble_system now calls - // solve itself, and is thus - // called assemble_and_solve, and - // that the output function was - // dropped since the solution - // function is so boring that it is - // not worth being viewed. - // - // The only other noteworthy change - // is that the constructor takes a - // value representing the polynomial - // degree of the mapping to be used - // later on, and that it has another - // member variable representing - // exactly this mapping. In general, - // this variable will occur in real - // applications at the same places - // where the finite element is - // declared or used. -template -class LaplaceProblem +namespace Step11 { - public: - LaplaceProblem (const unsigned int mapping_degree); - void run (); - - private: - void setup_system (); - void assemble_and_solve (); - void solve (); - - Triangulation triangulation; - FE_Q fe; - DoFHandler dof_handler; - MappingQ mapping; - - SparsityPattern sparsity_pattern; - SparseMatrix system_matrix; - ConstraintMatrix mean_value_constraints; - - Vector solution; - Vector system_rhs; - - TableHandler output_table; -}; - - - - // Construct such an object, by - // initializing the variables. Here, - // we use linear finite elements (the - // argument to the fe variable - // denotes the polynomial degree), - // and mappings of given order. Print - // to screen what we are about to do. -template -LaplaceProblem::LaplaceProblem (const unsigned int mapping_degree) : - fe (1), - dof_handler (triangulation), - mapping (mapping_degree) -{ - std::cout << "Using mapping with degree " << mapping_degree << ":" - << std::endl - << "============================" - << std::endl; -} - - - - // The first task is to set up the - // variables for this problem. This - // includes generating a valid - // DoFHandler object, as well as - // the sparsity patterns for the - // matrix, and the object - // representing the constraints that - // the mean value of the degrees of - // freedom on the boundary be zero. -template -void LaplaceProblem::setup_system () -{ - // The first task is trivial: - // generate an enumeration of the - // degrees of freedom, and - // initialize solution and right - // hand side vector to their - // correct sizes: - dof_handler.distribute_dofs (fe); - solution.reinit (dof_handler.n_dofs()); - system_rhs.reinit (dof_handler.n_dofs()); - - // Next task is to construct the - // object representing the - // constraint that the mean value - // of the degrees of freedom on the - // boundary shall be zero. For - // this, we first want a list of - // those nodes which are actually - // at the boundary. The - // DoFTools class has a - // function that returns an array - // of boolean values where true - // indicates that the node is at - // the boundary. The second - // argument denotes a mask - // selecting which components of - // vector valued finite elements we - // want to be considered. Since we - // have a scalar finite element - // anyway, this mask consists of - // only one entry, and its value - // must be true. - std::vector boundary_dofs (dof_handler.n_dofs(), false); - DoFTools::extract_boundary_dofs (dof_handler, std::vector(1,true), - boundary_dofs); - - // Now first for the generation of - // the constraints: as mentioned in - // the introduction, we constrain - // one of the nodes on the boundary - // by the values of all other DoFs - // on the boundary. So, let us - // first pick out the first - // boundary node from this list. We - // do that by searching for the - // first true value in the - // array (note that std::find - // returns an iterator to this - // element), and computing its - // distance to the overall first - // element in the array to get its - // index: - const unsigned int first_boundary_dof - = std::distance (boundary_dofs.begin(), - std::find (boundary_dofs.begin(), - boundary_dofs.end(), - true)); - - // Then generate a constraints - // object with just this one - // constraint. First clear all - // previous content (which might - // reside there from the previous - // computation on a once coarser - // grid), then add this one line - // constraining the - // first_boundary_dof to the - // sum of other boundary DoFs each - // with weight -1. Finally, close - // the constraints object, i.e. do - // some internal bookkeeping on it - // for faster processing of what is - // to come later: - mean_value_constraints.clear (); - mean_value_constraints.add_line (first_boundary_dof); - for (unsigned int i=first_boundary_dof+1; iDoFTools::make_sparsity_pattern - // and condense the result using - // the hanging node constraints. We - // have no hanging node constraints - // here (since we only refine - // globally in this example), but - // we have this global constraint - // on the boundary. This poses one - // severe problem in this context: - // the SparsityPattern class - // wants us to state beforehand the - // maximal number of entries per - // row, either for all rows or for - // each row separately. There are - // functions in the library which - // can tell you this number in case - // you just have hanging node - // constraints (namely - // DoFHandler::max_coupling_between_dofs), - // but how is this for the present - // case? The difficulty arises - // because the elimination of the - // constrained degree of freedom - // requires a number of additional - // entries in the matrix at places - // that are not so simple to - // determine. We would therefore - // have a problem had we to give a - // maximal number of entries per - // row here. - // - // Since this can be so difficult - // that no reasonable answer can be - // given that allows allocation of - // only a reasonable amount of - // memory, there is a class - // CompressedSparsityPattern, - // that can help us out here. It - // does not require that we know in - // advance how many entries rows - // could have, but allows just - // about any length. It is thus - // significantly more flexible in - // case you do not have good - // estimates of row lengths, - // however at the price that - // building up such a pattern is - // also significantly more - // expensive than building up a - // pattern for which you had - // information in - // advance. Nevertheless, as we - // have no other choice here, we'll - // just build such an object by - // initializing it with the - // dimensions of the matrix and - // calling another function - // DoFTools::make_sparsity_pattern - // to get the sparsity pattern due - // to the differential operator, - // then condense it with the - // constraints object which adds - // those positions in the sparsity - // pattern that are required for - // the elimination of the - // constraint. - CompressedSparsityPattern csp (dof_handler.n_dofs(), - dof_handler.n_dofs()); - DoFTools::make_sparsity_pattern (dof_handler, csp); - mean_value_constraints.condense (csp); - - // Finally, once we have the full - // pattern, we can initialize an - // object of type - // SparsityPattern from it and - // in turn initialize the matrix - // with it. Note that this is - // actually necessary, since the - // CompressedSparsityPattern is - // so inefficient compared to the - // SparsityPattern class due to - // the more flexible data - // structures it has to use, that - // we can impossibly base the - // sparse matrix class on it, but - // rather need an object of type - // SparsityPattern, which we - // generate by copying from the - // intermediate object. - // - // As a further sidenote, you will - // notice that we do not explicitly - // have to compress the - // sparsity pattern here. This, of - // course, is due to the fact that - // the copy_from function - // generates a compressed object - // right from the start, to which - // you cannot add new entries - // anymore. The compress call - // is therefore implicit in the - // copy_from call. - sparsity_pattern.copy_from (csp); - system_matrix.reinit (sparsity_pattern); -} - - - - // The next function then assembles - // the linear system of equations, - // solves it, and evaluates the - // solution. This then makes three - // actions, and we will put them into - // eight true statements (excluding - // declaration of variables, and - // handling of temporary - // vectors). Thus, this function is - // something for the very - // lazy. Nevertheless, the functions - // called are rather powerful, and - // through them this function uses a - // good deal of the whole - // library. But let's look at each of - // the steps. -template -void LaplaceProblem::assemble_and_solve () -{ - - // First, we have to assemble the - // matrix and the right hand - // side. In all previous examples, - // we have investigated various - // ways how to do this - // manually. However, since the - // Laplace matrix and simple right - // hand sides appear so frequently - // in applications, the library - // provides functions for actually - // doing this for you, i.e. they - // perform the loop over all cells, - // setting up the local matrices - // and vectors, and putting them - // together for the end result. - // - // The following are the two most - // commonly used ones: creation of - // the Laplace matrix and creation - // of a right hand side vector from - // body or boundary forces. They - // take the mapping object, the - // DoFHandler object - // representing the degrees of - // freedom and the finite element - // in use, a quadrature formula to - // be used, and the output - // object. The function that - // creates a right hand side vector - // also has to take a function - // object describing the - // (continuous) right hand side - // function. - // - // Let us look at the way the - // matrix and body forces are - // integrated: - const unsigned int gauss_degree - = std::max (static_cast(std::ceil(1.*(mapping.get_degree()+1)/2)), - 2U); - MatrixTools::create_laplace_matrix (mapping, dof_handler, - QGauss(gauss_degree), - system_matrix); - VectorTools::create_right_hand_side (mapping, dof_handler, - QGauss(gauss_degree), - ConstantFunction(-2), - system_rhs); - // That's quite simple, right? + using namespace dealii; + + // Then we declare a class which + // represents the solution of a + // Laplace problem. As this example + // program is based on step-5, the + // class looks rather the same, with + // the sole structural difference + // that the functions + // assemble_system now calls + // solve itself, and is thus + // called assemble_and_solve, and + // that the output function was + // dropped since the solution + // function is so boring that it is + // not worth being viewed. // - // Two remarks are in order, - // though: First, these functions - // are used in a lot of - // contexts. Maybe you want to - // create a Laplace or mass matrix - // for a vector values finite - // element; or you want to use the - // default Q1 mapping; or you want - // to assembled the matrix with a - // coefficient in the Laplace - // operator. For this reason, there - // are quite a large number of - // variants of these functions in - // the MatrixCreator and - // MatrixTools - // classes. Whenever you need a - // slightly different version of - // these functions than the ones - // called above, it is certainly - // worthwhile to take a look at the - // documentation and to check - // whether something fits your - // needs. - // - // The second remark concerns the - // quadrature formula we use: we - // want to integrate over bilinear - // shape functions, so we know that - // we have to use at least a Gauss2 - // quadrature formula. On the other - // hand, we want to have the - // quadrature rule to have at least - // the order of the boundary - // approximation. Since the order - // of Gauss-r is 2r, and the order - // of the boundary approximation - // using polynomials of degree p is - // p+1, we know that 2r@>=p+1. Since - // r has to be an integer and (as - // mentioned above) has to be at - // least 2, this makes up for the - // formula above computing - // gauss_degree. - // - // Since the generation of the body - // force contributions to the right - // hand side vector was so simple, - // we do that all over again for - // the boundary forces as well: - // allocate a vector of the right - // size and call the right - // function. The boundary function - // has constant values, so we can - // generate an object from the - // library on the fly, and we use - // the same quadrature formula as - // above, but this time of lower - // dimension since we integrate - // over faces now instead of cells: - Vector tmp (system_rhs.size()); - VectorTools::create_boundary_right_hand_side (mapping, dof_handler, - QGauss(gauss_degree), - ConstantFunction(1), - tmp); - // Then add the contributions from - // the boundary to those from the - // interior of the domain: - system_rhs += tmp; - // For assembling the right hand - // side, we had to use two - // different vector objects, and - // later add them together. The - // reason we had to do so is that - // the - // VectorTools::create_right_hand_side - // and - // VectorTools::create_boundary_right_hand_side - // functions first clear the output - // vector, rather than adding up - // their results to previous - // contents. This can reasonably be - // called a design flaw in the - // library made in its infancy, but - // unfortunately things are as they - // are for some time now and it is - // difficult to change such things - // that silently break existing - // code, so we have to live with - // that. - - // Now, the linear system is set - // up, so we can eliminate the one - // degree of freedom which we - // constrained to the other DoFs on - // the boundary for the mean value - // constraint from matrix and right - // hand side vector, and solve the - // system. After that, distribute - // the constraints again, which in - // this case means setting the - // constrained degree of freedom to - // its proper value - mean_value_constraints.condense (system_matrix); - mean_value_constraints.condense (system_rhs); - - solve (); - mean_value_constraints.distribute (solution); - - // Finally, evaluate what we got as - // solution. As stated in the - // introduction, we are interested - // in the H1 semi-norm of the - // solution. Here, as well, we have - // a function in the library that - // does this, although in a - // slightly non-obvious way: the - // VectorTools::integrate_difference - // function integrates the norm of - // the difference between a finite - // element function and a - // continuous function. If we - // therefore want the norm of a - // finite element field, we just - // put the continuous function to - // zero. Note that this function, - // just as so many other ones in - // the library as well, has at - // least two versions, one which - // takes a mapping as argument - // (which we make us of here), and - // the one which we have used in - // previous examples which - // implicitly uses MappingQ1. - // Also note that we take a - // quadrature formula of one degree - // higher, in order to avoid - // superconvergence effects where - // the solution happens to be - // especially close to the exact - // solution at certain points (we - // don't know whether this might be - // the case here, but there are - // cases known of this, and we just - // want to make sure): - Vector norm_per_cell (triangulation.n_active_cells()); - VectorTools::integrate_difference (mapping, dof_handler, - solution, - ZeroFunction(), - norm_per_cell, - QGauss(gauss_degree+1), - VectorTools::H1_seminorm); - // Then, the function just called - // returns its results as a vector - // of values each of which denotes - // the norm on one cell. To get the - // global norm, a simple - // computation shows that we have - // to take the l2 norm of the - // vector: - const double norm = norm_per_cell.l2_norm(); - - // Last task -- generate output: - output_table.add_value ("cells", triangulation.n_active_cells()); - output_table.add_value ("|u|_1", norm); - output_table.add_value ("error", std::fabs(norm-std::sqrt(3.14159265358/2))); -} - - - - // The following function solving the - // linear system of equations is - // copied from step-5 and is - // explained there in some detail: -template -void LaplaceProblem::solve () -{ - SolverControl solver_control (1000, 1e-12); - SolverCG<> cg (solver_control); - - PreconditionSSOR<> preconditioner; - preconditioner.initialize(system_matrix, 1.2); - - cg.solve (system_matrix, solution, system_rhs, - preconditioner); + // The only other noteworthy change + // is that the constructor takes a + // value representing the polynomial + // degree of the mapping to be used + // later on, and that it has another + // member variable representing + // exactly this mapping. In general, + // this variable will occur in real + // applications at the same places + // where the finite element is + // declared or used. + template + class LaplaceProblem + { + public: + LaplaceProblem (const unsigned int mapping_degree); + void run (); + + private: + void setup_system (); + void assemble_and_solve (); + void solve (); + + Triangulation triangulation; + FE_Q fe; + DoFHandler dof_handler; + MappingQ mapping; + + SparsityPattern sparsity_pattern; + SparseMatrix system_matrix; + ConstraintMatrix mean_value_constraints; + + Vector solution; + Vector system_rhs; + + TableHandler output_table; + }; + + + + // Construct such an object, by + // initializing the variables. Here, + // we use linear finite elements (the + // argument to the fe variable + // denotes the polynomial degree), + // and mappings of given order. Print + // to screen what we are about to do. + template + LaplaceProblem::LaplaceProblem (const unsigned int mapping_degree) : + fe (1), + dof_handler (triangulation), + mapping (mapping_degree) + { + std::cout << "Using mapping with degree " << mapping_degree << ":" + << std::endl + << "============================" + << std::endl; + } + + + + // The first task is to set up the + // variables for this problem. This + // includes generating a valid + // DoFHandler object, as well as + // the sparsity patterns for the + // matrix, and the object + // representing the constraints that + // the mean value of the degrees of + // freedom on the boundary be zero. + template + void LaplaceProblem::setup_system () + { + // The first task is trivial: + // generate an enumeration of the + // degrees of freedom, and + // initialize solution and right + // hand side vector to their + // correct sizes: + dof_handler.distribute_dofs (fe); + solution.reinit (dof_handler.n_dofs()); + system_rhs.reinit (dof_handler.n_dofs()); + + // Next task is to construct the + // object representing the + // constraint that the mean value + // of the degrees of freedom on the + // boundary shall be zero. For + // this, we first want a list of + // those nodes which are actually + // at the boundary. The + // DoFTools class has a + // function that returns an array + // of boolean values where true + // indicates that the node is at + // the boundary. The second + // argument denotes a mask + // selecting which components of + // vector valued finite elements we + // want to be considered. Since we + // have a scalar finite element + // anyway, this mask consists of + // only one entry, and its value + // must be true. + std::vector boundary_dofs (dof_handler.n_dofs(), false); + DoFTools::extract_boundary_dofs (dof_handler, std::vector(1,true), + boundary_dofs); + + // Now first for the generation of + // the constraints: as mentioned in + // the introduction, we constrain + // one of the nodes on the boundary + // by the values of all other DoFs + // on the boundary. So, let us + // first pick out the first + // boundary node from this list. We + // do that by searching for the + // first true value in the + // array (note that std::find + // returns an iterator to this + // element), and computing its + // distance to the overall first + // element in the array to get its + // index: + const unsigned int first_boundary_dof + = std::distance (boundary_dofs.begin(), + std::find (boundary_dofs.begin(), + boundary_dofs.end(), + true)); + + // Then generate a constraints + // object with just this one + // constraint. First clear all + // previous content (which might + // reside there from the previous + // computation on a once coarser + // grid), then add this one line + // constraining the + // first_boundary_dof to the + // sum of other boundary DoFs each + // with weight -1. Finally, close + // the constraints object, i.e. do + // some internal bookkeeping on it + // for faster processing of what is + // to come later: + mean_value_constraints.clear (); + mean_value_constraints.add_line (first_boundary_dof); + for (unsigned int i=first_boundary_dof+1; iDoFTools::make_sparsity_pattern + // and condense the result using + // the hanging node constraints. We + // have no hanging node constraints + // here (since we only refine + // globally in this example), but + // we have this global constraint + // on the boundary. This poses one + // severe problem in this context: + // the SparsityPattern class + // wants us to state beforehand the + // maximal number of entries per + // row, either for all rows or for + // each row separately. There are + // functions in the library which + // can tell you this number in case + // you just have hanging node + // constraints (namely + // DoFHandler::max_coupling_between_dofs), + // but how is this for the present + // case? The difficulty arises + // because the elimination of the + // constrained degree of freedom + // requires a number of additional + // entries in the matrix at places + // that are not so simple to + // determine. We would therefore + // have a problem had we to give a + // maximal number of entries per + // row here. + // + // Since this can be so difficult + // that no reasonable answer can be + // given that allows allocation of + // only a reasonable amount of + // memory, there is a class + // CompressedSparsityPattern, + // that can help us out here. It + // does not require that we know in + // advance how many entries rows + // could have, but allows just + // about any length. It is thus + // significantly more flexible in + // case you do not have good + // estimates of row lengths, + // however at the price that + // building up such a pattern is + // also significantly more + // expensive than building up a + // pattern for which you had + // information in + // advance. Nevertheless, as we + // have no other choice here, we'll + // just build such an object by + // initializing it with the + // dimensions of the matrix and + // calling another function + // DoFTools::make_sparsity_pattern + // to get the sparsity pattern due + // to the differential operator, + // then condense it with the + // constraints object which adds + // those positions in the sparsity + // pattern that are required for + // the elimination of the + // constraint. + CompressedSparsityPattern csp (dof_handler.n_dofs(), + dof_handler.n_dofs()); + DoFTools::make_sparsity_pattern (dof_handler, csp); + mean_value_constraints.condense (csp); + + // Finally, once we have the full + // pattern, we can initialize an + // object of type + // SparsityPattern from it and + // in turn initialize the matrix + // with it. Note that this is + // actually necessary, since the + // CompressedSparsityPattern is + // so inefficient compared to the + // SparsityPattern class due to + // the more flexible data + // structures it has to use, that + // we can impossibly base the + // sparse matrix class on it, but + // rather need an object of type + // SparsityPattern, which we + // generate by copying from the + // intermediate object. + // + // As a further sidenote, you will + // notice that we do not explicitly + // have to compress the + // sparsity pattern here. This, of + // course, is due to the fact that + // the copy_from function + // generates a compressed object + // right from the start, to which + // you cannot add new entries + // anymore. The compress call + // is therefore implicit in the + // copy_from call. + sparsity_pattern.copy_from (csp); + system_matrix.reinit (sparsity_pattern); + } + + + + // The next function then assembles + // the linear system of equations, + // solves it, and evaluates the + // solution. This then makes three + // actions, and we will put them into + // eight true statements (excluding + // declaration of variables, and + // handling of temporary + // vectors). Thus, this function is + // something for the very + // lazy. Nevertheless, the functions + // called are rather powerful, and + // through them this function uses a + // good deal of the whole + // library. But let's look at each of + // the steps. + template + void LaplaceProblem::assemble_and_solve () + { + + // First, we have to assemble the + // matrix and the right hand + // side. In all previous examples, + // we have investigated various + // ways how to do this + // manually. However, since the + // Laplace matrix and simple right + // hand sides appear so frequently + // in applications, the library + // provides functions for actually + // doing this for you, i.e. they + // perform the loop over all cells, + // setting up the local matrices + // and vectors, and putting them + // together for the end result. + // + // The following are the two most + // commonly used ones: creation of + // the Laplace matrix and creation + // of a right hand side vector from + // body or boundary forces. They + // take the mapping object, the + // DoFHandler object + // representing the degrees of + // freedom and the finite element + // in use, a quadrature formula to + // be used, and the output + // object. The function that + // creates a right hand side vector + // also has to take a function + // object describing the + // (continuous) right hand side + // function. + // + // Let us look at the way the + // matrix and body forces are + // integrated: + const unsigned int gauss_degree + = std::max (static_cast(std::ceil(1.*(mapping.get_degree()+1)/2)), + 2U); + MatrixTools::create_laplace_matrix (mapping, dof_handler, + QGauss(gauss_degree), + system_matrix); + VectorTools::create_right_hand_side (mapping, dof_handler, + QGauss(gauss_degree), + ConstantFunction(-2), + system_rhs); + // That's quite simple, right? + // + // Two remarks are in order, + // though: First, these functions + // are used in a lot of + // contexts. Maybe you want to + // create a Laplace or mass matrix + // for a vector values finite + // element; or you want to use the + // default Q1 mapping; or you want + // to assembled the matrix with a + // coefficient in the Laplace + // operator. For this reason, there + // are quite a large number of + // variants of these functions in + // the MatrixCreator and + // MatrixTools + // classes. Whenever you need a + // slightly different version of + // these functions than the ones + // called above, it is certainly + // worthwhile to take a look at the + // documentation and to check + // whether something fits your + // needs. + // + // The second remark concerns the + // quadrature formula we use: we + // want to integrate over bilinear + // shape functions, so we know that + // we have to use at least a Gauss2 + // quadrature formula. On the other + // hand, we want to have the + // quadrature rule to have at least + // the order of the boundary + // approximation. Since the order + // of Gauss-r is 2r, and the order + // of the boundary approximation + // using polynomials of degree p is + // p+1, we know that 2r@>=p+1. Since + // r has to be an integer and (as + // mentioned above) has to be at + // least 2, this makes up for the + // formula above computing + // gauss_degree. + // + // Since the generation of the body + // force contributions to the right + // hand side vector was so simple, + // we do that all over again for + // the boundary forces as well: + // allocate a vector of the right + // size and call the right + // function. The boundary function + // has constant values, so we can + // generate an object from the + // library on the fly, and we use + // the same quadrature formula as + // above, but this time of lower + // dimension since we integrate + // over faces now instead of cells: + Vector tmp (system_rhs.size()); + VectorTools::create_boundary_right_hand_side (mapping, dof_handler, + QGauss(gauss_degree), + ConstantFunction(1), + tmp); + // Then add the contributions from + // the boundary to those from the + // interior of the domain: + system_rhs += tmp; + // For assembling the right hand + // side, we had to use two + // different vector objects, and + // later add them together. The + // reason we had to do so is that + // the + // VectorTools::create_right_hand_side + // and + // VectorTools::create_boundary_right_hand_side + // functions first clear the output + // vector, rather than adding up + // their results to previous + // contents. This can reasonably be + // called a design flaw in the + // library made in its infancy, but + // unfortunately things are as they + // are for some time now and it is + // difficult to change such things + // that silently break existing + // code, so we have to live with + // that. + + // Now, the linear system is set + // up, so we can eliminate the one + // degree of freedom which we + // constrained to the other DoFs on + // the boundary for the mean value + // constraint from matrix and right + // hand side vector, and solve the + // system. After that, distribute + // the constraints again, which in + // this case means setting the + // constrained degree of freedom to + // its proper value + mean_value_constraints.condense (system_matrix); + mean_value_constraints.condense (system_rhs); + + solve (); + mean_value_constraints.distribute (solution); + + // Finally, evaluate what we got as + // solution. As stated in the + // introduction, we are interested + // in the H1 semi-norm of the + // solution. Here, as well, we have + // a function in the library that + // does this, although in a + // slightly non-obvious way: the + // VectorTools::integrate_difference + // function integrates the norm of + // the difference between a finite + // element function and a + // continuous function. If we + // therefore want the norm of a + // finite element field, we just + // put the continuous function to + // zero. Note that this function, + // just as so many other ones in + // the library as well, has at + // least two versions, one which + // takes a mapping as argument + // (which we make us of here), and + // the one which we have used in + // previous examples which + // implicitly uses MappingQ1. + // Also note that we take a + // quadrature formula of one degree + // higher, in order to avoid + // superconvergence effects where + // the solution happens to be + // especially close to the exact + // solution at certain points (we + // don't know whether this might be + // the case here, but there are + // cases known of this, and we just + // want to make sure): + Vector norm_per_cell (triangulation.n_active_cells()); + VectorTools::integrate_difference (mapping, dof_handler, + solution, + ZeroFunction(), + norm_per_cell, + QGauss(gauss_degree+1), + VectorTools::H1_seminorm); + // Then, the function just called + // returns its results as a vector + // of values each of which denotes + // the norm on one cell. To get the + // global norm, a simple + // computation shows that we have + // to take the l2 norm of the + // vector: + const double norm = norm_per_cell.l2_norm(); + + // Last task -- generate output: + output_table.add_value ("cells", triangulation.n_active_cells()); + output_table.add_value ("|u|_1", norm); + output_table.add_value ("error", std::fabs(norm-std::sqrt(3.14159265358/2))); + } + + + + // The following function solving the + // linear system of equations is + // copied from step-5 and is + // explained there in some detail: + template + void LaplaceProblem::solve () + { + SolverControl solver_control (1000, 1e-12); + SolverCG<> cg (solver_control); + + PreconditionSSOR<> preconditioner; + preconditioner.initialize(system_matrix, 1.2); + + cg.solve (system_matrix, solution, system_rhs, + preconditioner); + } + + + + // Finally the main function + // controlling the different steps to + // be performed. Its content is + // rather straightforward, generating + // a triangulation of a circle, + // associating a boundary to it, and + // then doing several cycles on + // subsequently finer grids. Note + // again that we have put mesh + // refinement into the loop header; + // this may be something for a test + // program, but for real applications + // you should consider that this + // implies that the mesh is refined + // after the loop is executed the + // last time since the increment + // clause (the last part of the + // three-parted loop header) is + // executed before the comparison + // part (the second one), which may + // be rather costly if the mesh is + // already quite refined. In that + // case, you should arrange code such + // that the mesh is not further + // refined after the last loop run + // (or you should do it at the + // beginning of each run except for + // the first one). + template + void LaplaceProblem::run () + { + GridGenerator::hyper_ball (triangulation); + static const HyperBallBoundary boundary; + triangulation.set_boundary (0, boundary); + + for (unsigned int cycle=0; cycle<6; ++cycle, triangulation.refine_global(1)) + { + setup_system (); + assemble_and_solve (); + }; + + // After all the data is generated, + // write a table of results to the + // screen: + output_table.set_precision("|u|_1", 6); + output_table.set_precision("error", 6); + output_table.write_text (std::cout); + std::cout << std::endl; + } } - // Finally the main function - // controlling the different steps to - // be performed. Its content is - // rather straightforward, generating - // a triangulation of a circle, - // associating a boundary to it, and - // then doing several cycles on - // subsequently finer grids. Note - // again that we have put mesh - // refinement into the loop header; - // this may be something for a test - // program, but for real applications - // you should consider that this - // implies that the mesh is refined - // after the loop is executed the - // last time since the increment - // clause (the last part of the - // three-parted loop header) is - // executed before the comparison - // part (the second one), which may - // be rather costly if the mesh is - // already quite refined. In that - // case, you should arrange code such - // that the mesh is not further - // refined after the last loop run - // (or you should do it at the - // beginning of each run except for - // the first one). -template -void LaplaceProblem::run () -{ - GridGenerator::hyper_ball (triangulation); - static const HyperBallBoundary boundary; - triangulation.set_boundary (0, boundary); - - for (unsigned int cycle=0; cycle<6; ++cycle, triangulation.refine_global(1)) - { - setup_system (); - assemble_and_solve (); - }; - - // After all the data is generated, - // write a table of results to the - // screen: - output_table.set_precision("|u|_1", 6); - output_table.set_precision("error", 6); - output_table.write_text (std::cout); - std::cout << std::endl; -} - - - // Finally the main function. It's // structure is the same as that used // in several of the previous // examples, so probably needs no // more explanation. -int main () +int main () { try { - deallog.depth_console (0); + dealii::deallog.depth_console (0); std::cout.precision(5); // This is the main loop, doing @@ -672,7 +675,7 @@ int main () // subsequent to which it is // immediately destroyed again. for (unsigned int mapping_degree=1; mapping_degree<=3; ++mapping_degree) - LaplaceProblem<2>(mapping_degree).run (); + Step11::LaplaceProblem<2>(mapping_degree).run (); } catch (std::exception &exc) { @@ -686,7 +689,7 @@ int main () << std::endl; return 1; } - catch (...) + catch (...) { std::cerr << std::endl << std::endl << "----------------------------------------------------" diff --git a/deal.II/examples/step-12/step-12.cc b/deal.II/examples/step-12/step-12.cc index a2ac8bf8f9..1c2dff25f7 100644 --- a/deal.II/examples/step-12/step-12.cc +++ b/deal.II/examples/step-12/step-12.cc @@ -3,7 +3,7 @@ /* $Id$ */ /* */ -/* Copyright (C) 2010 by the deal.II authors */ +/* Copyright (C) 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 */ @@ -31,16 +31,16 @@ #include #include #include - // Here the discontinuous finite - // elements are defined. They are - // used in the same way as all other - // finite elements, though -- as you - // have seen in previous tutorial + // Here the discontinuous finite elements are + // defined. They are used in the same way as + // all other finite elements, though -- as + // you have seen in previous tutorial // programs -- there isn't much user - // interaction with finite element - // classes at all: the are passed to - // DoFHandler and FEValues - // objects, and that is about it. + // interaction with finite element classes at + // all: the are passed to + // DoFHandler and + // FEValues objects, and that is + // about it. #include // We are going to use the simplest // possible solver, called Richardson @@ -73,734 +73,747 @@ #include #include -using namespace dealii; - - // @sect3{Equation data} - // - // First, we define a class - // describing the inhomogeneous - // boundary data. Since only its - // values are used, we implement - // value_list(), but leave all other - // functions of Function undefined. -template -class BoundaryValues: public Function -{ - public: - BoundaryValues () {}; - virtual void value_list (const std::vector > &points, - std::vector &values, - const unsigned int component=0) const; -}; - - // Given the flow direction, the inflow - // boundary of the unit square $[0,1]^2$ are - // the right and the lower boundaries. We - // prescribe discontinuous boundary values 1 - // and 0 on the x-axis and value 0 on the - // right boundary. The values of this - // function on the outflow boundaries will - // not be used within the DG scheme. -template -void BoundaryValues::value_list(const std::vector > &points, - std::vector &values, - const unsigned int) const -{ - Assert(values.size()==points.size(), - ExcDimensionMismatch(values.size(),points.size())); - for (unsigned int i=0; i -class Step12 -{ - public: - Step12 (); - void run (); - - private: - void setup_system (); - void assemble_system (); - void solve (Vector &solution); - void refine_grid (); - void output_results (const unsigned int cycle) const; - - Triangulation triangulation; - const MappingQ1 mapping; - - // Furthermore we want to use DG - // elements of degree 1 (but this - // is only specified in the - // constructor). If you want to - // use a DG method of a different - // degree the whole program stays - // the same, only replace 1 in - // the constructor by the desired - // polynomial degree. - FE_DGQ fe; - DoFHandler dof_handler; - - // The next four members represent the - // linear system to be - // solved. system_matrix and - // right_hand_side are - // generated by - // assemble_system(), the - // solution is computed in - // solve(). The - // sparsity_pattern is used - // to determine the location of nonzero - // elements in - // system_matrix. - SparsityPattern sparsity_pattern; - SparseMatrix system_matrix; - - Vector solution; - Vector right_hand_side; - - // Finally, we have to provide - // functions that assemble the - // cell, boundary, and inner face - // terms. Within the MeshWorker - // framework, the loop over all - // cells and much of the setup of - // operations will be done - // outside this class, so all we - // have to provide are these - // three operations. They will - // then work on intermediate - // objects for which first, we - // here define typedefs to the - // info objects handed to the - // local integration functions in - // order to make our life easier - // below. - typedef MeshWorker::DoFInfo DoFInfo; - typedef MeshWorker::IntegrationInfo CellInfo; - - // The following three functions - // are then the ones that get called - // inside the generic loop over all - // cells and faces. They are the - // ones doing the actual - // integration. - // - // In our code below, these - // functions do not access member - // variables of the current - // class, so we can mark them as - // static and simply - // pass pointers to these - // functions to the MeshWorker - // framework. If, however, these - // functions would want to access - // member variables (or needed - // additional arguments beyond - // the ones specified below), we - // could use the facilities of - // boost::bind (or std::bind, - // respectively) to provide the - // MeshWorker framework with - // objects that act as if they - // had the required number and - // types of arguments, but have - // in fact other arguments - // already bound. - static void integrate_cell_term (DoFInfo& dinfo, CellInfo& info); - static void integrate_boundary_term (DoFInfo& dinfo, CellInfo& info); - static void integrate_face_term (DoFInfo& dinfo1, DoFInfo& dinfo2, - CellInfo& info1, CellInfo& info2); -}; - - - // We start with the constructor. The 1 in - // the constructor call of fe is - // the polynomial degree. -template -Step12::Step12 () - : - mapping (), - fe (1), - dof_handler (triangulation) -{} - - -template -void Step12::setup_system () +namespace Step12 { - // In the function that sets up the usual - // finite element data structures, we first - // need to distribute the DoFs. - dof_handler.distribute_dofs (fe); - - // We start by generating the sparsity - // pattern. To this end, we first fill an - // intermediate object of type - // CompressedSparsityPattern with the - // couplings appearing in the system. After - // building the pattern, this object is - // copied to sparsity_pattern - // and can be discarded. - - // To build the sparsity pattern for DG - // discretizations, we can call the - // function analogue to - // DoFTools::make_sparsity_pattern, which - // is called - // DoFTools::make_flux_sparsity_pattern: - CompressedSparsityPattern c_sparsity(dof_handler.n_dofs()); - DoFTools::make_flux_sparsity_pattern (dof_handler, c_sparsity); - sparsity_pattern.copy_from(c_sparsity); - - // Finally, we set up the structure - // of all components of the linear system. - system_matrix.reinit (sparsity_pattern); - solution.reinit (dof_handler.n_dofs()); - right_hand_side.reinit (dof_handler.n_dofs()); -} + using namespace dealii; - // @sect4{Function: assemble_system} - - // Here we see the major difference to - // assembling by hand. Instead of writing - // loops over cells and faces, we leave all - // this to the MeshWorker framework. In order - // to do so, we just have to define local - // integration functions and use one of the - // classes in namespace MeshWorker::Assembler - // to build the global system. -template -void Step12::assemble_system () -{ - // This is the magic object, which - // knows everything about the data - // structures and local - // integration. This is the object - // doing the work in the function - // MeshWorker::loop(), which is - // implicitly called by - // MeshWorker::integration_loop() - // below. After the functions to - // which we provide pointers did - // the local integration, the - // MeshWorker::Assembler::SystemSimple - // object distributes these into - // the global sparse matrix and the - // right hand side vector. - MeshWorker::IntegrationInfoBox info_box; - - // First, we initialize the - // quadrature formulae and the - // update flags in the worker base - // class. For quadrature, we play - // safe and use a QGauss formula - // with number of points one higher - // than the polynomial degree - // used. Since the quadratures for - // cells, boundary and interior - // faces can be selected - // independently, we have to hand - // over this value three times. - const unsigned int n_gauss_points = dof_handler.get_fe().degree+1; - info_box.initialize_gauss_quadrature(n_gauss_points, - n_gauss_points, - n_gauss_points); - - // These are the types of values we - // need for integrating our - // system. They are added to the - // flags used on cells, boundary - // and interior faces, as well as - // interior neighbor faces, which is - // forced by the four @p true - // values. - info_box.initialize_update_flags(); - UpdateFlags update_flags = update_quadrature_points | - update_values | - update_gradients; - info_box.add_update_flags(update_flags, true, true, true, true); - - // After preparing all data in - // info_box, we initialize - // the FEValus objects in there. - info_box.initialize(fe, mapping); - - // The object created so far helps - // us do the local integration on - // each cell and face. Now, we need - // an object which receives the - // integrated (local) data and - // forwards them to the assembler. - MeshWorker::DoFInfo dof_info(dof_handler); - - // Now, we have to create the - // assembler object and tell it, - // where to put the local - // data. These will be our system - // matrix and the right hand side. - MeshWorker::Assembler::SystemSimple, Vector > - assembler; - assembler.initialize(system_matrix, right_hand_side); - - // Finally, the integration loop - // over all active cells - // (determined by the first - // argument, which is an active - // iterator). + // @sect3{Equation data} // - // As noted in the discussion when - // declaring the local integration - // functions in the class - // declaration, the arguments - // expected by the assembling - // integrator class are not - // actually function - // pointers. Rather, they are - // objects that can be called like - // functions with a certain number - // of arguments. Consequently, we - // could also pass objects with - // appropriate operator() - // implementations here, or the - // result of std::bind if the local - // integrators were, for example, - // non-static member functions. - MeshWorker::integration_loop - (dof_handler.begin_active(), dof_handler.end(), - dof_info, info_box, - &Step12::integrate_cell_term, - &Step12::integrate_boundary_term, - &Step12::integrate_face_term, - assembler, true); -} - - - // @sect4{The local integrators} - - // These functions are analogous to - // step-12 and differ only in the - // data structures. Instead of - // providing the local matrices - // explicitly in the argument list, - // they are part of the info object. - - // Note that here we still have the - // local integration loop inside the - // following functions. The program - // would be even shorter, if we used - // pre-made operators from the - // Operators namespace (which will be - // added soon). - -template -void Step12::integrate_cell_term (DoFInfo& dinfo, CellInfo& info) -{ - // First, let us retrieve some of - // the objects used here from - // @p info. Note that these objects - // can handle much more complex - // structures, thus the access here - // looks more complicated than - // might seem necessary. - const FEValuesBase& fe_v = info.fe_values(); - FullMatrix& local_matrix = dinfo.matrix(0).matrix; - const std::vector &JxW = fe_v.get_JxW_values (); - - // With these objects, we continue - // local integration like - // always. First, we loop over the - // quadrature points and compute - // the advection vector in the - // current point. - for (unsigned int point=0; point beta; - beta(0) = -fe_v.quadrature_point(point)(1); - beta(1) = fe_v.quadrature_point(point)(0); - beta /= beta.norm(); - - // We solve a homogeneous - // equation, thus no right - // hand side shows up in - // the cell term. - // What's left is - // integrating the matrix entries. - for (unsigned int i=0; i -void Step12::integrate_boundary_term (DoFInfo& dinfo, CellInfo& info) -{ - const FEValuesBase& fe_v = info.fe_values(); - FullMatrix& local_matrix = dinfo.matrix(0).matrix; - Vector& local_vector = dinfo.vector(0).block(0); - - const std::vector &JxW = fe_v.get_JxW_values (); - const std::vector > &normals = fe_v.get_normal_vectors (); - - std::vector g(fe_v.n_quadrature_points); - - static BoundaryValues boundary_function; - boundary_function.value_list (fe_v.get_quadrature_points(), g); - - for (unsigned int point=0; point beta; - beta(0) = -fe_v.quadrature_point(point)(1); - beta(1) = fe_v.quadrature_point(point)(0); - beta /= beta.norm(); - - const double beta_n=beta * normals[point]; - if (beta_n>0) + // First, we define a class + // describing the inhomogeneous + // boundary data. Since only its + // values are used, we implement + // value_list(), but leave all other + // functions of Function undefined. + template + class BoundaryValues: public Function + { + public: + BoundaryValues () {}; + virtual void value_list (const std::vector > &points, + std::vector &values, + const unsigned int component=0) const; + }; + + // Given the flow direction, the inflow + // boundary of the unit square $[0,1]^2$ are + // the right and the lower boundaries. We + // prescribe discontinuous boundary values 1 + // and 0 on the x-axis and value 0 on the + // right boundary. The values of this + // function on the outflow boundaries will + // not be used within the DG scheme. + template + void BoundaryValues::value_list(const std::vector > &points, + std::vector &values, + const unsigned int) const + { + Assert(values.size()==points.size(), + ExcDimensionMismatch(values.size(),points.size())); + + for (unsigned int i=0; i + class AdvectionProblem + { + public: + AdvectionProblem (); + void run (); + + private: + void setup_system (); + void assemble_system (); + void solve (Vector &solution); + void refine_grid (); + void output_results (const unsigned int cycle) const; + + Triangulation triangulation; + const MappingQ1 mapping; + + // Furthermore we want to use DG + // elements of degree 1 (but this + // is only specified in the + // constructor). If you want to + // use a DG method of a different + // degree the whole program stays + // the same, only replace 1 in + // the constructor by the desired + // polynomial degree. + FE_DGQ fe; + DoFHandler dof_handler; + + // The next four members represent the + // linear system to be + // solved. system_matrix and + // right_hand_side are + // generated by + // assemble_system(), the + // solution is computed in + // solve(). The + // sparsity_pattern is used + // to determine the location of nonzero + // elements in + // system_matrix. + SparsityPattern sparsity_pattern; + SparseMatrix system_matrix; + + Vector solution; + Vector right_hand_side; + + // Finally, we have to provide + // functions that assemble the + // cell, boundary, and inner face + // terms. Within the MeshWorker + // framework, the loop over all + // cells and much of the setup of + // operations will be done + // outside this class, so all we + // have to provide are these + // three operations. They will + // then work on intermediate + // objects for which first, we + // here define typedefs to the + // info objects handed to the + // local integration functions in + // order to make our life easier + // below. + typedef MeshWorker::DoFInfo DoFInfo; + typedef MeshWorker::IntegrationInfo CellInfo; + + // The following three functions + // are then the ones that get called + // inside the generic loop over all + // cells and faces. They are the + // ones doing the actual + // integration. + // + // In our code below, these + // functions do not access member + // variables of the current + // class, so we can mark them as + // static and simply + // pass pointers to these + // functions to the MeshWorker + // framework. If, however, these + // functions would want to access + // member variables (or needed + // additional arguments beyond + // the ones specified below), we + // could use the facilities of + // boost::bind (or std::bind, + // respectively) to provide the + // MeshWorker framework with + // objects that act as if they + // had the required number and + // types of arguments, but have + // in fact other arguments + // already bound. + static void integrate_cell_term (DoFInfo& dinfo, + CellInfo& info); + static void integrate_boundary_term (DoFInfo& dinfo, + CellInfo& info); + static void integrate_face_term (DoFInfo& dinfo1, + DoFInfo& dinfo2, + CellInfo& info1, + CellInfo& info2); + }; + + + // We start with the constructor. The 1 in + // the constructor call of fe is + // the polynomial degree. + template + AdvectionProblem::AdvectionProblem () + : + mapping (), + fe (1), + dof_handler (triangulation) + {} + + + template + void AdvectionProblem::setup_system () + { + // In the function that sets up the usual + // finite element data structures, we first + // need to distribute the DoFs. + dof_handler.distribute_dofs (fe); + + // We start by generating the sparsity + // pattern. To this end, we first fill an + // intermediate object of type + // CompressedSparsityPattern with the + // couplings appearing in the system. After + // building the pattern, this object is + // copied to sparsity_pattern + // and can be discarded. + + // To build the sparsity pattern for DG + // discretizations, we can call the + // function analogue to + // DoFTools::make_sparsity_pattern, which + // is called + // DoFTools::make_flux_sparsity_pattern: + CompressedSparsityPattern c_sparsity(dof_handler.n_dofs()); + DoFTools::make_flux_sparsity_pattern (dof_handler, c_sparsity); + sparsity_pattern.copy_from(c_sparsity); + + // Finally, we set up the structure + // of all components of the linear system. + system_matrix.reinit (sparsity_pattern); + solution.reinit (dof_handler.n_dofs()); + right_hand_side.reinit (dof_handler.n_dofs()); + } + + // @sect4{The assemble_system function} + + // Here we see the major difference to + // assembling by hand. Instead of writing + // loops over cells and faces, we leave all + // this to the MeshWorker framework. In order + // to do so, we just have to define local + // integration functions and use one of the + // classes in namespace MeshWorker::Assembler + // to build the global system. + template + void AdvectionProblem::assemble_system () + { + // This is the magic object, which + // knows everything about the data + // structures and local + // integration. This is the object + // doing the work in the function + // MeshWorker::loop(), which is + // implicitly called by + // MeshWorker::integration_loop() + // below. After the functions to + // which we provide pointers did + // the local integration, the + // MeshWorker::Assembler::SystemSimple + // object distributes these into + // the global sparse matrix and the + // right hand side vector. + MeshWorker::IntegrationInfoBox info_box; + + // First, we initialize the + // quadrature formulae and the + // update flags in the worker base + // class. For quadrature, we play + // safe and use a QGauss formula + // with number of points one higher + // than the polynomial degree + // used. Since the quadratures for + // cells, boundary and interior + // faces can be selected + // independently, we have to hand + // over this value three times. + const unsigned int n_gauss_points = dof_handler.get_fe().degree+1; + info_box.initialize_gauss_quadrature(n_gauss_points, + n_gauss_points, + n_gauss_points); + + // These are the types of values we + // need for integrating our + // system. They are added to the + // flags used on cells, boundary + // and interior faces, as well as + // interior neighbor faces, which is + // forced by the four @p true + // values. + info_box.initialize_update_flags(); + UpdateFlags update_flags = update_quadrature_points | + update_values | + update_gradients; + info_box.add_update_flags(update_flags, true, true, true, true); + + // After preparing all data in + // info_box, we initialize + // the FEValus objects in there. + info_box.initialize(fe, mapping); + + // The object created so far helps + // us do the local integration on + // each cell and face. Now, we need + // an object which receives the + // integrated (local) data and + // forwards them to the assembler. + MeshWorker::DoFInfo dof_info(dof_handler); + + // Now, we have to create the + // assembler object and tell it, + // where to put the local + // data. These will be our system + // matrix and the right hand side. + MeshWorker::Assembler::SystemSimple, Vector > + assembler; + assembler.initialize(system_matrix, right_hand_side); + + // Finally, the integration loop + // over all active cells + // (determined by the first + // argument, which is an active + // iterator). + // + // As noted in the discussion when + // declaring the local integration + // functions in the class + // declaration, the arguments + // expected by the assembling + // integrator class are not + // actually function + // pointers. Rather, they are + // objects that can be called like + // functions with a certain number + // of arguments. Consequently, we + // could also pass objects with + // appropriate operator() + // implementations here, or the + // result of std::bind if the local + // integrators were, for example, + // non-static member functions. + MeshWorker::integration_loop + (dof_handler.begin_active(), dof_handler.end(), + dof_info, info_box, + &AdvectionProblem::integrate_cell_term, + &AdvectionProblem::integrate_boundary_term, + &AdvectionProblem::integrate_face_term, + assembler, true); + } + + + // @sect4{The local integrators} + + // These functions are analogous to + // step-12 and differ only in the + // data structures. Instead of + // providing the local matrices + // explicitly in the argument list, + // they are part of the info object. + + // Note that here we still have the + // local integration loop inside the + // following functions. The program + // would be even shorter, if we used + // pre-made operators from the + // Operators namespace (which will be + // added soon). + + template + void AdvectionProblem::integrate_cell_term (DoFInfo& dinfo, + CellInfo& info) + { + // First, let us retrieve some of + // the objects used here from + // @p info. Note that these objects + // can handle much more complex + // structures, thus the access here + // looks more complicated than + // might seem necessary. + const FEValuesBase& fe_v = info.fe_values(); + FullMatrix& local_matrix = dinfo.matrix(0).matrix; + const std::vector &JxW = fe_v.get_JxW_values (); + + // With these objects, we continue + // local integration like + // always. First, we loop over the + // quadrature points and compute + // the advection vector in the + // current point. + for (unsigned int point=0; point beta; + beta(0) = -fe_v.quadrature_point(point)(1); + beta(1) = fe_v.quadrature_point(point)(0); + beta /= beta.norm(); + + // We solve a homogeneous + // equation, thus no right + // hand side shows up in + // the cell term. + // What's left is + // integrating the matrix entries. for (unsigned int i=0; i -void Step12::integrate_face_term (DoFInfo& dinfo1, DoFInfo& dinfo2, - CellInfo& info1, CellInfo& info2) -{ - // For quadrature points, weights, - // etc., we use the - // FEValuesBase object of the - // first argument. - const FEValuesBase& fe_v = info1.fe_values(); - - // For additional shape functions, - // we have to ask the neighbors - // FEValuesBase. - const FEValuesBase& fe_v_neighbor = info2.fe_values(); - - // Then we get references to the - // four local matrices. The letters - // u and v refer to trial and test - // functions, respectively. The - // %numbers indicate the cells - // provided by info1 and info2. By - // convention, the two matrices in - // each info object refer to the - // test functions on the respective - // cell. The first matrix contains the - // interior couplings of that cell, - // while the second contains the - // couplings between cells. - FullMatrix& u1_v1_matrix = dinfo1.matrix(0,false).matrix; - FullMatrix& u2_v1_matrix = dinfo1.matrix(0,true).matrix; - FullMatrix& u1_v2_matrix = dinfo2.matrix(0,true).matrix; - FullMatrix& u2_v2_matrix = dinfo2.matrix(0,false).matrix; - - // Here, following the previous - // functions, we would have the - // local right hand side - // vectors. Fortunately, the - // interface terms only involve the - // solution and the right hand side - // does not receive any contributions. - - const std::vector &JxW = fe_v.get_JxW_values (); - const std::vector > &normals = fe_v.get_normal_vectors (); - - for (unsigned int point=0; point beta; - beta(0) = -fe_v.quadrature_point(point)(1); - beta(1) = fe_v.quadrature_point(point)(0); - beta /= beta.norm(); - - const double beta_n=beta * normals[point]; - if (beta_n>0) - { - // This term we've already - // seen: + } + } + + // Now the same for the boundary terms. Note + // that now we use FEValuesBase, the base + // class for both FEFaceValues and + // FESubfaceValues, in order to get access to + // normal vectors. + template + void AdvectionProblem::integrate_boundary_term (DoFInfo& dinfo, + CellInfo& info) + { + const FEValuesBase& fe_v = info.fe_values(); + FullMatrix& local_matrix = dinfo.matrix(0).matrix; + Vector& local_vector = dinfo.vector(0).block(0); + + const std::vector &JxW = fe_v.get_JxW_values (); + const std::vector > &normals = fe_v.get_normal_vectors (); + + std::vector g(fe_v.n_quadrature_points); + + static BoundaryValues boundary_function; + boundary_function.value_list (fe_v.get_quadrature_points(), g); + + for (unsigned int point=0; point beta; + beta(0) = -fe_v.quadrature_point(point)(1); + beta(1) = fe_v.quadrature_point(point)(0); + beta /= beta.norm(); + + const double beta_n=beta * normals[point]; + if (beta_n>0) for (unsigned int i=0; i -void Step12::solve (Vector &solution) -{ - SolverControl solver_control (1000, 1e-12); - SolverRichardson<> solver (solver_control); - - // Here we create the - // preconditioner, - PreconditionBlockSSOR > preconditioner; - - // then assign the matrix to it and - // set the right block size: - preconditioner.initialize(system_matrix, fe.dofs_per_cell); - - // After these preparations we are - // ready to start the linear solver. - solver.solve (system_matrix, solution, right_hand_side, - preconditioner); -} - - - // We refine the grid according to a - // very simple refinement criterion, - // namely an approximation to the - // gradient of the solution. As here - // we consider the DG(1) method - // (i.e. we use piecewise bilinear - // shape functions) we could simply - // compute the gradients on each - // cell. But we do not want to base - // our refinement indicator on the - // gradients on each cell only, but - // want to base them also on jumps of - // the discontinuous solution - // function over faces between - // neighboring cells. The simplest - // way of doing that is to compute - // approximative gradients by - // difference quotients including the - // cell under consideration and its - // neighbors. This is done by the - // DerivativeApproximation class - // that computes the approximate - // gradients in a way similar to the - // GradientEstimation described - // in step-9 of this tutorial. In - // fact, the - // DerivativeApproximation class - // was developed following the - // GradientEstimation class of - // step-9. Relating to the - // discussion in step-9, here we - // consider $h^{1+d/2}|\nabla_h - // u_h|$. Furthermore we note that we - // do not consider approximate second - // derivatives because solutions to - // the linear advection equation are - // in general not in $H^2$ but in $H^1$ - // (to be more precise, in $H^1_\beta$) - // only. -template -void Step12::refine_grid () -{ - // The DerivativeApproximation - // class computes the gradients to - // float precision. This is - // sufficient as they are - // approximate and serve as - // refinement indicators only. - Vector gradient_indicator (triangulation.n_active_cells()); - - // Now the approximate gradients - // are computed - DerivativeApproximation::approximate_gradient (mapping, - dof_handler, - solution, - gradient_indicator); - - // and they are cell-wise scaled by - // the factor $h^{1+d/2}$ - typename DoFHandler::active_cell_iterator - cell = dof_handler.begin_active(), - endc = dof_handler.end(); - for (unsigned int cell_no=0; cell!=endc; ++cell, ++cell_no) - gradient_indicator(cell_no)*=std::pow(cell->diameter(), 1+1.0*dim/2); - - // Finally they serve as refinement - // indicator. - GridRefinement::refine_and_coarsen_fixed_number (triangulation, - gradient_indicator, - 0.3, 0.1); - - triangulation.execute_coarsening_and_refinement (); -} - - - // The output of this program - // consists of eps-files of the - // adaptively refined grids and the - // numerical solutions given in - // gnuplot format. This was covered - // in previous examples and will not - // be further commented on. -template -void Step12::output_results (const unsigned int cycle) const -{ - // Write the grid in eps format. - std::string filename = "grid-"; - filename += ('0' + cycle); - Assert (cycle < 10, ExcInternalError()); - - filename += ".eps"; - deallog << "Writing grid to <" << filename << ">" << std::endl; - std::ofstream eps_output (filename.c_str()); - - GridOut grid_out; - grid_out.write_eps (triangulation, eps_output); - - // Output of the solution in - // gnuplot format. - filename = "sol-"; - filename += ('0' + cycle); - Assert (cycle < 10, ExcInternalError()); + else + for (unsigned int i=0; i + void AdvectionProblem::integrate_face_term (DoFInfo& dinfo1, + DoFInfo& dinfo2, + CellInfo& info1, + CellInfo& info2) + { + // For quadrature points, weights, + // etc., we use the + // FEValuesBase object of the + // first argument. + const FEValuesBase& fe_v = info1.fe_values(); + + // For additional shape functions, + // we have to ask the neighbors + // FEValuesBase. + const FEValuesBase& fe_v_neighbor = info2.fe_values(); + + // Then we get references to the + // four local matrices. The letters + // u and v refer to trial and test + // functions, respectively. The + // %numbers indicate the cells + // provided by info1 and info2. By + // convention, the two matrices in + // each info object refer to the + // test functions on the respective + // cell. The first matrix contains the + // interior couplings of that cell, + // while the second contains the + // couplings between cells. + FullMatrix& u1_v1_matrix = dinfo1.matrix(0,false).matrix; + FullMatrix& u2_v1_matrix = dinfo1.matrix(0,true).matrix; + FullMatrix& u1_v2_matrix = dinfo2.matrix(0,true).matrix; + FullMatrix& u2_v2_matrix = dinfo2.matrix(0,false).matrix; + + // Here, following the previous + // functions, we would have the + // local right hand side + // vectors. Fortunately, the + // interface terms only involve the + // solution and the right hand side + // does not receive any contributions. + + const std::vector &JxW = fe_v.get_JxW_values (); + const std::vector > &normals = fe_v.get_normal_vectors (); + + for (unsigned int point=0; point beta; + beta(0) = -fe_v.quadrature_point(point)(1); + beta(1) = fe_v.quadrature_point(point)(0); + beta /= beta.norm(); + + const double beta_n=beta * normals[point]; + if (beta_n>0) + { + // This term we've already + // seen: + for (unsigned int i=0; i + void AdvectionProblem::solve (Vector &solution) + { + SolverControl solver_control (1000, 1e-12); + SolverRichardson<> solver (solver_control); + + // Here we create the + // preconditioner, + PreconditionBlockSSOR > preconditioner; + + // then assign the matrix to it and + // set the right block size: + preconditioner.initialize(system_matrix, fe.dofs_per_cell); + + // After these preparations we are + // ready to start the linear solver. + solver.solve (system_matrix, solution, right_hand_side, + preconditioner); + } + + + // We refine the grid according to a + // very simple refinement criterion, + // namely an approximation to the + // gradient of the solution. As here + // we consider the DG(1) method + // (i.e. we use piecewise bilinear + // shape functions) we could simply + // compute the gradients on each + // cell. But we do not want to base + // our refinement indicator on the + // gradients on each cell only, but + // want to base them also on jumps of + // the discontinuous solution + // function over faces between + // neighboring cells. The simplest + // way of doing that is to compute + // approximative gradients by + // difference quotients including the + // cell under consideration and its + // neighbors. This is done by the + // DerivativeApproximation class + // that computes the approximate + // gradients in a way similar to the + // GradientEstimation described + // in step-9 of this tutorial. In + // fact, the + // DerivativeApproximation class + // was developed following the + // GradientEstimation class of + // step-9. Relating to the + // discussion in step-9, here we + // consider $h^{1+d/2}|\nabla_h + // u_h|$. Furthermore we note that we + // do not consider approximate second + // derivatives because solutions to + // the linear advection equation are + // in general not in $H^2$ but in $H^1$ + // (to be more precise, in $H^1_\beta$) + // only. + template + void AdvectionProblem::refine_grid () + { + // The DerivativeApproximation + // class computes the gradients to + // float precision. This is + // sufficient as they are + // approximate and serve as + // refinement indicators only. + Vector gradient_indicator (triangulation.n_active_cells()); + + // Now the approximate gradients + // are computed + DerivativeApproximation::approximate_gradient (mapping, + dof_handler, + solution, + gradient_indicator); + + // and they are cell-wise scaled by + // the factor $h^{1+d/2}$ + typename DoFHandler::active_cell_iterator + cell = dof_handler.begin_active(), + endc = dof_handler.end(); + for (unsigned int cell_no=0; cell!=endc; ++cell, ++cell_no) + gradient_indicator(cell_no)*=std::pow(cell->diameter(), 1+1.0*dim/2); + + // Finally they serve as refinement + // indicator. + GridRefinement::refine_and_coarsen_fixed_number (triangulation, + gradient_indicator, + 0.3, 0.1); + + triangulation.execute_coarsening_and_refinement (); + } + + + // The output of this program + // consists of eps-files of the + // adaptively refined grids and the + // numerical solutions given in + // gnuplot format. This was covered + // in previous examples and will not + // be further commented on. + template + void AdvectionProblem::output_results (const unsigned int cycle) const + { + // Write the grid in eps format. + std::string filename = "grid-"; + filename += ('0' + cycle); + Assert (cycle < 10, ExcInternalError()); + + filename += ".eps"; + deallog << "Writing grid to <" << filename << ">" << std::endl; + std::ofstream eps_output (filename.c_str()); + + GridOut grid_out; + grid_out.write_eps (triangulation, eps_output); + + // Output of the solution in + // gnuplot format. + filename = "sol-"; + filename += ('0' + cycle); + Assert (cycle < 10, ExcInternalError()); + + filename += ".gnuplot"; + deallog << "Writing solution to <" << filename << ">" << std::endl; + std::ofstream gnuplot_output (filename.c_str()); + + DataOut data_out; + data_out.attach_dof_handler (dof_handler); + data_out.add_data_vector (solution, "u"); + + data_out.build_patches (); + + data_out.write_gnuplot(gnuplot_output); + } + + + // The following run function is + // similar to previous examples. + template + void AdvectionProblem::run () + { + for (unsigned int cycle=0; cycle<6; ++cycle) + { + deallog << "Cycle " << cycle << std::endl; + + if (cycle == 0) + { + GridGenerator::hyper_cube (triangulation); + + triangulation.refine_global (3); + } + else + refine_grid (); + + + deallog << "Number of active cells: " + << triangulation.n_active_cells() + << std::endl; - filename += ".gnuplot"; - deallog << "Writing solution to <" << filename << ">" << std::endl; - std::ofstream gnuplot_output (filename.c_str()); + setup_system (); - DataOut data_out; - data_out.attach_dof_handler (dof_handler); - data_out.add_data_vector (solution, "u"); + deallog << "Number of degrees of freedom: " + << dof_handler.n_dofs() + << std::endl; - data_out.build_patches (); + assemble_system (); + solve (solution); - data_out.write_gnuplot(gnuplot_output); + output_results (cycle); + } + } } - // The following run function is - // similar to previous examples. -template -void Step12::run () -{ - for (unsigned int cycle=0; cycle<6; ++cycle) - { - deallog << "Cycle " << cycle << std::endl; - - if (cycle == 0) - { - GridGenerator::hyper_cube (triangulation); - - triangulation.refine_global (3); - } - else - refine_grid (); - - - deallog << "Number of active cells: " - << triangulation.n_active_cells() - << std::endl; - - setup_system (); - - deallog << "Number of degrees of freedom: " - << dof_handler.n_dofs() - << std::endl; - - assemble_system (); - solve (solution); - - output_results (cycle); - } -} - // The following main function is // similar to previous examples as well, and // need not be commented on. @@ -808,7 +821,7 @@ int main () { try { - Step12<2> dgmethod; + Step12::AdvectionProblem<2> dgmethod; dgmethod.run (); } catch (std::exception &exc) diff --git a/deal.II/examples/step-13/step-13.cc b/deal.II/examples/step-13/step-13.cc index ee2a5d8299..bd6fee2888 100644 --- a/deal.II/examples/step-13/step-13.cc +++ b/deal.II/examples/step-13/step-13.cc @@ -3,7 +3,7 @@ /* $Id$ */ /* */ -/* Copyright (C) 2001, 2002, 2003, 2004, 2006, 2007, 2008, 2009 by the deal.II authors */ +/* Copyright (C) 2001, 2002, 2003, 2004, 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 */ @@ -54,1994 +54,1997 @@ // The last step is as in all // previous programs: -using namespace dealii; - - // @sect3{Evaluation of the solution} - - // As for the program itself, we - // first define classes that evaluate - // the solutions of a Laplace - // equation. In fact, they can - // evaluate every kind of solution, - // as long as it is described by a - // DoFHandler object, and a - // solution vector. We define them - // here first, even before the - // classes that actually generate the - // solution to be evaluated, since we - // need to declare an abstract base - // class that the solver classes can - // refer to. - // - // From an abstract point of view, we - // declare a pure base class - // that provides an evaluation - // operator() which will - // do the evaluation of the solution - // (whatever derived classes might - // consider an evaluation). Since - // this is the only real function of - // this base class (except for some - // bookkeeping machinery), one - // usually terms such a class that - // only has an operator() a - // functor in C++ terminology, - // since it is used just like a - // function object. - // - // Objects of this functor type will - // then later be passed to the solver - // object, which applies it to the - // solution just computed. The - // evaluation objects may then - // extract any quantity they like - // from the solution. The advantage - // of putting these evaluation - // functions into a separate - // hierarchy of classes is that by - // design they cannot use the - // internals of the solver object and - // are therefore independent of - // changes to the way the solver - // works. Furthermore, it is trivial - // to write another evaluation class - // without modifying the solver - // class, which speeds up programming - // (not being able to use internals - // of another class also means that - // you do not have to worry about - // them -- programming evaluators is - // usually a rather quickly done - // task), as well as compilation (if - // solver and evaluation classes are - // put into different files: the - // solver only needs to see the - // declaration of the abstract base - // class, and therefore does not need - // to be recompiled upon addition of - // a new evaluation class, or - // modification of an old one). - // On a related note, you can reuse - // the evaluation classes for other - // projects, solving different - // equations. - // - // In order to improve separation of - // code into different modules, we - // put the evaluation classes into a - // namespace of their own. This makes - // it easier to actually solve - // different equations in the same - // program, by assembling it from - // existing building blocks. The - // reason for this is that classes - // for similar purposes tend to have - // the same name, although they were - // developed in different - // contexts. In order to be able to - // use them together in one program, - // it is necessary that they are - // placed in different - // namespaces. This we do here: -namespace Evaluation +namespace Step13 { - - // Now for the abstract base class - // of evaluation classes: its main - // purpose is to declare a pure - // virtual function operator() - // taking a DoFHandler object, - // and the solution vector. In - // order to be able to use pointers - // to this base class only, it also - // has to declare a virtual - // destructor, which however does - // nothing. Besides this, it only - // provides for a little bit of - // bookkeeping: since we usually - // want to evaluate solutions on - // subsequent refinement levels, we - // store the number of the present - // refinement cycle, and provide a - // function to change this number. - template - class EvaluationBase + using namespace dealii; + + // @sect3{Evaluation of the solution} + + // As for the program itself, we + // first define classes that evaluate + // the solutions of a Laplace + // equation. In fact, they can + // evaluate every kind of solution, + // as long as it is described by a + // DoFHandler object, and a + // solution vector. We define them + // here first, even before the + // classes that actually generate the + // solution to be evaluated, since we + // need to declare an abstract base + // class that the solver classes can + // refer to. + // + // From an abstract point of view, we + // declare a pure base class + // that provides an evaluation + // operator() which will + // do the evaluation of the solution + // (whatever derived classes might + // consider an evaluation). Since + // this is the only real function of + // this base class (except for some + // bookkeeping machinery), one + // usually terms such a class that + // only has an operator() a + // functor in C++ terminology, + // since it is used just like a + // function object. + // + // Objects of this functor type will + // then later be passed to the solver + // object, which applies it to the + // solution just computed. The + // evaluation objects may then + // extract any quantity they like + // from the solution. The advantage + // of putting these evaluation + // functions into a separate + // hierarchy of classes is that by + // design they cannot use the + // internals of the solver object and + // are therefore independent of + // changes to the way the solver + // works. Furthermore, it is trivial + // to write another evaluation class + // without modifying the solver + // class, which speeds up programming + // (not being able to use internals + // of another class also means that + // you do not have to worry about + // them -- programming evaluators is + // usually a rather quickly done + // task), as well as compilation (if + // solver and evaluation classes are + // put into different files: the + // solver only needs to see the + // declaration of the abstract base + // class, and therefore does not need + // to be recompiled upon addition of + // a new evaluation class, or + // modification of an old one). + // On a related note, you can reuse + // the evaluation classes for other + // projects, solving different + // equations. + // + // In order to improve separation of + // code into different modules, we + // put the evaluation classes into a + // namespace of their own. This makes + // it easier to actually solve + // different equations in the same + // program, by assembling it from + // existing building blocks. The + // reason for this is that classes + // for similar purposes tend to have + // the same name, although they were + // developed in different + // contexts. In order to be able to + // use them together in one program, + // it is necessary that they are + // placed in different + // namespaces. This we do here: + namespace Evaluation { - public: - virtual ~EvaluationBase (); - - void set_refinement_cycle (const unsigned int refinement_cycle); - - virtual void operator () (const DoFHandler &dof_handler, - const Vector &solution) const = 0; - protected: - unsigned int refinement_cycle; - }; + // Now for the abstract base class + // of evaluation classes: its main + // purpose is to declare a pure + // virtual function operator() + // taking a DoFHandler object, + // and the solution vector. In + // order to be able to use pointers + // to this base class only, it also + // has to declare a virtual + // destructor, which however does + // nothing. Besides this, it only + // provides for a little bit of + // bookkeeping: since we usually + // want to evaluate solutions on + // subsequent refinement levels, we + // store the number of the present + // refinement cycle, and provide a + // function to change this number. + template + class EvaluationBase + { + public: + virtual ~EvaluationBase (); - // After the declaration has been - // discussed above, the - // implementation is rather - // straightforward: - template - EvaluationBase::~EvaluationBase () - {} - + void set_refinement_cycle (const unsigned int refinement_cycle); - - template - void - EvaluationBase::set_refinement_cycle (const unsigned int step) - { - refinement_cycle = step; - } + virtual void operator () (const DoFHandler &dof_handler, + const Vector &solution) const = 0; + protected: + unsigned int refinement_cycle; + }; - // @sect4{%Point evaluation} + // After the declaration has been + // discussed above, the + // implementation is rather + // straightforward: + template + EvaluationBase::~EvaluationBase () + {} - // The next thing is to implement - // actual evaluation classes. As - // noted in the introduction, we'd - // like to extract a point value - // from the solution, so the first - // class does this in its - // operator(). The actual point - // is given to this class through - // the constructor, as well as a - // table object into which it will - // put its findings. - // - // Finding out the value of a - // finite element field at an - // arbitrary point is rather - // difficult, if we cannot rely on - // knowing the actual finite - // element used, since then we - // cannot, for example, interpolate - // between nodes. For simplicity, - // we therefore assume here that - // the point at which we want to - // evaluate the field is actually a - // node. If, in the process of - // evaluating the solution, we find - // that we did not encounter this - // point upon looping over all - // vertices, we then have to throw - // an exception in order to signal - // to the calling functions that - // something has gone wrong, rather - // than silently ignore this error. - // - // In the step-9 example program, - // we have already seen how such an - // exception class can be declared, - // using the DeclExceptionN - // macros. We use this mechanism - // here again. - // - // From this, the actual - // declaration of this class should - // be evident. Note that of course - // even if we do not list a - // destructor explicitely, an - // implicit destructor is generated - // from the compiler, and it is - // virtual just as the one of the - // base class. - template - class PointValueEvaluation : public EvaluationBase - { - public: - PointValueEvaluation (const Point &evaluation_point, - TableHandler &results_table); - - virtual void operator () (const DoFHandler &dof_handler, - const Vector &solution) const; - - DeclException1 (ExcEvaluationPointNotFound, - Point, - << "The evaluation point " << arg1 - << " was not found among the vertices of the present grid."); - private: - const Point evaluation_point; - TableHandler &results_table; - }; - // As for the definition, the - // constructor is trivial, just - // taking data and storing it in - // object-local ones: - template - PointValueEvaluation:: - PointValueEvaluation (const Point &evaluation_point, - TableHandler &results_table) - : - evaluation_point (evaluation_point), - results_table (results_table) - {} - - - - // Now for the function that is - // mainly of interest in this - // class, the computation of the - // point value: - template - void - PointValueEvaluation:: - operator () (const DoFHandler &dof_handler, - const Vector &solution) const - { - // First allocate a variable that - // will hold the point - // value. Initialize it with a - // value that is clearly bogus, - // so that if we fail to set it - // to a reasonable value, we will - // note at once. This may not be - // necessary in a function as - // small as this one, since we - // can easily see all possible - // paths of execution here, but - // it proved to be helpful for - // more complex cases, and so we - // employ this strategy here as - // well. - double point_value = 1e20; - - // Then loop over all cells and - // all their vertices, and check - // whether a vertex matches the - // evaluation point. If this is - // the case, then extract the - // point value, set a flag that - // we have found the point of - // interest, and exit the loop. - typename DoFHandler::active_cell_iterator - cell = dof_handler.begin_active(), - endc = dof_handler.end(); - bool evaluation_point_found = false; - for (; (cell!=endc) && !evaluation_point_found; ++cell) - for (unsigned int vertex=0; - vertex::vertices_per_cell; - ++vertex) - if (cell->vertex(vertex) == evaluation_point) - { - // In order to extract - // the point value from - // the global solution - // vector, pick that - // component that belongs - // to the vertex of - // interest, and, in case - // the solution is - // vector-valued, take - // the first component of - // it: - point_value = solution(cell->vertex_dof_index(vertex,0)); - // Note that by this we - // have made an - // assumption that is not - // valid always and - // should be documented - // in the class - // declaration if this - // were code for a real - // application rather - // than a tutorial - // program: we assume - // that the finite - // element used for the - // solution we try to - // evaluate actually has - // degrees of freedom - // associated with - // vertices. This, for - // example, does not hold - // for discontinuous - // elements, were the - // support points for the - // shape functions - // happen to be located - // at the vertices, but - // are not associated - // with the vertices but - // rather with the cell - // interior, since - // association with - // vertices would imply - // continuity there. It - // would also not hold - // for edge oriented - // elements, and the - // like. - // - // Ideally, we would - // check this at the - // beginning of the - // function, for example - // by a statement like - // Assert - // (dof_handler.get_fe().dofs_per_vertex - // @> 0, - // ExcNotImplemented()), - // which should make it - // quite clear what is - // going wrong when the - // exception is - // triggered. In this - // case, we omit it - // (which is indeed bad - // style), but knowing - // that that does not - // hurt here, since the - // statement - // cell-@>vertex_dof_index(vertex,0) - // would fail if we asked - // it to give us the DoF - // index of a vertex if - // there were none. - // - // We stress again that - // this restriction on - // the allowed finite - // elements should be - // stated in the class - // documentation. - - // Since we found the - // right point, we now - // set the respective - // flag and exit the - // innermost loop. The - // outer loop will the - // also be terminated due - // to the set flag. - evaluation_point_found = true; - break; - }; + template + void + EvaluationBase::set_refinement_cycle (const unsigned int step) + { + refinement_cycle = step; + } - // Finally, we'd like to make - // sure that we have indeed found - // the evaluation point, since if - // that were not so we could not - // give a reasonable value of the - // solution there and the rest of - // the computations were useless - // anyway. So make sure through - // the AssertThrow macro - // already used in the step-9 - // program that we have indeed - // found this point. If this is - // not so, the macro throws an - // exception of the type that is - // given to it as second - // argument, but compared to a - // straightforward throw - // statement, it fills the - // exception object with a set of - // additional information, for - // example the source file and - // line number where the - // exception was generated, and - // the condition that failed. If - // you have a catch clause in - // your main function (as this - // program has), you will catch - // all exceptions that are not - // caught somewhere in between - // and thus already handled, and - // this additional information - // will help you find out what - // happened and where it went - // wrong. - AssertThrow (evaluation_point_found, - ExcEvaluationPointNotFound(evaluation_point)); - // Note that we have used the - // Assert macro in other - // example programs as well. It - // differed from the - // AssertThrow macro used - // here in that it simply aborts - // the program, rather than - // throwing an exception, and - // that it did so only in debug - // mode. It was the right macro - // to use to check about the size - // of vectors passed as arguments - // to functions, and the like. + + // @sect4{%Point evaluation} + + // The next thing is to implement + // actual evaluation classes. As + // noted in the introduction, we'd + // like to extract a point value + // from the solution, so the first + // class does this in its + // operator(). The actual point + // is given to this class through + // the constructor, as well as a + // table object into which it will + // put its findings. // - // However, here the situation is - // different: whether we find the - // evaluation point or not may - // change from refinement to - // refinement (for example, if - // the four cells around point - // are coarsened away, then the - // point may vanish after - // refinement and - // coarsening). This is something - // that cannot be predicted from - // a few number of runs of the - // program in debug mode, but - // should be checked always, also - // in production runs. Thus the - // use of the AssertThrow - // macro here. - - // Now, if we are sure that we - // have found the evaluation - // point, we can add the results - // into the table of results: - results_table.add_value ("DoFs", dof_handler.n_dofs()); - results_table.add_value ("u(x_0)", point_value); - } + // Finding out the value of a + // finite element field at an + // arbitrary point is rather + // difficult, if we cannot rely on + // knowing the actual finite + // element used, since then we + // cannot, for example, interpolate + // between nodes. For simplicity, + // we therefore assume here that + // the point at which we want to + // evaluate the field is actually a + // node. If, in the process of + // evaluating the solution, we find + // that we did not encounter this + // point upon looping over all + // vertices, we then have to throw + // an exception in order to signal + // to the calling functions that + // something has gone wrong, rather + // than silently ignore this error. + // + // In the step-9 example program, + // we have already seen how such an + // exception class can be declared, + // using the DeclExceptionN + // macros. We use this mechanism + // here again. + // + // From this, the actual + // declaration of this class should + // be evident. Note that of course + // even if we do not list a + // destructor explicitely, an + // implicit destructor is generated + // from the compiler, and it is + // virtual just as the one of the + // base class. + template + class PointValueEvaluation : public EvaluationBase + { + public: + PointValueEvaluation (const Point &evaluation_point, + TableHandler &results_table); + + virtual void operator () (const DoFHandler &dof_handler, + const Vector &solution) const; + + DeclException1 (ExcEvaluationPointNotFound, + Point, + << "The evaluation point " << arg1 + << " was not found among the vertices of the present grid."); + private: + const Point evaluation_point; + TableHandler &results_table; + }; + // As for the definition, the + // constructor is trivial, just + // taking data and storing it in + // object-local ones: + template + PointValueEvaluation:: + PointValueEvaluation (const Point &evaluation_point, + TableHandler &results_table) + : + evaluation_point (evaluation_point), + results_table (results_table) + {} + + + + // Now for the function that is + // mainly of interest in this + // class, the computation of the + // point value: + template + void + PointValueEvaluation:: + operator () (const DoFHandler &dof_handler, + const Vector &solution) const + { + // First allocate a variable that + // will hold the point + // value. Initialize it with a + // value that is clearly bogus, + // so that if we fail to set it + // to a reasonable value, we will + // note at once. This may not be + // necessary in a function as + // small as this one, since we + // can easily see all possible + // paths of execution here, but + // it proved to be helpful for + // more complex cases, and so we + // employ this strategy here as + // well. + double point_value = 1e20; + + // Then loop over all cells and + // all their vertices, and check + // whether a vertex matches the + // evaluation point. If this is + // the case, then extract the + // point value, set a flag that + // we have found the point of + // interest, and exit the loop. + typename DoFHandler::active_cell_iterator + cell = dof_handler.begin_active(), + endc = dof_handler.end(); + bool evaluation_point_found = false; + for (; (cell!=endc) && !evaluation_point_found; ++cell) + for (unsigned int vertex=0; + vertex::vertices_per_cell; + ++vertex) + if (cell->vertex(vertex) == evaluation_point) + { + // In order to extract + // the point value from + // the global solution + // vector, pick that + // component that belongs + // to the vertex of + // interest, and, in case + // the solution is + // vector-valued, take + // the first component of + // it: + point_value = solution(cell->vertex_dof_index(vertex,0)); + // Note that by this we + // have made an + // assumption that is not + // valid always and + // should be documented + // in the class + // declaration if this + // were code for a real + // application rather + // than a tutorial + // program: we assume + // that the finite + // element used for the + // solution we try to + // evaluate actually has + // degrees of freedom + // associated with + // vertices. This, for + // example, does not hold + // for discontinuous + // elements, were the + // support points for the + // shape functions + // happen to be located + // at the vertices, but + // are not associated + // with the vertices but + // rather with the cell + // interior, since + // association with + // vertices would imply + // continuity there. It + // would also not hold + // for edge oriented + // elements, and the + // like. + // + // Ideally, we would + // check this at the + // beginning of the + // function, for example + // by a statement like + // Assert + // (dof_handler.get_fe().dofs_per_vertex + // @> 0, + // ExcNotImplemented()), + // which should make it + // quite clear what is + // going wrong when the + // exception is + // triggered. In this + // case, we omit it + // (which is indeed bad + // style), but knowing + // that that does not + // hurt here, since the + // statement + // cell-@>vertex_dof_index(vertex,0) + // would fail if we asked + // it to give us the DoF + // index of a vertex if + // there were none. + // + // We stress again that + // this restriction on + // the allowed finite + // elements should be + // stated in the class + // documentation. + + // Since we found the + // right point, we now + // set the respective + // flag and exit the + // innermost loop. The + // outer loop will the + // also be terminated due + // to the set flag. + evaluation_point_found = true; + break; + }; + + // Finally, we'd like to make + // sure that we have indeed found + // the evaluation point, since if + // that were not so we could not + // give a reasonable value of the + // solution there and the rest of + // the computations were useless + // anyway. So make sure through + // the AssertThrow macro + // already used in the step-9 + // program that we have indeed + // found this point. If this is + // not so, the macro throws an + // exception of the type that is + // given to it as second + // argument, but compared to a + // straightforward throw + // statement, it fills the + // exception object with a set of + // additional information, for + // example the source file and + // line number where the + // exception was generated, and + // the condition that failed. If + // you have a catch clause in + // your main function (as this + // program has), you will catch + // all exceptions that are not + // caught somewhere in between + // and thus already handled, and + // this additional information + // will help you find out what + // happened and where it went + // wrong. + AssertThrow (evaluation_point_found, + ExcEvaluationPointNotFound(evaluation_point)); + // Note that we have used the + // Assert macro in other + // example programs as well. It + // differed from the + // AssertThrow macro used + // here in that it simply aborts + // the program, rather than + // throwing an exception, and + // that it did so only in debug + // mode. It was the right macro + // to use to check about the size + // of vectors passed as arguments + // to functions, and the like. + // + // However, here the situation is + // different: whether we find the + // evaluation point or not may + // change from refinement to + // refinement (for example, if + // the four cells around point + // are coarsened away, then the + // point may vanish after + // refinement and + // coarsening). This is something + // that cannot be predicted from + // a few number of runs of the + // program in debug mode, but + // should be checked always, also + // in production runs. Thus the + // use of the AssertThrow + // macro here. + + // Now, if we are sure that we + // have found the evaluation + // point, we can add the results + // into the table of results: + results_table.add_value ("DoFs", dof_handler.n_dofs()); + results_table.add_value ("u(x_0)", point_value); + } - // @sect4{Generating output} - - // A different, maybe slightly odd - // kind of evaluation of a - // solution is to output it to a - // file in a graphical - // format. Since in the evaluation - // functions we are given a - // DoFHandler object and the - // solution vector, we have all we - // need to do this, so we can do it - // in an evaluation class. The - // reason for actually doing so - // instead of putting it into the - // class that computed the solution - // is that this way we have more - // flexibility: if we choose to - // only output certain aspects of - // it, or not output it at all. In - // any case, we do not need to - // modify the solver class, we just - // have to modify one of the - // modules out of which we build - // this program. This form of - // encapsulation, as above, helps - // us to keep each part of the - // program rather simple as the - // interfaces are kept simple, and - // no access to hidden data is - // possible. - // - // Since this class which generates - // the output is derived from the - // common EvaluationBase base - // class, its main interface is the - // operator() - // function. Furthermore, it has a - // constructor taking a string that - // will be used as the base part of - // the file name to which output - // will be sent (we will augment it - // by a number indicating the - // number of the refinement cycle - // -- the base class has this - // information at hand --, and a - // suffix), and the constructor - // also takes a value that - // indicates which format is - // requested, i.e. for which - // graphics program we shall - // generate output (from this we - // will then also generate the - // suffix of the filename to which - // we write). - // - // Regarding the output format, the - // DataOutInterface class - // (which is a base class of - // DataOut through which we - // will access its fields) provides - // an enumeration field - // OutputFormat, which lists - // names for all supported output - // formats. At the time of writing - // of this program, the supported - // graphics formats are represented - // by the enum values ucd, - // gnuplot, povray, - // eps, gmv, tecplot, - // tecplot_binary, dx, and - // vtk, but this list will - // certainly grow over time. Now, - // within various functions of that - // base class, you can use values - // of this type to get information - // about these graphics formats - // (for example the default suffix - // used for files of each format), - // and you can call a generic - // write function, which then - // branches to the - // write_gnuplot, - // write_ucd, etc functions - // which we have used in previous - // examples already, based on the - // value of a second argument given - // to it denoting the required - // output format. This mechanism - // makes it simple to write an - // extensible program that can - // decide which output format to - // use at runtime, and it also - // makes it rather simple to write - // the program in a way such that - // it takes advantage of newly - // implemented output formats, - // without the need to change the - // application program. - // - // Of these two fields, the base - // name and the output format - // descriptor, the constructor - // takes values and stores them for - // later use by the actual - // evaluation function. - template - class SolutionOutput : public EvaluationBase - { - public: - SolutionOutput (const std::string &output_name_base, - const typename DataOut::OutputFormat output_format); - - virtual void operator () (const DoFHandler &dof_handler, - const Vector &solution) const; - private: - const std::string output_name_base; - const typename DataOut::OutputFormat output_format; - }; - template - SolutionOutput:: - SolutionOutput (const std::string &output_name_base, - const typename DataOut::OutputFormat output_format) - : - output_name_base (output_name_base), - output_format (output_format) - {} - - - // After the description above, the - // function generating the actual - // output is now relatively - // straightforward. The only - // particularly interesting feature - // over previous example programs - // is the use of the - // DataOut::default_suffix - // function, returning the usual - // suffix for files of a given - // format (e.g. ".eps" for - // encapsulated postscript files, - // ".gnuplot" for Gnuplot files), - // and of the generic - // DataOut::write function with - // a second argument, which - // branches to the actual output - // functions for the different - // graphics formats, based on the - // value of the format descriptor - // passed as second argument. - // - // Also note that we have to prefix - // this-@> to access a member - // variable of the template - // dependent base class. The reason - // here, and further down in the - // program is the same as the one - // described in the step-7 example - // program (look for two-stage - // name lookup there). - template - void - SolutionOutput::operator () (const DoFHandler &dof_handler, - const Vector &solution) const - { - DataOut data_out; - data_out.attach_dof_handler (dof_handler); - data_out.add_data_vector (solution, "solution"); - data_out.build_patches (); - - std::ostringstream filename; - filename << output_name_base << "-" - << this->refinement_cycle - << data_out.default_suffix (output_format) - << std::ends; - std::ofstream out (filename.str().c_str()); - - data_out.write (out, output_format); - } + // @sect4{Generating output} + + // A different, maybe slightly odd + // kind of evaluation of a + // solution is to output it to a + // file in a graphical + // format. Since in the evaluation + // functions we are given a + // DoFHandler object and the + // solution vector, we have all we + // need to do this, so we can do it + // in an evaluation class. The + // reason for actually doing so + // instead of putting it into the + // class that computed the solution + // is that this way we have more + // flexibility: if we choose to + // only output certain aspects of + // it, or not output it at all. In + // any case, we do not need to + // modify the solver class, we just + // have to modify one of the + // modules out of which we build + // this program. This form of + // encapsulation, as above, helps + // us to keep each part of the + // program rather simple as the + // interfaces are kept simple, and + // no access to hidden data is + // possible. + // + // Since this class which generates + // the output is derived from the + // common EvaluationBase base + // class, its main interface is the + // operator() + // function. Furthermore, it has a + // constructor taking a string that + // will be used as the base part of + // the file name to which output + // will be sent (we will augment it + // by a number indicating the + // number of the refinement cycle + // -- the base class has this + // information at hand --, and a + // suffix), and the constructor + // also takes a value that + // indicates which format is + // requested, i.e. for which + // graphics program we shall + // generate output (from this we + // will then also generate the + // suffix of the filename to which + // we write). + // + // Regarding the output format, the + // DataOutInterface class + // (which is a base class of + // DataOut through which we + // will access its fields) provides + // an enumeration field + // OutputFormat, which lists + // names for all supported output + // formats. At the time of writing + // of this program, the supported + // graphics formats are represented + // by the enum values ucd, + // gnuplot, povray, + // eps, gmv, tecplot, + // tecplot_binary, dx, and + // vtk, but this list will + // certainly grow over time. Now, + // within various functions of that + // base class, you can use values + // of this type to get information + // about these graphics formats + // (for example the default suffix + // used for files of each format), + // and you can call a generic + // write function, which then + // branches to the + // write_gnuplot, + // write_ucd, etc functions + // which we have used in previous + // examples already, based on the + // value of a second argument given + // to it denoting the required + // output format. This mechanism + // makes it simple to write an + // extensible program that can + // decide which output format to + // use at runtime, and it also + // makes it rather simple to write + // the program in a way such that + // it takes advantage of newly + // implemented output formats, + // without the need to change the + // application program. + // + // Of these two fields, the base + // name and the output format + // descriptor, the constructor + // takes values and stores them for + // later use by the actual + // evaluation function. + template + class SolutionOutput : public EvaluationBase + { + public: + SolutionOutput (const std::string &output_name_base, + const typename DataOut::OutputFormat output_format); + + virtual void operator () (const DoFHandler &dof_handler, + const Vector &solution) const; + private: + const std::string output_name_base; + const typename DataOut::OutputFormat output_format; + }; + template + SolutionOutput:: + SolutionOutput (const std::string &output_name_base, + const typename DataOut::OutputFormat output_format) + : + output_name_base (output_name_base), + output_format (output_format) + {} + + + // After the description above, the + // function generating the actual + // output is now relatively + // straightforward. The only + // particularly interesting feature + // over previous example programs + // is the use of the + // DataOut::default_suffix + // function, returning the usual + // suffix for files of a given + // format (e.g. ".eps" for + // encapsulated postscript files, + // ".gnuplot" for Gnuplot files), + // and of the generic + // DataOut::write function with + // a second argument, which + // branches to the actual output + // functions for the different + // graphics formats, based on the + // value of the format descriptor + // passed as second argument. + // + // Also note that we have to prefix + // this-@> to access a member + // variable of the template + // dependent base class. The reason + // here, and further down in the + // program is the same as the one + // described in the step-7 example + // program (look for two-stage + // name lookup there). + template + void + SolutionOutput::operator () (const DoFHandler &dof_handler, + const Vector &solution) const + { + DataOut data_out; + data_out.attach_dof_handler (dof_handler); + data_out.add_data_vector (solution, "solution"); + data_out.build_patches (); + + std::ostringstream filename; + filename << output_name_base << "-" + << this->refinement_cycle + << data_out.default_suffix (output_format) + << std::ends; + std::ofstream out (filename.str().c_str()); + + data_out.write (out, output_format); + } - // @sect4{Other evaluations} - - // In practical applications, one - // would add here a list of other - // possible evaluation classes, - // representing quantities that one - // may be interested in. For this - // example, that much shall be - // sufficient, so we close the - // namespace. -} - - // @sect3{The Laplace solver classes} - - // After defining what we want to - // know of the solution, we should - // now care how to get at it. We will - // pack everything we need into a - // namespace of its own, for much the - // same reasons as for the - // evaluations above. - // - // Since we have discussed Laplace - // solvers already in considerable - // detail in previous examples, there - // is not much new stuff - // following. Rather, we have to a - // great extent cannibalized previous - // examples and put them, in slightly - // different form, into this example - // program. We will therefore mostly - // be concerned with discussing the - // differences to previous examples. - // - // Basically, as already said in the - // introduction, the lack of new - // stuff in this example is - // deliberate, as it is more to - // demonstrate software design - // practices, rather than - // mathematics. The emphasis in - // explanations below will therefore - // be more on the actual - // implementation. -namespace LaplaceSolver -{ - // @sect4{An abstract base class} - - // In defining a Laplace solver, we - // start out by declaring an - // abstract base class, that has no - // functionality itself except for - // taking and storing a pointer to - // the triangulation to be used - // later. - // - // This base class is very general, - // and could as well be used for - // any other stationary problem. It - // provides declarations of - // functions that shall, in derived - // classes, solve a problem, - // postprocess the solution with a - // list of evaluation objects, and - // refine the grid, - // respectively. None of these - // functions actually does - // something itself in the base - // class. - // - // Due to the lack of actual - // functionality, the programming - // style of declaring very abstract - // base classes reminds of the - // style used in Smalltalk or Java - // programs, where all classes are - // derived from entirely abstract - // classes Object, even number - // representations. The author - // admits that he does not - // particularly like the use of - // such a style in C++, as it puts - // style over reason. Furthermore, - // it promotes the use of virtual - // functions for everything (for - // example, in Java, all functions - // are virtual per se), which, - // however, has proven to be rather - // inefficient in many applications - // where functions are often only - // accessing data, not doing - // computations, and therefore - // quickly return; the overhead of - // virtual functions can then be - // significant. The opinion of the - // author is to have abstract base - // classes wherever at least some - // part of the code of actual - // implementations can be shared - // and thus separated into the base - // class. - // - // Besides all these theoretical - // questions, we here have a good - // reason, which will become - // clearer to the reader - // below. Basically, we want to be - // able to have a family of - // different Laplace solvers that - // differ so much that no larger - // common subset of functionality - // could be found. We therefore - // just declare such an abstract - // base class, taking a pointer to - // a triangulation in the - // constructor and storing it - // henceforth. Since this - // triangulation will be used - // throughout all computations, we - // have to make sure that the - // triangulation exists until the - // destructor exits. We do this by - // keeping a SmartPointer to - // this triangulation, which uses a - // counter in the triangulation - // class to denote the fact that - // there is still an object out - // there using this triangulation, - // thus leading to an abort in case - // the triangulation is attempted - // to be destructed while this - // object still uses it. - // - // Note that while the pointer - // itself is declared constant - // (i.e. throughout the lifetime of - // this object, the pointer points - // to the same object), it is not - // declared as a pointer to a - // constant triangulation. In fact, - // by this we allow that derived - // classes refine or coarsen the - // triangulation within the - // refine_grid function. - // - // Finally, we have a function - // n_dofs is only a tool for - // the driver functions to decide - // whether we want to go on with - // mesh refinement or not. It - // returns the number of degrees of - // freedom the present simulation - // has. - template - class Base - { - public: - Base (Triangulation &coarse_grid); - virtual ~Base (); - - virtual void solve_problem () = 0; - virtual void postprocess (const Evaluation::EvaluationBase &postprocessor) const = 0; - virtual void refine_grid () = 0; - virtual unsigned int n_dofs () const = 0; - - protected: - const SmartPointer > triangulation; - }; + // @sect4{Other evaluations} - // The implementation of the only - // two non-abstract functions is - // then rather boring: - template - Base::Base (Triangulation &coarse_grid) - : - triangulation (&coarse_grid) - {} + // In practical applications, one + // would add here a list of other + // possible evaluation classes, + // representing quantities that one + // may be interested in. For this + // example, that much shall be + // sufficient, so we close the + // namespace. + } - template - Base::~Base () - {} - - - // @sect4{A general solver class} - - // Following now the main class - // that implements assembling the - // matrix of the linear system, - // solving it, and calling the - // postprocessor objects on the - // solution. It implements the - // solve_problem and - // postprocess functions - // declared in the base class. It - // does not, however, implement the - // refine_grid method, as mesh - // refinement will be implemented - // in a number of derived classes. - // - // It also declares a new abstract - // virtual function, - // assemble_rhs, that needs to - // be overloaded in subclasses. The - // reason is that we will implement - // two different classes that will - // implement different methods to - // assemble the right hand side - // vector. This function might also - // be interesting in cases where - // the right hand side depends not - // simply on a continuous function, - // but on something else as well, - // for example the solution of - // another discretized problem, - // etc. The latter happens - // frequently in non-linear - // problems. - // - // As we mentioned previously, the - // actual content of this class is - // not new, but a mixture of - // various techniques already used - // in previous examples. We will - // therefore not discuss them in - // detail, but refer the reader to - // these programs. - // - // Basically, in a few words, the - // constructor of this class takes - // pointers to a triangulation, a - // finite element, and a function - // object representing the boundary - // values. These are either passed - // down to the base class's - // constructor, or are stored and - // used to generate a - // DoFHandler object - // later. Since finite elements and - // quadrature formula should match, - // it is also passed a quadrature - // object. - // - // The solve_problem sets up - // the data structures for the - // actual solution, calls the - // functions to assemble the linear - // system, and solves it. + // @sect3{The Laplace solver classes} + + // After defining what we want to + // know of the solution, we should + // now care how to get at it. We will + // pack everything we need into a + // namespace of its own, for much the + // same reasons as for the + // evaluations above. // - // The postprocess function - // finally takes an evaluation - // object and applies it to the - // computed solution. + // Since we have discussed Laplace + // solvers already in considerable + // detail in previous examples, there + // is not much new stuff + // following. Rather, we have to a + // great extent cannibalized previous + // examples and put them, in slightly + // different form, into this example + // program. We will therefore mostly + // be concerned with discussing the + // differences to previous examples. // - // The n_dofs function finally - // implements the pure virtual - // function of the base class. - template - class Solver : public virtual Base + // Basically, as already said in the + // introduction, the lack of new + // stuff in this example is + // deliberate, as it is more to + // demonstrate software design + // practices, rather than + // mathematics. The emphasis in + // explanations below will therefore + // be more on the actual + // implementation. + namespace LaplaceSolver { - public: - Solver (Triangulation &triangulation, - const FiniteElement &fe, - const Quadrature &quadrature, - const Function &boundary_values); - virtual - ~Solver (); - - virtual - void - solve_problem (); - - virtual - void - postprocess (const Evaluation::EvaluationBase &postprocessor) const; - - virtual - unsigned int - n_dofs () const; - - // In the protected section of - // this class, we first have a - // number of member variables, - // of which the use should be - // clear from the previous - // examples: - protected: - const SmartPointer > fe; - const SmartPointer > quadrature; - DoFHandler dof_handler; - Vector solution; - const SmartPointer > boundary_values; - - // Then we declare an abstract - // function that will be used - // to assemble the right hand - // side. As explained above, - // there are various cases for - // which this action differs - // strongly in what is - // necessary, so we defer this - // to derived classes: - virtual void assemble_rhs (Vector &rhs) const = 0; - - // Next, in the private - // section, we have a small - // class which represents an - // entire linear system, i.e. a - // matrix, a right hand side, - // and a solution vector, as - // well as the constraints that - // are applied to it, such as - // those due to hanging - // nodes. Its constructor - // initializes the various - // subobjects, and there is a - // function that implements a - // conjugate gradient method as - // solver. - private: - struct LinearSystem - { - LinearSystem (const DoFHandler &dof_handler); - - void solve (Vector &solution) const; - - ConstraintMatrix hanging_node_constraints; - SparsityPattern sparsity_pattern; - SparseMatrix matrix; - Vector rhs; - }; + // @sect4{An abstract base class} + + // In defining a Laplace solver, we + // start out by declaring an + // abstract base class, that has no + // functionality itself except for + // taking and storing a pointer to + // the triangulation to be used + // later. + // + // This base class is very general, + // and could as well be used for + // any other stationary problem. It + // provides declarations of + // functions that shall, in derived + // classes, solve a problem, + // postprocess the solution with a + // list of evaluation objects, and + // refine the grid, + // respectively. None of these + // functions actually does + // something itself in the base + // class. + // + // Due to the lack of actual + // functionality, the programming + // style of declaring very abstract + // base classes reminds of the + // style used in Smalltalk or Java + // programs, where all classes are + // derived from entirely abstract + // classes Object, even number + // representations. The author + // admits that he does not + // particularly like the use of + // such a style in C++, as it puts + // style over reason. Furthermore, + // it promotes the use of virtual + // functions for everything (for + // example, in Java, all functions + // are virtual per se), which, + // however, has proven to be rather + // inefficient in many applications + // where functions are often only + // accessing data, not doing + // computations, and therefore + // quickly return; the overhead of + // virtual functions can then be + // significant. The opinion of the + // author is to have abstract base + // classes wherever at least some + // part of the code of actual + // implementations can be shared + // and thus separated into the base + // class. + // + // Besides all these theoretical + // questions, we here have a good + // reason, which will become + // clearer to the reader + // below. Basically, we want to be + // able to have a family of + // different Laplace solvers that + // differ so much that no larger + // common subset of functionality + // could be found. We therefore + // just declare such an abstract + // base class, taking a pointer to + // a triangulation in the + // constructor and storing it + // henceforth. Since this + // triangulation will be used + // throughout all computations, we + // have to make sure that the + // triangulation exists until the + // destructor exits. We do this by + // keeping a SmartPointer to + // this triangulation, which uses a + // counter in the triangulation + // class to denote the fact that + // there is still an object out + // there using this triangulation, + // thus leading to an abort in case + // the triangulation is attempted + // to be destructed while this + // object still uses it. + // + // Note that while the pointer + // itself is declared constant + // (i.e. throughout the lifetime of + // this object, the pointer points + // to the same object), it is not + // declared as a pointer to a + // constant triangulation. In fact, + // by this we allow that derived + // classes refine or coarsen the + // triangulation within the + // refine_grid function. + // + // Finally, we have a function + // n_dofs is only a tool for + // the driver functions to decide + // whether we want to go on with + // mesh refinement or not. It + // returns the number of degrees of + // freedom the present simulation + // has. + template + class Base + { + public: + Base (Triangulation &coarse_grid); + virtual ~Base (); - // Finally, there is a pair of - // functions which will be used - // to assemble the actual - // system matrix. It calls the - // virtual function assembling - // the right hand side, and - // installs a number threads - // each running the second - // function which assembles - // part of the system - // matrix. The mechanism for - // doing so is the same as in - // the step-9 example program. - void - assemble_linear_system (LinearSystem &linear_system); - - void - assemble_matrix (LinearSystem &linear_system, - const typename DoFHandler::active_cell_iterator &begin_cell, - const typename DoFHandler::active_cell_iterator &end_cell, - Threads::ThreadMutex &mutex) const; - }; + virtual void solve_problem () = 0; + virtual void postprocess (const Evaluation::EvaluationBase &postprocessor) const = 0; + virtual void refine_grid () = 0; + virtual unsigned int n_dofs () const = 0; + protected: + const SmartPointer > triangulation; + }; - // Now here comes the constructor - // of the class. It does not do - // much except store pointers to - // the objects given, and generate - // DoFHandler object - // initialized with the given - // pointer to a triangulation. This - // causes the DoF handler to store - // that pointer, but does not - // already generate a finite - // element numbering (we only ask - // for that in the - // solve_problem function). - template - Solver::Solver (Triangulation &triangulation, - const FiniteElement &fe, - const Quadrature &quadrature, - const Function &boundary_values) - : - Base (triangulation), - fe (&fe), - quadrature (&quadrature), - dof_handler (triangulation), - boundary_values (&boundary_values) - {} - - - // The destructor is simple, it - // only clears the information - // stored in the DoF handler object - // to release the memory. - template - Solver::~Solver () - { - dof_handler.clear (); - } + // The implementation of the only + // two non-abstract functions is + // then rather boring: + template + Base::Base (Triangulation &coarse_grid) + : + triangulation (&coarse_grid) + {} + + + template + Base::~Base () + {} + + + // @sect4{A general solver class} + + // Following now the main class + // that implements assembling the + // matrix of the linear system, + // solving it, and calling the + // postprocessor objects on the + // solution. It implements the + // solve_problem and + // postprocess functions + // declared in the base class. It + // does not, however, implement the + // refine_grid method, as mesh + // refinement will be implemented + // in a number of derived classes. + // + // It also declares a new abstract + // virtual function, + // assemble_rhs, that needs to + // be overloaded in subclasses. The + // reason is that we will implement + // two different classes that will + // implement different methods to + // assemble the right hand side + // vector. This function might also + // be interesting in cases where + // the right hand side depends not + // simply on a continuous function, + // but on something else as well, + // for example the solution of + // another discretized problem, + // etc. The latter happens + // frequently in non-linear + // problems. + // + // As we mentioned previously, the + // actual content of this class is + // not new, but a mixture of + // various techniques already used + // in previous examples. We will + // therefore not discuss them in + // detail, but refer the reader to + // these programs. + // + // Basically, in a few words, the + // constructor of this class takes + // pointers to a triangulation, a + // finite element, and a function + // object representing the boundary + // values. These are either passed + // down to the base class's + // constructor, or are stored and + // used to generate a + // DoFHandler object + // later. Since finite elements and + // quadrature formula should match, + // it is also passed a quadrature + // object. + // + // The solve_problem sets up + // the data structures for the + // actual solution, calls the + // functions to assemble the linear + // system, and solves it. + // + // The postprocess function + // finally takes an evaluation + // object and applies it to the + // computed solution. + // + // The n_dofs function finally + // implements the pure virtual + // function of the base class. + template + class Solver : public virtual Base + { + public: + Solver (Triangulation &triangulation, + const FiniteElement &fe, + const Quadrature &quadrature, + const Function &boundary_values); + virtual + ~Solver (); + + virtual + void + solve_problem (); + + virtual + void + postprocess (const Evaluation::EvaluationBase &postprocessor) const; + + virtual + unsigned int + n_dofs () const; + + // In the protected section of + // this class, we first have a + // number of member variables, + // of which the use should be + // clear from the previous + // examples: + protected: + const SmartPointer > fe; + const SmartPointer > quadrature; + DoFHandler dof_handler; + Vector solution; + const SmartPointer > boundary_values; + + // Then we declare an abstract + // function that will be used + // to assemble the right hand + // side. As explained above, + // there are various cases for + // which this action differs + // strongly in what is + // necessary, so we defer this + // to derived classes: + virtual void assemble_rhs (Vector &rhs) const = 0; + + // Next, in the private + // section, we have a small + // class which represents an + // entire linear system, i.e. a + // matrix, a right hand side, + // and a solution vector, as + // well as the constraints that + // are applied to it, such as + // those due to hanging + // nodes. Its constructor + // initializes the various + // subobjects, and there is a + // function that implements a + // conjugate gradient method as + // solver. + private: + struct LinearSystem + { + LinearSystem (const DoFHandler &dof_handler); + void solve (Vector &solution) const; - // The next function is the one - // which delegates the main work in - // solving the problem: it sets up - // the DoF handler object with the - // finite element given to the - // constructor of this object, the - // creates an object that denotes - // the linear system (i.e. the - // matrix, the right hand side - // vector, and the solution - // vector), calls the function to - // assemble it, and finally solves - // it: - template - void - Solver::solve_problem () - { - dof_handler.distribute_dofs (*fe); - solution.reinit (dof_handler.n_dofs()); + ConstraintMatrix hanging_node_constraints; + SparsityPattern sparsity_pattern; + SparseMatrix matrix; + Vector rhs; + }; - LinearSystem linear_system (dof_handler); - assemble_linear_system (linear_system); - linear_system.solve (solution); - } + // Finally, there is a pair of + // functions which will be used + // to assemble the actual + // system matrix. It calls the + // virtual function assembling + // the right hand side, and + // installs a number threads + // each running the second + // function which assembles + // part of the system + // matrix. The mechanism for + // doing so is the same as in + // the step-9 example program. + void + assemble_linear_system (LinearSystem &linear_system); + + void + assemble_matrix (LinearSystem &linear_system, + const typename DoFHandler::active_cell_iterator &begin_cell, + const typename DoFHandler::active_cell_iterator &end_cell, + Threads::ThreadMutex &mutex) const; + }; - // As stated above, the - // postprocess function takes - // an evaluation object, and - // applies it to the computed - // solution. This function may be - // called multiply, once for each - // evaluation of the solution which - // the user required. - template - void - Solver:: - postprocess (const Evaluation::EvaluationBase &postprocessor) const - { - postprocessor (dof_handler, solution); - } + // Now here comes the constructor + // of the class. It does not do + // much except store pointers to + // the objects given, and generate + // DoFHandler object + // initialized with the given + // pointer to a triangulation. This + // causes the DoF handler to store + // that pointer, but does not + // already generate a finite + // element numbering (we only ask + // for that in the + // solve_problem function). + template + Solver::Solver (Triangulation &triangulation, + const FiniteElement &fe, + const Quadrature &quadrature, + const Function &boundary_values) + : + Base (triangulation), + fe (&fe), + quadrature (&quadrature), + dof_handler (triangulation), + boundary_values (&boundary_values) + {} + + + // The destructor is simple, it + // only clears the information + // stored in the DoF handler object + // to release the memory. + template + Solver::~Solver () + { + dof_handler.clear (); + } - // The n_dofs function should - // be self-explanatory: - template - unsigned int - Solver::n_dofs () const - { - return dof_handler.n_dofs(); - } - - - // The following function assembles matrix - // and right hand side of the linear system - // to be solved in each step. It goes along - // the same lines as used in previous - // examples, so we explain it only - // briefly. Note that we do a number of - // things in parallel, a process described - // in more detail in the @ref threads - // module. - template - void - Solver::assemble_linear_system (LinearSystem &linear_system) - { - // First define a convenience - // abbreviation for these lengthy - // iterator names... - typedef - typename DoFHandler::active_cell_iterator - active_cell_iterator; - - // ... and use it to split up the - // set of cells into a number of - // pieces of equal size. The - // number of blocks is set to the - // default number of threads to - // be used, which by default is - // set to the number of - // processors found in your - // computer at startup of the - // program: - const unsigned int n_threads = multithread_info.n_default_threads; - std::vector > - thread_ranges - = Threads::split_range (dof_handler.begin_active (), - dof_handler.end (), - n_threads); - - // These ranges are then assigned - // to a number of threads which - // we create next. Each will - // assemble the local cell - // matrices on the assigned - // cells, and fill the matrix - // object with it. Since there is - // need for synchronization when - // filling the same matrix from - // different threads, we need a - // mutex here: - Threads::ThreadMutex mutex; - Threads::ThreadGroup<> threads; - for (unsigned int thread=0; thread::assemble_matrix, - *this, - linear_system, - thread_ranges[thread].first, - thread_ranges[thread].second, - mutex); - - // While the new threads - // assemble the system matrix, we - // can already compute the right - // hand side vector in the main - // thread, and condense away the - // constraints due to hanging - // nodes: - assemble_rhs (linear_system.rhs); - linear_system.hanging_node_constraints.condense (linear_system.rhs); - - // And while we're already - // computing things in parallel, - // interpolating boundary values - // is one more thing that can be - // done independently, so we do - // it here: - std::map boundary_value_map; - VectorTools::interpolate_boundary_values (dof_handler, - 0, - *boundary_values, - boundary_value_map); - - - // If this is done, wait for the - // matrix assembling threads, and - // condense the constraints in - // the matrix as well: - threads.join_all (); - linear_system.hanging_node_constraints.condense (linear_system.matrix); - - // Now that we have the linear - // system, we can also treat - // boundary values, which need to - // be eliminated from both the - // matrix and the right hand - // side: - MatrixTools::apply_boundary_values (boundary_value_map, - linear_system.matrix, - solution, - linear_system.rhs); - } + // The next function is the one + // which delegates the main work in + // solving the problem: it sets up + // the DoF handler object with the + // finite element given to the + // constructor of this object, the + // creates an object that denotes + // the linear system (i.e. the + // matrix, the right hand side + // vector, and the solution + // vector), calls the function to + // assemble it, and finally solves + // it: + template + void + Solver::solve_problem () + { + dof_handler.distribute_dofs (*fe); + solution.reinit (dof_handler.n_dofs()); + LinearSystem linear_system (dof_handler); + assemble_linear_system (linear_system); + linear_system.solve (solution); + } - // The second of this pair of - // functions takes a range of cell - // iterators, and assembles the - // system matrix on this part of - // the domain. Since it's actions - // have all been explained in - // previous programs, we do not - // comment on it any more, except - // for one pointe below. - template - void - Solver::assemble_matrix (LinearSystem &linear_system, - const typename DoFHandler::active_cell_iterator &begin_cell, - const typename DoFHandler::active_cell_iterator &end_cell, - Threads::ThreadMutex &mutex) const - { - FEValues fe_values (*fe, *quadrature, - update_gradients | update_JxW_values); - const unsigned int dofs_per_cell = fe->dofs_per_cell; - const unsigned int n_q_points = quadrature->size(); + // As stated above, the + // postprocess function takes + // an evaluation object, and + // applies it to the computed + // solution. This function may be + // called multiply, once for each + // evaluation of the solution which + // the user required. + template + void + Solver:: + postprocess (const Evaluation::EvaluationBase &postprocessor) const + { + postprocessor (dof_handler, solution); + } + - FullMatrix cell_matrix (dofs_per_cell, dofs_per_cell); + // The n_dofs function should + // be self-explanatory: + template + unsigned int + Solver::n_dofs () const + { + return dof_handler.n_dofs(); + } - std::vector local_dof_indices (dofs_per_cell); - for (typename DoFHandler::active_cell_iterator cell=begin_cell; - cell!=end_cell; ++cell) - { - cell_matrix = 0; + // The following function assembles matrix + // and right hand side of the linear system + // to be solved in each step. It goes along + // the same lines as used in previous + // examples, so we explain it only + // briefly. Note that we do a number of + // things in parallel, a process described + // in more detail in the @ref threads + // module. + template + void + Solver::assemble_linear_system (LinearSystem &linear_system) + { + // First define a convenience + // abbreviation for these lengthy + // iterator names... + typedef + typename DoFHandler::active_cell_iterator + active_cell_iterator; + + // ... and use it to split up the + // set of cells into a number of + // pieces of equal size. The + // number of blocks is set to the + // default number of threads to + // be used, which by default is + // set to the number of + // processors found in your + // computer at startup of the + // program: + const unsigned int n_threads = multithread_info.n_default_threads; + std::vector > + thread_ranges + = Threads::split_range (dof_handler.begin_active (), + dof_handler.end (), + n_threads); + + // These ranges are then assigned + // to a number of threads which + // we create next. Each will + // assemble the local cell + // matrices on the assigned + // cells, and fill the matrix + // object with it. Since there is + // need for synchronization when + // filling the same matrix from + // different threads, we need a + // mutex here: + Threads::ThreadMutex mutex; + Threads::ThreadGroup<> threads; + for (unsigned int thread=0; thread::assemble_matrix, + *this, + linear_system, + thread_ranges[thread].first, + thread_ranges[thread].second, + mutex); + + // While the new threads + // assemble the system matrix, we + // can already compute the right + // hand side vector in the main + // thread, and condense away the + // constraints due to hanging + // nodes: + assemble_rhs (linear_system.rhs); + linear_system.hanging_node_constraints.condense (linear_system.rhs); + + // And while we're already + // computing things in parallel, + // interpolating boundary values + // is one more thing that can be + // done independently, so we do + // it here: + std::map boundary_value_map; + VectorTools::interpolate_boundary_values (dof_handler, + 0, + *boundary_values, + boundary_value_map); + + + // If this is done, wait for the + // matrix assembling threads, and + // condense the constraints in + // the matrix as well: + threads.join_all (); + linear_system.hanging_node_constraints.condense (linear_system.matrix); + + // Now that we have the linear + // system, we can also treat + // boundary values, which need to + // be eliminated from both the + // matrix and the right hand + // side: + MatrixTools::apply_boundary_values (boundary_value_map, + linear_system.matrix, + solution, + linear_system.rhs); - fe_values.reinit (cell); + } + + + // The second of this pair of + // functions takes a range of cell + // iterators, and assembles the + // system matrix on this part of + // the domain. Since it's actions + // have all been explained in + // previous programs, we do not + // comment on it any more, except + // for one pointe below. + template + void + Solver::assemble_matrix (LinearSystem &linear_system, + const typename DoFHandler::active_cell_iterator &begin_cell, + const typename DoFHandler::active_cell_iterator &end_cell, + Threads::ThreadMutex &mutex) const + { + FEValues fe_values (*fe, *quadrature, + update_gradients | update_JxW_values); - for (unsigned int q_point=0; q_pointdofs_per_cell; + const unsigned int n_q_points = quadrature->size(); + + FullMatrix cell_matrix (dofs_per_cell, dofs_per_cell); + + std::vector local_dof_indices (dofs_per_cell); + + for (typename DoFHandler::active_cell_iterator cell=begin_cell; + cell!=end_cell; ++cell) + { + cell_matrix = 0; + + fe_values.reinit (cell); + + for (unsigned int q_point=0; q_pointget_dof_indices (local_dof_indices); + + // In the step-9 program, we + // have shown that you have + // to use the mutex to lock + // the matrix when copying + // the elements from the + // local to the global + // matrix. This was necessary + // to avoid that two threads + // access it at the same + // time, eventually + // overwriting their + // respective + // work. Previously, we have + // used the acquire and + // release functions of + // the mutex to lock and + // unlock the mutex, + // respectively. While this + // is valid, there is one + // possible catch: if between + // the locking operation and + // the unlocking operation an + // exception is thrown, the + // mutex remains in the + // locked state, and in some + // cases this might lead to + // deadlocks. A similar + // situation arises, when one + // changes the code to have a + // return statement somewhere + // in the middle of the + // locked block, and forgets + // that before we call + // return, we also have + // to unlock the mutex. This + // all is not be a problem + // here, but we want to show + // the general technique to + // cope with these problems + // nevertheless: have an + // object that upon + // initialization (i.e. in + // its constructor) locks the + // mutex, and on running the + // destructor unlocks it + // again. This is called the + // scoped lock pattern + // (apparently invented by + // Doug Schmidt originally), + // and it works because + // destructors of local + // objects are also run when + // we exit the function + // either through a + // return statement, or + // when an exception is + // raised. Thus, it is + // guaranteed that the mutex + // will always be unlocked + // when we exit this part of + // the program, whether the + // operation completed + // successfully or not, + // whether the exit path was + // something we implemented + // willfully or whether the + // function was exited by an + // exception that we did not + // forsee. + // + // deal.II implements the + // scoped locking pattern in + // the + // ThreadMutex::ScopedLock + // class: it takes the mutex + // in the constructor and + // locks it; in its + // destructor, it unlocks it + // again. So here is how it + // is used: + Threads::ThreadMutex::ScopedLock lock (mutex); for (unsigned int i=0; iget_dof_indices (local_dof_indices); - - // In the step-9 program, we - // have shown that you have - // to use the mutex to lock - // the matrix when copying - // the elements from the - // local to the global - // matrix. This was necessary - // to avoid that two threads - // access it at the same - // time, eventually - // overwriting their - // respective - // work. Previously, we have - // used the acquire and - // release functions of - // the mutex to lock and - // unlock the mutex, - // respectively. While this - // is valid, there is one - // possible catch: if between - // the locking operation and - // the unlocking operation an - // exception is thrown, the - // mutex remains in the - // locked state, and in some - // cases this might lead to - // deadlocks. A similar - // situation arises, when one - // changes the code to have a - // return statement somewhere - // in the middle of the - // locked block, and forgets - // that before we call - // return, we also have - // to unlock the mutex. This - // all is not be a problem - // here, but we want to show - // the general technique to - // cope with these problems - // nevertheless: have an - // object that upon - // initialization (i.e. in - // its constructor) locks the - // mutex, and on running the - // destructor unlocks it - // again. This is called the - // scoped lock pattern - // (apparently invented by - // Doug Schmidt originally), - // and it works because - // destructors of local - // objects are also run when - // we exit the function - // either through a - // return statement, or - // when an exception is - // raised. Thus, it is - // guaranteed that the mutex - // will always be unlocked - // when we exit this part of - // the program, whether the - // operation completed - // successfully or not, - // whether the exit path was - // something we implemented - // willfully or whether the - // function was exited by an - // exception that we did not - // forsee. - // - // deal.II implements the - // scoped locking pattern in - // the - // ThreadMutex::ScopedLock - // class: it takes the mutex - // in the constructor and - // locks it; in its - // destructor, it unlocks it - // again. So here is how it - // is used: - Threads::ThreadMutex::ScopedLock lock (mutex); - for (unsigned int i=0; ilock variable goes out - // of existence and its - // destructor the mutex is - // unlocked. - }; - } + linear_system.matrix.add (local_dof_indices[i], + local_dof_indices[j], + cell_matrix(i,j)); + // Here, at the brace, the + // current scope ends, so the + // lock variable goes out + // of existence and its + // destructor the mutex is + // unlocked. + }; + } - // Now for the functions that - // implement actions in the linear - // system class. First, the - // constructor initializes all data - // elements to their correct sizes, - // and sets up a number of - // additional data structures, such - // as constraints due to hanging - // nodes. Since setting up the - // hanging nodes and finding out - // about the nonzero elements of - // the matrix is independent, we do - // that in parallel (if the library - // was configured to use - // concurrency, at least; - // otherwise, the actions are - // performed sequentially). Note - // that we start only one thread, - // and do the second action in the - // main thread. Since only one - // thread is generated, we don't - // use the Threads::ThreadGroup - // class here, but rather use the - // one created thread object - // directly to wait for this - // particular thread's exit. - // - // Note that taking up the address - // of the - // DoFTools::make_hanging_node_constraints - // function is a little tricky, - // since there are actually three - // of them, one for each supported - // space dimension. Taking - // addresses of overloaded - // functions is somewhat - // complicated in C++, since the - // address-of operator & in - // that case returns more like a - // set of values (the addresses of - // all functions with that name), - // and selecting the right one is - // then the next step. If the - // context dictates which one to - // take (for example by assigning - // to a function pointer of known - // type), then the compiler can do - // that by itself, but if this set - // of pointers shall be given as - // the argument to a function that - // takes a template, the compiler - // could choose all without having - // a preference for one. We - // therefore have to make it clear - // to the compiler which one we - // would like to have; for this, we - // could use a cast, but for more - // clarity, we assign it to a - // temporary mhnc_p (short for - // pointer to - // make_hanging_node_constraints) - // with the right type, and using - // this pointer instead. - template - Solver::LinearSystem:: - LinearSystem (const DoFHandler &dof_handler) - { - hanging_node_constraints.clear (); - - void (*mhnc_p) (const DoFHandler &, - ConstraintMatrix &) - = &DoFTools::make_hanging_node_constraints; - - Threads::Thread<> - mhnc_thread = Threads::new_thread (mhnc_p, - dof_handler, - hanging_node_constraints); - - 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); - - // Wait until the - // hanging_node_constraints - // object is fully set up, then - // close it and use it to - // condense the sparsity pattern: - mhnc_thread.join (); - hanging_node_constraints.close (); - hanging_node_constraints.condense (sparsity_pattern); - - // Finally, close the sparsity - // pattern, initialize the - // matrix, and set the right hand - // side vector to the right size. - sparsity_pattern.compress(); - matrix.reinit (sparsity_pattern); - rhs.reinit (dof_handler.n_dofs()); - } + // Now for the functions that + // implement actions in the linear + // system class. First, the + // constructor initializes all data + // elements to their correct sizes, + // and sets up a number of + // additional data structures, such + // as constraints due to hanging + // nodes. Since setting up the + // hanging nodes and finding out + // about the nonzero elements of + // the matrix is independent, we do + // that in parallel (if the library + // was configured to use + // concurrency, at least; + // otherwise, the actions are + // performed sequentially). Note + // that we start only one thread, + // and do the second action in the + // main thread. Since only one + // thread is generated, we don't + // use the Threads::ThreadGroup + // class here, but rather use the + // one created thread object + // directly to wait for this + // particular thread's exit. + // + // Note that taking up the address + // of the + // DoFTools::make_hanging_node_constraints + // function is a little tricky, + // since there are actually three + // of them, one for each supported + // space dimension. Taking + // addresses of overloaded + // functions is somewhat + // complicated in C++, since the + // address-of operator & in + // that case returns more like a + // set of values (the addresses of + // all functions with that name), + // and selecting the right one is + // then the next step. If the + // context dictates which one to + // take (for example by assigning + // to a function pointer of known + // type), then the compiler can do + // that by itself, but if this set + // of pointers shall be given as + // the argument to a function that + // takes a template, the compiler + // could choose all without having + // a preference for one. We + // therefore have to make it clear + // to the compiler which one we + // would like to have; for this, we + // could use a cast, but for more + // clarity, we assign it to a + // temporary mhnc_p (short for + // pointer to + // make_hanging_node_constraints) + // with the right type, and using + // this pointer instead. + template + Solver::LinearSystem:: + LinearSystem (const DoFHandler &dof_handler) + { + hanging_node_constraints.clear (); + + void (*mhnc_p) (const DoFHandler &, + ConstraintMatrix &) + = &DoFTools::make_hanging_node_constraints; + + Threads::Thread<> + mhnc_thread = Threads::new_thread (mhnc_p, + dof_handler, + hanging_node_constraints); + + 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); + + // Wait until the + // hanging_node_constraints + // object is fully set up, then + // close it and use it to + // condense the sparsity pattern: + mhnc_thread.join (); + hanging_node_constraints.close (); + hanging_node_constraints.condense (sparsity_pattern); + + // Finally, close the sparsity + // pattern, initialize the + // matrix, and set the right hand + // side vector to the right size. + sparsity_pattern.compress(); + matrix.reinit (sparsity_pattern); + rhs.reinit (dof_handler.n_dofs()); + } - // The second function of this - // class simply solves the linear - // system by a preconditioned - // conjugate gradient method. This - // has been extensively discussed - // before, so we don't dwell into - // it any more. - template - void - Solver::LinearSystem::solve (Vector &solution) const - { - SolverControl solver_control (1000, 1e-12); - SolverCG<> cg (solver_control); + // The second function of this + // class simply solves the linear + // system by a preconditioned + // conjugate gradient method. This + // has been extensively discussed + // before, so we don't dwell into + // it any more. + template + void + Solver::LinearSystem::solve (Vector &solution) const + { + SolverControl solver_control (1000, 1e-12); + SolverCG<> cg (solver_control); - PreconditionSSOR<> preconditioner; - preconditioner.initialize(matrix, 1.2); + PreconditionSSOR<> preconditioner; + preconditioner.initialize(matrix, 1.2); - cg.solve (matrix, solution, rhs, preconditioner); + cg.solve (matrix, solution, rhs, preconditioner); - hanging_node_constraints.distribute (solution); - } + hanging_node_constraints.distribute (solution); + } - // @sect4{A primal solver} + // @sect4{A primal solver} - // In the previous section, a base - // class for Laplace solvers was - // implemented, that lacked the - // functionality to assemble the - // right hand side vector, however, - // for reasons that were explained - // there. Now we implement a - // corresponding class that can do - // this for the case that the right - // hand side of a problem is given - // as a function object. - // - // The actions of the class are - // rather what you have seen - // already in previous examples - // already, so a brief explanation - // should suffice: the constructor - // takes the same data as does that - // of the underlying class (to - // which it passes all information) - // except for one function object - // that denotes the right hand side - // of the problem. A pointer to - // this object is stored (again as - // a SmartPointer, in order to - // make sure that the function - // object is not deleted as long as - // it is still used by this class). - // - // The only functional part of this - // class is the assemble_rhs - // method that does what its name - // suggests. - template - class PrimalSolver : public Solver - { - public: - PrimalSolver (Triangulation &triangulation, - const FiniteElement &fe, - const Quadrature &quadrature, - const Function &rhs_function, - const Function &boundary_values); - protected: - const SmartPointer > rhs_function; - virtual void assemble_rhs (Vector &rhs) const; - }; + // In the previous section, a base + // class for Laplace solvers was + // implemented, that lacked the + // functionality to assemble the + // right hand side vector, however, + // for reasons that were explained + // there. Now we implement a + // corresponding class that can do + // this for the case that the right + // hand side of a problem is given + // as a function object. + // + // The actions of the class are + // rather what you have seen + // already in previous examples + // already, so a brief explanation + // should suffice: the constructor + // takes the same data as does that + // of the underlying class (to + // which it passes all information) + // except for one function object + // that denotes the right hand side + // of the problem. A pointer to + // this object is stored (again as + // a SmartPointer, in order to + // make sure that the function + // object is not deleted as long as + // it is still used by this class). + // + // The only functional part of this + // class is the assemble_rhs + // method that does what its name + // suggests. + template + class PrimalSolver : public Solver + { + public: + PrimalSolver (Triangulation &triangulation, + const FiniteElement &fe, + const Quadrature &quadrature, + const Function &rhs_function, + const Function &boundary_values); + protected: + const SmartPointer > rhs_function; + virtual void assemble_rhs (Vector &rhs) const; + }; - // The constructor of this class - // basically does what it is - // announced to do above... - template - PrimalSolver:: - PrimalSolver (Triangulation &triangulation, - const FiniteElement &fe, - const Quadrature &quadrature, - const Function &rhs_function, - const Function &boundary_values) - : - Base (triangulation), - Solver (triangulation, fe, - quadrature, boundary_values), - rhs_function (&rhs_function) - {} - - - - // ... as does the assemble_rhs - // function. Since this is - // explained in several of the - // previous example programs, we - // leave it at that. - template - void - PrimalSolver:: - assemble_rhs (Vector &rhs) const - { - FEValues fe_values (*this->fe, *this->quadrature, - update_values | update_quadrature_points | - update_JxW_values); + // The constructor of this class + // basically does what it is + // announced to do above... + template + PrimalSolver:: + PrimalSolver (Triangulation &triangulation, + const FiniteElement &fe, + const Quadrature &quadrature, + const Function &rhs_function, + const Function &boundary_values) + : + Base (triangulation), + Solver (triangulation, fe, + quadrature, boundary_values), + rhs_function (&rhs_function) + {} + + + + // ... as does the assemble_rhs + // function. Since this is + // explained in several of the + // previous example programs, we + // leave it at that. + template + void + PrimalSolver:: + assemble_rhs (Vector &rhs) const + { + FEValues fe_values (*this->fe, *this->quadrature, + update_values | update_quadrature_points | + update_JxW_values); - const unsigned int dofs_per_cell = this->fe->dofs_per_cell; - const unsigned int n_q_points = this->quadrature->size(); + const unsigned int dofs_per_cell = this->fe->dofs_per_cell; + const unsigned int n_q_points = this->quadrature->size(); - Vector cell_rhs (dofs_per_cell); - std::vector rhs_values (n_q_points); - std::vector local_dof_indices (dofs_per_cell); + Vector cell_rhs (dofs_per_cell); + std::vector rhs_values (n_q_points); + std::vector local_dof_indices (dofs_per_cell); - typename DoFHandler::active_cell_iterator - cell = this->dof_handler.begin_active(), - endc = this->dof_handler.end(); - for (; cell!=endc; ++cell) - { - cell_rhs = 0; - fe_values.reinit (cell); - rhs_function->value_list (fe_values.get_quadrature_points(), - rhs_values); - - for (unsigned int q_point=0; q_point::active_cell_iterator + cell = this->dof_handler.begin_active(), + endc = this->dof_handler.end(); + for (; cell!=endc; ++cell) + { + cell_rhs = 0; + fe_values.reinit (cell); + rhs_function->value_list (fe_values.get_quadrature_points(), + rhs_values); + + for (unsigned int q_point=0; q_pointget_dof_indices (local_dof_indices); for (unsigned int i=0; iget_dof_indices (local_dof_indices); - for (unsigned int i=0; irefine_grid function + // have been implemented. We will + // now have two classes that + // implement this function for the + // PrimalSolver class, one + // doing global refinement, one a + // form of local refinement. + // + // The first, doing global + // refinement, is rather simple: + // its main function just calls + // triangulation-@>refine_global + // (1);, which does all the work. + // + // Note that since the Base + // base class of the Solver + // class is virtual, we have to + // declare a constructor that + // initializes the immediate base + // class as well as the abstract + // virtual one. + // + // Apart from this technical + // complication, the class is + // probably simple enough to be + // left without further comments. + template + class RefinementGlobal : public PrimalSolver + { + public: + RefinementGlobal (Triangulation &coarse_grid, + const FiniteElement &fe, + const Quadrature &quadrature, + const Function &rhs_function, + const Function &boundary_values); + + virtual void refine_grid (); + }; - // By now, all functions of the - // abstract base class except for - // the refine_grid function - // have been implemented. We will - // now have two classes that - // implement this function for the - // PrimalSolver class, one - // doing global refinement, one a - // form of local refinement. - // - // The first, doing global - // refinement, is rather simple: - // its main function just calls - // triangulation-@>refine_global - // (1);, which does all the work. - // - // Note that since the Base - // base class of the Solver - // class is virtual, we have to - // declare a constructor that - // initializes the immediate base - // class as well as the abstract - // virtual one. - // - // Apart from this technical - // complication, the class is - // probably simple enough to be - // left without further comments. - template - class RefinementGlobal : public PrimalSolver - { - public: - RefinementGlobal (Triangulation &coarse_grid, - const FiniteElement &fe, - const Quadrature &quadrature, - const Function &rhs_function, - const Function &boundary_values); - virtual void refine_grid (); - }; + template + RefinementGlobal:: + RefinementGlobal (Triangulation &coarse_grid, + const FiniteElement &fe, + const Quadrature &quadrature, + const Function &rhs_function, + const Function &boundary_values) + : + Base (coarse_grid), + PrimalSolver (coarse_grid, fe, quadrature, + rhs_function, boundary_values) + {} - template - RefinementGlobal:: - RefinementGlobal (Triangulation &coarse_grid, - const FiniteElement &fe, - const Quadrature &quadrature, - const Function &rhs_function, - const Function &boundary_values) - : - Base (coarse_grid), - PrimalSolver (coarse_grid, fe, quadrature, - rhs_function, boundary_values) - {} + template + void + RefinementGlobal::refine_grid () + { + this->triangulation->refine_global (1); + } - template - void - RefinementGlobal::refine_grid () - { - this->triangulation->refine_global (1); + // @sect4{Local refinement by the Kelly error indicator} + + // The second class implementing + // refinement strategies uses the + // Kelly refinemet indicator used + // in various example programs + // before. Since this indicator is + // already implemented in a class + // of its own inside the deal.II + // library, there is not much t do + // here except cal the function + // computing the indicator, then + // using it to select a number of + // cells for refinement and + // coarsening, and refinement the + // mesh accordingly. + // + // Again, this should now be + // sufficiently standard to allow + // the omission of further + // comments. + template + class RefinementKelly : public PrimalSolver + { + public: + RefinementKelly (Triangulation &coarse_grid, + const FiniteElement &fe, + const Quadrature &quadrature, + const Function &rhs_function, + const Function &boundary_values); + + virtual void refine_grid (); + }; + + + + template + RefinementKelly:: + RefinementKelly (Triangulation &coarse_grid, + const FiniteElement &fe, + const Quadrature &quadrature, + const Function &rhs_function, + const Function &boundary_values) + : + Base (coarse_grid), + PrimalSolver (coarse_grid, fe, quadrature, + rhs_function, boundary_values) + {} + + + + template + void + RefinementKelly::refine_grid () + { + Vector estimated_error_per_cell (this->triangulation->n_active_cells()); + KellyErrorEstimator::estimate (this->dof_handler, + QGauss(3), + typename FunctionMap::type(), + this->solution, + estimated_error_per_cell); + GridRefinement::refine_and_coarsen_fixed_number (*this->triangulation, + estimated_error_per_cell, + 0.3, 0.03); + this->triangulation->execute_coarsening_and_refinement (); + } + } - // @sect4{Local refinement by the Kelly error indicator} - - // The second class implementing - // refinement strategies uses the - // Kelly refinemet indicator used - // in various example programs - // before. Since this indicator is - // already implemented in a class - // of its own inside the deal.II - // library, there is not much t do - // here except cal the function - // computing the indicator, then - // using it to select a number of - // cells for refinement and - // coarsening, and refinement the - // mesh accordingly. + + + // @sect3{Equation data} + + // As this is one more academic + // example, we'd like to compare + // exact and computed solution + // against each other. For this, we + // need to declare function classes + // representing the exact solution + // (for comparison and for the + // Dirichlet boundary values), as + // well as a class that denotes the + // right hand side of the equation + // (this is simply the Laplace + // operator applied to the exact + // solution we'd like to recover). // - // Again, this should now be - // sufficiently standard to allow - // the omission of further - // comments. + // For this example, let us choose as + // exact solution the function + // $u(x,y)=exp(x+sin(10y+5x^2))$. In more + // than two dimensions, simply repeat + // the sine-factor with y + // replaced by z and so on. Given + // this, the following two classes + // are probably straightforward from + // the previous examples. + // + // As in previous examples, the C++ + // language forces us to declare and + // define a constructor to the + // following classes even though they + // are empty. This is due to the fact + // that the base class has no default + // constructor (i.e. one without + // arguments), even though it has a + // constructor which has default + // values for all arguments. template - class RefinementKelly : public PrimalSolver + class Solution : public Function { public: - RefinementKelly (Triangulation &coarse_grid, - const FiniteElement &fe, - const Quadrature &quadrature, - const Function &rhs_function, - const Function &boundary_values); + Solution () : Function () {} - virtual void refine_grid (); + virtual double value (const Point &p, + const unsigned int component) const; }; - - template - RefinementKelly:: - RefinementKelly (Triangulation &coarse_grid, - const FiniteElement &fe, - const Quadrature &quadrature, - const Function &rhs_function, - const Function &boundary_values) - : - Base (coarse_grid), - PrimalSolver (coarse_grid, fe, quadrature, - rhs_function, boundary_values) - {} - - - template - void - RefinementKelly::refine_grid () + double + Solution::value (const Point &p, + const unsigned int /*component*/) const { - Vector estimated_error_per_cell (this->triangulation->n_active_cells()); - KellyErrorEstimator::estimate (this->dof_handler, - QGauss(3), - typename FunctionMap::type(), - this->solution, - estimated_error_per_cell); - GridRefinement::refine_and_coarsen_fixed_number (*this->triangulation, - estimated_error_per_cell, - 0.3, 0.03); - this->triangulation->execute_coarsening_and_refinement (); + double q = p(0); + for (unsigned int i=1; i + class RightHandSide : public Function + { + public: + RightHandSide () : Function () {} + virtual double value (const Point &p, + const unsigned int component) const; + }; - // @sect3{Equation data} - - // As this is one more academic - // example, we'd like to compare - // exact and computed solution - // against each other. For this, we - // need to declare function classes - // representing the exact solution - // (for comparison and for the - // Dirichlet boundary values), as - // well as a class that denotes the - // right hand side of the equation - // (this is simply the Laplace - // operator applied to the exact - // solution we'd like to recover). - // - // For this example, let us choose as - // exact solution the function - // $u(x,y)=exp(x+sin(10y+5x^2))$. In more - // than two dimensions, simply repeat - // the sine-factor with y - // replaced by z and so on. Given - // this, the following two classes - // are probably straightforward from - // the previous examples. - // - // As in previous examples, the C++ - // language forces us to declare and - // define a constructor to the - // following classes even though they - // are empty. This is due to the fact - // that the base class has no default - // constructor (i.e. one without - // arguments), even though it has a - // constructor which has default - // values for all arguments. -template -class Solution : public Function -{ - public: - Solution () : Function () {} - - virtual double value (const Point &p, - const unsigned int component) const; -}; - - -template -double -Solution::value (const Point &p, - const unsigned int /*component*/) const -{ - double q = p(0); - for (unsigned int i=1; i + double + RightHandSide::value (const Point &p, + const unsigned int /*component*/) const + { + double q = p(0); + for (unsigned int i=1; i -class RightHandSide : public Function -{ - public: - RightHandSide () : Function () {} - - virtual double value (const Point &p, - const unsigned int component) const; -}; - - -template -double -RightHandSide::value (const Point &p, - const unsigned int /*component*/) const -{ - double q = p(0); - for (unsigned int i=1; i -void -run_simulation (LaplaceSolver::Base &solver, - const std::list *> &postprocessor_list) -{ - // We will give an indicator of the - // step we are presently computing, - // in order to keep the user - // informed that something is still - // happening, and that the program - // is not in an endless loop. This - // is the head of this status line: - std::cout << "Refinement cycle: "; - - // Then start a loop which only - // terminates once the number of - // degrees of freedom is larger - // than 20,000 (you may of course - // change this limit, if you need - // more -- or less -- accuracy from - // your program). - for (unsigned int step=0; true; ++step) - { - // Then give the alive - // indication for this - // iteration. Note that the - // std::flush is needed to - // have the text actually - // appear on the screen, rather - // than only in some buffer - // that is only flushed the - // next time we issue an - // end-line. - std::cout << step << " " << std::flush; - - // Now solve the problem on the - // present grid, and run the - // evaluators on it. The long - // type name of iterators into - // the list is a little - // annoying, but could be - // shortened by a typedef, if - // so desired. - solver.solve_problem (); - - for (typename std::list *>::const_iterator - i = postprocessor_list.begin(); - i != postprocessor_list.end(); ++i) - { - (*i)->set_refinement_cycle (step); - solver.postprocess (**i); - }; + // What is now missing are only the + // functions that actually select the + // various options, and run the + // simulation on successively finer + // grids to monitor the progress as + // the mesh is refined. + // + // This we do in the following + // function: it takes a solver + // object, and a list of + // postprocessing (evaluation) + // objects, and runs them with + // intermittent mesh refinement: + template + void + run_simulation (LaplaceSolver::Base &solver, + const std::list *> &postprocessor_list) + { + // We will give an indicator of the + // step we are presently computing, + // in order to keep the user + // informed that something is still + // happening, and that the program + // is not in an endless loop. This + // is the head of this status line: + std::cout << "Refinement cycle: "; + + // Then start a loop which only + // terminates once the number of + // degrees of freedom is larger + // than 20,000 (you may of course + // change this limit, if you need + // more -- or less -- accuracy from + // your program). + for (unsigned int step=0; true; ++step) + { + // Then give the alive + // indication for this + // iteration. Note that the + // std::flush is needed to + // have the text actually + // appear on the screen, rather + // than only in some buffer + // that is only flushed the + // next time we issue an + // end-line. + std::cout << step << " " << std::flush; + + // Now solve the problem on the + // present grid, and run the + // evaluators on it. The long + // type name of iterators into + // the list is a little + // annoying, but could be + // shortened by a typedef, if + // so desired. + solver.solve_problem (); + + for (typename std::list *>::const_iterator + i = postprocessor_list.begin(); + i != postprocessor_list.end(); ++i) + { + (*i)->set_refinement_cycle (step); + solver.postprocess (**i); + }; - // Now check whether more - // iterations are required, or - // whether the loop shall be - // ended: - if (solver.n_dofs() < 20000) - solver.refine_grid (); - else - break; - }; + // Now check whether more + // iterations are required, or + // whether the loop shall be + // ended: + if (solver.n_dofs() < 20000) + solver.refine_grid (); + else + break; + }; - // Finally end the line in which we - // displayed status reports: - std::cout << std::endl; -} + // Finally end the line in which we + // displayed status reports: + std::cout << std::endl; + } - // The final function is one which - // takes the name of a solver - // (presently "kelly" and "global" - // are allowed), creates a solver - // object out of it using a coarse - // grid (in this case the ubiquitous - // unit square) and a finite element - // object (here the likewise - // ubiquitous bilinear one), and uses - // that solver to ask for the - // solution of the problem on a - // sequence of successively refined - // grids. - // - // The function also sets up two of - // evaluation functions, one - // evaluating the solution at the - // point (0.5,0.5), the other writing - // out the solution to a file. -template -void solve_problem (const std::string &solver_name) -{ - // First minor task: tell the user - // what is going to happen. Thus - // write a header line, and a line - // with all '-' characters of the - // same length as the first one - // right below. - const std::string header = "Running tests with \"" + solver_name + - "\" refinement criterion:"; - std::cout << header << std::endl - << std::string (header.size(), '-') << std::endl; - - // Then set up triangulation, - // finite element, etc. - Triangulation triangulation; - GridGenerator::hyper_cube (triangulation, -1, 1); - triangulation.refine_global (2); - const FE_Q fe(1); - const QGauss quadrature(4); - const RightHandSide rhs_function; - const Solution boundary_values; - - // Create a solver object of the - // kind indicated by the argument - // to this function. If the name is - // not recognized, throw an - // exception! - LaplaceSolver::Base * solver = 0; - if (solver_name == "global") - solver = new LaplaceSolver::RefinementGlobal (triangulation, fe, - quadrature, - rhs_function, - boundary_values); - else if (solver_name == "kelly") - solver = new LaplaceSolver::RefinementKelly (triangulation, fe, - quadrature, - rhs_function, - boundary_values); - else - AssertThrow (false, ExcNotImplemented()); - - // Next create a table object in - // which the values of the - // numerical solution at the point - // (0.5,0.5) will be stored, and - // create a respective evaluation - // object: - TableHandler results_table; - Evaluation::PointValueEvaluation - postprocessor1 (Point(0.5,0.5), results_table); - - // Also generate an evaluator which - // writes out the solution: - Evaluation::SolutionOutput - postprocessor2 (std::string("solution-")+solver_name, - DataOut::gnuplot); - - // Take these two evaluation - // objects and put them in a - // list... - std::list *> postprocessor_list; - postprocessor_list.push_back (&postprocessor1); - postprocessor_list.push_back (&postprocessor2); - - // ... which we can then pass on to - // the function that actually runs - // the simulation on successively - // refined grids: - run_simulation (*solver, postprocessor_list); - - // When this all is done, write out - // the results of the point - // evaluations, and finally delete - // the solver object: - results_table.write_text (std::cout); - delete solver; - - // And one blank line after all - // results: - std::cout << std::endl; + // The final function is one which + // takes the name of a solver + // (presently "kelly" and "global" + // are allowed), creates a solver + // object out of it using a coarse + // grid (in this case the ubiquitous + // unit square) and a finite element + // object (here the likewise + // ubiquitous bilinear one), and uses + // that solver to ask for the + // solution of the problem on a + // sequence of successively refined + // grids. + // + // The function also sets up two of + // evaluation functions, one + // evaluating the solution at the + // point (0.5,0.5), the other writing + // out the solution to a file. + template + void solve_problem (const std::string &solver_name) + { + // First minor task: tell the user + // what is going to happen. Thus + // write a header line, and a line + // with all '-' characters of the + // same length as the first one + // right below. + const std::string header = "Running tests with \"" + solver_name + + "\" refinement criterion:"; + std::cout << header << std::endl + << std::string (header.size(), '-') << std::endl; + + // Then set up triangulation, + // finite element, etc. + Triangulation triangulation; + GridGenerator::hyper_cube (triangulation, -1, 1); + triangulation.refine_global (2); + const FE_Q fe(1); + const QGauss quadrature(4); + const RightHandSide rhs_function; + const Solution boundary_values; + + // Create a solver object of the + // kind indicated by the argument + // to this function. If the name is + // not recognized, throw an + // exception! + LaplaceSolver::Base * solver = 0; + if (solver_name == "global") + solver = new LaplaceSolver::RefinementGlobal (triangulation, fe, + quadrature, + rhs_function, + boundary_values); + else if (solver_name == "kelly") + solver = new LaplaceSolver::RefinementKelly (triangulation, fe, + quadrature, + rhs_function, + boundary_values); + else + AssertThrow (false, ExcNotImplemented()); + + // Next create a table object in + // which the values of the + // numerical solution at the point + // (0.5,0.5) will be stored, and + // create a respective evaluation + // object: + TableHandler results_table; + Evaluation::PointValueEvaluation + postprocessor1 (Point(0.5,0.5), results_table); + + // Also generate an evaluator which + // writes out the solution: + Evaluation::SolutionOutput + postprocessor2 (std::string("solution-")+solver_name, + DataOut::gnuplot); + + // Take these two evaluation + // objects and put them in a + // list... + std::list *> postprocessor_list; + postprocessor_list.push_back (&postprocessor1); + postprocessor_list.push_back (&postprocessor2); + + // ... which we can then pass on to + // the function that actually runs + // the simulation on successively + // refined grids: + run_simulation (*solver, postprocessor_list); + + // When this all is done, write out + // the results of the point + // evaluations, and finally delete + // the solver object: + results_table.write_text (std::cout); + delete solver; + + // And one blank line after all + // results: + std::cout << std::endl; + } } @@ -2054,14 +2057,14 @@ void solve_problem (const std::string &solver_name) // as much information as possible if // we should get some. The rest is // self-explanatory. -int main () +int main () { try { - deallog.depth_console (0); + dealii::deallog.depth_console (0); - solve_problem<2> ("global"); - solve_problem<2> ("kelly"); + Step13::solve_problem<2> ("global"); + Step13::solve_problem<2> ("kelly"); } catch (std::exception &exc) { @@ -2075,7 +2078,7 @@ int main () << std::endl; return 1; } - catch (...) + catch (...) { std::cerr << std::endl << std::endl << "----------------------------------------------------" diff --git a/deal.II/examples/step-14/step-14.cc b/deal.II/examples/step-14/step-14.cc index 6139079b7d..b5d21d969f 100644 --- a/deal.II/examples/step-14/step-14.cc +++ b/deal.II/examples/step-14/step-14.cc @@ -3,7 +3,7 @@ /* $Id$ */ /* */ -/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 by the deal.II authors */ +/* Copyright (C) 2002, 2003, 2004, 2005, 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 */ @@ -48,3883 +48,3886 @@ // The last step is as in all // previous programs: -using namespace dealii; - - // @sect3{Evaluating the solution} - - // As mentioned in the introduction, - // significant parts of the program - // have simply been taken over from - // the step-13 example program. We - // therefore only comment on those - // things that are new. - // - // First, the framework for - // evaluation of solutions is - // unchanged, i.e. the base class is - // the same, and the class to - // evaluate the solution at a grid - // point is unchanged: -namespace Evaluation +namespace Step14 { - // @sect4{The EvaluationBase class} - template - class EvaluationBase - { - public: - virtual ~EvaluationBase (); - - void set_refinement_cycle (const unsigned int refinement_cycle); - - virtual void operator () (const DoFHandler &dof_handler, - const Vector &solution) const = 0; - protected: - unsigned int refinement_cycle; - }; + using namespace dealii; + // @sect3{Evaluating the solution} - template - EvaluationBase::~EvaluationBase () - {} - + // As mentioned in the introduction, + // significant parts of the program + // have simply been taken over from + // the step-13 example program. We + // therefore only comment on those + // things that are new. + // + // First, the framework for + // evaluation of solutions is + // unchanged, i.e. the base class is + // the same, and the class to + // evaluate the solution at a grid + // point is unchanged: + namespace Evaluation + { + // @sect4{The EvaluationBase class} + template + class EvaluationBase + { + public: + virtual ~EvaluationBase (); - - template - void - EvaluationBase::set_refinement_cycle (const unsigned int step) - { - refinement_cycle = step; - } + void set_refinement_cycle (const unsigned int refinement_cycle); + virtual void operator () (const DoFHandler &dof_handler, + const Vector &solution) const = 0; + protected: + unsigned int refinement_cycle; + }; - // @sect4{The PointValueEvaluation class} - template - class PointValueEvaluation : public EvaluationBase - { - public: - PointValueEvaluation (const Point &evaluation_point); - - virtual void operator () (const DoFHandler &dof_handler, - const Vector &solution) const; - - DeclException1 (ExcEvaluationPointNotFound, - Point, - << "The evaluation point " << arg1 - << " was not found among the vertices of the present grid."); - private: - const Point evaluation_point; - }; + template + EvaluationBase::~EvaluationBase () + {} - template - PointValueEvaluation:: - PointValueEvaluation (const Point &evaluation_point) - : - evaluation_point (evaluation_point) - {} - - template - void - PointValueEvaluation:: - operator () (const DoFHandler &dof_handler, - const Vector &solution) const - { - double point_value = 1e20; - - typename DoFHandler::active_cell_iterator - cell = dof_handler.begin_active(), - endc = dof_handler.end(); - bool evaluation_point_found = false; - for (; (cell!=endc) && !evaluation_point_found; ++cell) - for (unsigned int vertex=0; - vertex::vertices_per_cell; - ++vertex) - if (cell->vertex(vertex).distance (evaluation_point) - < - cell->diameter() * 1e-8) - { - point_value = solution(cell->vertex_dof_index(vertex,0)); + template + void + EvaluationBase::set_refinement_cycle (const unsigned int step) + { + refinement_cycle = step; + } - evaluation_point_found = true; - break; - } - AssertThrow (evaluation_point_found, - ExcEvaluationPointNotFound(evaluation_point)); + // @sect4{The PointValueEvaluation class} + template + class PointValueEvaluation : public EvaluationBase + { + public: + PointValueEvaluation (const Point &evaluation_point); + + virtual void operator () (const DoFHandler &dof_handler, + const Vector &solution) const; + + DeclException1 (ExcEvaluationPointNotFound, + Point, + << "The evaluation point " << arg1 + << " was not found among the vertices of the present grid."); + private: + const Point evaluation_point; + }; - std::cout << " Point value=" << point_value - << std::endl; - } + template + PointValueEvaluation:: + PointValueEvaluation (const Point &evaluation_point) + : + evaluation_point (evaluation_point) + {} - // @sect4{The PointXDerivativeEvaluation class} - - // Besides the class implementing - // the evaluation of the solution - // at one point, we here provide - // one which evaluates the gradient - // at a grid point. Since in - // general the gradient of a finite - // element function is not - // continuous at a vertex, we have - // to be a little bit more careful - // here. What we do is to loop over - // all cells, even if we have found - // the point already on one cell, - // and use the mean value of the - // gradient at the vertex taken - // from all adjacent cells. - // - // Given the interface of the - // PointValueEvaluation class, - // the declaration of this class - // provides little surprise, and - // neither does the constructor: - template - class PointXDerivativeEvaluation : public EvaluationBase - { - public: - PointXDerivativeEvaluation (const Point &evaluation_point); - - virtual void operator () (const DoFHandler &dof_handler, - const Vector &solution) const; - - DeclException1 (ExcEvaluationPointNotFound, - Point, - << "The evaluation point " << arg1 - << " was not found among the vertices of the present grid."); - private: - const Point evaluation_point; - }; - template - PointXDerivativeEvaluation:: - PointXDerivativeEvaluation (const Point &evaluation_point) - : - evaluation_point (evaluation_point) - {} - + template + void + PointValueEvaluation:: + operator () (const DoFHandler &dof_handler, + const Vector &solution) const + { + double point_value = 1e20; - // The more interesting things - // happen inside the function doing - // the actual evaluation: - template - void - PointXDerivativeEvaluation:: - operator () (const DoFHandler &dof_handler, - const Vector &solution) const - { - // This time initialize the - // return value with something - // useful, since we will have to - // add up a number of - // contributions and take the - // mean value afterwards... - double point_derivative = 0; - - // ...then have some objects of - // which the meaning wil become - // clear below... - QTrapez vertex_quadrature; - FEValues fe_values (dof_handler.get_fe(), - vertex_quadrature, - update_gradients | update_quadrature_points); - std::vector > - solution_gradients (vertex_quadrature.size()); - - // ...and next loop over all cells - // and their vertices, and count - // how often the vertex has been - // found: - typename DoFHandler::active_cell_iterator - cell = dof_handler.begin_active(), - endc = dof_handler.end(); - unsigned int evaluation_point_hits = 0; - for (; cell!=endc; ++cell) - for (unsigned int vertex=0; - vertex::vertices_per_cell; - ++vertex) - if (cell->vertex(vertex) == evaluation_point) - { - // Things are now no more - // as simple, since we - // can't get the gradient - // of the finite element - // field as before, where - // we simply had to pick - // one degree of freedom - // at a vertex. - // - // Rather, we have to - // evaluate the finite - // element field on this - // cell, and at a certain - // point. As you know, - // evaluating finite - // element fields at - // certain points is done - // through the - // FEValues class, so - // we use that. The - // question is: the - // FEValues object - // needs to be a given a - // quadrature formula and - // can then compute the - // values of finite - // element quantities at - // the quadrature - // points. Here, we don't - // want to do quadrature, - // we simply want to - // specify some points! - // - // Nevertheless, the same - // way is chosen: use a - // special quadrature - // rule with points at - // the vertices, since - // these are what we are - // interested in. The - // appropriate rule is - // the trapezoidal rule, - // so that is the reason - // why we used that one - // above. - // - // Thus: initialize the - // FEValues object on - // this cell, - fe_values.reinit (cell); - // and extract the - // gradients of the - // solution vector at the - // vertices: - fe_values.get_function_grads (solution, - solution_gradients); - - // Now we have the - // gradients at all - // vertices, so pick out - // that one which belongs - // to the evaluation - // point (note that the - // order of vertices is - // not necessarily the - // same as that of the - // quadrature points): - unsigned int q_point = 0; - for (; q_point::active_cell_iterator + cell = dof_handler.begin_active(), + endc = dof_handler.end(); + bool evaluation_point_found = false; + for (; (cell!=endc) && !evaluation_point_found; ++cell) + for (unsigned int vertex=0; + vertex::vertices_per_cell; + ++vertex) + if (cell->vertex(vertex).distance (evaluation_point) + < + cell->diameter() * 1e-8) + { + point_value = solution(cell->vertex_dof_index(vertex,0)); - // Now we have looped over all - // cells and vertices, so check - // whether the point was found: - AssertThrow (evaluation_point_hits > 0, - ExcEvaluationPointNotFound(evaluation_point)); - - // We have simply summed up the - // contributions of all adjacent - // cells, so we still have to - // compute the mean value. Once - // this is done, report the status: - point_derivative /= evaluation_point_hits; - std::cout << " Point x-derivative=" << point_derivative - << std::endl; - } + evaluation_point_found = true; + break; + } + AssertThrow (evaluation_point_found, + ExcEvaluationPointNotFound(evaluation_point)); - - // @sect4{The GridOutput class} - - // Since this program has a more - // difficult structure (it computed - // a dual solution in addition to a - // primal one), writing out the - // solution is no more done by an - // evaluation object since we want - // to write both solutions at once - // into one file, and that requires - // some more information than - // available to the evaluation - // classes. - // - // However, we also want to look at - // the grids generated. This again - // can be done with one such - // class. Its structure is analog - // to the SolutionOutput class - // of the previous example program, - // so we do not discuss it here in - // more detail. Furthermore, - // everything that is used here has - // already been used in previous - // example programs. - template - class GridOutput : public EvaluationBase - { - public: - GridOutput (const std::string &output_name_base); - - virtual void operator () (const DoFHandler &dof_handler, - const Vector &solution) const; - private: - const std::string output_name_base; - }; + std::cout << " Point value=" << point_value + << std::endl; + } - template - GridOutput:: - GridOutput (const std::string &output_name_base) - : - output_name_base (output_name_base) - {} - + // @sect4{The PointXDerivativeEvaluation class} + + // Besides the class implementing + // the evaluation of the solution + // at one point, we here provide + // one which evaluates the gradient + // at a grid point. Since in + // general the gradient of a finite + // element function is not + // continuous at a vertex, we have + // to be a little bit more careful + // here. What we do is to loop over + // all cells, even if we have found + // the point already on one cell, + // and use the mean value of the + // gradient at the vertex taken + // from all adjacent cells. + // + // Given the interface of the + // PointValueEvaluation class, + // the declaration of this class + // provides little surprise, and + // neither does the constructor: + template + class PointXDerivativeEvaluation : public EvaluationBase + { + public: + PointXDerivativeEvaluation (const Point &evaluation_point); + + virtual void operator () (const DoFHandler &dof_handler, + const Vector &solution) const; + + DeclException1 (ExcEvaluationPointNotFound, + Point, + << "The evaluation point " << arg1 + << " was not found among the vertices of the present grid."); + private: + const Point evaluation_point; + }; - template - void - GridOutput::operator () (const DoFHandler &dof_handler, - const Vector &/*solution*/) const - { - std::ostringstream filename; - filename << output_name_base << "-" - << this->refinement_cycle - << ".eps" - << std::ends; - - std::ofstream out (filename.str().c_str()); - GridOut().write_eps (dof_handler.get_tria(), out); - } -} - - // @sect3{The Laplace solver classes} + template + PointXDerivativeEvaluation:: + PointXDerivativeEvaluation (const Point &evaluation_point) + : + evaluation_point (evaluation_point) + {} - // Next are the actual solver - // classes. Again, we discuss only - // the differences to the previous - // program. -namespace LaplaceSolver -{ - // Before everything else, - // forward-declare one class that - // we will have later, since we - // will want to make it a friend of - // some of the classes that follow, - // which requires the class to be - // known: - template class WeightedResidual; - - - // @sect4{The Laplace solver base class} - - // This class is almost unchanged, - // with the exception that it - // declares two more functions: - // output_solution will be used - // to generate output files from - // the actual solutions computed by - // derived classes, and the - // set_refinement_cycle - // function by which the testing - // framework sets the number of the - // refinement cycle to a local - // variable in this class; this - // number is later used to generate - // filenames for the solution - // output. - template - class Base - { - public: - Base (Triangulation &coarse_grid); - virtual ~Base (); - virtual void solve_problem () = 0; - virtual void postprocess (const Evaluation::EvaluationBase &postprocessor) const = 0; - virtual void refine_grid () = 0; - virtual unsigned int n_dofs () const = 0; + // The more interesting things + // happen inside the function doing + // the actual evaluation: + template + void + PointXDerivativeEvaluation:: + operator () (const DoFHandler &dof_handler, + const Vector &solution) const + { + // This time initialize the + // return value with something + // useful, since we will have to + // add up a number of + // contributions and take the + // mean value afterwards... + double point_derivative = 0; + + // ...then have some objects of + // which the meaning wil become + // clear below... + QTrapez vertex_quadrature; + FEValues fe_values (dof_handler.get_fe(), + vertex_quadrature, + update_gradients | update_quadrature_points); + std::vector > + solution_gradients (vertex_quadrature.size()); + + // ...and next loop over all cells + // and their vertices, and count + // how often the vertex has been + // found: + typename DoFHandler::active_cell_iterator + cell = dof_handler.begin_active(), + endc = dof_handler.end(); + unsigned int evaluation_point_hits = 0; + for (; cell!=endc; ++cell) + for (unsigned int vertex=0; + vertex::vertices_per_cell; + ++vertex) + if (cell->vertex(vertex) == evaluation_point) + { + // Things are now no more + // as simple, since we + // can't get the gradient + // of the finite element + // field as before, where + // we simply had to pick + // one degree of freedom + // at a vertex. + // + // Rather, we have to + // evaluate the finite + // element field on this + // cell, and at a certain + // point. As you know, + // evaluating finite + // element fields at + // certain points is done + // through the + // FEValues class, so + // we use that. The + // question is: the + // FEValues object + // needs to be a given a + // quadrature formula and + // can then compute the + // values of finite + // element quantities at + // the quadrature + // points. Here, we don't + // want to do quadrature, + // we simply want to + // specify some points! + // + // Nevertheless, the same + // way is chosen: use a + // special quadrature + // rule with points at + // the vertices, since + // these are what we are + // interested in. The + // appropriate rule is + // the trapezoidal rule, + // so that is the reason + // why we used that one + // above. + // + // Thus: initialize the + // FEValues object on + // this cell, + fe_values.reinit (cell); + // and extract the + // gradients of the + // solution vector at the + // vertices: + fe_values.get_function_grads (solution, + solution_gradients); + + // Now we have the + // gradients at all + // vertices, so pick out + // that one which belongs + // to the evaluation + // point (note that the + // order of vertices is + // not necessarily the + // same as that of the + // quadrature points): + unsigned int q_point = 0; + for (; q_point 0, + ExcEvaluationPointNotFound(evaluation_point)); + + // We have simply summed up the + // contributions of all adjacent + // cells, so we still have to + // compute the mean value. Once + // this is done, report the status: + point_derivative /= evaluation_point_hits; + std::cout << " Point x-derivative=" << point_derivative + << std::endl; + } - virtual void output_solution () const = 0; - - protected: - const SmartPointer > triangulation; - unsigned int refinement_cycle; - }; + // @sect4{The GridOutput class} - template - Base::Base (Triangulation &coarse_grid) - : - triangulation (&coarse_grid) - {} + // Since this program has a more + // difficult structure (it computed + // a dual solution in addition to a + // primal one), writing out the + // solution is no more done by an + // evaluation object since we want + // to write both solutions at once + // into one file, and that requires + // some more information than + // available to the evaluation + // classes. + // + // However, we also want to look at + // the grids generated. This again + // can be done with one such + // class. Its structure is analog + // to the SolutionOutput class + // of the previous example program, + // so we do not discuss it here in + // more detail. Furthermore, + // everything that is used here has + // already been used in previous + // example programs. + template + class GridOutput : public EvaluationBase + { + public: + GridOutput (const std::string &output_name_base); + virtual void operator () (const DoFHandler &dof_handler, + const Vector &solution) const; + private: + const std::string output_name_base; + }; - template - Base::~Base () - {} + template + GridOutput:: + GridOutput (const std::string &output_name_base) + : + output_name_base (output_name_base) + {} - template - void - Base::set_refinement_cycle (const unsigned int cycle) - { - refinement_cycle = cycle; + template + void + GridOutput::operator () (const DoFHandler &dof_handler, + const Vector &/*solution*/) const + { + std::ostringstream filename; + filename << output_name_base << "-" + << this->refinement_cycle + << ".eps" + << std::ends; + + std::ofstream out (filename.str().c_str()); + GridOut().write_eps (dof_handler.get_tria(), out); + } } - - // @sect4{The Laplace Solver class} - // Likewise, the Solver class - // is entirely unchanged and will - // thus not be discussed. - template - class Solver : public virtual Base - { - public: - Solver (Triangulation &triangulation, - const FiniteElement &fe, - const Quadrature &quadrature, - const Quadrature &face_quadrature, - const Function &boundary_values); - virtual - ~Solver (); - - virtual - void - solve_problem (); - - virtual - void - postprocess (const Evaluation::EvaluationBase &postprocessor) const; - - virtual - unsigned int - n_dofs () const; - - protected: - const SmartPointer > fe; - const SmartPointer > quadrature; - const SmartPointer > face_quadrature; - DoFHandler dof_handler; - Vector solution; - const SmartPointer > boundary_values; - - virtual void assemble_rhs (Vector &rhs) const = 0; - - private: - struct LinearSystem - { - LinearSystem (const DoFHandler &dof_handler); - - void solve (Vector &solution) const; - - ConstraintMatrix hanging_node_constraints; - SparsityPattern sparsity_pattern; - SparseMatrix matrix; - Vector rhs; - }; + // @sect3{The Laplace solver classes} + + // Next are the actual solver + // classes. Again, we discuss only + // the differences to the previous + // program. + namespace LaplaceSolver + { + // Before everything else, + // forward-declare one class that + // we will have later, since we + // will want to make it a friend of + // some of the classes that follow, + // which requires the class to be + // known: + template class WeightedResidual; + + + // @sect4{The Laplace solver base class} + + // This class is almost unchanged, + // with the exception that it + // declares two more functions: + // output_solution will be used + // to generate output files from + // the actual solutions computed by + // derived classes, and the + // set_refinement_cycle + // function by which the testing + // framework sets the number of the + // refinement cycle to a local + // variable in this class; this + // number is later used to generate + // filenames for the solution + // output. + template + class Base + { + public: + Base (Triangulation &coarse_grid); + virtual ~Base (); - void - assemble_linear_system (LinearSystem &linear_system); + virtual void solve_problem () = 0; + virtual void postprocess (const Evaluation::EvaluationBase &postprocessor) const = 0; + virtual void refine_grid () = 0; + virtual unsigned int n_dofs () const = 0; - void - assemble_matrix (LinearSystem &linear_system, - const typename DoFHandler::active_cell_iterator &begin_cell, - const typename DoFHandler::active_cell_iterator &end_cell, - Threads::ThreadMutex &mutex) const; - }; + virtual void set_refinement_cycle (const unsigned int cycle); + virtual void output_solution () const = 0; + protected: + const SmartPointer > triangulation; - template - Solver::Solver (Triangulation &triangulation, - const FiniteElement &fe, - const Quadrature &quadrature, - const Quadrature &face_quadrature, - const Function &boundary_values) - : - Base (triangulation), - fe (&fe), - quadrature (&quadrature), - face_quadrature (&face_quadrature), - dof_handler (triangulation), - boundary_values (&boundary_values) - {} + unsigned int refinement_cycle; + }; - template - Solver::~Solver () - { - dof_handler.clear (); - } + template + Base::Base (Triangulation &coarse_grid) + : + triangulation (&coarse_grid) + {} - template - void - Solver::solve_problem () - { - dof_handler.distribute_dofs (*fe); - solution.reinit (dof_handler.n_dofs()); + template + Base::~Base () + {} - LinearSystem linear_system (dof_handler); - assemble_linear_system (linear_system); - linear_system.solve (solution); - } - template - void - Solver:: - postprocess (const Evaluation::EvaluationBase &postprocessor) const - { - postprocessor (dof_handler, solution); - } + template + void + Base::set_refinement_cycle (const unsigned int cycle) + { + refinement_cycle = cycle; + } - template - unsigned int - Solver::n_dofs () const - { - return dof_handler.n_dofs(); - } - + // @sect4{The Laplace Solver class} - template - void - Solver::assemble_linear_system (LinearSystem &linear_system) - { - typedef - typename DoFHandler::active_cell_iterator - active_cell_iterator; - - const unsigned int n_threads = multithread_info.n_default_threads; - std::vector > - thread_ranges - = Threads::split_range (dof_handler.begin_active (), - dof_handler.end (), - n_threads); - - Threads::ThreadMutex mutex; - Threads::ThreadGroup<> threads; - for (unsigned int thread=0; thread::assemble_matrix, - *this, - linear_system, - thread_ranges[thread].first, - thread_ranges[thread].second, - mutex); - - assemble_rhs (linear_system.rhs); - linear_system.hanging_node_constraints.condense (linear_system.rhs); - - std::map boundary_value_map; - VectorTools::interpolate_boundary_values (dof_handler, - 0, - *boundary_values, - boundary_value_map); - - threads.join_all (); - linear_system.hanging_node_constraints.condense (linear_system.matrix); - - MatrixTools::apply_boundary_values (boundary_value_map, - linear_system.matrix, - solution, - linear_system.rhs); - } + // Likewise, the Solver class + // is entirely unchanged and will + // thus not be discussed. + template + class Solver : public virtual Base + { + public: + Solver (Triangulation &triangulation, + const FiniteElement &fe, + const Quadrature &quadrature, + const Quadrature &face_quadrature, + const Function &boundary_values); + virtual + ~Solver (); + + virtual + void + solve_problem (); + + virtual + void + postprocess (const Evaluation::EvaluationBase &postprocessor) const; + + virtual + unsigned int + n_dofs () const; + + protected: + const SmartPointer > fe; + const SmartPointer > quadrature; + const SmartPointer > face_quadrature; + DoFHandler dof_handler; + Vector solution; + const SmartPointer > boundary_values; + + virtual void assemble_rhs (Vector &rhs) const = 0; + + private: + struct LinearSystem + { + LinearSystem (const DoFHandler &dof_handler); + void solve (Vector &solution) const; - template - void - Solver::assemble_matrix (LinearSystem &linear_system, - const typename DoFHandler::active_cell_iterator &begin_cell, - const typename DoFHandler::active_cell_iterator &end_cell, - Threads::ThreadMutex &mutex) const - { - FEValues fe_values (*fe, *quadrature, - update_gradients | update_JxW_values); + ConstraintMatrix hanging_node_constraints; + SparsityPattern sparsity_pattern; + SparseMatrix matrix; + Vector rhs; + }; + + void + assemble_linear_system (LinearSystem &linear_system); - const unsigned int dofs_per_cell = fe->dofs_per_cell; - const unsigned int n_q_points = quadrature->size(); + void + assemble_matrix (LinearSystem &linear_system, + const typename DoFHandler::active_cell_iterator &begin_cell, + const typename DoFHandler::active_cell_iterator &end_cell, + Threads::ThreadMutex &mutex) const; + }; - FullMatrix cell_matrix (dofs_per_cell, dofs_per_cell); - std::vector local_dof_indices (dofs_per_cell); - for (typename DoFHandler::active_cell_iterator cell=begin_cell; - cell!=end_cell; ++cell) - { - cell_matrix = 0; + template + Solver::Solver (Triangulation &triangulation, + const FiniteElement &fe, + const Quadrature &quadrature, + const Quadrature &face_quadrature, + const Function &boundary_values) + : + Base (triangulation), + fe (&fe), + quadrature (&quadrature), + face_quadrature (&face_quadrature), + dof_handler (triangulation), + boundary_values (&boundary_values) + {} + + + template + Solver::~Solver () + { + dof_handler.clear (); + } + + + template + void + Solver::solve_problem () + { + dof_handler.distribute_dofs (*fe); + solution.reinit (dof_handler.n_dofs()); + + LinearSystem linear_system (dof_handler); + assemble_linear_system (linear_system); + linear_system.solve (solution); + } + + + template + void + Solver:: + postprocess (const Evaluation::EvaluationBase &postprocessor) const + { + postprocessor (dof_handler, solution); + } + + + template + unsigned int + Solver::n_dofs () const + { + return dof_handler.n_dofs(); + } - fe_values.reinit (cell); - for (unsigned int q_point=0; q_point + void + Solver::assemble_linear_system (LinearSystem &linear_system) + { + typedef + typename DoFHandler::active_cell_iterator + active_cell_iterator; + + const unsigned int n_threads = multithread_info.n_default_threads; + std::vector > + thread_ranges + = Threads::split_range (dof_handler.begin_active (), + dof_handler.end (), + n_threads); + + Threads::ThreadMutex mutex; + Threads::ThreadGroup<> threads; + for (unsigned int thread=0; thread::assemble_matrix, + *this, + linear_system, + thread_ranges[thread].first, + thread_ranges[thread].second, + mutex); + + assemble_rhs (linear_system.rhs); + linear_system.hanging_node_constraints.condense (linear_system.rhs); + + std::map boundary_value_map; + VectorTools::interpolate_boundary_values (dof_handler, + 0, + *boundary_values, + boundary_value_map); + + threads.join_all (); + linear_system.hanging_node_constraints.condense (linear_system.matrix); + + MatrixTools::apply_boundary_values (boundary_value_map, + linear_system.matrix, + solution, + linear_system.rhs); + } + + + template + void + Solver::assemble_matrix (LinearSystem &linear_system, + const typename DoFHandler::active_cell_iterator &begin_cell, + const typename DoFHandler::active_cell_iterator &end_cell, + Threads::ThreadMutex &mutex) const + { + FEValues fe_values (*fe, *quadrature, + update_gradients | update_JxW_values); + + const unsigned int dofs_per_cell = fe->dofs_per_cell; + const unsigned int n_q_points = quadrature->size(); + + FullMatrix cell_matrix (dofs_per_cell, dofs_per_cell); + + std::vector local_dof_indices (dofs_per_cell); + + for (typename DoFHandler::active_cell_iterator cell=begin_cell; + cell!=end_cell; ++cell) + { + cell_matrix = 0; + + fe_values.reinit (cell); + + for (unsigned int q_point=0; q_pointget_dof_indices (local_dof_indices); + Threads::ThreadMutex::ScopedLock lock (mutex); for (unsigned int i=0; iget_dof_indices (local_dof_indices); - Threads::ThreadMutex::ScopedLock lock (mutex); - for (unsigned int i=0; i - Solver::LinearSystem:: - LinearSystem (const DoFHandler &dof_handler) - { - hanging_node_constraints.clear (); - - void (*mhnc_p) (const DoFHandler &, - ConstraintMatrix &) - = &DoFTools::make_hanging_node_constraints; - - Threads::Thread<> - mhnc_thread = Threads::new_thread (mhnc_p, - dof_handler, - hanging_node_constraints); - - 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); - - mhnc_thread.join (); - hanging_node_constraints.close (); - hanging_node_constraints.condense (sparsity_pattern); - - sparsity_pattern.compress(); - matrix.reinit (sparsity_pattern); - rhs.reinit (dof_handler.n_dofs()); - } + template + Solver::LinearSystem:: + LinearSystem (const DoFHandler &dof_handler) + { + hanging_node_constraints.clear (); + void (*mhnc_p) (const DoFHandler &, + ConstraintMatrix &) + = &DoFTools::make_hanging_node_constraints; + Threads::Thread<> + mhnc_thread = Threads::new_thread (mhnc_p, + dof_handler, + hanging_node_constraints); - template - void - Solver::LinearSystem::solve (Vector &solution) const - { - SolverControl solver_control (5000, 1e-12); - SolverCG<> cg (solver_control); + 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); - PreconditionSSOR<> preconditioner; - preconditioner.initialize(matrix, 1.2); + mhnc_thread.join (); + hanging_node_constraints.close (); + hanging_node_constraints.condense (sparsity_pattern); - cg.solve (matrix, solution, rhs, preconditioner); + sparsity_pattern.compress(); + matrix.reinit (sparsity_pattern); + rhs.reinit (dof_handler.n_dofs()); + } - hanging_node_constraints.distribute (solution); - } + template + void + Solver::LinearSystem::solve (Vector &solution) const + { + SolverControl solver_control (5000, 1e-12); + SolverCG<> cg (solver_control); + PreconditionSSOR<> preconditioner; + preconditioner.initialize(matrix, 1.2); - // @sect4{The PrimalSolver class} - - // The PrimalSolver class is - // also mostly unchanged except for - // overloading the functions - // solve_problem, n_dofs, - // and postprocess of the base - // class, and implementing the - // output_solution - // function. These overloaded - // functions do nothing particular - // besides calling the functions of - // the base class -- that seems - // superfluous, but works around a - // bug in a popular compiler which - // requires us to write such - // functions for the following - // scenario: Besides the - // PrimalSolver class, we will - // have a DualSolver, both - // derived from Solver. We will - // then have a final classes which - // derived from these two, which - // will then have two instances of - // the Solver class as its base - // classes. If we want, for - // example, the number of degrees - // of freedom of the primal solver, - // we would have to indicate this - // like so: - // PrimalSolver::n_dofs(). - // However, the compiler does not - // accept this since the n_dofs - // function is actually from a base - // class of the PrimalSolver - // class, so we have to inject the - // name from the base to the - // derived class using these - // additional functions. - // - // Regarding the implementation of - // the output_solution - // function, we keep the - // GlobalRefinement and - // RefinementKelly classes in - // this program, and they can then - // rely on the default - // implementation of this function - // which simply outputs the primal - // solution. The class implementing - // dual weighted error estimators - // will overload this function - // itself, to also output the dual - // solution. - // - // Except for this, the class is - // unchanged with respect to the - // previous example. - template - class PrimalSolver : public Solver - { - public: - PrimalSolver (Triangulation &triangulation, - const FiniteElement &fe, - const Quadrature &quadrature, - const Quadrature &face_quadrature, - const Function &rhs_function, - const Function &boundary_values); - - virtual - void solve_problem (); - - virtual - unsigned int n_dofs () const; - - virtual - void postprocess (const Evaluation::EvaluationBase &postprocessor) const; - - virtual - void output_solution () const; - - protected: - const SmartPointer > rhs_function; - virtual void assemble_rhs (Vector &rhs) const; - - // Now, in order to work around - // some problems in one of the - // compilers this library can - // be compiled with, we will - // have to declare a - // class that is actually - // derived from the present - // one, as a friend (strange as - // that seems). The full - // rationale will be explained - // below. - friend class WeightedResidual; - }; + cg.solve (matrix, solution, rhs, preconditioner); + hanging_node_constraints.distribute (solution); + } - template - PrimalSolver:: - PrimalSolver (Triangulation &triangulation, - const FiniteElement &fe, - const Quadrature &quadrature, - const Quadrature &face_quadrature, - const Function &rhs_function, - const Function &boundary_values) - : - Base (triangulation), - Solver (triangulation, fe, - quadrature, face_quadrature, - boundary_values), - rhs_function (&rhs_function) - {} - template - void - PrimalSolver::solve_problem () - { - Solver::solve_problem (); - } + // @sect4{The PrimalSolver class} + + // The PrimalSolver class is + // also mostly unchanged except for + // overloading the functions + // solve_problem, n_dofs, + // and postprocess of the base + // class, and implementing the + // output_solution + // function. These overloaded + // functions do nothing particular + // besides calling the functions of + // the base class -- that seems + // superfluous, but works around a + // bug in a popular compiler which + // requires us to write such + // functions for the following + // scenario: Besides the + // PrimalSolver class, we will + // have a DualSolver, both + // derived from Solver. We will + // then have a final classes which + // derived from these two, which + // will then have two instances of + // the Solver class as its base + // classes. If we want, for + // example, the number of degrees + // of freedom of the primal solver, + // we would have to indicate this + // like so: + // PrimalSolver::n_dofs(). + // However, the compiler does not + // accept this since the n_dofs + // function is actually from a base + // class of the PrimalSolver + // class, so we have to inject the + // name from the base to the + // derived class using these + // additional functions. + // + // Regarding the implementation of + // the output_solution + // function, we keep the + // GlobalRefinement and + // RefinementKelly classes in + // this program, and they can then + // rely on the default + // implementation of this function + // which simply outputs the primal + // solution. The class implementing + // dual weighted error estimators + // will overload this function + // itself, to also output the dual + // solution. + // + // Except for this, the class is + // unchanged with respect to the + // previous example. + template + class PrimalSolver : public Solver + { + public: + PrimalSolver (Triangulation &triangulation, + const FiniteElement &fe, + const Quadrature &quadrature, + const Quadrature &face_quadrature, + const Function &rhs_function, + const Function &boundary_values); + + virtual + void solve_problem (); + + virtual + unsigned int n_dofs () const; + + virtual + void postprocess (const Evaluation::EvaluationBase &postprocessor) const; + + virtual + void output_solution () const; + + protected: + const SmartPointer > rhs_function; + virtual void assemble_rhs (Vector &rhs) const; + + // Now, in order to work around + // some problems in one of the + // compilers this library can + // be compiled with, we will + // have to declare a + // class that is actually + // derived from the present + // one, as a friend (strange as + // that seems). The full + // rationale will be explained + // below. + friend class WeightedResidual; + }; - template - unsigned int - PrimalSolver::n_dofs() const - { - return Solver::n_dofs(); - } + template + PrimalSolver:: + PrimalSolver (Triangulation &triangulation, + const FiniteElement &fe, + const Quadrature &quadrature, + const Quadrature &face_quadrature, + const Function &rhs_function, + const Function &boundary_values) + : + Base (triangulation), + Solver (triangulation, fe, + quadrature, face_quadrature, + boundary_values), + rhs_function (&rhs_function) + {} + + + template + void + PrimalSolver::solve_problem () + { + Solver::solve_problem (); + } - template - void - PrimalSolver:: - postprocess (const Evaluation::EvaluationBase &postprocessor) const - { - Solver::postprocess(postprocessor); - } + template + unsigned int + PrimalSolver::n_dofs() const + { + return Solver::n_dofs(); + } - template - void - PrimalSolver::output_solution () const - { - DataOut data_out; - data_out.attach_dof_handler (this->dof_handler); - data_out.add_data_vector (this->solution, "solution"); - data_out.build_patches (); - - std::ostringstream filename; - filename << "solution-" - << this->refinement_cycle - << ".gnuplot" - << std::ends; - - std::ofstream out (filename.str().c_str()); - data_out.write (out, DataOut::gnuplot); - } - + template + void + PrimalSolver:: + postprocess (const Evaluation::EvaluationBase &postprocessor) const + { + Solver::postprocess(postprocessor); + } - template - void - PrimalSolver:: - assemble_rhs (Vector &rhs) const - { - FEValues fe_values (*this->fe, *this->quadrature, - update_values | update_quadrature_points | - update_JxW_values); - const unsigned int dofs_per_cell = this->fe->dofs_per_cell; - const unsigned int n_q_points = this->quadrature->size(); + template + void + PrimalSolver::output_solution () const + { + DataOut data_out; + data_out.attach_dof_handler (this->dof_handler); + data_out.add_data_vector (this->solution, "solution"); + data_out.build_patches (); + + std::ostringstream filename; + filename << "solution-" + << this->refinement_cycle + << ".gnuplot" + << std::ends; + + std::ofstream out (filename.str().c_str()); + data_out.write (out, DataOut::gnuplot); + } - Vector cell_rhs (dofs_per_cell); - std::vector rhs_values (n_q_points); - std::vector local_dof_indices (dofs_per_cell); - typename DoFHandler::active_cell_iterator - cell = this->dof_handler.begin_active(), - endc = this->dof_handler.end(); - for (; cell!=endc; ++cell) - { - cell_rhs = 0; - fe_values.reinit (cell); + template + void + PrimalSolver:: + assemble_rhs (Vector &rhs) const + { + FEValues fe_values (*this->fe, *this->quadrature, + update_values | update_quadrature_points | + update_JxW_values); + + const unsigned int dofs_per_cell = this->fe->dofs_per_cell; + const unsigned int n_q_points = this->quadrature->size(); + + Vector cell_rhs (dofs_per_cell); + std::vector rhs_values (n_q_points); + std::vector local_dof_indices (dofs_per_cell); + + typename DoFHandler::active_cell_iterator + cell = this->dof_handler.begin_active(), + endc = this->dof_handler.end(); + for (; cell!=endc; ++cell) + { + cell_rhs = 0; + + fe_values.reinit (cell); + + rhs_function->value_list (fe_values.get_quadrature_points(), + rhs_values); + + for (unsigned int q_point=0; q_pointvalue_list (fe_values.get_quadrature_points(), - rhs_values); - - for (unsigned int q_point=0; q_pointget_dof_indices (local_dof_indices); for (unsigned int i=0; iget_dof_indices (local_dof_indices); - for (unsigned int i=0; i + class RefinementGlobal : public PrimalSolver + { + public: + RefinementGlobal (Triangulation &coarse_grid, + const FiniteElement &fe, + const Quadrature &quadrature, + const Quadrature &face_quadrature, + const Function &rhs_function, + const Function &boundary_values); + + virtual void refine_grid (); + }; - // For the following two classes, - // the same applies as for most of - // the above: the class is taken - // from the previous example as-is: - template - class RefinementGlobal : public PrimalSolver - { - public: - RefinementGlobal (Triangulation &coarse_grid, - const FiniteElement &fe, - const Quadrature &quadrature, - const Quadrature &face_quadrature, - const Function &rhs_function, - const Function &boundary_values); - - virtual void refine_grid (); - }; + template + RefinementGlobal:: + RefinementGlobal (Triangulation &coarse_grid, + const FiniteElement &fe, + const Quadrature &quadrature, + const Quadrature &face_quadrature, + const Function &rhs_function, + const Function &boundary_values) + : + Base (coarse_grid), + PrimalSolver (coarse_grid, fe, quadrature, + face_quadrature, rhs_function, + boundary_values) + {} - template - RefinementGlobal:: - RefinementGlobal (Triangulation &coarse_grid, - const FiniteElement &fe, - const Quadrature &quadrature, - const Quadrature &face_quadrature, - const Function &rhs_function, - const Function &boundary_values) - : - Base (coarse_grid), - PrimalSolver (coarse_grid, fe, quadrature, - face_quadrature, rhs_function, - boundary_values) - {} + template + void + RefinementGlobal::refine_grid () + { + this->triangulation->refine_global (1); + } - template - void - RefinementGlobal::refine_grid () - { - this->triangulation->refine_global (1); - } + template + class RefinementKelly : public PrimalSolver + { + public: + RefinementKelly (Triangulation &coarse_grid, + const FiniteElement &fe, + const Quadrature &quadrature, + const Quadrature &face_quadrature, + const Function &rhs_function, + const Function &boundary_values); + + virtual void refine_grid (); + }; - template - class RefinementKelly : public PrimalSolver - { - public: - RefinementKelly (Triangulation &coarse_grid, - const FiniteElement &fe, - const Quadrature &quadrature, - const Quadrature &face_quadrature, - const Function &rhs_function, - const Function &boundary_values); - - virtual void refine_grid (); - }; + template + RefinementKelly:: + RefinementKelly (Triangulation &coarse_grid, + const FiniteElement &fe, + const Quadrature &quadrature, + const Quadrature &face_quadrature, + const Function &rhs_function, + const Function &boundary_values) + : + Base (coarse_grid), + PrimalSolver (coarse_grid, fe, quadrature, + face_quadrature, + rhs_function, boundary_values) + {} - template - RefinementKelly:: - RefinementKelly (Triangulation &coarse_grid, - const FiniteElement &fe, - const Quadrature &quadrature, - const Quadrature &face_quadrature, - const Function &rhs_function, - const Function &boundary_values) - : - Base (coarse_grid), - PrimalSolver (coarse_grid, fe, quadrature, - face_quadrature, - rhs_function, boundary_values) - {} + + + template + void + RefinementKelly::refine_grid () + { + Vector estimated_error_per_cell (this->triangulation->n_active_cells()); + KellyErrorEstimator::estimate (this->dof_handler, + QGauss(3), + typename FunctionMap::type(), + this->solution, + estimated_error_per_cell); + GridRefinement::refine_and_coarsen_fixed_number (*this->triangulation, + estimated_error_per_cell, + 0.3, 0.03); + this->triangulation->execute_coarsening_and_refinement (); + } - template - void - RefinementKelly::refine_grid () - { - Vector estimated_error_per_cell (this->triangulation->n_active_cells()); - KellyErrorEstimator::estimate (this->dof_handler, - QGauss(3), - typename FunctionMap::type(), - this->solution, - estimated_error_per_cell); - GridRefinement::refine_and_coarsen_fixed_number (*this->triangulation, - estimated_error_per_cell, - 0.3, 0.03); - this->triangulation->execute_coarsening_and_refinement (); + // @sect4{The RefinementWeightedKelly class} + + // This class is a variant of the + // previous one, in that it allows + // to weight the refinement + // indicators we get from the + // library's Kelly indicator by + // some function. We include this + // class since the goal of this + // example program is to + // demonstrate automatic refinement + // criteria even for complex output + // quantities such as point values + // or stresses. If we did not solve + // a dual problem and compute the + // weights thereof, we would + // probably be tempted to give a + // hand-crafted weighting to the + // indicators to account for the + // fact that we are going to + // evaluate these quantities. This + // class accepts such a weighting + // function as argument to its + // constructor: + template + class RefinementWeightedKelly : public PrimalSolver + { + public: + RefinementWeightedKelly (Triangulation &coarse_grid, + const FiniteElement &fe, + const Quadrature &quadrature, + const Quadrature &face_quadrature, + const Function &rhs_function, + const Function &boundary_values, + const Function &weighting_function); + + virtual void refine_grid (); + + private: + const SmartPointer > weighting_function; + }; + + + + template + RefinementWeightedKelly:: + RefinementWeightedKelly (Triangulation &coarse_grid, + const FiniteElement &fe, + const Quadrature &quadrature, + const Quadrature &face_quadrature, + const Function &rhs_function, + const Function &boundary_values, + const Function &weighting_function) + : + Base (coarse_grid), + PrimalSolver (coarse_grid, fe, quadrature, + face_quadrature, + rhs_function, boundary_values), + weighting_function (&weighting_function) + {} + + + + // Now, here comes the main + // function, including the + // weighting: + template + void + RefinementWeightedKelly::refine_grid () + { + // First compute some residual + // based error indicators for all + // cells by a method already + // implemented in the + // library. What exactly is + // computed can be read in the + // documentation of that class. + Vector estimated_error (this->triangulation->n_active_cells()); + KellyErrorEstimator::estimate (this->dof_handler, + *this->face_quadrature, + typename FunctionMap::type(), + this->solution, + estimated_error); + + // Now we are going to weight + // these indicators by the value + // of the function given to the + // constructor: + typename DoFHandler::active_cell_iterator + cell = this->dof_handler.begin_active(), + endc = this->dof_handler.end(); + for (unsigned int cell_index=0; cell!=endc; ++cell, ++cell_index) + estimated_error(cell_index) + *= weighting_function->value (cell->center()); + + GridRefinement::refine_and_coarsen_fixed_number (*this->triangulation, + estimated_error, + 0.3, 0.03); + this->triangulation->execute_coarsening_and_refinement (); + } + } + // @sect3{Equation data} + // + // In this example program, we work + // with the same data sets as in the + // previous one, but as it may so + // happen that someone wants to run + // the program with different + // boundary values and right hand side + // functions, or on a different grid, + // we show a simple technique to do + // exactly that. For more clarity, we + // furthermore pack everything that + // has to do with equation data into + // a namespace of its own. + // + // The underlying assumption is that + // this is a research program, and + // that there we often have a number + // of test cases that consist of a + // domain, a right hand side, + // boundary values, possibly a + // specified coefficient, and a + // number of other parameters. They + // often vary all at the same time + // when shifting from one example to + // another. To make handling such + // sets of problem description + // parameters simple is the goal of + // the following. + // + // Basically, the idea is this: let + // us have a structure for each set + // of data, in which we pack + // everything that describes a test + // case: here, these are two + // subclasses, one called + // BoundaryValues for the + // boundary values of the exact + // solution, and one called + // RightHandSide, and then a way + // to generate the coarse grid. Since + // the solution of the previous + // example program looked like curved + // ridges, we use this name here for + // the enclosing class. Note that the + // names of the two inner classes + // have to be the same for all + // enclosing test case classes, and + // also that we have attached the + // dimension template argument to the + // enclosing class rather than to the + // inner ones, to make further + // processing simpler. (From a + // language viewpoint, a namespace + // would be better to encapsulate + // these inner classes, rather than a + // structure. However, namespaces + // cannot be given as template + // arguments, so we use a structure + // to allow a second object to select + // from within its given + // argument. The enclosing structure, + // of course, has no member variables + // apart from the classes it + // declares, and a static function to + // generate the coarse mesh; it will + // in general never be instantiated.) + // + // The idea is then the following + // (this is the right time to also + // take a brief look at the code + // below): we can generate objects + // for boundary values and + // right hand side by simply giving + // the name of the outer class as a + // template argument to a class which + // we call here Data::SetUp, and + // it then creates objects for the + // inner classes. In this case, to + // get all that characterizes the + // curved ridge solution, we would + // simply generate an instance of + // Data::SetUp@, + // and everything we need to know + // about the solution would be static + // member variables and functions of + // that object. + // + // This approach might seem like + // overkill in this case, but will + // become very handy once a certain + // set up is not only characterized + // by Dirichlet boundary values and a + // right hand side function, but in + // addition by material properties, + // Neumann values, different boundary + // descriptors, etc. In that case, + // the SetUp class might consist + // of a dozen or more objects, and + // each descriptor class (like the + // CurvedRidges class below) + // would have to provide them. Then, + // you will be happy to be able to + // change from one set of data to + // another by only changing the + // template argument to the SetUp + // class at one place, rather than at + // many. + // + // With this framework for different + // test cases, we are almost + // finished, but one thing remains: + // by now we can select statically, + // by changing one template argument, + // which data set to choose. In order + // to be able to do that dynamically, + // i.e. at run time, we need a base + // class. This we provide in the + // obvious way, see below, with + // virtual abstract functions. It + // forces us to introduce a second + // template parameter dim which + // we need for the base class (which + // could be avoided using some + // template magic, but we omit that), + // but that's all. + // + // Adding new testcases is now + // simple, you don't have to touch + // the framework classes, only a + // structure like the + // CurvedRidges one is needed. + namespace Data + { + // @sect4{The SetUpBase and SetUp classes} + + // Based on the above description, + // the SetUpBase class then + // looks as follows. To allow using + // the SmartPointer class with + // this class, we derived from the + // Subscriptor class. + template + struct SetUpBase : public Subscriptor + { + virtual + const Function & get_boundary_values () const = 0; - // @sect4{The RefinementWeightedKelly class} - - // This class is a variant of the - // previous one, in that it allows - // to weight the refinement - // indicators we get from the - // library's Kelly indicator by - // some function. We include this - // class since the goal of this - // example program is to - // demonstrate automatic refinement - // criteria even for complex output - // quantities such as point values - // or stresses. If we did not solve - // a dual problem and compute the - // weights thereof, we would - // probably be tempted to give a - // hand-crafted weighting to the - // indicators to account for the - // fact that we are going to - // evaluate these quantities. This - // class accepts such a weighting - // function as argument to its - // constructor: - template - class RefinementWeightedKelly : public PrimalSolver - { - public: - RefinementWeightedKelly (Triangulation &coarse_grid, - const FiniteElement &fe, - const Quadrature &quadrature, - const Quadrature &face_quadrature, - const Function &rhs_function, - const Function &boundary_values, - const Function &weighting_function); - - virtual void refine_grid (); - - private: - const SmartPointer > weighting_function; - }; + virtual + const Function & get_right_hand_side () const = 0; + virtual + void create_coarse_grid (Triangulation &coarse_grid) const = 0; + }; - template - RefinementWeightedKelly:: - RefinementWeightedKelly (Triangulation &coarse_grid, - const FiniteElement &fe, - const Quadrature &quadrature, - const Quadrature &face_quadrature, - const Function &rhs_function, - const Function &boundary_values, - const Function &weighting_function) - : - Base (coarse_grid), - PrimalSolver (coarse_grid, fe, quadrature, - face_quadrature, - rhs_function, boundary_values), - weighting_function (&weighting_function) - {} + // And now for the derived class + // that takes the template argument + // as explained above. For some + // reason, C++ requires us to + // define a constructor (which + // maybe empty), as otherwise a + // warning is generated that some + // data is not initialized. + // + // Here we pack the data elements + // into private variables, and + // allow access to them through the + // methods of the base class. + template + struct SetUp : public SetUpBase + { + SetUp () {} + virtual + const Function & get_boundary_values () const; + virtual + const Function & get_right_hand_side () const; - // Now, here comes the main - // function, including the - // weighting: - template - void - RefinementWeightedKelly::refine_grid () - { - // First compute some residual - // based error indicators for all - // cells by a method already - // implemented in the - // library. What exactly is - // computed can be read in the - // documentation of that class. - Vector estimated_error (this->triangulation->n_active_cells()); - KellyErrorEstimator::estimate (this->dof_handler, - *this->face_quadrature, - typename FunctionMap::type(), - this->solution, - estimated_error); - - // Now we are going to weight - // these indicators by the value - // of the function given to the - // constructor: - typename DoFHandler::active_cell_iterator - cell = this->dof_handler.begin_active(), - endc = this->dof_handler.end(); - for (unsigned int cell_index=0; cell!=endc; ++cell, ++cell_index) - estimated_error(cell_index) - *= weighting_function->value (cell->center()); - - GridRefinement::refine_and_coarsen_fixed_number (*this->triangulation, - estimated_error, - 0.3, 0.03); - this->triangulation->execute_coarsening_and_refinement (); - } -} + virtual + void create_coarse_grid (Triangulation &coarse_grid) const; + private: + static const typename Traits::BoundaryValues boundary_values; + static const typename Traits::RightHandSide right_hand_side; + }; - // @sect3{Equation data} - // - // In this example program, we work - // with the same data sets as in the - // previous one, but as it may so - // happen that someone wants to run - // the program with different - // boundary values and right hand side - // functions, or on a different grid, - // we show a simple technique to do - // exactly that. For more clarity, we - // furthermore pack everything that - // has to do with equation data into - // a namespace of its own. - // - // The underlying assumption is that - // this is a research program, and - // that there we often have a number - // of test cases that consist of a - // domain, a right hand side, - // boundary values, possibly a - // specified coefficient, and a - // number of other parameters. They - // often vary all at the same time - // when shifting from one example to - // another. To make handling such - // sets of problem description - // parameters simple is the goal of - // the following. - // - // Basically, the idea is this: let - // us have a structure for each set - // of data, in which we pack - // everything that describes a test - // case: here, these are two - // subclasses, one called - // BoundaryValues for the - // boundary values of the exact - // solution, and one called - // RightHandSide, and then a way - // to generate the coarse grid. Since - // the solution of the previous - // example program looked like curved - // ridges, we use this name here for - // the enclosing class. Note that the - // names of the two inner classes - // have to be the same for all - // enclosing test case classes, and - // also that we have attached the - // dimension template argument to the - // enclosing class rather than to the - // inner ones, to make further - // processing simpler. (From a - // language viewpoint, a namespace - // would be better to encapsulate - // these inner classes, rather than a - // structure. However, namespaces - // cannot be given as template - // arguments, so we use a structure - // to allow a second object to select - // from within its given - // argument. The enclosing structure, - // of course, has no member variables - // apart from the classes it - // declares, and a static function to - // generate the coarse mesh; it will - // in general never be instantiated.) - // - // The idea is then the following - // (this is the right time to also - // take a brief look at the code - // below): we can generate objects - // for boundary values and - // right hand side by simply giving - // the name of the outer class as a - // template argument to a class which - // we call here Data::SetUp, and - // it then creates objects for the - // inner classes. In this case, to - // get all that characterizes the - // curved ridge solution, we would - // simply generate an instance of - // Data::SetUp@, - // and everything we need to know - // about the solution would be static - // member variables and functions of - // that object. - // - // This approach might seem like - // overkill in this case, but will - // become very handy once a certain - // set up is not only characterized - // by Dirichlet boundary values and a - // right hand side function, but in - // addition by material properties, - // Neumann values, different boundary - // descriptors, etc. In that case, - // the SetUp class might consist - // of a dozen or more objects, and - // each descriptor class (like the - // CurvedRidges class below) - // would have to provide them. Then, - // you will be happy to be able to - // change from one set of data to - // another by only changing the - // template argument to the SetUp - // class at one place, rather than at - // many. - // - // With this framework for different - // test cases, we are almost - // finished, but one thing remains: - // by now we can select statically, - // by changing one template argument, - // which data set to choose. In order - // to be able to do that dynamically, - // i.e. at run time, we need a base - // class. This we provide in the - // obvious way, see below, with - // virtual abstract functions. It - // forces us to introduce a second - // template parameter dim which - // we need for the base class (which - // could be avoided using some - // template magic, but we omit that), - // but that's all. - // - // Adding new testcases is now - // simple, you don't have to touch - // the framework classes, only a - // structure like the - // CurvedRidges one is needed. -namespace Data -{ - // @sect4{The SetUpBase and SetUp classes} - - // Based on the above description, - // the SetUpBase class then - // looks as follows. To allow using - // the SmartPointer class with - // this class, we derived from the - // Subscriptor class. - template - struct SetUpBase : public Subscriptor - { - virtual - const Function & get_boundary_values () const = 0; + // We have to provide definitions + // for the static member variables + // of the above class: + template + const typename Traits::BoundaryValues SetUp::boundary_values; + template + const typename Traits::RightHandSide SetUp::right_hand_side; + + // And definitions of the member + // functions: + template + const Function & + SetUp::get_boundary_values () const + { + return boundary_values; + } - virtual - const Function & get_right_hand_side () const = 0; - virtual - void create_coarse_grid (Triangulation &coarse_grid) const = 0; - }; + template + const Function & + SetUp::get_right_hand_side () const + { + return right_hand_side; + } - // And now for the derived class - // that takes the template argument - // as explained above. For some - // reason, C++ requires us to - // define a constructor (which - // maybe empty), as otherwise a - // warning is generated that some - // data is not initialized. - // - // Here we pack the data elements - // into private variables, and - // allow access to them through the - // methods of the base class. - template - struct SetUp : public SetUpBase - { - SetUp () {} + template + void + SetUp:: + create_coarse_grid (Triangulation &coarse_grid) const + { + Traits::create_coarse_grid (coarse_grid); + } - virtual - const Function & get_boundary_values () const; - virtual - const Function & get_right_hand_side () const; - + // @sect4{The CurvedRidges class} - virtual - void create_coarse_grid (Triangulation &coarse_grid) const; + // The class that is used to + // describe the boundary values and + // right hand side of the curved + // ridge problem already used in + // the step-13 example program is + // then like so: + template + struct CurvedRidges + { + class BoundaryValues : public Function + { + public: + BoundaryValues () : Function () {} - private: - static const typename Traits::BoundaryValues boundary_values; - static const typename Traits::RightHandSide right_hand_side; - }; + virtual double value (const Point &p, + const unsigned int component) const; + }; - // We have to provide definitions - // for the static member variables - // of the above class: - template - const typename Traits::BoundaryValues SetUp::boundary_values; - template - const typename Traits::RightHandSide SetUp::right_hand_side; - - // And definitions of the member - // functions: - template - const Function & - SetUp::get_boundary_values () const - { - return boundary_values; - } + class RightHandSide : public Function + { + public: + RightHandSide () : Function () {} - template - const Function & - SetUp::get_right_hand_side () const - { - return right_hand_side; - } + virtual double value (const Point &p, + const unsigned int component) const; + }; + static + void + create_coarse_grid (Triangulation &coarse_grid); + }; - template - void - SetUp:: - create_coarse_grid (Triangulation &coarse_grid) const - { - Traits::create_coarse_grid (coarse_grid); - } - - // @sect4{The CurvedRidges class} + template + double + CurvedRidges::BoundaryValues:: + value (const Point &p, + const unsigned int /*component*/) const + { + double q = p(0); + for (unsigned int i=1; icurved - // ridge problem already used in - // the step-13 example program is - // then like so: - template - struct CurvedRidges - { - class BoundaryValues : public Function - { - public: - BoundaryValues () : Function () {} - - virtual double value (const Point &p, - const unsigned int component) const; - }; - class RightHandSide : public Function - { - public: - RightHandSide () : Function () {} - - virtual double value (const Point &p, - const unsigned int component) const; - }; + template + double + CurvedRidges::RightHandSide::value (const Point &p, + const unsigned int /*component*/) const + { + double q = p(0); + for (unsigned int i=1; i &coarse_grid); - }; - - - template - double - CurvedRidges::BoundaryValues:: - value (const Point &p, - const unsigned int /*component*/) const - { - double q = p(0); - for (unsigned int i=1; i + void + CurvedRidges:: + create_coarse_grid (Triangulation &coarse_grid) + { + GridGenerator::hyper_cube (coarse_grid, -1, 1); + coarse_grid.refine_global (2); + } - template - double - CurvedRidges::RightHandSide::value (const Point &p, - const unsigned int /*component*/) const - { - double q = p(0); - for (unsigned int i=1; i + struct Exercise_2_3 + { + // We need a class to denote + // the boundary values of the + // problem. In this case, this + // is simple: it's the zero + // function, so don't even + // declare a class, just a + // typedef: + typedef ZeroFunction BoundaryValues; + + // Second, a class that denotes + // the right hand side. Since + // they are constant, just + // subclass the corresponding + // class of the library and be + // done: + class RightHandSide : public ConstantFunction + { + public: + RightHandSide () : ConstantFunction (1.) {} + }; - template - void - CurvedRidges:: - create_coarse_grid (Triangulation &coarse_grid) - { - GridGenerator::hyper_cube (coarse_grid, -1, 1); - coarse_grid.refine_global (2); - } - - - // @sect4{The Exercise_2_3 class} - - // This example program was written - // while giving practical courses - // for a lecture on adaptive finite - // element methods and duality - // based error estimates. For these - // courses, we had one exercise, - // which required to solve the - // Laplace equation with constant - // right hand side on a square - // domain with a square hole in the - // center, and zero boundary - // values. Since the implementation - // of the properties of this - // problem is so particularly - // simple here, lets do it. As the - // number of the exercise was 2.3, - // we take the liberty to retain - // this name for the class as well. - template - struct Exercise_2_3 - { - // We need a class to denote - // the boundary values of the - // problem. In this case, this - // is simple: it's the zero - // function, so don't even - // declare a class, just a - // typedef: - typedef ZeroFunction BoundaryValues; - - // Second, a class that denotes - // the right hand side. Since - // they are constant, just - // subclass the corresponding - // class of the library and be - // done: - class RightHandSide : public ConstantFunction - { - public: - RightHandSide () : ConstantFunction (1.) {} - }; - - // Finally a function to - // generate the coarse - // grid. This is somewhat more - // complicated here, see - // immediately below. - static - void - create_coarse_grid (Triangulation &coarse_grid); - }; + // Finally a function to + // generate the coarse + // grid. This is somewhat more + // complicated here, see + // immediately below. + static + void + create_coarse_grid (Triangulation &coarse_grid); + }; - // As stated above, the grid for - // this example is the square - // [-1,1]^2 with the square - // [-1/2,1/2]^2 as hole in it. We - // create the coarse grid as 4 - // times 4 cells with the middle - // four ones missing. - // - // Of course, the example has an - // extension to 3d, but since this - // function cannot be written in a - // dimension independent way we - // choose not to implement this - // here, but rather only specialize - // the template for dim=2. If you - // compile the program for 3d, - // you'll get a message from the - // linker that this function is not - // implemented for 3d, and needs to - // be provided. - // - // For the creation of this - // geometry, the library has no - // predefined method. In this case, - // the geometry is still simple - // enough to do the creation by - // hand, rather than using a mesh - // generator. - template <> - void - Exercise_2_3<2>:: - create_coarse_grid (Triangulation<2> &coarse_grid) - { - // First define the space - // dimension, to allow those - // parts of the function that are - // actually dimension independent - // to use this variable. That - // makes it simpler if you later - // takes this as a starting point - // to implement the 3d version. - const unsigned int dim = 2; - - // Then have a list of - // vertices. Here, they are 24 (5 - // times 5, with the middle one - // omitted). It is probably best - // to draw a sketch here. Note - // that we leave the number of - // vertices open at first, but - // then let the compiler compute - // this number afterwards. This - // reduces the possibility of - // having the dimension to large - // and leaving the last ones - // uninitialized. - static const Point<2> vertices_1[] - = { Point<2> (-1., -1.), + // As stated above, the grid for + // this example is the square + // [-1,1]^2 with the square + // [-1/2,1/2]^2 as hole in it. We + // create the coarse grid as 4 + // times 4 cells with the middle + // four ones missing. + // + // Of course, the example has an + // extension to 3d, but since this + // function cannot be written in a + // dimension independent way we + // choose not to implement this + // here, but rather only specialize + // the template for dim=2. If you + // compile the program for 3d, + // you'll get a message from the + // linker that this function is not + // implemented for 3d, and needs to + // be provided. + // + // For the creation of this + // geometry, the library has no + // predefined method. In this case, + // the geometry is still simple + // enough to do the creation by + // hand, rather than using a mesh + // generator. + template <> + void + Exercise_2_3<2>:: + create_coarse_grid (Triangulation<2> &coarse_grid) + { + // First define the space + // dimension, to allow those + // parts of the function that are + // actually dimension independent + // to use this variable. That + // makes it simpler if you later + // takes this as a starting point + // to implement the 3d version. + const unsigned int dim = 2; + + // Then have a list of + // vertices. Here, they are 24 (5 + // times 5, with the middle one + // omitted). It is probably best + // to draw a sketch here. Note + // that we leave the number of + // vertices open at first, but + // then let the compiler compute + // this number afterwards. This + // reduces the possibility of + // having the dimension to large + // and leaving the last ones + // uninitialized. + 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> (0., 1.), Point<2> (+1./2, 1.), Point<2> (+1, 1.) }; - const unsigned int - n_vertices = sizeof(vertices_1) / sizeof(vertices_1[0]); - - // From this static list of - // vertices, we generate an STL - // vector of the vertices, as - // this is the data type the - // library wants to see. - const std::vector > vertices (&vertices_1[0], - &vertices_1[n_vertices]); - - // Next, we have to define the - // cells and the vertices they - // contain. Here, we have 8 - // vertices, but leave the number - // open and let it be computed - // afterwards: - static const int cell_vertices[][GeometryInfo::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]); - - // Again, we generate a C++ - // vector type from this, but - // this time by looping over the - // cells (yes, this is - // boring). Additionally, we set - // the material indicator to zero - // for all the cells: - std::vector > cells (n_cells, CellData()); - for (unsigned int i=0; i::vertices_per_cell; - ++j) - cells[i].vertices[j] = cell_vertices[i][j]; - cells[i].material_id = 0; - } + const unsigned int + n_vertices = sizeof(vertices_1) / sizeof(vertices_1[0]); + + // From this static list of + // vertices, we generate an STL + // vector of the vertices, as + // this is the data type the + // library wants to see. + const std::vector > vertices (&vertices_1[0], + &vertices_1[n_vertices]); + + // Next, we have to define the + // cells and the vertices they + // contain. Here, we have 8 + // vertices, but leave the number + // open and let it be computed + // afterwards: + static const int cell_vertices[][GeometryInfo::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]); + + // Again, we generate a C++ + // vector type from this, but + // this time by looping over the + // cells (yes, this is + // boring). Additionally, we set + // the material indicator to zero + // for all the cells: + std::vector > cells (n_cells, CellData()); + for (unsigned int i=0; i::vertices_per_cell; + ++j) + cells[i].vertices[j] = cell_vertices[i][j]; + cells[i].material_id = 0; + } - // Finally pass all this - // information to the library to - // generate a triangulation. The - // last parameter may be used to - // pass information about - // non-zero boundary indicators - // at certain faces of the - // triangulation to the library, - // but we don't want that here, - // so we give an empty object: - coarse_grid.create_triangulation (vertices, - cells, - SubCellData()); - - // And since we want that the - // evaluation point (3/4,3/4) in - // this example is a grid point, - // we refine once globally: - coarse_grid.refine_global (1); + // Finally pass all this + // information to the library to + // generate a triangulation. The + // last parameter may be used to + // pass information about + // non-zero boundary indicators + // at certain faces of the + // triangulation to the library, + // but we don't want that here, + // so we give an empty object: + coarse_grid.create_triangulation (vertices, + cells, + SubCellData()); + + // And since we want that the + // evaluation point (3/4,3/4) in + // this example is a grid point, + // we refine once globally: + coarse_grid.refine_global (1); + } } -} - // @sect4{Discussion} - // - // As you have now read through this - // framework, you may be wondering - // why we have not chosen to - // implement the classes implementing - // a certain setup (like the - // CurvedRidges class) directly - // as classes derived from - // Data::SetUpBase. Indeed, we - // could have done very well so. The - // only reason is that then we would - // have to have member variables for - // the solution and right hand side - // classes in the CurvedRidges - // class, as well as member functions - // overloading the abstract functions - // of the base class giving access to - // these member variables. The - // SetUp class has the sole - // reason to relieve us from the need - // to reiterate these member - // variables and functions that would - // be necessary in all such - // classes. In some way, the template - // mechanism here only provides a way - // to have default implementations - // for a number of functions that - // depend on external quantities and - // can thus not be provided using - // normal virtual functions, at least - // not without the help of templates. - // - // However, there might be good - // reasons to actually implement - // classes derived from - // Data::SetUpBase, for example - // if the solution or right hand side - // classes require constructors that - // take arguments, which the - // Data::SetUpBase class cannot - // provide. In that case, subclassing - // is a worthwhile strategy. Other - // possibilities for special cases - // are to derive from - // Data::SetUp@ where - // SomeSetUp denotes a class, or - // even to explicitly specialize - // Data::SetUp@. The - // latter allows to transparently use - // the way the SetUp class is - // used for other set-ups, but with - // special actions taken for special - // arguments. - // - // A final observation favoring the - // approach taken here is the - // following: we have found numerous - // times that when starting a - // project, the number of parameters - // (usually boundary values, right - // hand side, coarse grid, just as - // here) was small, and the number of - // test cases was small as well. One - // then starts out by handcoding them - // into a number of switch - // statements. Over time, projects - // grow, and so does the number of - // test cases. The number of - // switch statements grows with - // that, and their length as well, - // and one starts to find ways to - // consider impossible examples where - // domains, boundary values, and - // right hand sides do not fit - // together any more, and starts - // loosing the overview over the - // whole structure. Encapsulating - // everything belonging to a certain - // test case into a structure of its - // own has proven worthwhile for - // this, as it keeps everything that - // belongs to one test case in one - // place. Furthermore, it allows to - // put these things all in one or - // more files that are only devoted - // to test cases and their data, - // without having to bring their - // actual implementation into contact - // with the rest of the program. - - - // @sect3{Dual functionals} - - // As with the other components of - // the program, we put everything we - // need to describe dual functionals - // into a namespace of its own, and - // define an abstract base class that - // provides the interface the class - // solving the dual problem needs for - // its work. - // - // We will then implement two such - // classes, for the evaluation of a - // point value and of the derivative - // of the solution at that point. For - // these functionals we already have - // the corresponding evaluation - // objects, so they are comlementary. -namespace DualFunctional -{ - // @sect4{The DualFunctionalBase class} - - // First start with the base class - // for dual functionals. Since for - // linear problems the - // characteristics of the dual - // problem play a role only in the - // right hand side, we only need to - // provide for a function that - // assembles the right hand side - // for a given discretization: - template - class DualFunctionalBase : public Subscriptor - { - public: - virtual - void - assemble_rhs (const DoFHandler &dof_handler, - Vector &rhs) const = 0; - }; + // @sect4{Discussion} + // + // As you have now read through this + // framework, you may be wondering + // why we have not chosen to + // implement the classes implementing + // a certain setup (like the + // CurvedRidges class) directly + // as classes derived from + // Data::SetUpBase. Indeed, we + // could have done very well so. The + // only reason is that then we would + // have to have member variables for + // the solution and right hand side + // classes in the CurvedRidges + // class, as well as member functions + // overloading the abstract functions + // of the base class giving access to + // these member variables. The + // SetUp class has the sole + // reason to relieve us from the need + // to reiterate these member + // variables and functions that would + // be necessary in all such + // classes. In some way, the template + // mechanism here only provides a way + // to have default implementations + // for a number of functions that + // depend on external quantities and + // can thus not be provided using + // normal virtual functions, at least + // not without the help of templates. + // + // However, there might be good + // reasons to actually implement + // classes derived from + // Data::SetUpBase, for example + // if the solution or right hand side + // classes require constructors that + // take arguments, which the + // Data::SetUpBase class cannot + // provide. In that case, subclassing + // is a worthwhile strategy. Other + // possibilities for special cases + // are to derive from + // Data::SetUp@ where + // SomeSetUp denotes a class, or + // even to explicitly specialize + // Data::SetUp@. The + // latter allows to transparently use + // the way the SetUp class is + // used for other set-ups, but with + // special actions taken for special + // arguments. + // + // A final observation favoring the + // approach taken here is the + // following: we have found numerous + // times that when starting a + // project, the number of parameters + // (usually boundary values, right + // hand side, coarse grid, just as + // here) was small, and the number of + // test cases was small as well. One + // then starts out by handcoding them + // into a number of switch + // statements. Over time, projects + // grow, and so does the number of + // test cases. The number of + // switch statements grows with + // that, and their length as well, + // and one starts to find ways to + // consider impossible examples where + // domains, boundary values, and + // right hand sides do not fit + // together any more, and starts + // loosing the overview over the + // whole structure. Encapsulating + // everything belonging to a certain + // test case into a structure of its + // own has proven worthwhile for + // this, as it keeps everything that + // belongs to one test case in one + // place. Furthermore, it allows to + // put these things all in one or + // more files that are only devoted + // to test cases and their data, + // without having to bring their + // actual implementation into contact + // with the rest of the program. + + + // @sect3{Dual functionals} + + // As with the other components of + // the program, we put everything we + // need to describe dual functionals + // into a namespace of its own, and + // define an abstract base class that + // provides the interface the class + // solving the dual problem needs for + // its work. + // + // We will then implement two such + // classes, for the evaluation of a + // point value and of the derivative + // of the solution at that point. For + // these functionals we already have + // the corresponding evaluation + // objects, so they are comlementary. + namespace DualFunctional + { + // @sect4{The DualFunctionalBase class} + + // First start with the base class + // for dual functionals. Since for + // linear problems the + // characteristics of the dual + // problem play a role only in the + // right hand side, we only need to + // provide for a function that + // assembles the right hand side + // for a given discretization: + template + class DualFunctionalBase : public Subscriptor + { + public: + virtual + void + assemble_rhs (const DoFHandler &dof_handler, + Vector &rhs) const = 0; + }; - // @sect4{The PointValueEvaluation class} - - // As a first application, we - // consider the functional - // corresponding to the evaluation - // of the solution's value at a - // given point which again we - // assume to be a vertex. Apart - // from the constructor that takes - // and stores the evaluation point, - // this class consists only of the - // function that implements - // assembling the right hand side. - template - class PointValueEvaluation : public DualFunctionalBase - { - public: - PointValueEvaluation (const Point &evaluation_point); - - virtual - void - assemble_rhs (const DoFHandler &dof_handler, - Vector &rhs) const; - - DeclException1 (ExcEvaluationPointNotFound, - Point, - << "The evaluation point " << arg1 - << " was not found among the vertices of the present grid."); - - protected: - const Point evaluation_point; - }; + // @sect4{The PointValueEvaluation class} + + // As a first application, we + // consider the functional + // corresponding to the evaluation + // of the solution's value at a + // given point which again we + // assume to be a vertex. Apart + // from the constructor that takes + // and stores the evaluation point, + // this class consists only of the + // function that implements + // assembling the right hand side. + template + class PointValueEvaluation : public DualFunctionalBase + { + public: + PointValueEvaluation (const Point &evaluation_point); + virtual + void + assemble_rhs (const DoFHandler &dof_handler, + Vector &rhs) const; - template - PointValueEvaluation:: - PointValueEvaluation (const Point &evaluation_point) - : - evaluation_point (evaluation_point) - {} - - - // As for doing the main purpose of - // the class, assembling the right - // hand side, let us first consider - // what is necessary: The right - // hand side of the dual problem is - // a vector of values J(phi_i), - // where J is the error functional, - // and phi_i is the i-th shape - // function. Here, J is the - // evaluation at the point x0, - // i.e. J(phi_i)=phi_i(x0). - // - // Now, we have assumed that the - // evaluation point is a - // vertex. Thus, for the usual - // finite elements we might be - // using in this program, we can - // take for granted that at such a - // point exactly one shape function - // is nonzero, and in particular - // has the value one. Thus, we set - // the right hand side vector to - // all-zeros, then seek for the - // shape function associated with - // that point and set the - // corresponding value of the right - // hand side vector to one: - template - void - PointValueEvaluation:: - assemble_rhs (const DoFHandler &dof_handler, - Vector &rhs) const - { - // So, first set everything to - // zeros... - rhs.reinit (dof_handler.n_dofs()); - - // ...then loop over cells and - // find the evaluation point - // among the vertices (or very - // close to a vertex, which may - // happen due to floating point - // round-off): - typename DoFHandler::active_cell_iterator - cell = dof_handler.begin_active(), - endc = dof_handler.end(); - for (; cell!=endc; ++cell) - for (unsigned int vertex=0; - vertex::vertices_per_cell; - ++vertex) - if (cell->vertex(vertex).distance(evaluation_point) - < cell->diameter()*1e-8) + DeclException1 (ExcEvaluationPointNotFound, + Point, + << "The evaluation point " << arg1 + << " was not found among the vertices of the present grid."); + + protected: + const Point evaluation_point; + }; + + + template + PointValueEvaluation:: + PointValueEvaluation (const Point &evaluation_point) + : + evaluation_point (evaluation_point) + {} + + + // As for doing the main purpose of + // the class, assembling the right + // hand side, let us first consider + // what is necessary: The right + // hand side of the dual problem is + // a vector of values J(phi_i), + // where J is the error functional, + // and phi_i is the i-th shape + // function. Here, J is the + // evaluation at the point x0, + // i.e. J(phi_i)=phi_i(x0). + // + // Now, we have assumed that the + // evaluation point is a + // vertex. Thus, for the usual + // finite elements we might be + // using in this program, we can + // take for granted that at such a + // point exactly one shape function + // is nonzero, and in particular + // has the value one. Thus, we set + // the right hand side vector to + // all-zeros, then seek for the + // shape function associated with + // that point and set the + // corresponding value of the right + // hand side vector to one: + template + void + PointValueEvaluation:: + assemble_rhs (const DoFHandler &dof_handler, + Vector &rhs) const + { + // So, first set everything to + // zeros... + rhs.reinit (dof_handler.n_dofs()); + + // ...then loop over cells and + // find the evaluation point + // among the vertices (or very + // close to a vertex, which may + // happen due to floating point + // round-off): + typename DoFHandler::active_cell_iterator + cell = dof_handler.begin_active(), + endc = dof_handler.end(); + for (; cell!=endc; ++cell) + for (unsigned int vertex=0; + vertex::vertices_per_cell; + ++vertex) + if (cell->vertex(vertex).distance(evaluation_point) + < cell->diameter()*1e-8) + { + // Ok, found, so set + // corresponding entry, + // and leave function + // since we are finished: + rhs(cell->vertex_dof_index(vertex,0)) = 1; + return; + } + + // Finally, a sanity check: if we + // somehow got here, then we must + // have missed the evaluation + // point, so raise an exception + // unconditionally: + AssertThrow (false, ExcEvaluationPointNotFound(evaluation_point)); + } + + + // @sect4{The PointXDerivativeEvaluation class} + + // As second application, we again + // consider the evaluation of the + // x-derivative of the solution at + // one point. Again, the + // declaration of the class, and + // the implementation of its + // constructor is not too + // interesting: + template + class PointXDerivativeEvaluation : public DualFunctionalBase + { + public: + PointXDerivativeEvaluation (const Point &evaluation_point); + + virtual + void + assemble_rhs (const DoFHandler &dof_handler, + Vector &rhs) const; + + DeclException1 (ExcEvaluationPointNotFound, + Point, + << "The evaluation point " << arg1 + << " was not found among the vertices of the present grid."); + + protected: + const Point evaluation_point; + }; + + + template + PointXDerivativeEvaluation:: + PointXDerivativeEvaluation (const Point &evaluation_point) + : + evaluation_point (evaluation_point) + {} + + + // What is interesting is the + // implementation of this + // functional: here, + // J(phi_i)=d/dx phi_i(x0). + // + // We could, as in the + // implementation of the respective + // evaluation object take the + // average of the gradients of each + // shape function phi_i at this + // evaluation point. However, we + // take a slightly different + // approach: we simply take the + // average over all cells that + // surround this point. The + // question which cells + // surrounds the evaluation + // point is made dependent on the + // mesh width by including those + // cells for which the distance of + // the cell's midpoint to the + // evaluation point is less than + // the cell's diameter. + // + // Taking the average of the + // gradient over the area/volume of + // these cells leads to a dual + // solution which is very close to + // the one which would result from + // the point evaluation of the + // gradient. It is simple to + // justify theoretically that this + // does not change the method + // significantly. + template + void + PointXDerivativeEvaluation:: + assemble_rhs (const DoFHandler &dof_handler, + Vector &rhs) const + { + // Again, first set all entries + // to zero: + rhs.reinit (dof_handler.n_dofs()); + + // Initialize a FEValues + // object with a quadrature + // formula, have abbreviations + // for the number of quadrature + // points and shape functions... + QGauss quadrature(4); + FEValues fe_values (dof_handler.get_fe(), quadrature, + update_gradients | + update_quadrature_points | + update_JxW_values); + const unsigned int n_q_points = fe_values.n_quadrature_points; + const unsigned int dofs_per_cell = dof_handler.get_fe().dofs_per_cell; + + // ...and have two objects that + // are used to store the global + // indices of the degrees of + // freedom on a cell, and the + // values of the gradients of the + // shape functions at the + // quadrature points: + Vector cell_rhs (dofs_per_cell); + std::vector local_dof_indices (dofs_per_cell); + + // Finally have a variable in + // which we will sum up the + // area/volume of the cells over + // which we integrate, by + // integrating the unit functions + // on these cells: + double total_volume = 0; + + // Then start the loop over all + // cells, and select those cells + // which are close enough to the + // evaluation point: + typename DoFHandler::active_cell_iterator + cell = dof_handler.begin_active(), + endc = dof_handler.end(); + for (; cell!=endc; ++cell) + if (cell->center().distance(evaluation_point) <= + cell->diameter()) { - // Ok, found, so set - // corresponding entry, - // and leave function - // since we are finished: - rhs(cell->vertex_dof_index(vertex,0)) = 1; - return; - } + // If we have found such a + // cell, then initialize + // the FEValues object + // and integrate the + // x-component of the + // gradient of each shape + // function, as well as the + // unit function for the + // total area/volume. + fe_values.reinit (cell); + cell_rhs = 0; - // Finally, a sanity check: if we - // somehow got here, then we must - // have missed the evaluation - // point, so raise an exception - // unconditionally: - AssertThrow (false, ExcEvaluationPointNotFound(evaluation_point)); - } + for (unsigned int q=0; qget_dof_indices (local_dof_indices); + for (unsigned int i=0; i - class PointXDerivativeEvaluation : public DualFunctionalBase - { - public: - PointXDerivativeEvaluation (const Point &evaluation_point); - - virtual - void - assemble_rhs (const DoFHandler &dof_handler, - Vector &rhs) const; - - DeclException1 (ExcEvaluationPointNotFound, - Point, - << "The evaluation point " << arg1 - << " was not found among the vertices of the present grid."); - - protected: - const Point evaluation_point; - }; + // After we have looped over all + // cells, check whether we have + // found any at all, by making + // sure that their volume is + // non-zero. If not, then the + // results will be botched, as + // the right hand side should + // then still be zero, so throw + // an exception: + AssertThrow (total_volume > 0, + ExcEvaluationPointNotFound(evaluation_point)); + + // Finally, we have by now only + // integrated the gradients of + // the shape functions, not + // taking their mean value. We + // fix this by dividing by the + // measure of the volume over + // which we have integrated: + rhs.scale (1./total_volume); + } - template - PointXDerivativeEvaluation:: - PointXDerivativeEvaluation (const Point &evaluation_point) - : - evaluation_point (evaluation_point) - {} - + } - // What is interesting is the - // implementation of this - // functional: here, - // J(phi_i)=d/dx phi_i(x0). - // - // We could, as in the - // implementation of the respective - // evaluation object take the - // average of the gradients of each - // shape function phi_i at this - // evaluation point. However, we - // take a slightly different - // approach: we simply take the - // average over all cells that - // surround this point. The - // question which cells - // surrounds the evaluation - // point is made dependent on the - // mesh width by including those - // cells for which the distance of - // the cell's midpoint to the - // evaluation point is less than - // the cell's diameter. - // - // Taking the average of the - // gradient over the area/volume of - // these cells leads to a dual - // solution which is very close to - // the one which would result from - // the point evaluation of the - // gradient. It is simple to - // justify theoretically that this - // does not change the method - // significantly. - template - void - PointXDerivativeEvaluation:: - assemble_rhs (const DoFHandler &dof_handler, - Vector &rhs) const + + // @sect3{Extending the LaplaceSolver namespace} + namespace LaplaceSolver { - // Again, first set all entries - // to zero: - rhs.reinit (dof_handler.n_dofs()); - - // Initialize a FEValues - // object with a quadrature - // formula, have abbreviations - // for the number of quadrature - // points and shape functions... - QGauss quadrature(4); - FEValues fe_values (dof_handler.get_fe(), quadrature, - update_gradients | - update_quadrature_points | - update_JxW_values); - const unsigned int n_q_points = fe_values.n_quadrature_points; - const unsigned int dofs_per_cell = dof_handler.get_fe().dofs_per_cell; - - // ...and have two objects that - // are used to store the global - // indices of the degrees of - // freedom on a cell, and the - // values of the gradients of the - // shape functions at the - // quadrature points: - Vector cell_rhs (dofs_per_cell); - std::vector local_dof_indices (dofs_per_cell); - - // Finally have a variable in - // which we will sum up the - // area/volume of the cells over - // which we integrate, by - // integrating the unit functions - // on these cells: - double total_volume = 0; - - // Then start the loop over all - // cells, and select those cells - // which are close enough to the - // evaluation point: - typename DoFHandler::active_cell_iterator - cell = dof_handler.begin_active(), - endc = dof_handler.end(); - for (; cell!=endc; ++cell) - if (cell->center().distance(evaluation_point) <= - cell->diameter()) - { - // If we have found such a - // cell, then initialize - // the FEValues object - // and integrate the - // x-component of the - // gradient of each shape - // function, as well as the - // unit function for the - // total area/volume. - fe_values.reinit (cell); - cell_rhs = 0; - - for (unsigned int q=0; qget_dof_indices (local_dof_indices); - for (unsigned int i=0; iPrimalSolver class above, we + // now implement a + // DualSolver. It has all the + // same features, the only + // difference is that it does not + // take a function object denoting + // a right hand side object, but + // now takes a + // DualFunctionalBase object + // that will assemble the right + // hand side vector of the dual + // problem. The rest of the class + // is rather trivial. + // + // Since both primal and dual + // solver will use the same + // triangulation, but different + // discretizations, it now becomes + // clear why we have made the + // Base class a virtual one: + // since the final class will be + // derived from both + // PrimalSolver as well as + // DualSolver, it would have + // two Base instances, would we + // not have marked the inheritance + // as virtual. Since in many + // applications the base class + // would store much more + // information than just the + // triangulation which needs to be + // shared between primal and dual + // solvers, we do not usually want + // to use two such base classes. + template + class DualSolver : public Solver + { + public: + DualSolver (Triangulation &triangulation, + const FiniteElement &fe, + const Quadrature &quadrature, + const Quadrature &face_quadrature, + const DualFunctional::DualFunctionalBase &dual_functional); - // After we have looped over all - // cells, check whether we have - // found any at all, by making - // sure that their volume is - // non-zero. If not, then the - // results will be botched, as - // the right hand side should - // then still be zero, so throw - // an exception: - AssertThrow (total_volume > 0, - ExcEvaluationPointNotFound(evaluation_point)); - - // Finally, we have by now only - // integrated the gradients of - // the shape functions, not - // taking their mean value. We - // fix this by dividing by the - // measure of the volume over - // which we have integrated: - rhs.scale (1./total_volume); - } - + virtual + void + solve_problem (); -} + virtual + unsigned int + n_dofs () const; + virtual + void + postprocess (const Evaluation::EvaluationBase &postprocessor) const; - // @sect3{Extending the LaplaceSolver namespace} -namespace LaplaceSolver -{ + protected: + const SmartPointer > dual_functional; + virtual void assemble_rhs (Vector &rhs) const; - // @sect4{The DualSolver class} - - // In the same way as the - // PrimalSolver class above, we - // now implement a - // DualSolver. It has all the - // same features, the only - // difference is that it does not - // take a function object denoting - // a right hand side object, but - // now takes a - // DualFunctionalBase object - // that will assemble the right - // hand side vector of the dual - // problem. The rest of the class - // is rather trivial. - // - // Since both primal and dual - // solver will use the same - // triangulation, but different - // discretizations, it now becomes - // clear why we have made the - // Base class a virtual one: - // since the final class will be - // derived from both - // PrimalSolver as well as - // DualSolver, it would have - // two Base instances, would we - // not have marked the inheritance - // as virtual. Since in many - // applications the base class - // would store much more - // information than just the - // triangulation which needs to be - // shared between primal and dual - // solvers, we do not usually want - // to use two such base classes. - template - class DualSolver : public Solver - { - public: - DualSolver (Triangulation &triangulation, - const FiniteElement &fe, - const Quadrature &quadrature, - const Quadrature &face_quadrature, - const DualFunctional::DualFunctionalBase &dual_functional); - - virtual - void - solve_problem (); - - virtual - unsigned int - n_dofs () const; - - virtual - void - postprocess (const Evaluation::EvaluationBase &postprocessor) const; - - protected: - const SmartPointer > dual_functional; - virtual void assemble_rhs (Vector &rhs) const; - - static const ZeroFunction boundary_values; - - // Same as above -- make a - // derived class a friend of - // this one: - friend class WeightedResidual; - }; + static const ZeroFunction boundary_values; - template - const ZeroFunction DualSolver::boundary_values; + // Same as above -- make a + // derived class a friend of + // this one: + friend class WeightedResidual; + }; - template - DualSolver:: - DualSolver (Triangulation &triangulation, - const FiniteElement &fe, - const Quadrature &quadrature, - const Quadrature &face_quadrature, - const DualFunctional::DualFunctionalBase &dual_functional) - : - Base (triangulation), - Solver (triangulation, fe, - quadrature, face_quadrature, - boundary_values), - dual_functional (&dual_functional) - {} + template + const ZeroFunction DualSolver::boundary_values; + template + DualSolver:: + DualSolver (Triangulation &triangulation, + const FiniteElement &fe, + const Quadrature &quadrature, + const Quadrature &face_quadrature, + const DualFunctional::DualFunctionalBase &dual_functional) + : + Base (triangulation), + Solver (triangulation, fe, + quadrature, face_quadrature, + boundary_values), + dual_functional (&dual_functional) + {} + + + template + void + DualSolver::solve_problem () + { + Solver::solve_problem (); + } - template - void - DualSolver::solve_problem () - { - Solver::solve_problem (); - } + template + unsigned int + DualSolver::n_dofs() const + { + return Solver::n_dofs(); + } - template - unsigned int - DualSolver::n_dofs() const - { - return Solver::n_dofs(); - } + template + void + DualSolver:: + postprocess (const Evaluation::EvaluationBase &postprocessor) const + { + Solver::postprocess(postprocessor); + } - template - void - DualSolver:: - postprocess (const Evaluation::EvaluationBase &postprocessor) const - { - Solver::postprocess(postprocessor); - } - - template - void - DualSolver:: - assemble_rhs (Vector &rhs) const - { - dual_functional->assemble_rhs (this->dof_handler, rhs); - } + template + void + DualSolver:: + assemble_rhs (Vector &rhs) const + { + dual_functional->assemble_rhs (this->dof_handler, rhs); + } - // @sect4{The WeightedResidual class} - - // Here finally comes the main - // class of this program, the one - // that implements the dual - // weighted residual error - // estimator. It joins the primal - // and dual solver classes to use - // them for the computation of - // primal and dual solutions, and - // implements the error - // representation formula for use - // as error estimate and mesh - // refinement. - // - // The first few of the functions - // of this class are mostly - // overriders of the respective - // functions of the base class: - template - class WeightedResidual : public PrimalSolver, - public DualSolver - { - public: - WeightedResidual (Triangulation &coarse_grid, - const FiniteElement &primal_fe, - const FiniteElement &dual_fe, - const Quadrature &quadrature, - const Quadrature &face_quadrature, - const Function &rhs_function, - const Function &boundary_values, - const DualFunctional::DualFunctionalBase &dual_functional); - - virtual - void - solve_problem (); - - virtual - void - postprocess (const Evaluation::EvaluationBase &postprocessor) const; - - virtual - unsigned int - n_dofs () const; - - virtual void refine_grid (); - - virtual - void - output_solution () const; - - private: - // In the private section, we - // have two functions that are - // used to call the - // solve_problem functions - // of the primal and dual base - // classes. These two functions - // will be called in parallel - // by the solve_problem - // function of this class. - void solve_primal_problem (); - void solve_dual_problem (); - // Then declare abbreviations - // for active cell iterators, - // to avoid that we have to - // write this lengthy name - // over and over again: - - typedef - typename DoFHandler::active_cell_iterator - active_cell_iterator; - - // Next, declare a data type - // that we will us to store the - // contribution of faces to the - // error estimator. The idea is - // that we can compute the face - // terms from each of the two - // cells to this face, as they - // are the same when viewed - // from both sides. What we - // will do is to compute them - // only once, based on some - // rules explained below which - // of the two adjacent cells - // will be in charge to do - // so. We then store the - // contribution of each face in - // a map mapping faces to their - // values, and only collect the - // contributions for each cell - // by looping over the cells a - // second time and grabbing the - // values from the map. - // - // The data type of this map is - // declared here: - typedef - typename std::map::face_iterator,double> - FaceIntegrals; - - // In the computation of the - // error estimates on cells and - // faces, we need a number of - // helper objects, such as - // FEValues and - // FEFaceValues functions, - // but also temporary objects - // storing the values and - // gradients of primal and dual - // solutions, for - // example. These fields are - // needed in the three - // functions that do the - // integration on cells, and - // regular and irregular faces, - // respectively. - // - // There are three reasonable - // ways to provide these - // fields: first, as local - // variables in the function - // that needs them; second, as - // member variables of this - // class; third, as arguments - // passed to that function. - // - // These three alternatives all - // have drawbacks: the third - // that their number is not - // neglectable and would make - // calling these functions a - // lengthy enterprise. The - // second has the drawback that - // it disallows - // parallelization, since the - // threads that will compute - // the error estimate have to - // have their own copies of - // these variables each, so - // member variables of the - // enclosing class will not - // work. The first approach, - // although straightforward, - // has a subtle but important - // drawback: we will call these - // functions over and over - // again, many thousands of times - // maybe; it has now turned out - // that allocating vectors and - // other objects that need - // memory from the heap is an - // expensive business in terms - // of run-time, since memory - // allocation is expensive when - // several threads are - // involved. In our experience, - // more than 20 per cent of the - // total run time of error - // estimation functions are due - // to memory allocation, if - // done on a per-call level. It - // is thus significantly better - // to allocate the memory only - // once, and recycle the - // objects as often as - // possible. - // - // What to do? Our answer is to - // use a variant of the third - // strategy, namely generating - // these variables once in the - // main function of each - // thread, and passing them - // down to the functions that - // do the actual work. To avoid - // that we have to give these - // functions a dozen or so - // arguments, we pack all these - // variables into two - // structures, one which is - // used for the computations on - // cells, the other doing them - // on the faces. Instead of - // many individual objects, we - // will then only pass one such - // object to these functions, - // making their calling - // sequence simpler. - struct CellData - { - FEValues fe_values; - const SmartPointer > right_hand_side; - - std::vector cell_residual; - std::vector rhs_values; - std::vector dual_weights; - std::vector cell_laplacians; - CellData (const FiniteElement &fe, - const Quadrature &quadrature, - const Function &right_hand_side); - }; + // @sect4{The WeightedResidual class} + + // Here finally comes the main + // class of this program, the one + // that implements the dual + // weighted residual error + // estimator. It joins the primal + // and dual solver classes to use + // them for the computation of + // primal and dual solutions, and + // implements the error + // representation formula for use + // as error estimate and mesh + // refinement. + // + // The first few of the functions + // of this class are mostly + // overriders of the respective + // functions of the base class: + template + class WeightedResidual : public PrimalSolver, + public DualSolver + { + public: + WeightedResidual (Triangulation &coarse_grid, + const FiniteElement &primal_fe, + const FiniteElement &dual_fe, + const Quadrature &quadrature, + const Quadrature &face_quadrature, + const Function &rhs_function, + const Function &boundary_values, + const DualFunctional::DualFunctionalBase &dual_functional); + + virtual + void + solve_problem (); + + virtual + void + postprocess (const Evaluation::EvaluationBase &postprocessor) const; + + virtual + unsigned int + n_dofs () const; + + virtual void refine_grid (); + + virtual + void + output_solution () const; + + private: + // In the private section, we + // have two functions that are + // used to call the + // solve_problem functions + // of the primal and dual base + // classes. These two functions + // will be called in parallel + // by the solve_problem + // function of this class. + void solve_primal_problem (); + void solve_dual_problem (); + // Then declare abbreviations + // for active cell iterators, + // to avoid that we have to + // write this lengthy name + // over and over again: + + typedef + typename DoFHandler::active_cell_iterator + active_cell_iterator; + + // Next, declare a data type + // that we will us to store the + // contribution of faces to the + // error estimator. The idea is + // that we can compute the face + // terms from each of the two + // cells to this face, as they + // are the same when viewed + // from both sides. What we + // will do is to compute them + // only once, based on some + // rules explained below which + // of the two adjacent cells + // will be in charge to do + // so. We then store the + // contribution of each face in + // a map mapping faces to their + // values, and only collect the + // contributions for each cell + // by looping over the cells a + // second time and grabbing the + // values from the map. + // + // The data type of this map is + // declared here: + typedef + typename std::map::face_iterator,double> + FaceIntegrals; + + // In the computation of the + // error estimates on cells and + // faces, we need a number of + // helper objects, such as + // FEValues and + // FEFaceValues functions, + // but also temporary objects + // storing the values and + // gradients of primal and dual + // solutions, for + // example. These fields are + // needed in the three + // functions that do the + // integration on cells, and + // regular and irregular faces, + // respectively. + // + // There are three reasonable + // ways to provide these + // fields: first, as local + // variables in the function + // that needs them; second, as + // member variables of this + // class; third, as arguments + // passed to that function. + // + // These three alternatives all + // have drawbacks: the third + // that their number is not + // neglectable and would make + // calling these functions a + // lengthy enterprise. The + // second has the drawback that + // it disallows + // parallelization, since the + // threads that will compute + // the error estimate have to + // have their own copies of + // these variables each, so + // member variables of the + // enclosing class will not + // work. The first approach, + // although straightforward, + // has a subtle but important + // drawback: we will call these + // functions over and over + // again, many thousands of times + // maybe; it has now turned out + // that allocating vectors and + // other objects that need + // memory from the heap is an + // expensive business in terms + // of run-time, since memory + // allocation is expensive when + // several threads are + // involved. In our experience, + // more than 20 per cent of the + // total run time of error + // estimation functions are due + // to memory allocation, if + // done on a per-call level. It + // is thus significantly better + // to allocate the memory only + // once, and recycle the + // objects as often as + // possible. + // + // What to do? Our answer is to + // use a variant of the third + // strategy, namely generating + // these variables once in the + // main function of each + // thread, and passing them + // down to the functions that + // do the actual work. To avoid + // that we have to give these + // functions a dozen or so + // arguments, we pack all these + // variables into two + // structures, one which is + // used for the computations on + // cells, the other doing them + // on the faces. Instead of + // many individual objects, we + // will then only pass one such + // object to these functions, + // making their calling + // sequence simpler. + struct CellData + { + FEValues fe_values; + const SmartPointer > right_hand_side; + + std::vector cell_residual; + std::vector rhs_values; + std::vector dual_weights; + std::vector cell_laplacians; + CellData (const FiniteElement &fe, + const Quadrature &quadrature, + const Function &right_hand_side); + }; - struct FaceData - { - FEFaceValues fe_face_values_cell; - FEFaceValues fe_face_values_neighbor; - FESubfaceValues fe_subface_values_cell; - - std::vector jump_residual; - std::vector dual_weights; - typename std::vector > cell_grads; - typename std::vector > neighbor_grads; - FaceData (const FiniteElement &fe, - const Quadrature &face_quadrature); - }; + struct FaceData + { + FEFaceValues fe_face_values_cell; + FEFaceValues fe_face_values_neighbor; + FESubfaceValues fe_subface_values_cell; + + std::vector jump_residual; + std::vector dual_weights; + typename std::vector > cell_grads; + typename std::vector > neighbor_grads; + FaceData (const FiniteElement &fe, + const Quadrature &face_quadrature); + }; - - - // Regarding the evaluation of - // the error estimator, we have - // two driver functions that do - // this: the first is called to - // generate the cell-wise - // estimates, and splits up the - // task in a number of threads - // each of which work on a - // subset of the cells. The - // first function will run the - // second for each of these - // threads: - void estimate_error (Vector &error_indicators) const; - - void estimate_some (const Vector &primal_solution, - const Vector &dual_weights, - const unsigned int n_threads, - const unsigned int this_thread, - Vector &error_indicators, - FaceIntegrals &face_integrals) const; - - // Then we have functions that - // do the actual integration of - // the error representation - // formula. They will treat the - // terms on the cell interiors, - // on those faces that have no - // hanging nodes, and on those - // faces with hanging nodes, - // respectively: - void - integrate_over_cell (const active_cell_iterator &cell, - const unsigned int cell_index, - const Vector &primal_solution, - const Vector &dual_weights, - CellData &cell_data, - Vector &error_indicators) const; - - void - integrate_over_regular_face (const active_cell_iterator &cell, - const unsigned int face_no, - const Vector &primal_solution, - const Vector &dual_weights, - FaceData &face_data, - FaceIntegrals &face_integrals) const; - void - integrate_over_irregular_face (const active_cell_iterator &cell, + + + // Regarding the evaluation of + // the error estimator, we have + // two driver functions that do + // this: the first is called to + // generate the cell-wise + // estimates, and splits up the + // task in a number of threads + // each of which work on a + // subset of the cells. The + // first function will run the + // second for each of these + // threads: + void estimate_error (Vector &error_indicators) const; + + void estimate_some (const Vector &primal_solution, + const Vector &dual_weights, + const unsigned int n_threads, + const unsigned int this_thread, + Vector &error_indicators, + FaceIntegrals &face_integrals) const; + + // Then we have functions that + // do the actual integration of + // the error representation + // formula. They will treat the + // terms on the cell interiors, + // on those faces that have no + // hanging nodes, and on those + // faces with hanging nodes, + // respectively: + void + integrate_over_cell (const active_cell_iterator &cell, + const unsigned int cell_index, + const Vector &primal_solution, + const Vector &dual_weights, + CellData &cell_data, + Vector &error_indicators) const; + + void + integrate_over_regular_face (const active_cell_iterator &cell, const unsigned int face_no, const Vector &primal_solution, const Vector &dual_weights, FaceData &face_data, FaceIntegrals &face_integrals) const; - }; + void + integrate_over_irregular_face (const active_cell_iterator &cell, + const unsigned int face_no, + const Vector &primal_solution, + const Vector &dual_weights, + FaceData &face_data, + FaceIntegrals &face_integrals) const; + }; - // In the implementation of this - // class, we first have the - // constructors of the CellData - // and FaceData member classes, - // and the WeightedResidual - // constructor. They only - // initialize fields to their - // correct lengths, so we do not - // have to discuss them to length. - template - WeightedResidual::CellData:: - CellData (const FiniteElement &fe, - const Quadrature &quadrature, - const Function &right_hand_side) - : - fe_values (fe, quadrature, - update_values | - update_hessians | - update_quadrature_points | - update_JxW_values), - right_hand_side (&right_hand_side), - cell_residual (quadrature.size()), - rhs_values (quadrature.size()), - dual_weights (quadrature.size()), - cell_laplacians (quadrature.size()) - {} - - + // In the implementation of this + // class, we first have the + // constructors of the CellData + // and FaceData member classes, + // and the WeightedResidual + // constructor. They only + // initialize fields to their + // correct lengths, so we do not + // have to discuss them to length. + template + WeightedResidual::CellData:: + CellData (const FiniteElement &fe, + const Quadrature &quadrature, + const Function &right_hand_side) + : + fe_values (fe, quadrature, + update_values | + update_hessians | + update_quadrature_points | + update_JxW_values), + right_hand_side (&right_hand_side), + cell_residual (quadrature.size()), + rhs_values (quadrature.size()), + dual_weights (quadrature.size()), + cell_laplacians (quadrature.size()) + {} + + + + template + WeightedResidual::FaceData:: + FaceData (const FiniteElement &fe, + const Quadrature &face_quadrature) + : + fe_face_values_cell (fe, face_quadrature, + update_values | + update_gradients | + update_JxW_values | + update_normal_vectors), + fe_face_values_neighbor (fe, face_quadrature, + update_values | + update_gradients | + update_JxW_values | + update_normal_vectors), + fe_subface_values_cell (fe, face_quadrature, + update_gradients) + { + const unsigned int n_face_q_points + = face_quadrature.size(); - template - WeightedResidual::FaceData:: - FaceData (const FiniteElement &fe, - const Quadrature &face_quadrature) - : - fe_face_values_cell (fe, face_quadrature, - update_values | - update_gradients | - update_JxW_values | - update_normal_vectors), - fe_face_values_neighbor (fe, face_quadrature, - update_values | - update_gradients | - update_JxW_values | - update_normal_vectors), - fe_subface_values_cell (fe, face_quadrature, - update_gradients) - { - const unsigned int n_face_q_points - = face_quadrature.size(); - - jump_residual.resize(n_face_q_points); - dual_weights.resize(n_face_q_points); - cell_grads.resize(n_face_q_points); - neighbor_grads.resize(n_face_q_points); - } - + jump_residual.resize(n_face_q_points); + dual_weights.resize(n_face_q_points); + cell_grads.resize(n_face_q_points); + neighbor_grads.resize(n_face_q_points); + } - template - WeightedResidual:: - WeightedResidual (Triangulation &coarse_grid, - const FiniteElement &primal_fe, - const FiniteElement &dual_fe, - const Quadrature &quadrature, - const Quadrature &face_quadrature, - const Function &rhs_function, - const Function &bv, - const DualFunctional::DualFunctionalBase &dual_functional) - : - Base (coarse_grid), - PrimalSolver (coarse_grid, primal_fe, + + template + WeightedResidual:: + WeightedResidual (Triangulation &coarse_grid, + const FiniteElement &primal_fe, + const FiniteElement &dual_fe, + const Quadrature &quadrature, + const Quadrature &face_quadrature, + const Function &rhs_function, + const Function &bv, + const DualFunctional::DualFunctionalBase &dual_functional) + : + Base (coarse_grid), + PrimalSolver (coarse_grid, primal_fe, + quadrature, face_quadrature, + rhs_function, bv), + DualSolver (coarse_grid, dual_fe, quadrature, face_quadrature, - rhs_function, bv), - DualSolver (coarse_grid, dual_fe, - quadrature, face_quadrature, - dual_functional) - {} + dual_functional) + {} + + + // The next five functions are + // boring, as they simply relay + // their work to the base + // classes. The first calls the + // primal and dual solvers in + // parallel, while postprocessing + // the solution and retrieving the + // number of degrees of freedom is + // done by the primal class. + template + void + WeightedResidual::solve_problem () + { + Threads::ThreadGroup<> threads; + threads += Threads::new_thread (&WeightedResidual::solve_primal_problem, + *this); + threads += Threads::new_thread (&WeightedResidual::solve_dual_problem, + *this); + threads.join_all (); + } - // The next five functions are - // boring, as they simply relay - // their work to the base - // classes. The first calls the - // primal and dual solvers in - // parallel, while postprocessing - // the solution and retrieving the - // number of degrees of freedom is - // done by the primal class. - template - void - WeightedResidual::solve_problem () - { - Threads::ThreadGroup<> threads; - threads += Threads::new_thread (&WeightedResidual::solve_primal_problem, - *this); - threads += Threads::new_thread (&WeightedResidual::solve_dual_problem, - *this); - threads.join_all (); - } + template + void + WeightedResidual::solve_primal_problem () + { + PrimalSolver::solve_problem (); + } - - template - void - WeightedResidual::solve_primal_problem () - { - PrimalSolver::solve_problem (); - } + template + void + WeightedResidual::solve_dual_problem () + { + DualSolver::solve_problem (); + } - template - void - WeightedResidual::solve_dual_problem () - { - DualSolver::solve_problem (); - } - - template - void - WeightedResidual:: - postprocess (const Evaluation::EvaluationBase &postprocessor) const - { - PrimalSolver::postprocess (postprocessor); - } - - - template - unsigned int - WeightedResidual::n_dofs () const - { - return PrimalSolver::n_dofs(); - } + template + void + WeightedResidual:: + postprocess (const Evaluation::EvaluationBase &postprocessor) const + { + PrimalSolver::postprocess (postprocessor); + } + template + unsigned int + WeightedResidual::n_dofs () const + { + return PrimalSolver::n_dofs(); + } - // Now, it is becoming more - // interesting: the refine_grid - // function asks the error - // estimator to compute the - // cell-wise error indicators, then - // uses their absolute values for - // mesh refinement. - template - void - WeightedResidual::refine_grid () - { - // First call the function that - // computes the cell-wise and - // global error: - Vector error_indicators (this->triangulation->n_active_cells()); - estimate_error (error_indicators); - - // Then note that marking cells - // for refinement or coarsening - // only works if all indicators - // are positive, to allow their - // comparison. Thus, drop the - // signs on all these indicators: - for (Vector::iterator i=error_indicators.begin(); - i != error_indicators.end(); ++i) - *i = std::fabs (*i); - - // Finally, we can select between - // different strategies for - // refinement. The default here - // is to refine those cells with - // the largest error indicators - // that make up for a total of 80 - // per cent of the error, while - // we coarsen those with the - // smallest indicators that make - // up for the bottom 2 per cent - // of the error. - GridRefinement::refine_and_coarsen_fixed_fraction (*this->triangulation, - error_indicators, - 0.8, 0.02); - this->triangulation->execute_coarsening_and_refinement (); - } - - - // Since we want to output both the - // primal and the dual solution, we - // overload the output_solution - // function. The only interesting - // feature of this function is that - // the primal and dual solutions - // are defined on different finite - // element spaces, which is not the - // format the DataOut class - // expects. Thus, we have to - // transfer them to a common finite - // element space. Since we want the - // solutions only to see them - // qualitatively, we contend - // ourselves with interpolating the - // dual solution to the (smaller) - // primal space. For the - // interpolation, there is a - // library function, that takes a - // ConstraintMatrix object - // including the hanging node - // constraints. The rest is - // standard. - // - // There is, however, one - // work-around worth mentioning: in - // this function, as in a couple of - // following ones, we have to - // access the DoFHandler - // objects and solutions of both - // the primal as well as of the - // dual solver. Since these are - // members of the Solver base - // class which exists twice in the - // class hierarchy leading to the - // present class (once as base - // class of the PrimalSolver - // class, once as base class of the - // DualSolver class), we have - // to disambiguate accesses to them - // by telling the compiler a member - // of which of these two instances - // we want to access. The way to do - // this would be identify the - // member by pointing a path - // through the class hierarchy - // which disambiguates the base - // class, for example writing - // PrimalSolver::dof_handler to - // denote the member variable - // dof_handler from the - // Solver base class of the - // PrimalSolver - // class. Unfortunately, this - // confuses gcc's version 2.96 (a - // version that was intended as a - // development snapshot, but - // delivered as system compiler by - // Red Hat in their 7.x releases) - // so much that it bails out and - // refuses to compile the code. - // - // Thus, we have to work around - // this problem. We do this by - // introducing references to the - // PrimalSolver and - // DualSolver components of the - // WeightedResidual object at - // the beginning of the - // function. Since each of these - // has an unambiguous base class - // Solver, we can access the - // member variables we want through - // these references. However, we - // are now accessing protected - // member variables of these - // classes through a pointer other - // than the this pointer (in - // fact, this is of course the - // this pointer, but not - // explicitly). This finally is the - // reason why we had to declare the - // present class a friend of the - // classes we so access. - template - void - WeightedResidual::output_solution () const - { - const PrimalSolver &primal_solver = *this; - const DualSolver &dual_solver = *this; - - ConstraintMatrix primal_hanging_node_constraints; - DoFTools::make_hanging_node_constraints (primal_solver.dof_handler, - primal_hanging_node_constraints); - primal_hanging_node_constraints.close(); - Vector dual_solution (primal_solver.dof_handler.n_dofs()); - FETools::interpolate (dual_solver.dof_handler, - dual_solver.solution, - primal_solver.dof_handler, - primal_hanging_node_constraints, - dual_solution); - - DataOut data_out; - data_out.attach_dof_handler (primal_solver.dof_handler); - - // Add the data vectors for which - // we want output. Add them both, - // the DataOut functions can - // handle as many data vectors as - // you wish to write to output: - data_out.add_data_vector (primal_solver.solution, - "primal_solution"); - data_out.add_data_vector (dual_solution, - "dual_solution"); - - data_out.build_patches (); - - std::ostringstream filename; - filename << "solution-" - << this->refinement_cycle - << ".gnuplot" - << std::ends; - - std::ofstream out (filename.str().c_str()); - data_out.write (out, DataOut::gnuplot); - } - // @sect3{Estimating errors} + // Now, it is becoming more + // interesting: the refine_grid + // function asks the error + // estimator to compute the + // cell-wise error indicators, then + // uses their absolute values for + // mesh refinement. + template + void + WeightedResidual::refine_grid () + { + // First call the function that + // computes the cell-wise and + // global error: + Vector error_indicators (this->triangulation->n_active_cells()); + estimate_error (error_indicators); + + // Then note that marking cells + // for refinement or coarsening + // only works if all indicators + // are positive, to allow their + // comparison. Thus, drop the + // signs on all these indicators: + for (Vector::iterator i=error_indicators.begin(); + i != error_indicators.end(); ++i) + *i = std::fabs (*i); + + // Finally, we can select between + // different strategies for + // refinement. The default here + // is to refine those cells with + // the largest error indicators + // that make up for a total of 80 + // per cent of the error, while + // we coarsen those with the + // smallest indicators that make + // up for the bottom 2 per cent + // of the error. + GridRefinement::refine_and_coarsen_fixed_fraction (*this->triangulation, + error_indicators, + 0.8, 0.02); + this->triangulation->execute_coarsening_and_refinement (); + } + - // @sect4{Error estimation driver functions} - // - // As for the actual computation of - // error estimates, let's start - // with the function that drives - // all this, i.e. calls those - // functions that actually do the - // work, and finally collects the - // results. - - template - void - WeightedResidual:: - estimate_error (Vector &error_indicators) const - { - const PrimalSolver &primal_solver = *this; - const DualSolver &dual_solver = *this; - - // The first task in computing - // the error is to set up vectors - // that denote the primal - // solution, and the weights - // (z-z_h)=(z-I_hz), both in the - // finite element space for which - // we have computed the dual - // solution. For this, we have to - // interpolate the primal - // solution to the dual finite - // element space, and to subtract - // the interpolation of the - // computed dual solution to the - // primal finite element - // space. Fortunately, the - // library provides functions for - // the interpolation into larger - // or smaller finite element - // spaces, so this is mostly - // obvious. - // - // First, let's do that for the - // primal solution: it is - // cell-wise interpolated into - // the finite element space in - // which we have solved the dual - // problem: But, again as in the - // WeightedResidual::output_solution - // function we first need to - // create a ConstraintMatrix + // Since we want to output both the + // primal and the dual solution, we + // overload the output_solution + // function. The only interesting + // feature of this function is that + // the primal and dual solutions + // are defined on different finite + // element spaces, which is not the + // format the DataOut class + // expects. Thus, we have to + // transfer them to a common finite + // element space. Since we want the + // solutions only to see them + // qualitatively, we contend + // ourselves with interpolating the + // dual solution to the (smaller) + // primal space. For the + // interpolation, there is a + // library function, that takes a + // ConstraintMatrix object // including the hanging node - // constraints, but this time of - // the dual finite element space. - ConstraintMatrix dual_hanging_node_constraints; - DoFTools::make_hanging_node_constraints (dual_solver.dof_handler, - dual_hanging_node_constraints); - dual_hanging_node_constraints.close(); - Vector primal_solution (dual_solver.dof_handler.n_dofs()); - FETools::interpolate (primal_solver.dof_handler, - primal_solver.solution, - dual_solver.dof_handler, - dual_hanging_node_constraints, - primal_solution); - - // Then for computing the - // interpolation of the - // numerically approximated dual - // solution z into the finite - // element space of the primal - // solution and subtracting it - // from z: use the - // interpolate_difference - // function, that gives (z-I_hz) - // in the element space of the - // dual solution. - ConstraintMatrix primal_hanging_node_constraints; - DoFTools::make_hanging_node_constraints (primal_solver.dof_handler, - primal_hanging_node_constraints); - primal_hanging_node_constraints.close(); - Vector dual_weights (dual_solver.dof_handler.n_dofs()); - FETools::interpolation_difference (dual_solver.dof_handler, - dual_hanging_node_constraints, - dual_solver.solution, - primal_solver.dof_handler, - primal_hanging_node_constraints, - dual_weights); - - // Note that this could probably - // have been more efficient since - // those constraints have been - // used previously when - // assembling matrix and right - // hand side for the primal - // problem and writing out the - // dual solution. We leave the - // optimization of the program in - // this respect as an exercise. - - // Having computed the dual - // weights we now proceed with - // computing the cell and face - // residuals of the primal - // solution. First we set up a - // map between face iterators and - // their jump term contributions - // of faces to the error - // estimator. The reason is that - // we compute the jump terms only - // once, from one side of the - // face, and want to collect them - // only afterwards when looping - // over all cells a second time. + // constraints. The rest is + // standard. // - // We initialize this map already - // with a value of -1e20 for all - // faces, since this value will - // strike in the results if - // something should go wrong and - // we fail to compute the value - // for a face for some - // reason. Secondly, we - // initialize the map once before - // we branch to different threads - // since this way the map's - // structure is no more modified - // by the individual threads, - // only existing entries are set - // to new values. This relieves - // us from the necessity to - // synchronise the threads - // through a mutex each time they - // write to (and modify the - // structure of) this map. - FaceIntegrals face_integrals; - for (active_cell_iterator cell=dual_solver.dof_handler.begin_active(); - cell!=dual_solver.dof_handler.end(); - ++cell) - for (unsigned int face_no=0; - face_no::faces_per_cell; - ++face_no) - face_integrals[cell->face(face_no)] = -1e20; - - // Then set up a vector with - // error indicators. Reserve one - // slot for each cell and set it - // to zero. - error_indicators.reinit (dual_solver.dof_handler - .get_tria().n_active_cells()); - - // Now start a number of threads - // which compute the error - // formula on parts of all the - // cells, and once they are all - // started wait until they have - // all finished: - const unsigned int n_threads = multithread_info.n_default_threads; - Threads::ThreadGroup<> threads; - for (unsigned int i=0; i::estimate_some, - *this, - primal_solution, - dual_weights, - n_threads, i, - error_indicators, - face_integrals); - threads.join_all(); - - // Once the error contributions - // are computed, sum them up. For - // this, note that the cell terms - // are already set, and that only - // the edge terms need to be - // collected. Thus, loop over all - // cells and their faces, make - // sure that the contributions of - // each of the faces are there, - // and add them up. Only take - // minus one half of the jump - // term, since the other half - // will be taken by the - // neighboring cell. - unsigned int present_cell=0; - for (active_cell_iterator cell=dual_solver.dof_handler.begin_active(); - cell!=dual_solver.dof_handler.end(); - ++cell, ++present_cell) - for (unsigned int face_no=0; face_no::faces_per_cell; - ++face_no) - { - Assert(face_integrals.find(cell->face(face_no)) != - face_integrals.end(), - ExcInternalError()); - error_indicators(present_cell) - -= 0.5*face_integrals[cell->face(face_no)]; - } - std::cout << " Estimated error=" - << std::accumulate (error_indicators.begin(), - error_indicators.end(), 0.) - << std::endl; - } + // There is, however, one + // work-around worth mentioning: in + // this function, as in a couple of + // following ones, we have to + // access the DoFHandler + // objects and solutions of both + // the primal as well as of the + // dual solver. Since these are + // members of the Solver base + // class which exists twice in the + // class hierarchy leading to the + // present class (once as base + // class of the PrimalSolver + // class, once as base class of the + // DualSolver class), we have + // to disambiguate accesses to them + // by telling the compiler a member + // of which of these two instances + // we want to access. The way to do + // this would be identify the + // member by pointing a path + // through the class hierarchy + // which disambiguates the base + // class, for example writing + // PrimalSolver::dof_handler to + // denote the member variable + // dof_handler from the + // Solver base class of the + // PrimalSolver + // class. Unfortunately, this + // confuses gcc's version 2.96 (a + // version that was intended as a + // development snapshot, but + // delivered as system compiler by + // Red Hat in their 7.x releases) + // so much that it bails out and + // refuses to compile the code. + // + // Thus, we have to work around + // this problem. We do this by + // introducing references to the + // PrimalSolver and + // DualSolver components of the + // WeightedResidual object at + // the beginning of the + // function. Since each of these + // has an unambiguous base class + // Solver, we can access the + // member variables we want through + // these references. However, we + // are now accessing protected + // member variables of these + // classes through a pointer other + // than the this pointer (in + // fact, this is of course the + // this pointer, but not + // explicitly). This finally is the + // reason why we had to declare the + // present class a friend of the + // classes we so access. + template + void + WeightedResidual::output_solution () const + { + const PrimalSolver &primal_solver = *this; + const DualSolver &dual_solver = *this; + + ConstraintMatrix primal_hanging_node_constraints; + DoFTools::make_hanging_node_constraints (primal_solver.dof_handler, + primal_hanging_node_constraints); + primal_hanging_node_constraints.close(); + Vector dual_solution (primal_solver.dof_handler.n_dofs()); + FETools::interpolate (dual_solver.dof_handler, + dual_solver.solution, + primal_solver.dof_handler, + primal_hanging_node_constraints, + dual_solution); + + DataOut data_out; + data_out.attach_dof_handler (primal_solver.dof_handler); + + // Add the data vectors for which + // we want output. Add them both, + // the DataOut functions can + // handle as many data vectors as + // you wish to write to output: + data_out.add_data_vector (primal_solver.solution, + "primal_solution"); + data_out.add_data_vector (dual_solution, + "dual_solution"); + + data_out.build_patches (); + + std::ostringstream filename; + filename << "solution-" + << this->refinement_cycle + << ".gnuplot" + << std::ends; + + std::ofstream out (filename.str().c_str()); + data_out.write (out, DataOut::gnuplot); + } - // @sect4{Estimating on a subset of cells} + // @sect3{Estimating errors} - // Next we have the function that - // is called to estimate the error - // on a subset of cells. The - // function may be called multiply - // if the library was configured to - // use multi-threading. Here it - // goes: - template - void - WeightedResidual:: - estimate_some (const Vector &primal_solution, - const Vector &dual_weights, - const unsigned int n_threads, - const unsigned int this_thread, - Vector &error_indicators, - FaceIntegrals &face_integrals) const - { - const PrimalSolver &primal_solver = *this; - const DualSolver &dual_solver = *this; - - // At the beginning, we - // initialize two variables for - // each thread which may be - // running this function. The - // reason for these functions was - // discussed above, when the - // respective classes were - // discussed, so we here only - // point out that since they are - // local to the function that is - // spawned when running more than - // one thread, the data of these - // objects exists actually once - // per thread, so we don't have - // to take care about - // synchronising access to them. - CellData cell_data (*dual_solver.fe, - *dual_solver.quadrature, - *primal_solver.rhs_function); - FaceData face_data (*dual_solver.fe, - *dual_solver.face_quadrature); - - // Then calculate the start cell - // for this thread. We let the - // different threads run on - // interleaved cells, i.e. for - // example if we have 4 threads, - // then the first thread treates - // cells 0, 4, 8, etc, while the - // second threads works on cells 1, - // 5, 9, and so on. The reason is - // that it takes vastly more time - // to work on cells with hanging - // nodes than on regular cells, but - // such cells are not evenly - // distributed across the range of - // cell iterators, so in order to - // have the different threads do - // approximately the same amount of - // work, we have to let them work - // interleaved to the effect of a - // pseudorandom distribution of the - // `hard' cells to the different - // threads. - active_cell_iterator cell=dual_solver.dof_handler.begin_active(); - for (unsigned int t=0; - (terror_indicators - // variable: - integrate_over_cell (cell, cell_index, - primal_solution, - dual_weights, - cell_data, - error_indicators); - - // After computing the cell - // terms, turn to the face - // terms. For this, loop over - // all faces of the present - // cell, and see whether - // something needs to be - // computed on it: + // @sect4{Error estimation driver functions} + // + // As for the actual computation of + // error estimates, let's start + // with the function that drives + // all this, i.e. calls those + // functions that actually do the + // work, and finally collects the + // results. + + template + void + WeightedResidual:: + estimate_error (Vector &error_indicators) const + { + const PrimalSolver &primal_solver = *this; + const DualSolver &dual_solver = *this; + + // The first task in computing + // the error is to set up vectors + // that denote the primal + // solution, and the weights + // (z-z_h)=(z-I_hz), both in the + // finite element space for which + // we have computed the dual + // solution. For this, we have to + // interpolate the primal + // solution to the dual finite + // element space, and to subtract + // the interpolation of the + // computed dual solution to the + // primal finite element + // space. Fortunately, the + // library provides functions for + // the interpolation into larger + // or smaller finite element + // spaces, so this is mostly + // obvious. + // + // First, let's do that for the + // primal solution: it is + // cell-wise interpolated into + // the finite element space in + // which we have solved the dual + // problem: But, again as in the + // WeightedResidual::output_solution + // function we first need to + // create a ConstraintMatrix + // including the hanging node + // constraints, but this time of + // the dual finite element space. + ConstraintMatrix dual_hanging_node_constraints; + DoFTools::make_hanging_node_constraints (dual_solver.dof_handler, + dual_hanging_node_constraints); + dual_hanging_node_constraints.close(); + Vector primal_solution (dual_solver.dof_handler.n_dofs()); + FETools::interpolate (primal_solver.dof_handler, + primal_solver.solution, + dual_solver.dof_handler, + dual_hanging_node_constraints, + primal_solution); + + // Then for computing the + // interpolation of the + // numerically approximated dual + // solution z into the finite + // element space of the primal + // solution and subtracting it + // from z: use the + // interpolate_difference + // function, that gives (z-I_hz) + // in the element space of the + // dual solution. + ConstraintMatrix primal_hanging_node_constraints; + DoFTools::make_hanging_node_constraints (primal_solver.dof_handler, + primal_hanging_node_constraints); + primal_hanging_node_constraints.close(); + Vector dual_weights (dual_solver.dof_handler.n_dofs()); + FETools::interpolation_difference (dual_solver.dof_handler, + dual_hanging_node_constraints, + dual_solver.solution, + primal_solver.dof_handler, + primal_hanging_node_constraints, + dual_weights); + + // Note that this could probably + // have been more efficient since + // those constraints have been + // used previously when + // assembling matrix and right + // hand side for the primal + // problem and writing out the + // dual solution. We leave the + // optimization of the program in + // this respect as an exercise. + + // Having computed the dual + // weights we now proceed with + // computing the cell and face + // residuals of the primal + // solution. First we set up a + // map between face iterators and + // their jump term contributions + // of faces to the error + // estimator. The reason is that + // we compute the jump terms only + // once, from one side of the + // face, and want to collect them + // only afterwards when looping + // over all cells a second time. + // + // We initialize this map already + // with a value of -1e20 for all + // faces, since this value will + // strike in the results if + // something should go wrong and + // we fail to compute the value + // for a face for some + // reason. Secondly, we + // initialize the map once before + // we branch to different threads + // since this way the map's + // structure is no more modified + // by the individual threads, + // only existing entries are set + // to new values. This relieves + // us from the necessity to + // synchronise the threads + // through a mutex each time they + // write to (and modify the + // structure of) this map. + FaceIntegrals face_integrals; + for (active_cell_iterator cell=dual_solver.dof_handler.begin_active(); + cell!=dual_solver.dof_handler.end(); + ++cell) for (unsigned int face_no=0; face_no::faces_per_cell; ++face_no) + face_integrals[cell->face(face_no)] = -1e20; + + // Then set up a vector with + // error indicators. Reserve one + // slot for each cell and set it + // to zero. + error_indicators.reinit (dual_solver.dof_handler + .get_tria().n_active_cells()); + + // Now start a number of threads + // which compute the error + // formula on parts of all the + // cells, and once they are all + // started wait until they have + // all finished: + const unsigned int n_threads = multithread_info.n_default_threads; + Threads::ThreadGroup<> threads; + for (unsigned int i=0; i::estimate_some, + *this, + primal_solution, + dual_weights, + n_threads, i, + error_indicators, + face_integrals); + threads.join_all(); + + // Once the error contributions + // are computed, sum them up. For + // this, note that the cell terms + // are already set, and that only + // the edge terms need to be + // collected. Thus, loop over all + // cells and their faces, make + // sure that the contributions of + // each of the faces are there, + // and add them up. Only take + // minus one half of the jump + // term, since the other half + // will be taken by the + // neighboring cell. + unsigned int present_cell=0; + for (active_cell_iterator cell=dual_solver.dof_handler.begin_active(); + cell!=dual_solver.dof_handler.end(); + ++cell, ++present_cell) + for (unsigned int face_no=0; face_no::faces_per_cell; + ++face_no) { - // First, if this face is - // part of the boundary, - // then there is nothing - // to do. However, to - // make things easier - // when summing up the - // contributions of the - // faces of cells, we - // enter this face into - // the list of faces with - // a zero contribution to - // the error. - if (cell->face(face_no)->at_boundary()) - { - face_integrals[cell->face(face_no)] = 0; + Assert(face_integrals.find(cell->face(face_no)) != + face_integrals.end(), + ExcInternalError()); + error_indicators(present_cell) + -= 0.5*face_integrals[cell->face(face_no)]; + } + std::cout << " Estimated error=" + << std::accumulate (error_indicators.begin(), + error_indicators.end(), 0.) + << std::endl; + } + + + // @sect4{Estimating on a subset of cells} + + // Next we have the function that + // is called to estimate the error + // on a subset of cells. The + // function may be called multiply + // if the library was configured to + // use multi-threading. Here it + // goes: + template + void + WeightedResidual:: + estimate_some (const Vector &primal_solution, + const Vector &dual_weights, + const unsigned int n_threads, + const unsigned int this_thread, + Vector &error_indicators, + FaceIntegrals &face_integrals) const + { + const PrimalSolver &primal_solver = *this; + const DualSolver &dual_solver = *this; + + // At the beginning, we + // initialize two variables for + // each thread which may be + // running this function. The + // reason for these functions was + // discussed above, when the + // respective classes were + // discussed, so we here only + // point out that since they are + // local to the function that is + // spawned when running more than + // one thread, the data of these + // objects exists actually once + // per thread, so we don't have + // to take care about + // synchronising access to them. + CellData cell_data (*dual_solver.fe, + *dual_solver.quadrature, + *primal_solver.rhs_function); + FaceData face_data (*dual_solver.fe, + *dual_solver.face_quadrature); + + // Then calculate the start cell + // for this thread. We let the + // different threads run on + // interleaved cells, i.e. for + // example if we have 4 threads, + // then the first thread treates + // cells 0, 4, 8, etc, while the + // second threads works on cells 1, + // 5, 9, and so on. The reason is + // that it takes vastly more time + // to work on cells with hanging + // nodes than on regular cells, but + // such cells are not evenly + // distributed across the range of + // cell iterators, so in order to + // have the different threads do + // approximately the same amount of + // work, we have to let them work + // interleaved to the effect of a + // pseudorandom distribution of the + // `hard' cells to the different + // threads. + active_cell_iterator cell=dual_solver.dof_handler.begin_active(); + for (unsigned int t=0; + (terror_indicators + // variable: + integrate_over_cell (cell, cell_index, + primal_solution, + dual_weights, + cell_data, + error_indicators); + + // After computing the cell + // terms, turn to the face + // terms. For this, loop over + // all faces of the present + // cell, and see whether + // something needs to be + // computed on it: + for (unsigned int face_no=0; + face_no::faces_per_cell; + ++face_no) + { + // First, if this face is + // part of the boundary, + // then there is nothing + // to do. However, to + // make things easier + // when summing up the + // contributions of the + // faces of cells, we + // enter this face into + // the list of faces with + // a zero contribution to + // the error. + if (cell->face(face_no)->at_boundary()) + { + face_integrals[cell->face(face_no)] = 0; + continue; + } + + // Next, note that since + // we want to compute the + // jump terms on each + // face only once + // although we access it + // twice (if it is not at + // the boundary), we have + // to define some rules + // who is responsible for + // computing on a face: + // + // First, if the + // neighboring cell is on + // the same level as this + // one, i.e. neither + // further refined not + // coarser, then the one + // with the lower index + // within this level does + // the work. In other + // words: if the other + // one has a lower index, + // then skip work on this + // face: + if ((cell->neighbor(face_no)->has_children() == false) && + (cell->neighbor(face_no)->level() == cell->level()) && + (cell->neighbor(face_no)->index() < cell->index())) continue; - } - - // Next, note that since - // we want to compute the - // jump terms on each - // face only once - // although we access it - // twice (if it is not at - // the boundary), we have - // to define some rules - // who is responsible for - // computing on a face: - // - // First, if the - // neighboring cell is on - // the same level as this - // one, i.e. neither - // further refined not - // coarser, then the one - // with the lower index - // within this level does - // the work. In other - // words: if the other - // one has a lower index, - // then skip work on this - // face: - if ((cell->neighbor(face_no)->has_children() == false) && - (cell->neighbor(face_no)->level() == cell->level()) && - (cell->neighbor(face_no)->index() < cell->index())) - continue; - - // Likewise, we always - // work from the coarser - // cell if this and its - // neighbor differ in - // refinement. Thus, if - // the neighboring cell - // is less refined than - // the present one, then - // do nothing since we - // integrate over the - // subfaces when we visit - // the coarse cell. - if (cell->at_boundary(face_no) == false) - if (cell->neighbor(face_no)->level() < cell->level()) - continue; - - - // Now we know that we - // are in charge here, so - // actually compute the - // face jump terms. If - // the face is a regular - // one, i.e. the other - // side's cell is neither - // coarser not finer than - // this cell, then call - // one function, and if - // the cell on the other - // side is further - // refined, then use - // another function. Note - // that the case that the - // cell on the other side - // is coarser cannot - // happen since we have - // decided above that we - // handle this case when - // we pass over that - // other cell. - if (cell->face(face_no)->has_children() == false) - integrate_over_regular_face (cell, face_no, - primal_solution, - dual_weights, - face_data, - face_integrals); - else - integrate_over_irregular_face (cell, face_no, + + // Likewise, we always + // work from the coarser + // cell if this and its + // neighbor differ in + // refinement. Thus, if + // the neighboring cell + // is less refined than + // the present one, then + // do nothing since we + // integrate over the + // subfaces when we visit + // the coarse cell. + if (cell->at_boundary(face_no) == false) + if (cell->neighbor(face_no)->level() < cell->level()) + continue; + + + // Now we know that we + // are in charge here, so + // actually compute the + // face jump terms. If + // the face is a regular + // one, i.e. the other + // side's cell is neither + // coarser not finer than + // this cell, then call + // one function, and if + // the cell on the other + // side is further + // refined, then use + // another function. Note + // that the case that the + // cell on the other side + // is coarser cannot + // happen since we have + // decided above that we + // handle this case when + // we pass over that + // other cell. + if (cell->face(face_no)->has_children() == false) + integrate_over_regular_face (cell, face_no, primal_solution, dual_weights, face_data, face_integrals); - } - - // After computing the cell - // contributions and looping - // over the faces, go to the - // next cell for this - // thread. Note again that - // the cells for each of the - // threads are interleaved. - // If we are at the end of - // our workload, jump out - // of the loop. - for (unsigned int t=0; - ((t - void WeightedResidual:: - integrate_over_cell (const active_cell_iterator &cell, - const unsigned int cell_index, - const Vector &primal_solution, - const Vector &dual_weights, - CellData &cell_data, - Vector &error_indicators) const - { - // The tasks to be done are what - // appears natural from looking - // at the error estimation - // formula: first get the - // right hand side and - // Laplacian of the numerical - // solution at the quadrature - // points for the cell residual, - cell_data.fe_values.reinit (cell); - cell_data.right_hand_side - ->value_list (cell_data.fe_values.get_quadrature_points(), - cell_data.rhs_values); - cell_data.fe_values.get_function_laplacians (primal_solution, - cell_data.cell_laplacians); - - // ...then get the dual weights... - cell_data.fe_values.get_function_values (dual_weights, - cell_data.dual_weights); - - // ...and finally build the sum - // over all quadrature points and - // store it with the present - // cell: - double sum = 0; - for (unsigned int p=0; p - void WeightedResidual:: - integrate_over_regular_face (const active_cell_iterator &cell, - const unsigned int face_no, - const Vector &primal_solution, - const Vector &dual_weights, - FaceData &face_data, - FaceIntegrals &face_integrals) const - { - const unsigned int - n_q_points = face_data.fe_face_values_cell.n_quadrature_points; - - // The first step is to get the - // values of the gradients at the - // quadrature points of the - // finite element field on the - // present cell. For this, - // initialize the - // FEFaceValues object - // corresponding to this side of - // the face, and extract the - // gradients using that - // object. - face_data.fe_face_values_cell.reinit (cell, face_no); - face_data.fe_face_values_cell.get_function_grads (primal_solution, - face_data.cell_grads); - - // The second step is then to - // extract the gradients of the - // finite element solution at the - // quadrature points on the other - // side of the face, i.e. from - // the neighboring cell. - // - // For this, do a sanity check - // before: make sure that the - // neigbor actually exists (yes, - // we should not have come here - // if the neighbor did not exist, - // but in complicated software - // there are bugs, so better - // check this), and if this is - // not the case throw an error. - Assert (cell->neighbor(face_no).state() == IteratorState::valid, - ExcInternalError()); - // If we have that, then we need - // to find out with which face of - // the neighboring cell we have - // to work, i.e. the - // home-manythe neighbor the - // present cell is of the cell - // behind the present face. For - // this, there is a function, and - // we put the result into a - // variable with the name - // neighbor_neighbor: - const unsigned int - neighbor_neighbor = cell->neighbor_of_neighbor (face_no); - // Then define an abbreviation - // for the neigbor cell, - // initialize the - // FEFaceValues object on - // that cell, and extract the - // gradients on that cell: - const active_cell_iterator neighbor = cell->neighbor(face_no); - face_data.fe_face_values_neighbor.reinit (neighbor, neighbor_neighbor); - face_data.fe_face_values_neighbor.get_function_grads (primal_solution, - face_data.neighbor_grads); - - // Now that we have the gradients - // on this and the neighboring - // cell, compute the jump - // residual by multiplying the - // jump in the gradient with the - // normal vector: - for (unsigned int p=0; pface(face_no)) != face_integrals.end(), - ExcInternalError()); - Assert (face_integrals[cell->face(face_no)] == -1e20, - ExcInternalError()); - - // ...then store computed value - // at assigned location. Note - // that the stored value does not - // contain the factor 1/2 that - // appears in the error - // representation. The reason is - // that the term actually does - // not have this factor if we - // loop over all faces in the - // triangulation, but only - // appears if we write it as a - // sum over all cells and all - // faces of each cell; we thus - // visit the same face twice. We - // take account of this by using - // this factor -1/2 later, when we - // sum up the contributions for - // each cell individually. - face_integrals[cell->face(face_no)] = face_integral; - } + // As for the actual computation of + // the error contributions, first + // turn to the cell terms: + template + void WeightedResidual:: + integrate_over_cell (const active_cell_iterator &cell, + const unsigned int cell_index, + const Vector &primal_solution, + const Vector &dual_weights, + CellData &cell_data, + Vector &error_indicators) const + { + // The tasks to be done are what + // appears natural from looking + // at the error estimation + // formula: first get the + // right hand side and + // Laplacian of the numerical + // solution at the quadrature + // points for the cell residual, + cell_data.fe_values.reinit (cell); + cell_data.right_hand_side + ->value_list (cell_data.fe_values.get_quadrature_points(), + cell_data.rhs_values); + cell_data.fe_values.get_function_laplacians (primal_solution, + cell_data.cell_laplacians); + + // ...then get the dual weights... + cell_data.fe_values.get_function_values (dual_weights, + cell_data.dual_weights); + + // ...and finally build the sum + // over all quadrature points and + // store it with the present + // cell: + double sum = 0; + for (unsigned int p=0; p - void WeightedResidual:: - integrate_over_irregular_face (const active_cell_iterator &cell, + // @sect4{Computing edge term error contributions -- 1} + + // On the other hand, computation + // of the edge terms for the error + // estimate is not so + // simple. First, we have to + // distinguish between faces with + // and without hanging + // nodes. Because it is the simple + // case, we first consider the case + // without hanging nodes on a face + // (let's call this the `regular' + // case): + template + void WeightedResidual:: + integrate_over_regular_face (const active_cell_iterator &cell, const unsigned int face_no, const Vector &primal_solution, const Vector &dual_weights, FaceData &face_data, FaceIntegrals &face_integrals) const + { + const unsigned int + n_q_points = face_data.fe_face_values_cell.n_quadrature_points; + + // The first step is to get the + // values of the gradients at the + // quadrature points of the + // finite element field on the + // present cell. For this, + // initialize the + // FEFaceValues object + // corresponding to this side of + // the face, and extract the + // gradients using that + // object. + face_data.fe_face_values_cell.reinit (cell, face_no); + face_data.fe_face_values_cell.get_function_grads (primal_solution, + face_data.cell_grads); + + // The second step is then to + // extract the gradients of the + // finite element solution at the + // quadrature points on the other + // side of the face, i.e. from + // the neighboring cell. + // + // For this, do a sanity check + // before: make sure that the + // neigbor actually exists (yes, + // we should not have come here + // if the neighbor did not exist, + // but in complicated software + // there are bugs, so better + // check this), and if this is + // not the case throw an error. + Assert (cell->neighbor(face_no).state() == IteratorState::valid, + ExcInternalError()); + // If we have that, then we need + // to find out with which face of + // the neighboring cell we have + // to work, i.e. the + // home-manythe neighbor the + // present cell is of the cell + // behind the present face. For + // this, there is a function, and + // we put the result into a + // variable with the name + // neighbor_neighbor: + const unsigned int + neighbor_neighbor = cell->neighbor_of_neighbor (face_no); + // Then define an abbreviation + // for the neigbor cell, + // initialize the + // FEFaceValues object on + // that cell, and extract the + // gradients on that cell: + const active_cell_iterator neighbor = cell->neighbor(face_no); + face_data.fe_face_values_neighbor.reinit (neighbor, neighbor_neighbor); + face_data.fe_face_values_neighbor.get_function_grads (primal_solution, + face_data.neighbor_grads); + + // Now that we have the gradients + // on this and the neighboring + // cell, compute the jump + // residual by multiplying the + // jump in the gradient with the + // normal vector: + for (unsigned int p=0; pface(face_no)) != face_integrals.end(), + ExcInternalError()); + Assert (face_integrals[cell->face(face_no)] == -1e20, + ExcInternalError()); + + // ...then store computed value + // at assigned location. Note + // that the stored value does not + // contain the factor 1/2 that + // appears in the error + // representation. The reason is + // that the term actually does + // not have this factor if we + // loop over all faces in the + // triangulation, but only + // appears if we write it as a + // sum over all cells and all + // faces of each cell; we thus + // visit the same face twice. We + // take account of this by using + // this factor -1/2 later, when we + // sum up the contributions for + // each cell individually. + face_integrals[cell->face(face_no)] = face_integral; + } + + + // @sect4{Computing edge term error contributions -- 2} + + // We are still missing the case of + // faces with hanging nodes. This + // is what is covered in this + // function: + template + void WeightedResidual:: + integrate_over_irregular_face (const active_cell_iterator &cell, + const unsigned int face_no, + const Vector &primal_solution, + const Vector &dual_weights, + FaceData &face_data, + FaceIntegrals &face_integrals) const + { + // First again two abbreviations, + // and some consistency checks + // whether the function is called + // only on faces for which it is + // supposed to be called: + const unsigned int + n_q_points = face_data.fe_face_values_cell.n_quadrature_points; + + const typename DoFHandler::face_iterator + face = cell->face(face_no); + const typename DoFHandler::cell_iterator + neighbor = cell->neighbor(face_no); + Assert (neighbor.state() == IteratorState::valid, + ExcInternalError()); + Assert (neighbor->has_children(), + ExcInternalError()); + + // Then find out which neighbor + // the present cell is of the + // adjacent cell. Note that we + // will operator on the children + // of this adjacent cell, but + // that their orientation is the + // same as that of their mother, + // i.e. the neigbor direction is + // the same. + const unsigned int + neighbor_neighbor = cell->neighbor_of_neighbor (face_no); + + // Then simply do everything we + // did in the previous function + // for one face for all the + // sub-faces now: + for (unsigned int subface_no=0; + subface_non_children(); ++subface_no) + { + // Start with some checks + // again: get an iterator + // pointing to the cell + // behind the present subface + // and check whether its face + // is a subface of the one we + // are considering. If that + // were not the case, then + // there would be either a + // bug in the + // neighbor_neighbor + // function called above, or + // -- worse -- some function + // in the library did not + // keep to some underlying + // assumptions about cells, + // their children, and their + // faces. In any case, even + // though this assertion + // should not be triggered, + // it does not harm to be + // cautious, and in optimized + // mode computations the + // assertion will be removed + // anyway. + const active_cell_iterator neighbor_child + = cell->neighbor_child_on_subface (face_no, subface_no); + Assert (neighbor_child->face(neighbor_neighbor) == + cell->face(face_no)->child(subface_no), + ExcInternalError()); + + // Now start the work by + // again getting the gradient + // of the solution first at + // this side of the + // interface, + face_data.fe_subface_values_cell.reinit (cell, face_no, subface_no); + face_data.fe_subface_values_cell.get_function_grads (primal_solution, + face_data.cell_grads); + // then at the other side, + face_data.fe_face_values_neighbor.reinit (neighbor_child, + neighbor_neighbor); + face_data.fe_face_values_neighbor.get_function_grads (primal_solution, + face_data.neighbor_grads); + + // and finally building the + // jump residuals. Since we + // take the normal vector + // from the other cell this + // time, revert the sign of + // the first term compared to + // the other function: + for (unsigned int p=0; pface(neighbor_neighbor)] + = face_integral; + } + + // Once the contributions of all + // sub-faces are computed, loop + // over all sub-faces to collect + // and store them with the mother + // face for simple use when later + // collecting the error terms of + // cells. Again make safety + // checks that the entries for + // the sub-faces have been + // computed and do not carry an + // invalid value. + double sum = 0; + for (unsigned int subface_no=0; + subface_non_children(); ++subface_no) + { + Assert (face_integrals.find(face->child(subface_no)) != + face_integrals.end(), + ExcInternalError()); + Assert (face_integrals[face->child(subface_no)] != -1e20, + ExcInternalError()); + + sum += face_integrals[face->child(subface_no)]; + } + // Finally store the value with + // the parent face. + face_integrals[face] = sum; + } + + } + + + // @sect3{A simulation framework} + + // In the previous example program, + // we have had two functions that + // were used to drive the process of + // solving on subsequently finer + // grids. We extend this here to + // allow for a number of parameters + // to be passed to these functions, + // and put all of that into framework + // class. + // + // You will have noted that this + // program is built up of a number of + // small parts (evaluation functions, + // solver classes implementing + // various refinement methods, + // different dual functionals, + // different problem and data + // descriptions), which makes the + // program relatively simple to + // extend, but also allows to solve a + // large number of different problems + // by replacing one part by + // another. We reflect this + // flexibility by declaring a + // structure in the following + // framework class that holds a + // number of parameters that may be + // set to test various combinations + // of the parts of this program, and + // which can be used to test it at + // various problems and + // discretizations in a simple way. + template + struct Framework { - // First again two abbreviations, - // and some consistency checks - // whether the function is called - // only on faces for which it is - // supposed to be called: - const unsigned int - n_q_points = face_data.fe_face_values_cell.n_quadrature_points; - - const typename DoFHandler::face_iterator - face = cell->face(face_no); - const typename DoFHandler::cell_iterator - neighbor = cell->neighbor(face_no); - Assert (neighbor.state() == IteratorState::valid, - ExcInternalError()); - Assert (neighbor->has_children(), - ExcInternalError()); - - // Then find out which neighbor - // the present cell is of the - // adjacent cell. Note that we - // will operator on the children - // of this adjacent cell, but - // that their orientation is the - // same as that of their mother, - // i.e. the neigbor direction is - // the same. - const unsigned int - neighbor_neighbor = cell->neighbor_of_neighbor (face_no); - - // Then simply do everything we - // did in the previous function - // for one face for all the - // sub-faces now: - for (unsigned int subface_no=0; - subface_non_children(); ++subface_no) + public: + // First, we declare two + // abbreviations for simple use + // of the respective data types: + typedef Evaluation::EvaluationBase Evaluator; + typedef std::list EvaluatorList; + + + // Then we have the structure + // which declares all the + // parameters that may be set. In + // the default constructor of the + // structure, these values are + // all set to default values, for + // simple use. + struct ProblemDescription { - // Start with some checks - // again: get an iterator - // pointing to the cell - // behind the present subface - // and check whether its face - // is a subface of the one we - // are considering. If that - // were not the case, then - // there would be either a - // bug in the - // neighbor_neighbor - // function called above, or - // -- worse -- some function - // in the library did not - // keep to some underlying - // assumptions about cells, - // their children, and their - // faces. In any case, even - // though this assertion - // should not be triggered, - // it does not harm to be - // cautious, and in optimized - // mode computations the - // assertion will be removed - // anyway. - const active_cell_iterator neighbor_child - = cell->neighbor_child_on_subface (face_no, subface_no); - Assert (neighbor_child->face(neighbor_neighbor) == - cell->face(face_no)->child(subface_no), - ExcInternalError()); - - // Now start the work by - // again getting the gradient - // of the solution first at - // this side of the - // interface, - face_data.fe_subface_values_cell.reinit (cell, face_no, subface_no); - face_data.fe_subface_values_cell.get_function_grads (primal_solution, - face_data.cell_grads); - // then at the other side, - face_data.fe_face_values_neighbor.reinit (neighbor_child, - neighbor_neighbor); - face_data.fe_face_values_neighbor.get_function_grads (primal_solution, - face_data.neighbor_grads); - - // and finally building the - // jump residuals. Since we - // take the normal vector - // from the other cell this - // time, revert the sign of - // the first term compared to - // the other function: - for (unsigned int p=0; pface(neighbor_neighbor)] - = face_integral; - } + // First allow for the + // degrees of the piecewise + // polynomials by which the + // primal and dual problems + // will be discretized. They + // default to (bi-, + // tri-)linear ansatz + // functions for the primal, + // and (bi-, tri-)quadratic + // ones for the dual + // problem. If a refinement + // criterion is chosen that + // does not need the solution + // of a dual problem, the + // value of the dual finite + // element degree is of + // course ignored. + unsigned int primal_fe_degree; + unsigned int dual_fe_degree; + + // Then have an object that + // describes the problem + // type, i.e. right hand + // side, domain, boundary + // values, etc. The pointer + // needed here defaults to + // the Null pointer, i.e. you + // will have to set it in + // actual instances of this + // object to make it useful. + SmartPointer > data; + + // Since we allow to use + // different refinement + // criteria (global + // refinement, refinement by + // the Kelly error indicator, + // possibly with a weight, + // and using the dual + // estimator), define a + // number of enumeration + // values, and subsequently a + // variable of that type. It + // will default to + // dual_weighted_error_estimator. + enum RefinementCriterion { + dual_weighted_error_estimator, + global_refinement, + kelly_indicator, + weighted_kelly_indicator + }; + + RefinementCriterion refinement_criterion; + + // Next, an object that + // describes the dual + // functional. It is only + // needed if the dual + // weighted residual + // refinement is chosen, and + // also defaults to a Null + // pointer. + SmartPointer > dual_functional; + + // Then a list of evaluation + // objects. Its default value + // is empty, i.e. no + // evaluation objects. + EvaluatorList evaluator_list; + + // Next to last, a function + // that is used as a weight + // to the + // RefinementWeightedKelly + // class. The default value + // of this pointer is zero, + // but you have to set it to + // some other value if you + // want to use the + // weighted_kelly_indicator + // refinement criterion. + SmartPointer > kelly_weight; + + // Finally, we have a + // variable that denotes the + // maximum number of degrees + // of freedom we allow for + // the (primal) + // discretization. If it is + // exceeded, we stop the + // process of solving and + // intermittend mesh + // refinement. Its default + // value is 20,000. + unsigned int max_degrees_of_freedom; + + // Finally the default + // constructor of this class: + ProblemDescription (); + }; - // Once the contributions of all - // sub-faces are computed, loop - // over all sub-faces to collect - // and store them with the mother - // face for simple use when later - // collecting the error terms of - // cells. Again make safety - // checks that the entries for - // the sub-faces have been - // computed and do not carry an - // invalid value. - double sum = 0; - for (unsigned int subface_no=0; - subface_non_children(); ++subface_no) - { - Assert (face_integrals.find(face->child(subface_no)) != - face_integrals.end(), - ExcInternalError()); - Assert (face_integrals[face->child(subface_no)] != -1e20, - ExcInternalError()); - - sum += face_integrals[face->child(subface_no)]; - } - // Finally store the value with - // the parent face. - face_integrals[face] = sum; - } - -} + // The driver framework class + // only has one method which + // calls solver and mesh + // refinement intermittently, and + // does some other small tasks in + // between. Since it does not + // need data besides the + // parameters given to it, we + // make it static: + static void run (const ProblemDescription &descriptor); + }; - // @sect3{A simulation framework} - - // In the previous example program, - // we have had two functions that - // were used to drive the process of - // solving on subsequently finer - // grids. We extend this here to - // allow for a number of parameters - // to be passed to these functions, - // and put all of that into framework - // class. - // - // You will have noted that this - // program is built up of a number of - // small parts (evaluation functions, - // solver classes implementing - // various refinement methods, - // different dual functionals, - // different problem and data - // descriptions), which makes the - // program relatively simple to - // extend, but also allows to solve a - // large number of different problems - // by replacing one part by - // another. We reflect this - // flexibility by declaring a - // structure in the following - // framework class that holds a - // number of parameters that may be - // set to test various combinations - // of the parts of this program, and - // which can be used to test it at - // various problems and - // discretizations in a simple way. -template -struct Framework -{ - public: - // First, we declare two - // abbreviations for simple use - // of the respective data types: - typedef Evaluation::EvaluationBase Evaluator; - typedef std::list EvaluatorList; - - - // Then we have the structure - // which declares all the - // parameters that may be set. In - // the default constructor of the - // structure, these values are - // all set to default values, for - // simple use. - struct ProblemDescription - { - // First allow for the - // degrees of the piecewise - // polynomials by which the - // primal and dual problems - // will be discretized. They - // default to (bi-, - // tri-)linear ansatz - // functions for the primal, - // and (bi-, tri-)quadratic - // ones for the dual - // problem. If a refinement - // criterion is chosen that - // does not need the solution - // of a dual problem, the - // value of the dual finite - // element degree is of - // course ignored. - unsigned int primal_fe_degree; - unsigned int dual_fe_degree; - - // Then have an object that - // describes the problem - // type, i.e. right hand - // side, domain, boundary - // values, etc. The pointer - // needed here defaults to - // the Null pointer, i.e. you - // will have to set it in - // actual instances of this - // object to make it useful. - SmartPointer > data; - - // Since we allow to use - // different refinement - // criteria (global - // refinement, refinement by - // the Kelly error indicator, - // possibly with a weight, - // and using the dual - // estimator), define a - // number of enumeration - // values, and subsequently a - // variable of that type. It - // will default to - // dual_weighted_error_estimator. - enum RefinementCriterion { - dual_weighted_error_estimator, - global_refinement, - kelly_indicator, - weighted_kelly_indicator - }; + // As for the implementation, first + // the constructor of the parameter + // object, setting all values to + // their defaults: + template + Framework::ProblemDescription::ProblemDescription () + : + primal_fe_degree (1), + dual_fe_degree (2), + refinement_criterion (dual_weighted_error_estimator), + max_degrees_of_freedom (20000) + {} - RefinementCriterion refinement_criterion; - - // Next, an object that - // describes the dual - // functional. It is only - // needed if the dual - // weighted residual - // refinement is chosen, and - // also defaults to a Null - // pointer. - SmartPointer > dual_functional; - - // Then a list of evaluation - // objects. Its default value - // is empty, i.e. no - // evaluation objects. - EvaluatorList evaluator_list; - - // Next to last, a function - // that is used as a weight - // to the - // RefinementWeightedKelly - // class. The default value - // of this pointer is zero, - // but you have to set it to - // some other value if you - // want to use the - // weighted_kelly_indicator - // refinement criterion. - SmartPointer > kelly_weight; - - // Finally, we have a - // variable that denotes the - // maximum number of degrees - // of freedom we allow for - // the (primal) - // discretization. If it is - // exceeded, we stop the - // process of solving and - // intermittend mesh - // refinement. Its default - // value is 20,000. - unsigned int max_degrees_of_freedom; - - // Finally the default - // constructor of this class: - ProblemDescription (); - }; - // The driver framework class - // only has one method which - // calls solver and mesh - // refinement intermittently, and - // does some other small tasks in - // between. Since it does not - // need data besides the - // parameters given to it, we - // make it static: - static void run (const ProblemDescription &descriptor); -}; - - - // As for the implementation, first - // the constructor of the parameter - // object, setting all values to - // their defaults: -template -Framework::ProblemDescription::ProblemDescription () - : - primal_fe_degree (1), - dual_fe_degree (2), - refinement_criterion (dual_weighted_error_estimator), - max_degrees_of_freedom (20000) -{} - - - - // Then the function which drives the - // whole process: -template -void Framework::run (const ProblemDescription &descriptor) -{ - // First create a triangulation - // from the given data object, - Triangulation - triangulation (Triangulation::smoothing_on_refinement); - descriptor.data->create_coarse_grid (triangulation); - - // then a set of finite elements - // and appropriate quadrature - // formula: - const FE_Q primal_fe(descriptor.primal_fe_degree); - const FE_Q dual_fe(descriptor.dual_fe_degree); - const QGauss quadrature(descriptor.dual_fe_degree+1); - const QGauss face_quadrature(descriptor.dual_fe_degree+1); - - // Next, select one of the classes - // implementing different - // refinement criteria. - LaplaceSolver::Base * solver = 0; - switch (descriptor.refinement_criterion) - { - case ProblemDescription::dual_weighted_error_estimator: - { - solver - = new LaplaceSolver::WeightedResidual (triangulation, - primal_fe, - dual_fe, - quadrature, - face_quadrature, - descriptor.data->get_right_hand_side(), - descriptor.data->get_boundary_values(), - *descriptor.dual_functional); - break; - } - - case ProblemDescription::global_refinement: - { - solver - = new LaplaceSolver::RefinementGlobal (triangulation, - primal_fe, - quadrature, - face_quadrature, - descriptor.data->get_right_hand_side(), - descriptor.data->get_boundary_values()); - break; - } - - case ProblemDescription::kelly_indicator: + + // Then the function which drives the + // whole process: + template + void Framework::run (const ProblemDescription &descriptor) + { + // First create a triangulation + // from the given data object, + Triangulation + triangulation (Triangulation::smoothing_on_refinement); + descriptor.data->create_coarse_grid (triangulation); + + // then a set of finite elements + // and appropriate quadrature + // formula: + const FE_Q primal_fe(descriptor.primal_fe_degree); + const FE_Q dual_fe(descriptor.dual_fe_degree); + const QGauss quadrature(descriptor.dual_fe_degree+1); + const QGauss face_quadrature(descriptor.dual_fe_degree+1); + + // Next, select one of the classes + // implementing different + // refinement criteria. + LaplaceSolver::Base * solver = 0; + switch (descriptor.refinement_criterion) { - solver - = new LaplaceSolver::RefinementKelly (triangulation, - primal_fe, - quadrature, - face_quadrature, - descriptor.data->get_right_hand_side(), - descriptor.data->get_boundary_values()); - break; + case ProblemDescription::dual_weighted_error_estimator: + { + solver + = new LaplaceSolver::WeightedResidual (triangulation, + primal_fe, + dual_fe, + quadrature, + face_quadrature, + descriptor.data->get_right_hand_side(), + descriptor.data->get_boundary_values(), + *descriptor.dual_functional); + break; + } + + case ProblemDescription::global_refinement: + { + solver + = new LaplaceSolver::RefinementGlobal (triangulation, + primal_fe, + quadrature, + face_quadrature, + descriptor.data->get_right_hand_side(), + descriptor.data->get_boundary_values()); + break; + } + + case ProblemDescription::kelly_indicator: + { + solver + = new LaplaceSolver::RefinementKelly (triangulation, + primal_fe, + quadrature, + face_quadrature, + descriptor.data->get_right_hand_side(), + descriptor.data->get_boundary_values()); + break; + } + + case ProblemDescription::weighted_kelly_indicator: + { + solver + = new LaplaceSolver::RefinementWeightedKelly (triangulation, + primal_fe, + quadrature, + face_quadrature, + descriptor.data->get_right_hand_side(), + descriptor.data->get_boundary_values(), + *descriptor.kelly_weight); + break; + } + + default: + AssertThrow (false, ExcInternalError()); } - case ProblemDescription::weighted_kelly_indicator: + // Now that all objects are in + // place, run the main loop. The + // stopping criterion is + // implemented at the bottom of the + // loop. + // + // In the loop, first set the new + // cycle number, then solve the + // problem, output its solution(s), + // apply the evaluation objects to + // it, then decide whether we want + // to refine the mesh further and + // solve again on this mesh, or + // jump out of the loop. + for (unsigned int step=0; true; ++step) { - solver - = new LaplaceSolver::RefinementWeightedKelly (triangulation, - primal_fe, - quadrature, - face_quadrature, - descriptor.data->get_right_hand_side(), - descriptor.data->get_boundary_values(), - *descriptor.kelly_weight); - break; + std::cout << "Refinement cycle: " << step + << std::endl; + + solver->set_refinement_cycle (step); + solver->solve_problem (); + solver->output_solution (); + + std::cout << " Number of degrees of freedom=" + << solver->n_dofs() << std::endl; + + for (typename EvaluatorList::const_iterator + e = descriptor.evaluator_list.begin(); + e != descriptor.evaluator_list.end(); ++e) + { + (*e)->set_refinement_cycle (step); + solver->postprocess (**e); + } + + + if (solver->n_dofs() < descriptor.max_degrees_of_freedom) + solver->refine_grid (); + else + break; } - - default: - AssertThrow (false, ExcInternalError()); - } - - // Now that all objects are in - // place, run the main loop. The - // stopping criterion is - // implemented at the bottom of the - // loop. - // - // In the loop, first set the new - // cycle number, then solve the - // problem, output its solution(s), - // apply the evaluation objects to - // it, then decide whether we want - // to refine the mesh further and - // solve again on this mesh, or - // jump out of the loop. - for (unsigned int step=0; true; ++step) - { - std::cout << "Refinement cycle: " << step - << std::endl; - - solver->set_refinement_cycle (step); - solver->solve_problem (); - solver->output_solution (); - - std::cout << " Number of degrees of freedom=" - << solver->n_dofs() << std::endl; - - for (typename EvaluatorList::const_iterator - e = descriptor.evaluator_list.begin(); - e != descriptor.evaluator_list.end(); ++e) - { - (*e)->set_refinement_cycle (step); - solver->postprocess (**e); - } - - if (solver->n_dofs() < descriptor.max_degrees_of_freedom) - solver->refine_grid (); - else - break; - } + // After the loop has run, clean up + // the screen, and delete objects + // no more needed: + std::cout << std::endl; + delete solver; + solver = 0; + } - // After the loop has run, clean up - // the screen, and delete objects - // no more needed: - std::cout << std::endl; - delete solver; - solver = 0; } - // @sect3{The main function} // Here finally comes the main @@ -3936,11 +3939,14 @@ void Framework::run (const ProblemDescription &descriptor) // etc), and passes them packed into // a structure to the frame work // class above. -int main () +int main () { - deallog.depth_console (0); try { + using namespace dealii; + using namespace Step14; + + deallog.depth_console (0); // Describe the problem we want // to solve here by passing a // descriptor object to the @@ -3983,7 +3989,7 @@ int main () // can also use // CurvedRidges@: descriptor.data = new Data::SetUp,dim> (); - + // Next set first a dual // functional, then a list of // evaluation objects. We @@ -4016,12 +4022,12 @@ int main () const Point evaluation_point (0.75, 0.75); descriptor.dual_functional = new DualFunctional::PointValueEvaluation (evaluation_point); - + Evaluation::PointValueEvaluation postprocessor1 (evaluation_point); Evaluation::GridOutput postprocessor2 ("grid"); - + descriptor.evaluator_list.push_back (&postprocessor1); descriptor.evaluator_list.push_back (&postprocessor2); @@ -4031,7 +4037,7 @@ int main () // stop refining the mesh // further: descriptor.max_degrees_of_freedom = 20000; - + // Finally pass the descriptor // object to a function that // runs the entire solution @@ -4054,7 +4060,7 @@ int main () << std::endl; return 1; } - catch (...) + catch (...) { std::cerr << std::endl << std::endl << "----------------------------------------------------"