Created
February 17, 2016 22:58
-
-
Save jwpeterson/41deb2381ac524124e19 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // C++ include files that we need | |
| #include <iostream> | |
| #include <algorithm> | |
| #include <math.h> | |
| // Basic include file needed for the mesh functionality. | |
| #include "libmesh/libmesh.h" | |
| #include "libmesh/mesh.h" | |
| #include "libmesh/mesh_generation.h" | |
| #include "libmesh/exodusII_io.h" | |
| #include "libmesh/gnuplot_io.h" | |
| #include "libmesh/linear_implicit_system.h" | |
| #include "libmesh/equation_systems.h" | |
| // Define the Finite Element object. | |
| #include "libmesh/fe.h" | |
| // Define Gauss quadrature rules. | |
| #include "libmesh/quadrature_gauss.h" | |
| // Define the DofMap, which handles degree of freedom | |
| // indexing. | |
| #include "libmesh/dof_map.h" | |
| // Define useful datatypes for finite element | |
| // matrix and vector components. | |
| #include "libmesh/sparse_matrix.h" | |
| #include "libmesh/numeric_vector.h" | |
| #include "libmesh/dense_matrix.h" | |
| #include "libmesh/dense_vector.h" | |
| // Define the PerfLog, a performance logging utility. | |
| // It is useful for timing events in a code and giving | |
| // you an idea where bottlenecks lie. | |
| #include "libmesh/perf_log.h" | |
| // The definition of a geometric element | |
| #include "libmesh/elem.h" | |
| #include "libmesh/string_to_enum.h" | |
| #include "libmesh/getpot.h" | |
| // Additional includes needed for MatGetColoring stuff | |
| #include "libmesh/petsc_matrix.h" | |
| // To use constraint-based Dirichlet boundary conditions. | |
| #include "libmesh/dirichlet_boundaries.h" | |
| #include "libmesh/analytic_function.h" | |
| using namespace libMesh; | |
| // Function prototype. This is the function that will assemble | |
| // the linear system for our Poisson problem. Note that the | |
| // function will take the \p EquationSystems object and the | |
| // name of the system we are assembling as input. From the | |
| // \p EquationSystems object we have acess to the \p Mesh and | |
| // other objects we might need. | |
| void assemble_poisson(EquationSystems& es, | |
| const std::string& system_name); | |
| // Exact solution function prototype. | |
| Real exact_solution (const Real x, | |
| const Real y, | |
| const Real /*z*/) | |
| { | |
| // static const Real pi = acos(-1.); | |
| // return cos(.5*pi*x)*sin(.5*pi*y)*cos(.5*pi*z); | |
| // Exact solution has Laplacian = 4 | |
| return x*x + y*y; | |
| } | |
| // This wraps the user's existing exact_solution() function into an interface | |
| // that works with DirichletBoundaries. | |
| void exact_solution_wrapper (DenseVector<Number> & output, | |
| const Point & p, | |
| const Real) | |
| { | |
| output(0) = exact_solution(p(0), | |
| (LIBMESH_DIM>1)?p(1):0, | |
| (LIBMESH_DIM>2)?p(2):0); | |
| } | |
| // Begin the main program. | |
| int main (int argc, char** argv) | |
| { | |
| // Initialize libMesh and any dependent libaries, like in example 2. | |
| LibMeshInit init (argc, argv); | |
| // Declare a performance log for the main program | |
| // PerfLog perf_main("Main Program"); | |
| // Create a GetPot object to parse the command line | |
| GetPot command_line (argc, argv); | |
| // Check for proper calling arguments. | |
| if (argc < 3) | |
| { | |
| if (init.comm().rank() == 0) | |
| libmesh_error_msg("Usage:\n" << "\t " << argv[0] << " -d 2(3)" << " -n 15"); | |
| } | |
| // Brief message to the user regarding the program name | |
| // and command line arguments. | |
| else | |
| { | |
| std::cout << "Running " << argv[0]; | |
| for (int i=1; i<argc; i++) | |
| std::cout << " " << argv[i]; | |
| std::cout << std::endl << std::endl; | |
| } | |
| // Read problem dimension from command line. Use int | |
| // instead of unsigned since the GetPot overload is ambiguous | |
| // otherwise. | |
| int dim = 2; | |
| if ( command_line.search(1, "-d") ) | |
| dim = command_line.next(dim); | |
| // Skip higher-dimensional examples on a lower-dimensional libMesh build | |
| libmesh_example_requires(dim <= LIBMESH_DIM, "2D/3D support"); | |
| // Create a mesh with user-defined dimension. | |
| // Read number of elements from command line | |
| int ps = 15; | |
| if ( command_line.search(1, "-n") ) | |
| ps = command_line.next(ps); | |
| // Read FE order from command line | |
| std::string order = "SECOND"; | |
| if ( command_line.search(2, "-Order", "-o") ) | |
| order = command_line.next(order); | |
| // Read FE Family from command line | |
| std::string family = "LAGRANGE"; | |
| if ( command_line.search(2, "-FEFamily", "-f") ) | |
| family = command_line.next(family); | |
| // Cannot use discontinuous basis. | |
| if ((family == "MONOMIAL") || (family == "XYZ")) | |
| { | |
| if (init.comm().rank() == 0) | |
| libmesh_error_msg("ex4 currently requires a C^0 (or higher) FE basis."); | |
| } | |
| Mesh mesh (init.comm(), dim); | |
| // Use the MeshTools::Generation mesh generator to create a uniform | |
| // grid on the square [-1,1]^D. We instruct the mesh generator | |
| // to build a mesh of 8x8 \p Quad9 elements in 2D, or \p Hex27 | |
| // elements in 3D. Building these higher-order elements allows | |
| // us to use higher-order approximation, as in example 3. | |
| Real halfwidth = dim > 1 ? 1. : 0.; | |
| Real halfheight = dim > 2 ? 1. : 0.; | |
| if ((family == "LAGRANGE") && (order == "FIRST")) | |
| { | |
| // No reason to use high-order geometric elements if we are | |
| // solving with low-order finite elements. | |
| MeshTools::Generation::build_cube (mesh, | |
| ps, | |
| (dim>1) ? ps : 0, | |
| (dim>2) ? ps : 0, | |
| -1., 1., | |
| -halfwidth, halfwidth, | |
| -halfheight, halfheight, | |
| (dim==1) ? EDGE2 : | |
| ((dim == 2) ? QUAD4 : HEX8)); | |
| } | |
| else // order == SECOND | |
| { | |
| MeshTools::Generation::build_cube (mesh, | |
| ps, | |
| (dim>1) ? ps : 0, | |
| (dim>2) ? ps : 0, | |
| -1., 1., | |
| -halfwidth, halfwidth, | |
| -halfheight, halfheight, | |
| (dim==1) ? EDGE3 : | |
| ((dim == 2) ? QUAD9 : HEX27)); | |
| } | |
| // Print information about the mesh to the screen. | |
| mesh.print_info(); | |
| // Create an equation systems object. | |
| EquationSystems equation_systems (mesh); | |
| // Declare the system and its variables. | |
| // Create a system named "Poisson" | |
| LinearImplicitSystem& system = | |
| equation_systems.add_system<LinearImplicitSystem> ("Poisson"); | |
| // Add the variable "u" to "Poisson". "u" | |
| // will be approximated using second-order approximation. | |
| unsigned int u_var = system.add_variable("u", | |
| Utility::string_to_enum<Order> (order), | |
| Utility::string_to_enum<FEFamily>(family)); | |
| // Give the system a pointer to the matrix assembly | |
| // function. | |
| system.attach_assemble_function (assemble_poisson); | |
| // Construct a Dirichlet boundary condition object | |
| // Indicate which boundary IDs we impose the BC on | |
| // We either build a line, a square or a cube, and | |
| // here we indicate the boundaries IDs in each case | |
| std::set<boundary_id_type> boundary_ids; | |
| // the dim==1 mesh has two boundaries with IDs 0 and 1 | |
| boundary_ids.insert(0); | |
| boundary_ids.insert(1); | |
| // the dim==2 mesh has four boundaries with IDs 0, 1, 2 and 3 | |
| if (dim>=2) | |
| { | |
| boundary_ids.insert(2); | |
| boundary_ids.insert(3); | |
| } | |
| // the dim==3 mesh has four boundaries with IDs 0, 1, 2, 3, 4 and 5 | |
| if (dim==3) | |
| { | |
| boundary_ids.insert(4); | |
| boundary_ids.insert(5); | |
| } | |
| // Create a vector storing the variable numbers which the BC applies to | |
| std::vector<unsigned int> variables(1); | |
| variables[0] = u_var; | |
| // Create an AnalyticFunction object that we use to project the BC | |
| // This function just calls the function exact_solution via exact_solution_wrapper | |
| AnalyticFunction<Number> exact_solution_object(exact_solution_wrapper); | |
| DirichletBoundary dirichlet_bc(boundary_ids, | |
| variables, | |
| &exact_solution_object); | |
| // We must add the Dirichlet boundary condition _before_ | |
| // we call equation_systems.init() | |
| system.get_dof_map().add_dirichlet_boundary(dirichlet_bc); | |
| // Initialize the data structures for the equation system. | |
| equation_systems.init(); | |
| // Print information about the system to the screen. | |
| equation_systems.print_info(); | |
| mesh.print_info(); | |
| // Solve the system "Poisson", just like example 2. | |
| equation_systems.get_system("Poisson").solve(); | |
| // Do an extra assembly with the solved system. This should | |
| // hopefully show non-zero second derivatives in our print | |
| // statements... | |
| equation_systems.get_system("Poisson").assemble(); | |
| system.matrix->close(); | |
| // After solving the system write the solution | |
| // to a GMV-formatted plot file. | |
| if(dim == 1) | |
| { | |
| GnuPlotIO plot(mesh,"Example 4, 1D",GnuPlotIO::GRID_ON); | |
| plot.write_equation_systems("out_1",equation_systems); | |
| } | |
| else | |
| { | |
| ExodusII_IO (mesh).write_equation_systems ("ex4_boundary_second_derivs.e", equation_systems); | |
| } | |
| // All done. | |
| return 0; | |
| } | |
| // We now define the matrix assembly function for the | |
| // Poisson system. We need to first compute element | |
| // matrices and right-hand sides, and then take into | |
| // account the boundary conditions, which will be handled | |
| // via a penalty method. | |
| void assemble_poisson(EquationSystems& es, | |
| const std::string& system_name) | |
| { | |
| // It is a good idea to make sure we are assembling | |
| // the proper system. | |
| libmesh_assert (system_name == "Poisson"); | |
| // Declare a performance log. Give it a descriptive | |
| // string to identify what part of the code we are | |
| // logging, since there may be many PerfLogs in an | |
| // application. | |
| PerfLog perf_log ("Matrix Assembly"); | |
| // Get a constant reference to the mesh object. | |
| const MeshBase& mesh = es.get_mesh(); | |
| // The dimension that we are running | |
| const unsigned int dim = mesh.mesh_dimension(); | |
| // Get a reference to the LinearImplicitSystem we are solving | |
| LinearImplicitSystem& system = es.get_system<LinearImplicitSystem>("Poisson"); | |
| // Get the variable number for "u". | |
| const unsigned int u_var = system.variable_number ("u"); | |
| // A reference to the \p DofMap object for this system. The \p DofMap | |
| // object handles the index translation from node and element numbers | |
| // to degree of freedom numbers. We will talk more about the \p DofMap | |
| // in future examples. | |
| const DofMap& dof_map = system.get_dof_map(); | |
| // Get a constant reference to the Finite Element type | |
| // for the first (and only) variable in the system. | |
| FEType fe_type = dof_map.variable_type(0); | |
| // Build a Finite Element object of the specified type. Since the | |
| // \p FEBase::build() member dynamically creates memory we will | |
| // store the object as an \p UniquePtr<FEBase>. This can be thought | |
| // of as a pointer that will clean up after itself. | |
| UniquePtr<FEBase> fe (FEBase::build(dim, fe_type)); | |
| // Use 2x2 (2D) quadrature to reduce the amount of output and mimic the | |
| // MOOSE example. | |
| QGauss qrule (dim, THIRD); | |
| // Tell the finite element object to use our quadrature rule. | |
| fe->attach_quadrature_rule (&qrule); | |
| // Declare a special finite element object for | |
| // boundary integration. | |
| UniquePtr<FEBase> fe_face (FEBase::build(dim, fe_type)); | |
| // Use 2-pt (1D) quadrature to reduce the amount of output and mimic the | |
| // MOOSE example. | |
| QGauss qface(dim-1, THIRD); | |
| // Tell the finte element object to use our | |
| // quadrature rule. | |
| fe_face->attach_quadrature_rule (&qface); | |
| // Here we define some references to cell-specific data that | |
| // will be used to assemble the linear system. | |
| // We begin with the element Jacobian * quadrature weight at each | |
| // integration point. | |
| const std::vector<Real>& JxW = fe->get_JxW(); | |
| // The physical XY locations of the quadrature points on the element. | |
| // These might be useful for evaluating spatially varying material | |
| // properties at the quadrature points. | |
| const std::vector<Point>& q_point = fe->get_xyz(); | |
| // The element shape functions evaluated at the quadrature points. | |
| const std::vector<std::vector<Real> >& phi = fe->get_phi(); | |
| // The element shape function gradients evaluated at the quadrature | |
| // points. | |
| const std::vector<std::vector<RealGradient> >& dphi = fe->get_dphi(); | |
| // Define data structures to contain the element matrix | |
| // and right-hand-side vector contribution. Following | |
| // basic finite element terminology we will denote these | |
| // "Ke" and "Fe". More detail is in example 3. | |
| DenseMatrix<Number> Ke; | |
| DenseVector<Number> Fe; | |
| // This vector will hold the degree of freedom indices for | |
| // the element. These define where in the global system | |
| // the element degrees of freedom get mapped. | |
| std::vector<unsigned int> dof_indices; | |
| std::vector<dof_id_type> dof_indices_u; | |
| // Now we will loop over all the elements in the mesh. | |
| // We will compute the element matrix and right-hand-side | |
| // contribution. See example 3 for a discussion of the | |
| // element iterators. | |
| MeshBase::const_element_iterator el = mesh.active_local_elements_begin(); | |
| const MeshBase::const_element_iterator end_el = mesh.active_local_elements_end(); | |
| for ( ; el != end_el; ++el) | |
| { | |
| // Start logging the shape function initialization. | |
| // This is done through a simple function call with | |
| // the name of the event to log. | |
| perf_log.push("elem init"); | |
| // Store a pointer to the element we are currently | |
| // working on. This allows for nicer syntax later. | |
| const Elem* elem = *el; | |
| // Get the degree of freedom indices for the | |
| // current element. These define where in the global | |
| // matrix and right-hand-side this element will | |
| // contribute to. | |
| dof_map.dof_indices (elem, dof_indices); | |
| // Compute the element-specific data for the current | |
| // element. This involves computing the location of the | |
| // quadrature points (q_point) and the shape functions | |
| // (phi, dphi) for the current element. | |
| fe->reinit (elem); | |
| // Zero the element matrix and right-hand side before | |
| // summing them. We use the resize member here because | |
| // the number of degrees of freedom might have changed from | |
| // the last element. Note that this will be the case if the | |
| // element type is different (i.e. the last element was a | |
| // triangle, now we are on a quadrilateral). | |
| Ke.resize (dof_indices.size(), | |
| dof_indices.size()); | |
| Fe.resize (dof_indices.size()); | |
| // Get dof indices for the u variable | |
| dof_map.dof_indices (elem, dof_indices_u, u_var); | |
| // Stop logging the shape function initialization. | |
| // If you forget to stop logging an event the PerfLog | |
| // object will probably catch the error and abort. | |
| perf_log.pop("elem init"); | |
| // Now we will build the element matrix. This involves | |
| // a double loop to integrate the test funcions (i) against | |
| // the trial functions (j). | |
| // | |
| // We have split the numeric integration into two loops | |
| // so that we can log the matrix and right-hand-side | |
| // computation seperately. | |
| // | |
| // Now start logging the element matrix computation | |
| perf_log.push ("Ke"); | |
| for (unsigned int qp=0; qp<qrule.n_points(); qp++) | |
| for (unsigned int i=0; i<phi.size(); i++) | |
| for (unsigned int j=0; j<phi.size(); j++) | |
| Ke(i,j) += JxW[qp]*(dphi[i][qp]*dphi[j][qp]); | |
| // Stop logging the matrix computation | |
| perf_log.pop ("Ke"); | |
| // Now we build the element right-hand-side contribution. | |
| // This involves a single loop in which we integrate the | |
| // "forcing function" in the PDE against the test functions. | |
| // | |
| // Start logging the right-hand-side computation | |
| perf_log.push ("Fe"); | |
| for (unsigned int qp=0; qp<qrule.n_points(); qp++) | |
| { | |
| // fxy is the forcing function for the Poisson equation. | |
| // In this case we set fxy to be a finite difference | |
| // Laplacian approximation to the (known) exact solution. | |
| // | |
| // We will use the second-order accurate FD Laplacian | |
| // approximation, which in 2D on a structured grid is | |
| // | |
| // u_xx + u_yy = (u(i-1,j) + u(i+1,j) + | |
| // u(i,j-1) + u(i,j+1) + | |
| // -4*u(i,j))/h^2 | |
| // | |
| // Since the value of the forcing function depends only | |
| // on the location of the quadrature point (q_point[qp]) | |
| // we will compute it here, outside of the i-loop | |
| const Real x = q_point[qp](0); | |
| const Real y = q_point[qp](1); | |
| const Real z = q_point[qp](2); | |
| const Real eps = 1.e-3; | |
| const Real uxx = (exact_solution(x-eps,y,z) + | |
| exact_solution(x+eps,y,z) + | |
| -2.*exact_solution(x,y,z))/eps/eps; | |
| const Real uyy = (exact_solution(x,y-eps,z) + | |
| exact_solution(x,y+eps,z) + | |
| -2.*exact_solution(x,y,z))/eps/eps; | |
| const Real uzz = (exact_solution(x,y,z-eps) + | |
| exact_solution(x,y,z+eps) + | |
| -2.*exact_solution(x,y,z))/eps/eps; | |
| Real fxy = - (uxx + uyy + ((dim==2) ? 0. : uzz)); | |
| // libMesh::out << "fxy=" << fxy << std::endl; | |
| // Add the RHS contribution | |
| for (unsigned int i=0; i<phi.size(); i++) | |
| Fe(i) += JxW[qp]*fxy*phi[i][qp]; | |
| } | |
| // Stop logging the right-hand-side computation | |
| perf_log.pop ("Fe"); | |
| // At this point the interior element integration has | |
| // been completed. However, we have not yet addressed | |
| // boundary conditions. For this example we will only | |
| // consider simple Dirichlet boundary conditions imposed | |
| // via the penalty method. This is discussed at length in | |
| // example 3. | |
| { | |
| // Start logging the boundary condition computation | |
| perf_log.push ("BCs"); | |
| // The following loops over the sides of the element. | |
| // If the element has no neighbor on a side then that | |
| // side MUST live on a boundary of the domain. | |
| for (unsigned int side=0; side<elem->n_sides(); side++) | |
| if (elem->neighbor(side) == NULL) | |
| { | |
| // BCs are now handled by the DirichletBoundaries object! | |
| // The penalty value. \frac{1}{\epsilon} | |
| // in the discussion above. | |
| // const Real penalty = 1.e10; | |
| // The value of the shape functions at the quadrature | |
| // points. | |
| const std::vector<std::vector<Real> >& phi_face = fe_face->get_phi(); | |
| // The Jacobian * Quadrature Weight at the quadrature | |
| // points on the face. | |
| const std::vector<Real>& JxW_face = fe_face->get_JxW(); | |
| // The XYZ locations (in physical space) of the | |
| // quadrature points on the face. This is where | |
| // we will interpolate the boundary value function. | |
| const std::vector<Point >& qface_point = fe_face->get_xyz(); | |
| // Request the second derivative values. | |
| const std::vector<std::vector<RealTensor> > & d2phi_face = fe_face->get_d2phi(); | |
| // Compute the shape function values on the element | |
| // face. | |
| fe_face->reinit(elem, side); | |
| // Get the boundary id for the current side | |
| std::vector<boundary_id_type> ids; | |
| mesh.get_boundary_info().boundary_ids(elem, side, ids); | |
| // Set a flag if we're on side 1. | |
| bool on_side_1 = std::count(ids.begin(), ids.end(), 1); | |
| // Loop over the face quadrature points for integration. | |
| for (unsigned int qp=0; qp<qface.n_points(); qp++) | |
| { | |
| // Check values: if we're on the righ boundary (id==1), print the d2phi_face tensor at this qp. | |
| if (on_side_1) | |
| { | |
| // Print a blank line to separate results of different qp's. | |
| libMesh::out << std::endl; | |
| // Verify that the second derivatives sum to 0. | |
| RealTensor sum_d2phi; | |
| // Compute second_u by summing u_i * d2phi[i][qp] | |
| RealTensor second_u; | |
| // Compute Laplacian(u) by summing u_i * Laplacian (phi_i) | |
| Real lap_u = 0.; | |
| libMesh::out << "qface_point[" << qp << "]=" << qface_point[qp] << std::endl; | |
| for (unsigned i=0; i<d2phi_face.size(); ++i) | |
| { | |
| // Debugging output: print d2phi_face and u at every qp. | |
| // libMesh::out << "d2phi_face[" << i << "][" << qp << "]=\n" << d2phi_face[i][qp] << std::endl; | |
| // libMesh::out << "u[" << i << "]=" << system.current_solution (dof_indices_u[i]) << std::endl; | |
| sum_d2phi += d2phi_face[i][qp]; | |
| // D2(u) = sum u_i D2(phi_i) | |
| second_u += d2phi_face[i][qp] * system.current_solution (dof_indices_u[i]); | |
| lap_u += d2phi_face[i][qp].tr() * system.current_solution (dof_indices_u[i]); | |
| } | |
| // If the sum of the second derivatives is not zero, throw an error | |
| if (sum_d2phi.size() > 1.e-12) | |
| { | |
| libMesh::out << "sum_d2phi[qp=" << qp << "]=\n" << sum_d2phi << std::endl; | |
| libmesh_error_msg("Error! Second derivatives should sum to zero!"); | |
| } | |
| libMesh::out << "second_u[qp=" << qp << "]=\n" << second_u; | |
| libMesh::out << "lap_u=" << lap_u << std::endl; | |
| } | |
| // BCs are now handled by the DirichletBoundaries object! | |
| // // The location on the boundary of the current | |
| // // face quadrature point. | |
| // const Real xf = qface_point[qp](0); | |
| // const Real yf = qface_point[qp](1); | |
| // const Real zf = qface_point[qp](2); | |
| // | |
| // // The boundary value. | |
| // const Real value = exact_solution(xf, yf, zf); | |
| // | |
| // // Verify the exact solution is 1 + y**2 on side 1. Since the | |
| // // 2-pt quadrature rule points are at (1,sqrt(3)/3), we should | |
| // // get u = 1 + 3/9 = 1.33333333. | |
| // if (on_side_1) | |
| // libMesh::out << "exact solution value=" << value << std::endl; | |
| // | |
| // // Matrix contribution of the L2 projection. | |
| // for (unsigned int i=0; i<phi_face.size(); i++) | |
| // for (unsigned int j=0; j<phi_face.size(); j++) | |
| // Ke(i,j) += JxW_face[qp]*penalty*phi_face[i][qp]*phi_face[j][qp]; | |
| // | |
| // // Right-hand-side contribution of the L2 | |
| // // projection. | |
| // for (unsigned int i=0; i<phi_face.size(); i++) | |
| // Fe(i) += JxW_face[qp]*penalty*value*phi_face[i][qp]; | |
| } | |
| } | |
| // Stop logging the boundary condition computation | |
| perf_log.pop ("BCs"); | |
| } | |
| // If this assembly program were to be used on an adaptive mesh, | |
| // we would have to apply any hanging node constraint equations | |
| // dof_map.constrain_element_matrix_and_vector (Ke, Fe, dof_indices); | |
| // We call heterogenously_constrain_element_matrix_and_vector() | |
| // to impose a inhomogeneous Dirichlet boundary conditions. | |
| dof_map.heterogenously_constrain_element_matrix_and_vector (Ke, Fe, dof_indices); | |
| // The element matrix and right-hand-side are now built | |
| // for this element. Add them to the global matrix and | |
| // right-hand-side vector. The \p SparseMatrix::add_matrix() | |
| // and \p NumericVector::add_vector() members do this for us. | |
| // Start logging the insertion of the local (element) | |
| // matrix and vector into the global matrix and vector | |
| perf_log.push ("matrix insertion"); | |
| system.matrix->add_matrix (Ke, dof_indices); | |
| system.rhs->add_vector (Fe, dof_indices); | |
| // Start logging the insertion of the local (element) | |
| // matrix and vector into the global matrix and vector | |
| perf_log.pop ("matrix insertion"); | |
| } | |
| // That's it. We don't need to do anything else to the | |
| // PerfLog. When it goes out of scope (at this function return) | |
| // it will print its log to the screen. Pretty easy, huh? | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment