PolyFEM
Loading...
Searching...
No Matches
PolyFEM

Build Commit License Stars

Code Structure

The high-level functions of PolyFEM are in polyfem::State, a class containing the application state and depending on all the PolyFEM submodules.

Finite Element Assembler

polyfem::assembler assembles the system matrix and right hand side (rhs). The assembler relies on finite element basis (polyfem::basis), quadrature rules (polyfem::quadrature), and for transient problems on polyfem::time_integrator.

Finite Element Bases

polyfem::basis contains the implementation of finite element basis functions.

The module supports the following 2 dimensional elements:

The module supports the following 3 dimensional elements:

Mesh data structure, Input/Output, and Navigation

polyfem::mesh contains data structures and algorithms for simplicial and polygonal meshes.

The interface is generic (Mesh2D.hpp, Mesh3D.hpp). There are two specializations: a conforming implementation that supports conforming polygonal meshes (CMesh2D.hpp, CMesh3D.hpp) without hanging nodes, and a non-conforming version (CMesh2D.hpp, CMesh3D.hpp) specialized for simplicial meshes only but supporting hanging nodes, which are used to implement adaptive h-refinement.

Many input formats are supported (see the JSON documentation for the complete list), and it outputs meshes in VTU (the format used by paraview/VTK).

In PolyFEM the mesh is used to define a collection of dofs (in case of linear elements, they correspond to the nodes of the mesh, but this is not the case for other elements). The mesh is then discarded and not used by other modules.

Quadrature Rules

polyfem::quadrature contains a set of quadrature rules for all elements implemented in polyfem::basis. The quadrature library is mostly independent from the rest of the codebase and it is based on the open-source library quadpy.

Time Integration

For transient problems, polyfem::time_integrator implements first-order (Implicit Euler ImplicitEuler.hpp) and higher-order time integrators (Implicit Newmak ImplicitNewmark.hpp, BDF BDF.hpp).

Collection of PDEs

Polyfem contains right hand side, boundary conditions, exact solutions, and initial conditions for many common PDEs. We refer to the documentation in the polyfem::problem section for more details. The major physics supported by polyfem are:

  1. Laplace
  2. Helmholtz
  3. Linear Elasticity
  4. Saint-Venant Elasticity
  5. Neo-Hookean Elasticity
  6. Stokes
  7. Navier-Stokes

From these, 1, 3–5, and 7 supports both static and transient problems, and 3–5 model contact and friction forces using the Incremental Potential Contact formulation (https://github.com/ipc-sim/ipc-toolkit).

Automatic Code Generation

polyfem::autogen contains python scripts to generate C++ code for:

The code autogenerated by the python scripts is directly committed in the code repository, as the autogeneration is time consuming.

Utilities

Smaller utilities are grouped in the polyfem::utils module, including mesh IO, hashing functions, logging, matrix utilities, parallelization, rasterization, function interpolation (RBFInterpolation.hpp), and timers (Timer.hpp).

Simple Example / Code Walkthrough

In this section, a brief, high-level code walkthrough for a simple example problem is given.

JSON File

For this example, we solve the Laplacian on a (triangular) plate-hole mesh with Dirichlet boundary conditions and a constant RHS. We choose P1 elements implicitly (as this is the default). See the JSON specification for more details.

{
"geometry": {
"advanced": {
"normalize_mesh": true
},
"mesh": "plate_hole.obj",
"surface_selection": {
"threshold": 1e-08
}
},
"materials": {
"type": "Laplacian"
},
"boundary_conditions": {
"dirichlet_boundary": [{
"id": 1,
"value": 0.0
}, {
"id": 4,
"value": 1.0
}],
"rhs": 10
},
"output": {
"json": "stats.json",
"paraview": {
"file_name": "result.vtu"
}
}
}

State

Internally, PolyFEM keeps a polyfem::State object that contains the entire state of the solve. The mesh, basis, boundary conditions, stiffness/mass matrices, RHS, and user-selected settings are all stored in polyfem::State. The polyfem::State class is also responsible for dispatch, meaning it contains member functions for the basic steps of a solve, including building a basis, assembling matrices, and actually performing the linear or nonlinear solve.

Basis Building

After creating a polyfem::State object and loading the input mesh, the next step in the code is to build the basis. In PolyFEM, there are two separate notions of basis: the finite element (FE) basis and the geometric basis. (They are the same in the isoparametric case.) The former is used to construct the solution whereas the latter is used to represent the geometric mapping, which is especially useful to implement curved elements.

Both the FE and geometric basis are stored as vectors of basis::ElementBases. Each basis::ElementBases object stores the basis functions for a given element as a vector of basis::Basis. Each basis::Basis stores functions representing a local basis function and its gradient.

The high-level function that constructs the FE and geometric bases is State::build_basis. In our example, it uses basis::LagrangeBasis2d to create the vector of basis::ElementBases.

Basis/Gradient Evaluation and Storage

In order to evaluate local basis functions and construct mass/stiffness matrices, PolyFEM makes use of a series of data storage classes. The lowest level of these classes is assembler::AssemblyValues, which stores the evaluation and gradient of a single local basis function on its element's quadrature points. A vector of basis::Local2Global objects is also stored. This is used to constrain nodes at the interface between different order elements (allowing for refinement).

The assembler::ElementAssemblyValues class stores per element information, including quadrature points in real space, the geometric mapping's determinant, and a vector of assembler::AssemblyValues objects (one for each local basis function).

Finally, assembler::AssemblyValsCache stores a vector of assembler::ElementAssemblyValues, boosting performance by caching the per element information. Its data can be accessed through its compute function.

These three classes are the core data storage objects used during matrix assembly. These classes use the previously constructed basis::ElementBases objects to evaluate basis functions and their gradients.

RHS Assembly

After building the basis, the next step is to assemble the RHS. This is done through the State::assemble_rhs function, which itself creates a assembler::RhsAssembler object. The assembler::RhsAssembler::assemble function is then called in order to construct the RHS vector of the weak form using assembler::AssemblyValsCache and the user-provided RHS function. In our simple example, the latter is just $10$. Note that the RHS function and boundary conditions are stored in assembler::GenericProblem.

Matrix Assembly

The virtual class assembler::Assembler provides the interface used to actually construct matrices through its assemble function. For our simple example, the relevant subclass is assembler::LinearAssembler, which is itself a virtual class that defines how to assemble global matrices. However, the function that defines how to evaluate local matrix entries according to the chosen bilinear form is a pure virtual function. In our example, the stiffness matrix is built using assembler::Laplacian, which is a subclass of assembler::LinearAssembler that defines the local matrix entries (ie grad u dot grad v). The assembler::Mass class is similar except for building the mass matrix.

The relevant high-level functions for matrix assembly are State::assemble_mass_mat and State::build_stiffness_mat. Note that the stiffness matrix is built later on (in the State::solve_linear function).

Solver

After assembling the necessary matrices/vectors, the State::solve_problem function dispatches State::init_linear_solve then State::solve_problem, which solves the linear system.

Diagram

A diagram showing the relationships discussed above is included below. Each block represents its own class. polyfem::State is shown in orange and basis::LagrangeBasis2d in red. The basis storage classes are blue, the value storage classes purple, and the assembly-related classes green. Relationships between classes are shown with arrows. Bold arrows represent calling a member function and dashed ones represent inheritance.

Overall diagram

Technical Note: Matrix Caching

In order to boost performance, PolyFEM caches sparsity structure when assembling matrices, taking advantage of the fact that the structure of stiffness/mass matrices remains unchanged assuming the basis is unchanged. The details can be found in MatrixCache.hpp. A brief overview of polyfem::utils::SparseMatrixCache is included here for reference.

In PolyFEM, sparse matrices are stored in Compressed Sparse Column (CSC) form. Thus, the matrix is represented as three arrays: two for storing non-zero indices and one for storing values.

Consider the first time a polyfem::utils::SparseMatrixCache object is filled with values. Each time SparseMatrixCache::add_value is called, the values and their global indices are placed in a temporary buffer. It is assumed that a given polyfem::utils::SparseMatrixCache always receives each entry from a given element in the same order.

The first time the matrix is retrieved using SparseMatrixCache::get_matrix, SparseMatrixCache::prune is called, which writes the buffered values to the actual matrix. Then, the stored index information is used to create a mapping from the ordering of values received from a given element to the corresponding CSC-style index. In future iterations, instead of recomputing the matrix structure, polyfem::utils::SparseMatrixCache uses this cached index to save the given value directly to the CSC-style values array at the correct position.

Building PolyFEM as a stand-alone executable

All the C++ dependencies required to build the code are included and are compiled statically in a single executable. PolyFEM is compiled using automatic builds on Windows, macOS, and Linux using:

mkdir build
cd build
cmake ..
make -j4

After compilation, unit and system tests (tests/main.cpp) can be run with:

./tests/unit_tests

Building PolyFEM as a static library

Polyfem can be added to an existing cmake project with

add_subdirectory(<path-to-polyfem> polyfem)

and linked with

target_link_library(<your_target> polyfem::polyfem)

in your cmake script. We do not currently support other building systems. PolyFEM will download the dependencies that it needs in the build folder.