Skip to content

Instantly share code, notes, and snippets.

@jwpeterson
Created February 17, 2016 22:58
Show Gist options
  • Select an option

  • Save jwpeterson/41deb2381ac524124e19 to your computer and use it in GitHub Desktop.

Select an option

Save jwpeterson/41deb2381ac524124e19 to your computer and use it in GitHub Desktop.
// 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