Creeping flow
creepingFlowScript solves steady, incompressible creeping (Stokes) flow in 2D, for velocity and pressure,
with Taylor–Hood mixed elements.
Governing equations
where is the velocity, the pressure and the dynamic viscosity. Inertia and body forces are neglected, which is valid when the Reynolds number is much smaller than one.
In version 0.2.0 the viscosity is fixed to . With velocity boundary conditions only, a constant viscosity does not change the velocity field and the pressure scales linearly with , so the pressure of a fluid with another viscosity is . A viscosity option was added in version 0.3.0.
Taylor–Hood elements
Velocity and pressure are approximated with different basis functions: biquadratic (Q2) for the velocity components and bilinear (Q1) for the pressure. This pairing satisfies the inf–sup (LBB) stability condition, so no stabilization is needed.
| Element shape | Velocity | Pressure |
|---|---|---|
| Quadrilateral | Q2 (9 nodes, biquadratic) | Q1 (4 corner nodes, bilinear) |
The mesh must therefore be 2D with quadratic quadrilaterals: a structured mesh with
elementOrder: "quadratic", or a quadratic Gmsh mesh of quadrilaterals. The triangular (P2–P1) elements in the
figure were added in version 0.3.0.
Weak form
With the quadratic (velocity) and the linear (pressure) basis functions, the Galerkin residuals are
Integration by parts leaves the boundary term , which is zero on stress-free boundaries. The model is linear, so the system is solved once.
Pressure level
If every boundary has a velocity condition, the pressure is defined only up to a constant. In that case
FEAScript sets at the first pressure node (for a structured mesh, the bottom-left corner of the domain).
When at least one boundary is "stressFree", the pressure level is set by that boundary and no node is fixed.
Usage
The lid-driven cavity: a cavity whose top wall moves with unit velocity.
import * as math from "mathjs";
global.math = math;
import { FEAScriptModel } from "feascript";
// 1. Create the model and select the physics
const model = new FEAScriptModel();
model.setModelConfig("creepingFlowScript");
// 2. Define the mesh
model.setMeshConfig({
meshDimension: "2D",
elementOrder: "quadratic", // Required (Taylor–Hood elements)
numElementsX: 12,
numElementsY: 6,
maxX: 4,
maxY: 2,
});
// 3. Apply the boundary conditions
model.addBoundaryCondition("0", ["constantVelocity", 0, 0]); // Bottom wall
model.addBoundaryCondition("1", ["constantVelocity", 0, 0]); // Left wall
model.addBoundaryCondition("2", ["constantVelocity", 1, 0]); // Moving lid
model.addBoundaryCondition("3", ["constantVelocity", 0, 0]); // Right wall
// 4. Solve
model.setSolverMethod("lusolve");
const result = model.solve();
The model takes no coefficient functions.
Boundary conditions
| Condition | Parameters | Kind |
|---|---|---|
["constantVelocity", ux, uy] | Velocity components | Dirichlet |
["stressFree"] | – | Neumann (natural) |
Use ["constantVelocity", 0, 0] for no-slip walls, a non-zero velocity for moving walls and inflows, and
["stressFree"] for outflows. A boundary without a condition behaves as "stressFree", but it does not prevent
the pressure from being fixed at the first pressure node: mark open boundaries explicitly with "stressFree".
Solvers
Only "lusolve" is supported. The pressure rows of the system have zero diagonal entries, which the Jacobi method
cannot handle, and the frontal solver is not implemented for this model.
Results
result.solutionVector holds the unknowns of the mixed formulation, in three consecutive blocks:
where is the number of mesh nodes and the number of pressure (corner) nodes. The velocity components
are in the order of nodesCoordinates. After solve(), the model stores the sizes of the blocks in
model._creepingFlowMetadata:
| Property | Description |
|---|---|
totalNodesVelocity | , the number of velocity nodes (all mesh nodes) |
totalNodesPressure | , the number of pressure nodes |
pressureNodeIndices | For each pressure unknown, the index of its mesh node (0-based) |
Slice the solution vector to get each field, and pass it to plotSolution with the
coordinates of the mesh nodes:
const { solutionVector, nodesCoordinates } = result;
const N = model._creepingFlowMetadata.totalNodesVelocity;
const u = solutionVector.slice(0, N);
const v = solutionVector.slice(N, 2 * N);
const velocityMagnitude = u.map((ui, i) => Math.sqrt(ui ** 2 + v[i] ** 2));
plotSolution(model, { solutionVector: velocityMagnitude, nodesCoordinates }, "contour", "velocityCanvas");
plotSolution(model, { solutionVector: u, nodesCoordinates }, "contour", "uVelocityCanvas");
The pressure has values at the corner nodes only: solutionVector[2 * N + k] is the pressure at mesh node
pressureNodeIndices[k].
Examples
- Node.js script in
examples/creepingFlowScriptof FEAScript-core 0.2.0