1 // The libMesh Finite Element Library.
2 // Copyright (C) 2002-2020 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner
3 
4 // This library is free software; you can redistribute it and/or
5 // modify it under the terms of the GNU Lesser General Public
6 // License as published by the Free Software Foundation; either
7 // version 2.1 of the License, or (at your option) any later version.
8 
9 // This library is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 // Lesser General Public License for more details.
13 
14 // You should have received a copy of the GNU Lesser General Public
15 // License along with this library; if not, write to the Free Software
16 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17 
18 
19 
20 // <h1>Adaptivity Example 1 - Solving 1D PDE Using Adaptive Mesh Refinement</h1>
21 // \author Benjamin S. Kirk
22 // \date 2003
23 //
24 // This example demonstrates how to solve a simple 1D problem
25 // using adaptive mesh refinement. The PDE that is solved is:
26 // -epsilon*u''(x) + u(x) = 1, on the domain [0,1] with boundary conditions
27 // u(0) = u(1) = 0 and where epsilon << 1.
28 //
29 // The approach used to solve 1D problems in libMesh is virtually identical to
30 // solving 2D or 3D problems, so in this sense this example represents a good
31 // starting point for new users. Note that many concepts are used in this
32 // example which are explained more fully in subsequent examples.
33 
34 // Libmesh includes
35 #include "libmesh/mesh.h"
36 #include "libmesh/mesh_generation.h"
37 #include "libmesh/edge_edge3.h"
38 #include "libmesh/gnuplot_io.h"
39 #include "libmesh/equation_systems.h"
40 #include "libmesh/linear_implicit_system.h"
41 #include "libmesh/fe.h"
42 #include "libmesh/getpot.h"
43 #include "libmesh/quadrature_gauss.h"
44 #include "libmesh/sparse_matrix.h"
45 #include "libmesh/dof_map.h"
46 #include "libmesh/numeric_vector.h"
47 #include "libmesh/dense_matrix.h"
48 #include "libmesh/dense_vector.h"
49 #include "libmesh/error_vector.h"
50 #include "libmesh/kelly_error_estimator.h"
51 #include "libmesh/mesh_refinement.h"
52 #include "libmesh/enum_solver_package.h"
53 
54 // Bring in everything from the libMesh namespace
55 using namespace libMesh;
56 
57 void assemble_1D(EquationSystems & es,
58                  const std::string & system_name);
59 
main(int argc,char ** argv)60 int main(int argc, char ** argv)
61 {
62   // Initialize the library.  This is necessary because the library
63   // may depend on a number of other libraries (i.e. MPI and PETSc)
64   // that require initialization before use.  When the LibMeshInit
65   // object goes out of scope, other libraries and resources are
66   // finalized.
67   LibMeshInit init (argc, argv);
68 
69   // This example requires a linear solver package.
70   libmesh_example_requires(libMesh::default_solver_package() != INVALID_SOLVER_PACKAGE,
71                            "--enable-petsc, --enable-trilinos, or --enable-eigen");
72 
73   // Skip adaptive examples on a non-adaptive libMesh build
74 #ifndef LIBMESH_ENABLE_AMR
75   libmesh_example_requires(false, "--enable-amr");
76 #else
77 
78   // Create a mesh, with dimension to be overridden later, on the
79   // default MPI communicator.
80   Mesh mesh(init.comm());
81 
82   GetPot command_line (argc, argv);
83 
84   int n = 4;
85   if (command_line.search(1, "-n"))
86     n = command_line.next(n);
87 
88   // Build a 1D mesh with 4 elements from x=0 to x=1, using
89   // EDGE3 (i.e. quadratic) 1D elements. They are called EDGE3 elements
90   // because a quadratic element contains 3 nodes.
91   MeshTools::Generation::build_line(mesh, n, 0., 1., EDGE3);
92 
93   // Define the equation systems object and the system we are going
94   // to solve. See Introduction Example 2 for more details.
95   EquationSystems equation_systems(mesh);
96   LinearImplicitSystem & system = equation_systems.add_system
97     <LinearImplicitSystem>("1D");
98 
99   // Add a variable "u" to the system, using second-order approximation
100   system.add_variable("u", SECOND);
101 
102   // Give the system a pointer to the matrix assembly function. This
103   // will be called when needed by the library.
104   system.attach_assemble_function(assemble_1D);
105 
106   // Define the mesh refinement object that takes care of adaptively
107   // refining the mesh.
108   MeshRefinement mesh_refinement(mesh);
109 
110   // These parameters determine the proportion of elements that will
111   // be refined and coarsened. Any element within 30% of the maximum
112   // error on any element will be refined, and any element within 30%
113   // of the minimum error on any element might be coarsened
114   mesh_refinement.refine_fraction()  = 0.7;
115   mesh_refinement.coarsen_fraction() = 0.3;
116   // We won't refine any element more than 5 times in total
117   mesh_refinement.max_h_level()      = 5;
118 
119   // Initialize the data structures for the equation system.
120   equation_systems.init();
121 
122   // Refinement parameters
123   const unsigned int max_r_steps = 5; // Refine the mesh 5 times
124 
125   // Define the refinement loop
126   for (unsigned int r_step=0; r_step<=max_r_steps; r_step++)
127     {
128       // Solve the equation system
129       equation_systems.get_system("1D").solve();
130 
131       // We need to ensure that the mesh is not refined on the last iteration
132       // of this loop, since we do not want to refine the mesh unless we are
133       // going to solve the equation system for that refined mesh.
134       if (r_step != max_r_steps)
135         {
136           // Error estimation objects, see Adaptivity Example 2 for details
137           ErrorVector error;
138           KellyErrorEstimator error_estimator;
139           error_estimator.use_unweighted_quadrature_rules = true;
140 
141           // Compute the error for each active element
142           error_estimator.estimate_error(system, error);
143 
144           // Output error estimate magnitude
145           libMesh::out << "Error estimate\nl2 norm = "
146                        << error.l2_norm()
147                        << "\nmaximum = "
148                        << error.maximum()
149                        << std::endl;
150 
151           // Flag elements to be refined and coarsened
152           mesh_refinement.flag_elements_by_error_fraction (error);
153 
154           // Perform refinement and coarsening
155           mesh_refinement.refine_and_coarsen_elements();
156 
157           // Reinitialize the equation_systems object for the newly refined
158           // mesh. One of the steps in this is project the solution onto the
159           // new mesh
160           equation_systems.reinit();
161         }
162     }
163 
164   // Construct gnuplot plotting object, pass in mesh, title of plot
165   // and boolean to indicate use of grid in plot. The grid is used to
166   // show the edges of each element in the mesh.
167   GnuPlotIO plot(mesh, "Adaptivity Example 1", GnuPlotIO::GRID_ON);
168 
169   // Write out script to be called from within gnuplot:
170   // Load gnuplot, then type "call 'gnuplot_script'" from gnuplot prompt
171   plot.write_equation_systems("gnuplot_script", equation_systems);
172 #endif // #ifndef LIBMESH_ENABLE_AMR
173 
174   // All done.  libMesh objects are destroyed here.  Because the
175   // LibMeshInit object was created first, its destruction occurs
176   // last, and it's destructor finalizes any external libraries and
177   // checks for leaked memory.
178   return 0;
179 }
180 
181 
182 
183 
184 // Define the matrix assembly function for the 1D PDE we are solving
assemble_1D(EquationSystems & es,const std::string & system_name)185 void assemble_1D(EquationSystems & es,
186                  const std::string & system_name)
187 {
188   // Ignore unused parameter warnings when !LIBMESH_ENABLE_AMR.
189   libmesh_ignore(es, system_name);
190 
191 #ifdef LIBMESH_ENABLE_AMR
192 
193   // It is a good idea to check we are solving the correct system
194   libmesh_assert_equal_to (system_name, "1D");
195 
196   // Get a reference to the mesh object
197   const MeshBase & mesh = es.get_mesh();
198 
199   // The dimension we are using, i.e. dim==1
200   const unsigned int dim = mesh.mesh_dimension();
201 
202   // Get a reference to the system we are solving
203   LinearImplicitSystem & system = es.get_system<LinearImplicitSystem>("1D");
204 
205   // Get a reference to the DofMap object for this system. The DofMap object
206   // handles the index translation from node and element numbers to degree of
207   // freedom numbers. DofMap's are discussed in more detail in future examples.
208   const DofMap & dof_map = system.get_dof_map();
209 
210   // Get a constant reference to the Finite Element type for the first
211   // (and only) variable in the system.
212   FEType fe_type = dof_map.variable_type(0);
213 
214   // Build a finite element object of the specified type. The build
215   // function dynamically allocates memory so we use a std::unique_ptr in this case.
216   // A std::unique_ptr is a pointer that cleans up after itself. See examples 3 and 4
217   // for more details on std::unique_ptr.
218   std::unique_ptr<FEBase> fe(FEBase::build(dim, fe_type));
219 
220   // Tell the finite element object to use fifth order Gaussian quadrature
221   QGauss qrule(dim, FIFTH);
222   fe->attach_quadrature_rule(&qrule);
223 
224   // Here we define some references to cell-specific data that will be used to
225   // assemble the linear system.
226 
227   // The element Jacobian * quadrature weight at each integration point.
228   const std::vector<Real> & JxW = fe->get_JxW();
229 
230   // The element shape functions evaluated at the quadrature points.
231   const std::vector<std::vector<Real>> & phi = fe->get_phi();
232 
233   // The element shape function gradients evaluated at the quadrature points.
234   const std::vector<std::vector<RealGradient>> & dphi = fe->get_dphi();
235 
236   // Declare a dense matrix and dense vector to hold the element matrix
237   // and right-hand-side contribution
238   DenseMatrix<Number> Ke;
239   DenseVector<Number> Fe;
240 
241   // This vector will hold the degree of freedom indices for the element.
242   // These define where in the global system the element degrees of freedom
243   // get mapped.
244   std::vector<dof_id_type> dof_indices;
245 
246   // We now loop over all the active elements in the mesh in order to calculate
247   // the matrix and right-hand-side contribution from each element. Use a
248   // const_element_iterator to loop over the elements. We make
249   // el_end const as it is used only for the stopping condition of the loop.
250   for (const auto & elem : mesh.active_local_element_ptr_range())
251     {
252       // Get the degree of freedom indices for the current element.
253       // These define where in the global matrix and right-hand-side this
254       // element will contribute to.
255       dof_map.dof_indices(elem, dof_indices);
256 
257       // Compute the element-specific data for the current element. This
258       // involves computing the location of the quadrature points (q_point)
259       // and the shape functions (phi, dphi) for the current element.
260       fe->reinit(elem);
261 
262       // Store the number of local degrees of freedom contained in this element
263       const unsigned int n_dofs =
264         cast_int<unsigned int>(dof_indices.size());
265       libmesh_assert_equal_to (n_dofs, phi.size());
266 
267       // We resize and zero out Ke and Fe (resize() also clears the matrix and
268       // vector). In this example, all elements in the mesh are EDGE3's, so
269       // Ke will always be 3x3, and Fe will always be 3x1. If the mesh contained
270       // different element types, then the size of Ke and Fe would change.
271       Ke.resize(n_dofs, n_dofs);
272       Fe.resize(n_dofs);
273 
274 
275       // Now loop over quadrature points to handle numerical integration
276       for (unsigned int qp=0; qp<qrule.n_points(); qp++)
277         {
278           // Now build the element matrix and right-hand-side using loops to
279           // integrate the test functions (i) against the trial functions (j).
280           for (unsigned int i=0; i != n_dofs; i++)
281             {
282               Fe(i) += JxW[qp]*phi[i][qp];
283 
284               for (unsigned int j=0; j != n_dofs; j++)
285                 {
286                   Ke(i,j) += JxW[qp]*(1.e-3*dphi[i][qp]*dphi[j][qp] +
287                                       phi[i][qp]*phi[j][qp]);
288                 }
289             }
290         }
291 
292 
293       // At this point we have completed the matrix and RHS summation. The
294       // final step is to apply boundary conditions, which in this case are
295       // simple Dirichlet conditions with u(0) = u(1) = 0.
296 
297       // Define the penalty parameter used to enforce the BC's
298       double penalty = 1.e10;
299 
300       // Loop over the sides of this element. For a 1D element, the "sides"
301       // are defined as the nodes on each edge of the element, i.e. 1D elements
302       // have 2 sides.
303       for (auto s : elem->side_index_range())
304         {
305           // If this element has a nullptr neighbor, then it is on the edge of the
306           // mesh and we need to enforce a boundary condition using the penalty
307           // method.
308           if (elem->neighbor_ptr(s) == nullptr)
309             {
310               Ke(s,s) += penalty;
311               Fe(s)   += 0*penalty;
312             }
313         }
314 
315       // This is a function call that is necessary when using adaptive
316       // mesh refinement. See Adaptivity Example 2 for more details.
317       dof_map.constrain_element_matrix_and_vector (Ke, Fe, dof_indices);
318 
319       // Add Ke and Fe to the global matrix and right-hand-side.
320       system.matrix->add_matrix(Ke, dof_indices);
321       system.rhs->add_vector(Fe, dof_indices);
322     }
323 #endif // #ifdef LIBMESH_ENABLE_AMR
324 }
325