/* --------------------------------------------------------------------- * * Copyright (C) 1999 - 2013 by the deal.II authors * * This file is part of the deal.II library. * * The deal.II library is free software; you can use it, redistribute * it, and/or modify it under the terms of the GNU Lesser General * Public License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * The full text of the license can be found in the file LICENSE at * the top level of the deal.II distribution. * * --------------------------------------------------------------------- * * Author: Wolfgang Bangerth, University of Heidelberg, 1999 */ // @sect3{Include files} // Again, the first few include files are already known, so we won't comment // on them: #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // This one is new. We want to read a triangulation from disk, and the class // which does this is declared in the following file: #include // We will use a circular domain, and the object describing the boundary of it // comes from this file: #include // This is C++ ... #include // ... and this is too: We will convert integers to strings using the C++ // stringstream class ostringstream: #include // Finally, this has been discussed in previous tutorial programs before: using namespace dealii; // @sect3{The Step5 class template} // The main class is mostly as in the previous example. The most visible // change is that the function make_grid_and_dofs has been // removed, since creating the grid is now done in the run // function and the rest of its functionality is now in // setup_system. Apart from this, everything is as before. template class Step5 { public: Step5 (); void run (); private: void setup_system (); void assemble_system (); void solve (); void output_results (const unsigned int cycle) const; Triangulation triangulation; FE_Q fe; DoFHandler dof_handler; SparsityPattern sparsity_pattern; SparseMatrix system_matrix; Vector solution; Vector system_rhs; }; // @sect3{Nonconstant coefficients, using Assert} // In step-4, we showed how to use non-constant boundary values and right hand // side. In this example, we want to use a variable coefficient in the // elliptic operator instead. Of course, the suitable object is a // Function, as we have used for the right hand side and boundary // values in the last example. We will use it again, but we implement another // function value_list which takes a list of points and returns // the values of the function at these points as a list. The reason why such a // function is reasonable although we can get all the information from the // value function as well will be explained below when assembling // the matrix. // // The need to declare a seemingly useless default constructor exists here // just as in the previous example. template class Coefficient : public Function { public: Coefficient () : Function() {} virtual double value (const Point &p, const unsigned int component = 0) const; virtual void value_list (const std::vector > &points, std::vector &values, const unsigned int component = 0) const; }; // This is the implementation of the coefficient function for a single // point. We let it return 20 if the distance to the origin is less than 0.5, // and 1 otherwise. As in the previous example, we simply ignore the second // parameter of the function that is used to denote different components of // vector-valued functions (we deal only with a scalar function here, after // all): template double Coefficient::value (const Point &p, const unsigned int /*component*/) const { if (p.square() < 0.5*0.5) return 20; else return 1; } // And this is the function that returns the value of the coefficient at a // whole list of points at once. Of course, we need to make sure that the // values are the same as if we would ask the value function for // each point individually. // // This method takes three parameters: a list of points at which to evaluate // the function, a list that will hold the values at these points, and the // vector component that should be zero here since we only have a single // scalar function. Now, of course the size of the output array // (values) must be the same as that of the input array // (points), and we could simply assume that. However, in // practice, it turns out that more than 90 per cent of programming errors are // invalid function parameters such as invalid array sizes, etc, so we should // try to make sure that the parameters are valid. For this, the // Assert macro is a good means, since it makes sure that the // condition which is given as first argument is valid, and if not throws an // exception (its second argument) which will usually terminate the program // giving information where the error occurred and what the reason was. This // generally reduces the time to find programming errors dramatically and we // have found assertions an invaluable means to program fast. // // On the other hand, all these checks (there are more than 4200 of them in // the library at present) should not slow down the program too much if you // want to do large computations. To this end, the Assert macro // is only used in debug mode and expands to nothing if in optimized // mode. Therefore, while you test your program on small problems and debug // it, the assertions will tell you where the problems are. Once your program // is stable, you can switch off debugging and the program will run your real // computations without the assertions and at maximum speed. (In fact, it // turns out the switching off all the checks in the library that prevent you // from calling functions with the wrong arguments by switching to optimized // mode, makes most programs run faster by about a factor of four. This // should, however, not try to induce you to always run in optimized mode: // Most people who have tried that soon realize that they introduce lots of // errors that would have easily been caught had they run the program in debug // mode while developing.) For those who want to try: The way to switch from // debug mode to optimized mode is to go edit the Makefile in this // directory. It should have a line debug-mode = on; simply // replace it by debug-mode = off and recompile your program. The // output of the make program should already indicate to you that // the program is now compiled in optimized mode, and it will later also be // linked to libraries that have been compiled for optimized mode. // // Here, as has been said above, we would like to make sure that the size of // the two arrays is equal, and if not throw an exception. Comparing the sizes // of two arrays is one of the most frequent checks, which is why there is // already an exception class ExcDimensionMismatch that takes the // sizes of two vectors and prints some output in case the condition is // violated: template void Coefficient::value_list (const std::vector > &points, std::vector &values, const unsigned int component) const { Assert (values.size() == points.size(), ExcDimensionMismatch (values.size(), points.size())); // Since examples are not very good if they do not demonstrate their point, // we will show how to trigger this exception at the end of the main // program, and what output results from this (see the Results // section of this example program). You will certainly notice that the // output is quite well suited to quickly find what the problem is and what // parameters are expected. An additional plus is that if the program is run // inside a debugger, it will stop at the point where the exception is // triggered, so you can go up the call stack to immediately find the place // where the the array with the wrong size was set up. // While we're at it, we can do another check: the coefficient is a scalar, // but the Function class also represents vector-valued // function. A scalar function must therefore be considered as a // vector-valued function with only one component, so the only valid // component for which a user might ask is zero (we always count from // zero). The following assertion checks this. If the condition in the // Assert call is violated, an exception of type // ExcRange will be triggered; that class takes the violating // index as first argument, and the second and third arguments denote a // range that includes the left point but is open at the right, i.e. here // the interval [0,1). For integer arguments, this means that the only value // in the range is the zero, of course. (The interval is half open since we // also want to write exceptions like ExcRange(i,0,v.size()), // where an index must be between zero but less than the size of an // array. To save us the effort of writing v.size()-1 in many // places, the range is defined as half-open.) Assert (component == 0, ExcIndexRange (component, 0, 1)); // The rest of the function is uneventful: we define n_q_points // as an abbreviation for the number of points for which function values are // requested, and then simply fill the output value: const unsigned int n_points = points.size(); for (unsigned int i=0; iStep5 class implementation} // @sect4{Step5::Step5} // This function is as before. template Step5::Step5 () : fe (1), dof_handler (triangulation) {} // @sect4{Step5::setup_system} // This is the function make_grid_and_dofs from the previous // example, minus the generation of the grid. Everything else is unchanged: template void Step5::setup_system () { dof_handler.distribute_dofs (fe); std::cout << " Number of degrees of freedom: " << dof_handler.n_dofs() << std::endl; CompressedSparsityPattern c_sparsity(dof_handler.n_dofs()); DoFTools::make_sparsity_pattern (dof_handler, c_sparsity); sparsity_pattern.copy_from(c_sparsity); system_matrix.reinit (sparsity_pattern); solution.reinit (dof_handler.n_dofs()); system_rhs.reinit (dof_handler.n_dofs()); } // @sect4{Step5::assemble_system} // As in the previous examples, this function is not changed much with regard // to its functionality, but there are still some optimizations which we will // show. For this, it is important to note that if efficient solvers are used // (such as the preconditions CG method), assembling the matrix and right hand // side can take a comparable time, and you should think about using one or // two optimizations at some places. // // What we will show here is how we can avoid calls to the shape_value, // shape_grad, and quadrature_point functions of the FEValues object, and in // particular optimize away most of the virtual function calls of the Function // object. The way to do so will be explained in the following, while those // parts of this function that are not changed with respect to the previous // example are not commented on. // // The first parts of the function are completely unchanged from before: template void Step5::assemble_system () { QGauss quadrature_formula(2); FEValues fe_values (fe, quadrature_formula, update_values | update_gradients | update_quadrature_points | update_JxW_values); const unsigned int dofs_per_cell = fe.dofs_per_cell; const unsigned int n_q_points = quadrature_formula.size(); FullMatrix cell_matrix (dofs_per_cell, dofs_per_cell); Vector cell_rhs (dofs_per_cell); std::vector local_dof_indices (dofs_per_cell); // Here is one difference: for this program, we will again use a constant // right hand side function and zero boundary values, but a variable // coefficient. We have already declared the class that represents this // coefficient above, so we only have to declare a corresponding object // here. // // Then, below, we will ask the coefficient function object to // compute the values of the coefficient at all quadrature points on one // cell at once. The reason for this is that, if you look back at how we did // this in step-4, you will realize that we called the function computing // the right hand side value inside nested loops over all degrees of freedom // and over all quadrature points, i.e. dofs_per_cell*n_q_points times. For // the coefficient that is used inside the matrix, this would actually be // dofs_per_cell*dofs_per_cell*n_q_points. On the other hand, the function // will of course return the same value every time it is called with the // same quadrature point, independently of what shape function we presently // treat; secondly, these are virtual function calls, so are rather // expensive. Obviously, there are only n_q_point different values, and we // shouldn't call the function more often than that. Or, even better than // this, compute all of these values at once, and get away with a single // function call per cell. // // This is exactly what we are going to do. For this, we need some space to // store the values in. We therefore also have to declare an array to hold // these values: const Coefficient coefficient; std::vector coefficient_values (n_q_points); // Next is the typical loop over all cells to compute local contributions // and then to transfer them into the global matrix and vector. // // The only two things in which this loop differs from step-4 is that we // want to compute the value of the coefficient in all quadrature points on // the present cell at the beginning, and then use it in the computation of // the local contributions. This is what we do in the call to // coefficient.value_list in the fourth line of the loop. // // The second change is how we make use of this coefficient in computing the // cell matrix contributions. This is in the obvious way, and not worth more // comments. For the right hand side, we use a constant value again. typename DoFHandler::active_cell_iterator cell = dof_handler.begin_active(), endc = dof_handler.end(); for (; cell!=endc; ++cell) { cell_matrix = 0; cell_rhs = 0; fe_values.reinit (cell); coefficient.value_list (fe_values.get_quadrature_points(), coefficient_values); for (unsigned int q_index=0; q_indexget_dof_indices (local_dof_indices); for (unsigned int i=0; i boundary_values; VectorTools::interpolate_boundary_values (dof_handler, 0, ZeroFunction(), boundary_values); MatrixTools::apply_boundary_values (boundary_values, system_matrix, solution, system_rhs); } // @sect4{Step5::solve} // The solution process again looks mostly like in the previous // examples. However, we will now use a preconditioned conjugate gradient // algorithm. It is not very difficult to make this change. In fact, the only // thing we have to alter is that we need an object which will act as a // preconditioner. We will use SSOR (symmetric successive overrelaxation), // with a relaxation factor of 1.2. For this purpose, the // SparseMatrix class has a function which does one SSOR step, // and we need to package the address of this function together with the // matrix on which it should act (which is the matrix to be inverted) and the // relaxation factor into one object. The PreconditionSSOR class // does this for us. (PreconditionSSOR class takes a template // argument denoting the matrix type it is supposed to work on. The default // value is SparseMatrix@, which is exactly what we need // here, so we simply stick with the default and do not specify anything in // the angle brackets.) // // Note that for the present case, SSOR doesn't really perform much better // than most other preconditioners (though better than no preconditioning at // all). A brief comparison of different preconditioners is presented in the // Results section of the next tutorial program, step-6. // // With this, the rest of the function is trivial: instead of the // PreconditionIdentity object we have created before, we now use // the preconditioner we have declared, and the CG solver will do the rest for // us: template void Step5::solve () { SolverControl solver_control (1000, 1e-12); SolverCG<> solver (solver_control); PreconditionSSOR<> preconditioner; preconditioner.initialize(system_matrix, 1.2); solver.solve (system_matrix, solution, system_rhs, preconditioner); std::cout << " " << solver_control.last_step() << " CG iterations needed to obtain convergence." << std::endl; } // @sect4{Step5::output_results and setting output flags} // Writing output to a file is mostly the same as for the previous example, // but here we will show how to modify some output options and how to // construct a different filename for each refinement cycle. template void Step5::output_results (const unsigned int cycle) const { DataOut data_out; data_out.attach_dof_handler (dof_handler); data_out.add_data_vector (solution, "solution"); data_out.build_patches (); // For this example, we would like to write the output directly to a file in // Encapsulated Postscript (EPS) format. The library supports this, but // things may be a bit more difficult sometimes, since EPS is a printing // format, unlike most other supported formats which serve as input for // graphical tools. Therefore, you can't scale or rotate the image after it // has been written to disk, and you have to decide about the viewpoint or // the scaling in advance. // // The defaults in the library are usually quite reasonable, and regarding // viewpoint and scaling they coincide with the defaults of // Gnuplot. However, since this is a tutorial, we will demonstrate how to // change them. For this, we first have to generate an object describing the // flags for EPS output (similar flag classes exist for all supported output // formats): DataOutBase::EpsFlags eps_flags; // They are initialized with the default values, so we only have to change // those that we don't like. For example, we would like to scale the z-axis // differently (stretch each data point in z-direction by a factor of four): eps_flags.z_scaling = 4; // Then we would also like to alter the viewpoint from which we look at the // solution surface. The default is at an angle of 60 degrees down from the // vertical axis, and 30 degrees rotated against it in mathematical positive // sense. We raise our viewpoint a bit and look more along the y-axis: eps_flags.azimut_angle = 40; eps_flags.turn_angle = 10; // That shall suffice. There are more flags, for example whether to draw the // mesh lines, which data vectors to use for colorization of the interior of // the cells, and so on. You may want to take a look at the documentation of // the EpsFlags structure to get an overview of what is possible. // // The only thing still to be done, is to tell the output object to use // these flags: data_out.set_flags (eps_flags); // The above way to modify flags requires recompilation each time we would // like to use different flags. This is inconvenient, and we will see more // advanced ways in step-19 where the output flags are determined at run // time using an input file (step-19 doesn't show many other things; you // should feel free to read over it even if you haven't done step-6 to // step-18 yet). // Finally, we need the filename to which the results are to be written. We // would like to have it of the form solution-N.eps, where N is // the number of the refinement cycle. Thus, we have to convert an integer // to a part of a string; this can be done using the sprintf // function, but in C++ there is a more elegant way: write everything into a // special stream (just like writing into a file or to the screen) and // retrieve what you wrote as a string. This applies the usual conversions // from integer to strings, and one could as well use stream modifiers such // as setw, setprecision, and so on. In C++, you // can do this by using the so-called stringstream classes: std::ostringstream filename; // In order to now actually generate a filename, we fill the stringstream // variable with the base of the filename, then the number part, and finally // the suffix indicating the file type: filename << "solution-" << cycle << ".eps"; // We can get whatever we wrote to the stream using the str() // function. The result is a string which we have to convert to a char* // using the c_str() function. Use that as filename for the // output stream and then write the data to the file: std::ofstream output (filename.str().c_str()); data_out.write_eps (output); } // @sect4{Step5::run} // The second to last thing in this program is the definition of the // run() function. In contrast to the previous programs, we will // compute on a sequence of meshes that after each iteration is globally // refined. The function therefore consists of a loop over 6 cycles. In each // cycle, we first print the cycle number, and then have to decide what to do // with the mesh. If this is not the first cycle, we simply refine the // existing mesh once globally. Before running through these cycles, however, // we have to generate a mesh: // In previous examples, we have already used some of the functions from the // GridGenerator class. Here we would like to read a grid from a // file where the cells are stored and which may originate from someone else, // or may be the product of a mesh generator tool. // // In order to read a grid from a file, we generate an object of data type // GridIn and associate the triangulation to it (i.e. we tell it to fill our // triangulation object when we ask it to read the file). Then we open the // respective file and initialize the triangulation with the data in the file: template void Step5::run () { GridIn grid_in; grid_in.attach_triangulation (triangulation); std::ifstream input_file("circle-grid.inp"); // We would now like to read the file. However, the input file is only for a // two-dimensional triangulation, while this function is a template for // arbitrary dimension. Since this is only a demonstration program, we will // not use different input files for the different dimensions, but rather // kill the whole program if we are not in 2D: Assert (dim==2, ExcInternalError()); // ExcInternalError is a globally defined exception, which may be thrown // whenever something is terribly wrong. Usually, one would like to use more // specific exceptions, and particular in this case one would of course try // to do something else if dim is not equal to two, e.g. create // a grid using library functions. Aborting a program is usually not a good // idea and assertions should really only be used for exceptional cases // which should not occur, but might due to stupidity of the programmer, // user, or someone else. The situation above is not a very clever use of // Assert, but again: this is a tutorial and it might be worth to show what // not to do, after all. // So if we got past the assertion, we know that dim==2, and we can now // actually read the grid. It is in UCD (unstructured cell data) format (but // the ending of the UCD-file is inp), as // supported as input format by the AVS Explorer (a visualization program), // for example: grid_in.read_ucd (input_file); // If you like to use another input format, you have to use an other // grid_in.read_xxx function. (See the documentation of the // GridIn class to find out what input formats are presently // supported.) // The grid in the file describes a circle. Therefore we have to use a // boundary object which tells the triangulation where to put new points on // the boundary when the grid is refined. This works in the same way as in // the first example. Note that the HyperBallBoundary constructor takes two // parameters, the center of the ball and the radius, but that their default // (the origin and 1.0) are the ones which we would like to use here. static const HyperBallBoundary boundary; triangulation.set_boundary (0, boundary); for (unsigned int cycle=0; cycle<6; ++cycle) { std::cout << "Cycle " << cycle << ':' << std::endl; if (cycle != 0) triangulation.refine_global (1); // Now that we have a mesh for sure, we write some output and do all the // things that we have already seen in the previous examples. std::cout << " Number of active cells: " << triangulation.n_active_cells() << std::endl << " Total number of cells: " << triangulation.n_cells() << std::endl; setup_system (); assemble_system (); solve (); output_results (cycle); } } // @sect3{The main function} // The main function looks mostly like the one in the previous example, so we // won't comment on it further: int main () { deallog.depth_console (0); Step5<2> laplace_problem_2d; laplace_problem_2d.run (); // Finally, we have promised to trigger an exception in the // Coefficient class through the Assert macro we // have introduced there. For this, we have to call its // value_list function with two arrays of different size (the // number in parentheses behind the declaration of the object). We have // commented out these lines in order to allow the program to exit // gracefully in normal situations (we use the program in day-to-day testing // of changes to the library as well), so you will only get the exception by // un-commenting the following lines. Take a look at the Results section of // the program to see what happens when the code is actually run: /* Coefficient<2> coefficient; std::vector > points (2); std::vector coefficient_values (1); coefficient.value_list (points, coefficient_values); */ return 0; }