libfem
Finite element method library: a framework for structural mechanics, modal analysis, and nonlinear problems
libfem is a flexible, extensible FEM framework that provides simulation capabilities for structural mechanics, heat transfer, modal analysis, and nonlinear problems.
Features
- 12 Element Types - Full library of 1D, 2D, and 3D elements (linear and quadratic)
- Multiple Physics - Diffusion, elasticity, and mass matrix integrators
- Solver Wrappers - Direct, iterative, and eigenvalue solvers
- Modal Analysis - Natural frequency extraction and mass participation factors
- Dynamic Analysis - Newmark-β, HHT-α, and explicit time integration
- Rayleigh Damping - Proportional damping with coefficient computation
- Nonlinear Analysis - Neo-Hookean hyperelasticity, J2 plasticity, Newton-Raphson solver
- Post-Processing - Stress recovery, failure criteria (von Mises, Tresca), safety factors
- Results Export - VTK for visualization in a VTK viewer, CSV/JSON for reporting
- Sparse Assembly - Efficient triplet-based sparse matrix construction
- Quadrature Rules - Gauss-Legendre and simplex quadrature for accurate integration
- Header-Only Dependencies - Eigen 3.4.0 and Spectra 1.0.1 bundled
Element Library
1D Elements
| Element | Nodes | Order | Use Case |
|---|---|---|---|
| Bar2 | 2 | Linear | Truss members, axial loading |
| Bar3 | 3 | Quadratic | Higher accuracy 1D problems |
2D Elements
| Element | Nodes | Order | Use Case |
|---|---|---|---|
| Tri3 | 3 | Linear | Simple triangular meshes |
| Tri6 | 6 | Quadratic | Curved boundaries, higher accuracy |
| Quad4 | 4 | Bilinear | Regular rectangular regions |
| Quad8 | 8 | Serendipity | Curved edges without center node |
| Quad9 | 9 | Biquadratic | Full quadratic accuracy |
3D Elements
| Element | Nodes | Order | Use Case |
|---|---|---|---|
| Tet4 | 4 | Linear | Simple tetrahedral meshes |
| Tet10 | 10 | Quadratic | Complex geometry, higher accuracy |
| Hex8 | 8 | Trilinear | Regular hexahedral regions |
| Hex20 | 20 | Serendipity | Curved surfaces |
| Hex27 | 27 | Triquadratic | Full quadratic accuracy in 3D |
Quick Start
Basic Mesh Setup
#include "fem/core/Mesh.hpp"
#include "fem/core/DOFHandler.hpp"
#include "fem/elements/Element.hpp"
using namespace fem;
// Create a 2D mesh
Mesh mesh(2);
// Add nodes
mesh.addNode(0.0, 0.0); // Node 0
mesh.addNode(1.0, 0.0); // Node 1
mesh.addNode(0.5, 1.0); // Node 2
// Add a triangular element
mesh.addElement(ElementType::Tri3, {0, 1, 2});
Applying Boundary Conditions
// Dirichlet BC: fix temperature at node 0 to 0.0
mesh.addDirichletBC(0, 0, 0.0);
// Dirichlet BC: fix temperature at node 1 to 100.0
mesh.addDirichletBC(1, 0, 100.0);
// Neumann BC: apply flux at node 2
mesh.addNeumannBC(2, 0, 10.0);
Assembling a Stiffness Matrix
#include "fem/integrators/DiffusionIntegrator.hpp"
// Setup DOF handler (1 DOF per node for scalar problems)
DOFHandler dof(mesh, 1);
dof.applyDirichletBCs();
// Create assembler
Assembler assembler(dof.totalDOFs());
// Create integrator with conductivity k = 1.0
DiffusionIntegrator diffusion(1.0);
// Get element and compute element matrix
auto elem = createElement(ElementType::Tri3);
// Extract node coordinates for element 0
MatrixX nodeCoords(3, 3);
for (int i = 0; i < 3; ++i) {
Vector3 pos = mesh.node(mesh.element(0).nodeIndices[i]);
nodeCoords.col(i) = pos;
}
// Compute and assemble element matrix
MatrixX Ke = diffusion.computeElementMatrix(*elem, nodeCoords);
auto elemDOFs = dof.elementDOFs(0);
assembler.addElementMatrix(elemDOFs, Ke);
// Build global stiffness matrix
SparseMatrix<double> K = assembler.buildMatrix();
Using Different Element Types
// Create elements via factory
auto bar2 = createElement(ElementType::Bar2);
auto tri6 = createElement(ElementType::Tri6);
auto tet10 = createElement(ElementType::Tet10);
auto hex27 = createElement(ElementType::Hex27);
// Or instantiate directly
Tri3Element tri3;
Quad8Element quad8;
Hex20Element hex20;
Physics Integrators
DiffusionIntegrator
For heat conduction and scalar diffusion problems:
DiffusionIntegrator diffusion(conductivity);
MatrixX Ke = diffusion.computeElementMatrix(element, nodeCoords);
ElasticityIntegrator
For structural mechanics:
Material mat(E, nu); // Young's modulus, Poisson's ratio
// Choose analysis type
ElasticityIntegrator elasticity(mat, ElasticityType::PlaneStress);
ElasticityIntegrator elasticity(mat, ElasticityType::PlaneStrain);
ElasticityIntegrator elasticity(mat, ElasticityType::ThreeD);
MatrixX Ke = elasticity.computeElementMatrix(element, nodeCoords);
MassIntegrator
For dynamic analysis:
MassIntegrator mass(density);
MatrixX Me = mass.computeElementMatrix(element, nodeCoords);
Solvers
LinearSolver (Direct)
For symmetric positive-definite systems (SimplicialLDLT):
#include "fem/solvers/LinearSolver.hpp"
LinearSolver solver(K); // Factorize
if (solver.success()) {
VectorX u = solver.solve(F); // Solve Ku = F
}
LUSolver (General)
For non-symmetric or indefinite systems:
LUSolver solver(A);
VectorX x = solver.solve(b);
IterativeSolver
Conjugate gradient with preconditioner support for large systems:
#include "fem/solvers/IterativeSolver.hpp"
IterativeSolver solver(Preconditioner::Diagonal); // Or IncompleteCholesky
solver.setTolerance(1e-10);
solver.setMaxIterations(1000);
solver.compute(K);
VectorX u = solver.solve(F);
if (solver.converged()) {
std::cout << "Converged in " << solver.iterations() << " iterations\n";
}
EigenSolver
Generalized eigenvalue problem Kx = λMx using Spectra:
#include "fem/solvers/EigenSolver.hpp"
EigenSolver solver(K, M);
solver.setNumModes(10);
solver.compute();
VectorX lambda = solver.eigenvalues();
VectorX omega = solver.angularFrequencies(); // rad/s
VectorX freq = solver.frequencies(); // Hz
MatrixX modes = solver.modeShapes(); // Mass-normalized
ModalAnalysis
High-level modal analysis with mass participation factors:
#include "fem/solvers/ModalAnalysis.hpp"
ModalAnalysis modal(K, M);
modal.setNumModes(10);
modal.compute();
// Get results
for (const auto& result : modal.results()) {
std::cout << "Mode " << result.modeNumber
<< ": " << result.frequency << " Hz"
<< " (T = " << result.period << " s)\n";
}
// Mass participation in X direction (for 3-DOF per node structures)
MassParticipation mpf = modal.massParticipationX(3);
std::cout << "Cumulative mass participation: "
<< mpf.cumulative(mpf.cumulative.size()-1) * 100 << "%\n";
Dynamic Analysis
RayleighDamping
Proportional damping (C = αM + βK):
#include "fem/solvers/RayleighDamping.hpp"
// Method 1: Specify coefficients directly
RayleighDamping damping(alpha, beta);
// Method 2: Compute from target damping ratios at two frequencies
RayleighDamping damping = RayleighDamping::fromDampingRatios(
omega1, xi1, // 5% damping at 10 rad/s
omega2, xi2); // 3% damping at 100 rad/s
// Method 3: Single-frequency approximations
RayleighDamping massD = RayleighDamping::massProportional(omega, 0.05);
RayleighDamping stiffD = RayleighDamping::stiffnessProportional(omega, 0.05);
// Build damping matrix
SparseMatrix C = damping.computeDampingMatrix(M, K);
// Query damping ratio at any frequency
Scalar xi = damping.dampingRatio(omega);
NewmarkSolver (Implicit)
Unconditionally stable implicit time integration:
#include "fem/solvers/NewmarkSolver.hpp"
NewmarkSolver solver(M, C, K);
solver.setMethod(NewmarkMethod::AverageAcceleration); // β=1/4, γ=1/2
// Or: LinearAcceleration (β=1/6, γ=1/2)
// Or: Custom parameters
solver.setTimeStep(0.001);
VectorX u0 = VectorX::Zero(n);
VectorX v0 = VectorX::Zero(n);
solver.setInitialConditions(u0, v0);
solver.initialize();
for (double t = 0; t < tEnd; t += dt) {
VectorX F = computeLoad(t + dt);
solver.step(F);
VectorX u = solver.displacement();
VectorX v = solver.velocity();
VectorX a = solver.acceleration();
}
HHTSolver (Numerical Damping)
HHT-α method with controllable numerical damping for high frequencies:
#include "fem/solvers/HHTSolver.hpp"
HHTSolver solver(M, C, K);
solver.setAlpha(-0.1); // α ∈ [-1/3, 0], more negative = more damping
solver.setTimeStep(0.001);
solver.setInitialConditions(u0, v0);
solver.initialize();
// Query numerical properties
Scalar rho = solver.spectralRadius(); // ρ∞
for (double t = 0; t < tEnd; t += dt) {
solver.step(F);
}
ExplicitSolver (Conditionally Stable)
Central difference method for fast explicit integration:
#include "fem/solvers/ExplicitSolver.hpp"
// Lump mass matrix for efficiency
SparseMatrix Mlumped = ExplicitSolver::lumpMass(M);
ExplicitSolver solver(Mlumped, C, K);
// Check stability requirement
Scalar dtCrit = ExplicitSolver::criticalTimeStep(omegaMax);
// Or estimate from element size
Scalar dtCrit = ExplicitSolver::criticalTimeStepFromElement(minSize, waveSpeed);
Scalar dt = 0.8 * dtCrit; // Safety factor
solver.setTimeStep(dt);
if (!solver.isStable(omegaMax)) {
// Time step too large!
}
solver.setInitialConditions(u0, v0);
solver.initialize();
// Explicit methods are very fast per step
for (int step = 0; step < nSteps; ++step) {
solver.step(F);
}
Shape Functions
Each element provides:
shapeFunctions(xi)- Evaluate N_i at parametric coordinatesshapeFunctionDerivatives(xi)- Evaluate dN_i/dxiquadraturePoints(order)- Get integration points
Quad9Element quad9;
VectorX xi(2);
xi << 0.5, 0.25;
VectorX N = quad9.shapeFunctions(xi); // 9 values
MatrixX dN = quad9.shapeFunctionDerivatives(xi); // 9x2 matrix
auto qpts = quad9.quadraturePoints(3); // 3x3 = 9 points
Reference Domains
| Element Type | Domain | Description |
|---|---|---|
| Bar | ξ ∈ [-1, 1] | Standard 1D interval |
| Quad | ξ,η ∈ [-1, 1] | Standard biunit square |
| Hex | ξ,η,ζ ∈ [-1, 1] | Standard biunit cube |
| Tri | ξ,η ≥ 0, ξ+η ≤ 1 | Standard simplex |
| Tet | ξ,η,ζ ≥ 0, ξ+η+ζ ≤ 1 | Standard simplex |
Build & Test
# Build the test suite and run it to its contract
kiln test test/testFEM
# Run with verbose output
./debug/testFEM/testFEM -v2
# Run specific element tests
./debug/testFEM/testFEM testHex27ShapeFunctionsAtNodes -v2
The program's path is that of a kiln build with no output.dir
(The build convention).
Dependencies
Both dependencies are bundled (header-only, MPL2 license):
| Library | Version | Purpose |
|---|---|---|
| Eigen | 3.4.0 | Linear algebra, sparse matrices |
| Spectra | 1.0.1 | Large-scale eigenvalue problems |
Architecture
src/libs/libfem/fem/
├── core/ # Core data structures
│ ├── Mesh.hpp/cpp
│ ├── DOFHandler.hpp/cpp
│ ├── Quadrature.hpp/cpp
│ └── Types.hpp
├── elements/ # Element implementations
│ ├── Element.hpp/cpp
│ ├── Bar2.hpp/cpp, Bar3.hpp/cpp
│ ├── Tri3.hpp/cpp, Tri6.hpp/cpp
│ ├── Quad4.hpp/cpp, Quad8.hpp/cpp, Quad9.hpp/cpp
│ ├── Tet4.hpp/cpp, Tet10.hpp/cpp
│ └── Hex8.hpp/cpp, Hex20.hpp/cpp, Hex27.hpp/cpp
├── integrators/ # Physics modules
│ ├── Integrator.hpp
│ ├── DiffusionIntegrator.hpp/cpp
│ ├── MassIntegrator.hpp/cpp
│ └── ElasticityIntegrator.hpp/cpp
└── solvers/ # Solver wrappers
├── LinearSolver.hpp/cpp
├── IterativeSolver.hpp/cpp
├── EigenSolver.hpp/cpp
├── ModalAnalysis.hpp/cpp
├── RayleighDamping.hpp/cpp
├── NewmarkSolver.hpp/cpp
├── HHTSolver.hpp/cpp
└── ExplicitSolver.hpp/cpp
Nonlinear Analysis
Strain Measures
Utilities for large deformation analysis:
#include "fem/nonlinear/StrainMeasures.hpp"
Matrix3 F = /* deformation gradient */;
// Strain tensors
Matrix3 E = StrainMeasures::greenLagrangeStrain(F); // E = 0.5(F^T F - I)
Matrix3 C = StrainMeasures::rightCauchyGreen(F); // C = F^T F
Matrix3 b = StrainMeasures::leftCauchyGreen(F); // b = F F^T
// Invariants and principal values
Vector3 inv = StrainMeasures::invariants(C); // I1, I2, I3
Vector3 stretches = StrainMeasures::principalStretches(F); // λ1, λ2, λ3
// Voigt notation conversions (for constitutive relations)
Vector6 Ev = StrainMeasures::strainToVoigt(E); // Engineering shear
Matrix3 E2 = StrainMeasures::voigtToStrain(Ev);
// Stress push-forward/pull-back
Matrix3 sigma = StrainMeasures::pushForwardStress(S, F); // PK2 → Cauchy
Matrix3 S2 = StrainMeasures::pullBackStress(sigma, F); // Cauchy → PK2
Neo-Hookean Material
Hyperelastic material model for rubber-like behavior:
#include "fem/nonlinear/NeoHookean.hpp"
// Create from Lamé parameters
NeoHookean mat(lambda, mu);
// Or from engineering constants
NeoHookean mat = NeoHookean::fromENu(1000.0, 0.3); // E, nu
// Compute stress from deformation gradient
MaterialState state;
Matrix3 F = /* deformation gradient */;
Matrix3 S = mat.computeStressTensor(F); // 2nd Piola-Kirchhoff
Vector6 Sv = mat.computeStressFromF(F, state); // Voigt form
// Compute tangent stiffness
Matrix6 D = mat.computeTangentFromF(F); // Material tangent
// Strain energy density
Scalar W = mat.computeStrainEnergyFromF(F);
J2 Plasticity
von Mises plasticity with isotropic hardening:
#include "fem/nonlinear/J2Plasticity.hpp"
// Create with E, nu, initial yield stress
J2Plasticity mat(200e3, 0.3, 250.0);
// Set hardening law
mat.setHardening(HardeningType::Linear, 1000.0); // Linear: σ_y + H*ε_p
mat.setHardening(HardeningType::Saturation, 100.0, 10.0); // Voce: σ_y + Q(1-exp(-b*ε_p))
mat.setHardening(HardeningType::Perfect); // No hardening
// Compute stress (performs return mapping internally)
MaterialState state; // Tracks plastic strain history
Vector6 strain = /* total strain in Voigt form */;
Vector6 stress = mat.computeStress(strain, state);
// Check yield state
if (state.yielded) {
Scalar epEq = state.equivalentPlasticStrain;
}
// Static utilities
Scalar sigmaEq = J2Plasticity::vonMisesStress(stress);
Vector6 s = J2Plasticity::deviatoricStress(stress);
Newton-Raphson Solver
Iterative nonlinear solver with line search:
#include "fem/nonlinear/NewtonRaphson.hpp"
NewtonRaphson nr;
nr.setTolerance(1e-6);
nr.setMaxIterations(20);
nr.setConvergenceCriterion(ConvergenceCriterion::Force);
// Options: Force, Displacement, Energy, ForceAndDisp, ForceOrDisp
nr.setLineSearch(LineSearchMethod::Backtracking);
// Options: None, Backtracking, Bisection
// Define problem
VectorX u = VectorX::Zero(n);
auto residual = [&](const VectorX& x) -> VectorX {
return Fext - computeInternalForce(x); // R = F_ext - F_int
};
auto tangent = [&](const VectorX& x) -> SparseMatrix {
return computeTangentStiffness(x); // K = dF_int/du
};
// Solve
NewtonResult result = nr.solve(u, residual, tangent);
if (result.converged) {
std::cout << "Converged in " << result.iterations << " iterations\n";
} else {
std::cout << "Failed to converge, residual = " << result.residualNorm << "\n";
}
Arc-Length Method
For tracing equilibrium paths through limit points:
#include "fem/nonlinear/ArcLength.hpp"
ArcLength arc;
arc.setArcLength(0.1);
arc.setTolerance(1e-6);
arc.setMaxIterations(20);
arc.setType(ArcLengthType::Spherical); // Or Cylindrical, Updated
VectorX u = VectorX::Zero(n);
Scalar lambda = 0.0; // Load factor
VectorX Fref = /* reference load */;
auto internalForce = [&](const VectorX& x) -> VectorX {
return computeInternalForce(x);
};
auto tangent = [&](const VectorX& x) -> SparseMatrix {
return computeTangentStiffness(x);
};
// Step along equilibrium path
while (lambda < lambdaMax) {
ArcLengthResult result = arc.step(u, lambda, Fref, internalForce, tangent);
if (!result.converged) break;
// lambda and u are updated automatically
recordEquilibriumPoint(lambda, u);
}
High-Level FEModel Pipeline
For complete end-to-end analysis workflows, the FEModel class provides a simplified interface:
Basic Usage
#include "fem/model/FEModel.hpp"
using namespace fem;
using namespace fem::model;
// Create model and load geometry
FEModel model;
model.loadSTL("part.stl"); // Automatically tetrahedralizes
// Set material
model.setMaterial(EngineeringMaterial::aluminum());
// Apply boundary conditions
auto bottomNodes = model.getNodesOnPlane(Vector3(0, 0, 0), Vector3(0, 0, 1));
auto topNodes = model.getNodesOnPlane(Vector3(0, 0, 10), Vector3(0, 0, 1));
model.addLoad(Load::fixNodes(bottomNodes));
model.addLoad(Load::applyForce(topNodes, Vector3(0, 0, -1000)));
// Solve and check results
if (model.solve()) {
std::cout << model.summary().toStdString() << std::endl;
std::cout << "Max displacement: " << model.results().maxDisplacement() << " m" << std::endl;
std::cout << "Min FOS: " << model.results().safety.minFOS << std::endl;
// Export for visualization
model.exportVTK("results.vtk");
model.exportCSV("results.csv");
}
Loading a mesh from libmesh
A mesh that already exists in memory, for example the Tet4 volume mesh that the
Construct SDF export writes, goes in through loadMesh(). This skips the STL
import and the tetrahedralization that the export already did. Both precisions
are accepted. A float mesh is widened to double before conversion.
#include "fem/model/FEModel.hpp"
#include "mesh/io/IORegistry.hpp"
auto loaded = mesh::io::IORegistry<float>::instance().load("volume.vtk");
fem::FEModel model;
if (!model.loadMesh(loaded.value())) {
qWarning() << model.errorString();
}
loadMesh() returns false and sets errorString() if the source mesh has
no vertices or no elements. A surface mesh loads, but it will not solve as a
volume. The end-to-end case is testMeshExport::testFEModelFromSDF.
Engineering Materials
Pre-defined material properties with yield strength for safety factor calculations:
// Built-in materials
EngineeringMaterial steel = EngineeringMaterial::steel(); // E=210 GPa, nu=0.3, σy=250 MPa
EngineeringMaterial alum = EngineeringMaterial::aluminum(); // E=70 GPa, nu=0.33, σy=270 MPa
EngineeringMaterial titan = EngineeringMaterial::titanium(); // E=116 GPa, nu=0.34, σy=880 MPa
EngineeringMaterial pla = EngineeringMaterial::pla(); // E=3.5 GPa, nu=0.36, σy=50 MPa
EngineeringMaterial abs = EngineeringMaterial::abs(); // E=2.3 GPa, nu=0.35, σy=40 MPa
// Custom material
EngineeringMaterial custom(E, nu, yieldStrength, density, "Custom Material");
Load Types
// Fix all DOFs at nodes
Load::fixNodes(nodeIndices);
// Apply force distributed across nodes
Load::applyForce(nodeIndices, Vector3(fx, fy, fz));
// Prescribed displacement
Load::applyDisplacement(nodeIndices, Vector3(dx, dy, dz));
Node Selection Helpers
// Get nodes in a bounding box
auto nodes = model.getNodesInBox(Vector3(xMin, yMin, zMin), Vector3(xMax, yMax, zMax));
// Get nodes on a plane (with tolerance)
auto planeNodes = model.getNodesOnPlane(pointOnPlane, planeNormal, tolerance);
STL Import
For loading surface meshes from STL files:
#include "fem/io/STLImporter.hpp"
STLImporter importer;
// Import as surface mesh (Tri3 shell elements)
auto surfaceMesh = importer.importSurface("part.stl");
// Import and tetrahedralize to volume mesh (Tet4 elements)
auto volumeMesh = importer.importVolume("part.stl");
// With tetrahedralization options
TetrahedralizeOptions opts;
opts.maxEdgeLength = 0.01; // Maximum edge length
opts.qualityRatio = 2.0; // Quality ratio
opts.preserveSurface = true; // Keep original surface triangles
auto mesh = importer.importVolume("part.stl", opts);
// Check for errors
if (!mesh) {
std::cerr << importer.errorString().toStdString() << std::endl;
}
Note: The built-in tetrahedralization uses a centroid-fan method suitable for convex shapes. For complex non-convex geometries, consider using an external tetrahedral mesher.
Post-Processing & Results
Stress Recovery
Compute stress and strain fields from displacement solutions:
#include "fem/postprocess/StressRecovery.hpp"
// Create stress recovery with material properties
StressRecovery sr(E, nu); // Young's modulus, Poisson's ratio
// For 2D analysis
StressRecovery sr2D(E, nu, ElasticityIntegrator::ProblemType::PlaneStress);
// Compute stress from displacement solution
StressResult stress = sr.compute(mesh, displacement);
// Access results
VectorX vonMises = stress.vonMises; // Per-node von Mises stress
VectorX tresca = stress.tresca; // Per-node Tresca stress
MatrixX sigma = stress.stress; // Full stress tensor (nodes x 6, Voigt)
MatrixX eps = stress.strain; // Full strain tensor
MatrixX principals = stress.principalStress; // Principal stresses (nodes x 3)
// Static utilities for stress analysis
Scalar vm = StressRecovery::vonMisesStress(stressVoigt);
Scalar tr = StressRecovery::trescaStress(stressVoigt);
Vector3 sigmaP = StressRecovery::principalStresses(stressVoigt);
// Get principal directions
Vector3 principals;
Matrix3 directions;
StressRecovery::principalStressesWithDirections(stressVoigt, principals, directions);
Failure Criteria
Check structural integrity using standard failure theories:
#include "fem/postprocess/FailureCriteria.hpp"
// Available criteria
FailureCriteria::Criterion::VonMises // Distortion energy (ductile metals)
FailureCriteria::Criterion::Tresca // Maximum shear (conservative)
FailureCriteria::Criterion::MaxPrincipal // Maximum tensile stress (brittle)
FailureCriteria::Criterion::MinPrincipal // Maximum compressive stress
FailureCriteria::Criterion::Rankine // Maximum normal stress
FailureCriteria::Criterion::MaxStrain // Maximum principal strain
// Check if failure occurred
bool failed = FailureCriteria::checkFailure(stressVoigt, criterion, yieldStrength);
// Compute equivalent stress
Scalar sigmaEq = FailureCriteria::equivalentStress(stressVoigt, criterion);
// Compute safety margin (positive = safe)
Scalar margin = FailureCriteria::computeMargin(stressVoigt, criterion, limit);
// Compute factor of safety
Scalar fos = FailureCriteria::computeFOS(stressVoigt, criterion, limit);
Safety Factor Analysis
Compute factor of safety across the entire structure:
#include "fem/postprocess/SafetyFactor.hpp"
// Create with yield and ultimate strengths
SafetyFactor sf(yieldStrength, ultimateStrength);
// Compute yield FOS using von Mises criterion
SafetyFactorResult fosResult = sf.computeYieldFOS(stress, mesh);
// Or use Tresca criterion
SafetyFactorResult fosResult = sf.computeYieldFOS(stress, mesh,
FailureCriteria::Criterion::Tresca);
// Compute ultimate FOS
SafetyFactorResult ultResult = sf.computeUltimateFOS(stress, mesh);
// Access results
Scalar minFOS = fosResult.minFOS; // Minimum FOS in structure
Index critNode = fosResult.criticalNode; // Node with minimum FOS
Vector3 critLoc = fosResult.criticalLocation; // Position of critical node
VectorX allFOS = fosResult.fosPerNode; // FOS at every node
bool passed = fosResult.passed; // True if minFOS >= required
// Set required FOS for pass/fail check
sf.setRequiredFOS(1.5); // Require 50% safety margin
VTK Export
Export results for visualization in a VTK viewer:
#include "fem/io/VTKExporter.hpp"
VTKExporter vtk;
// Set deformation scale for visualization
vtk.setDeformationScale(100.0); // Amplify deformation 100x
// Export mesh only
vtk.exportMesh("mesh.vtk", mesh);
// Export with displacement
vtk.exportDisplacement("displaced.vtk", mesh, displacement);
// Export with full stress results
vtk.exportResults("stress.vtk", mesh, displacement, stress);
// Export with safety factors
vtk.exportSafetyFactors("fos.vtk", mesh, displacement, fosResult);
// Export everything
vtk.exportAll("complete.vtk", mesh, displacement, stress, fosResult);
VTK output includes:
- Deformed mesh geometry
- Displacement vectors and magnitude
- von Mises and Tresca stress
- Principal stresses (σ₁, σ₂, σ₃)
- Full stress tensor
- Factor of safety per node
CSV/JSON Export
Export for reporting and data analysis:
#include "fem/io/ResultsExporter.hpp"
ResultsExporter exporter;
exporter.setPrecision(8); // Decimal places
// CSV export (spreadsheet-compatible)
exporter.exportCSV("results.csv", mesh, displacement, stress, fosResult);
// JSON export (programmatic access)
exporter.exportJSON("results.json", mesh, displacement, stress, fosResult);
// Generate text summary
std::string summary = exporter.generateSummary(mesh, stress, fosResult);
std::cout << summary;
Example summary output:
=== FEM Analysis Summary ===
Mesh Statistics:
Number of Nodes: 1247
Number of Elements: 4823
Spatial Dimension: 3
Bounding Box:
Min: (0, 0, 0)
Max: (100, 50, 25)
Safety Factor Results:
Minimum FOS: 1.42
Critical Node: 847
Critical Location: (45.2, 12.8, 24.9)
Status: PASSED
Stress Results:
Max von Mises Stress: 175.6 MPa
Max Tresca Stress: 182.3 MPa
Max Principal Stress (σ1): 168.4 MPa
Min Principal Stress (σ3): -42.1 MPa
Architecture
src/libs/libfem/fem/
├── core/ # Core data structures
│ ├── Mesh.hpp/cpp
│ ├── DOFHandler.hpp/cpp
│ ├── Quadrature.hpp/cpp
│ └── Types.hpp
├── elements/ # Element implementations
│ ├── Element.hpp/cpp
│ ├── Bar2.hpp/cpp, Bar3.hpp/cpp
│ ├── Tri3.hpp/cpp, Tri6.hpp/cpp
│ ├── Quad4.hpp/cpp, Quad8.hpp/cpp, Quad9.hpp/cpp
│ ├── Tet4.hpp/cpp, Tet10.hpp/cpp
│ └── Hex8.hpp/cpp, Hex20.hpp/cpp, Hex27.hpp/cpp
├── integrators/ # Physics modules
│ ├── Integrator.hpp
│ ├── DiffusionIntegrator.hpp/cpp
│ ├── MassIntegrator.hpp/cpp
│ └── ElasticityIntegrator.hpp/cpp
├── nonlinear/ # Nonlinear analysis
│ ├── StrainMeasures.hpp/cpp
│ ├── NonlinearMaterial.hpp/cpp
│ ├── NeoHookean.hpp/cpp
│ ├── J2Plasticity.hpp/cpp
│ ├── NewtonRaphson.hpp/cpp
│ └── ArcLength.hpp/cpp
├── postprocess/ # Post-processing
│ ├── StressRecovery.hpp/cpp
│ ├── FailureCriteria.hpp/cpp
│ └── SafetyFactor.hpp/cpp
├── io/ # Import/Export
│ ├── STLImporter.hpp/cpp
│ ├── VTKExporter.hpp/cpp
│ └── ResultsExporter.hpp/cpp
├── model/ # High-level pipeline
│ └── FEModel.hpp/cpp
└── solvers/ # Solver wrappers
├── LinearSolver.hpp/cpp
├── IterativeSolver.hpp/cpp
├── EigenSolver.hpp/cpp
├── ModalAnalysis.hpp/cpp
├── RayleighDamping.hpp/cpp
├── NewmarkSolver.hpp/cpp
├── HHTSolver.hpp/cpp
└── ExplicitSolver.hpp/cpp
References
- Eigen Documentation
- Spectra Documentation
- Hughes, T.J.R. "The Finite Element Method: Linear Static and Dynamic Finite Element Analysis"
- Belytschko, Liu, Moran. "Nonlinear Finite Elements for Continua and Structures"