Overview
The Q4 membrane element is a 4-node isoparametric quadrilateral element featuring:
4 nodes at the corners
Bilinear shape functions
2 DOF per node (translations in x and y)
4-point Gauss integration (reduced integration)
Serendipity element formulation
The Q4 element offers better performance than CST triangles but requires multiple elements to achieve good accuracy. For single-element accuracy, consider Q6, Q6i, or Q8.
Adding Q4 Elements
from milcapy.utils.types import ConstitutiveModel
model.add_membrane_q4(
id = 1 ,
node_ids = [ 1 , 2 , 3 , 4 ],
section_name = 'Shell_10mm' ,
state = ConstitutiveModel. PLANE_STRESS
)
Parameters:
id (int) → Element ID
node_ids (list[int]) → List of 4 node IDs (corner nodes)
section_name (str) → Section name (shell/membrane section)
state (ConstitutiveModel, optional) → Constitutive model
Available constitutive models:
'PLANE_STRESS' or ConstitutiveModel.PLANE_STRESS (default)
'PLANE_STRAIN' or ConstitutiveModel.PLANE_STRAIN
Critical: Nodes must be numbered counter-clockwise starting from any corner.
Node Ordering
Correct counter-clockwise ordering:
4 ----------- 3
| |
| |
| · | (element center)
| |
| |
1 ----------- 2
Node order: [1, 2, 3, 4] ✓
The element can be distorted (not rectangular), but avoid excessive distortion or aspect ratios > 3:1 for best accuracy.
Degrees of Freedom
Each corner node has 2 translational DOF:
Node 1: [Ux1, Uy1]
Node 2: [Ux2, Uy2]
Node 3: [Ux3, Uy3]
Node 4: [Ux4, Uy4]
Total: 8 DOF per element
Shape Functions
Bilinear shape functions in natural coordinates (ξ, η):
N1 = ( 1 - ξ) * ( 1 - η) / 4
N2 = ( 1 + ξ) * ( 1 - η) / 4
N3 = ( 1 + ξ) * ( 1 + η) / 4
N4 = ( 1 - ξ) * ( 1 + η) / 4
Where ξ, η ∈ [-1, 1] are natural coordinates.
Jacobian Matrix
The transformation from natural to physical coordinates:
J = ( 1 / 4 ) * [[J11, J12],
[J21, J22]]
J11 = - x1( 1 - η) + x2( 1 - η) + x3( 1 + η) - x4( 1 + η)
J12 = - y1( 1 - η) + y2( 1 - η) + y3( 1 + η) - y4( 1 + η)
J21 = - x1( 1 - ξ) - x2( 1 + ξ) + x3( 1 + ξ) + x4( 1 - ξ)
J22 = - y1( 1 - ξ) - y2( 1 + ξ) + y3( 1 + ξ) + y4( 1 - ξ)
Strain-Displacement Matrix
Where:
Ia = Strain operator matrix (3×4)
Γ = Inverse Jacobian (4×4 augmented)
dN = Shape function derivatives (4×8)
Gauss Integration
4-point reduced integration scheme:
ξ = η = [ - 1 / √ 3 , - 1 / √ 3 , 1 / √ 3 , 1 / √ 3 ]
weights = [ 1 , 1 , 1 , 1 ]
Stiffness Matrix
The 8×8 stiffness matrix is computed by Gauss quadrature:
K = Σ (B ^ T @ D @ B * det(J) * t * w_i)
Summed over 4 integration points.
Constitutive Models
Plane Stress
For thin structures (plates, shells):
state = ConstitutiveModel. PLANE_STRESS
D = (E / ( 1 - v²)) * [[ 1 , v, 0 ],
[v, 1 , 0 ],
[ 0 , 0 , ( 1 - v) / 2 ]]
Plane Strain
For thick structures (dams, foundations):
state = ConstitutiveModel. PLANE_STRAIN
k = E( 1 - v) / (( 1 + v)( 1 - 2v ))
D = k * [[ 1 , v / ( 1 - v), 0 ],
[v / ( 1 - v), 1 , 0 ],
[ 0 , 0 , ( 1 - 2v ) / ( 2 ( 1 - v)) ]]
Example: Single Plate Element
import milcapy as mx
from milcapy.utils.types import ConstitutiveModel
# Create model
model = mx.Model()
# Define nodes (1m × 1m square)
model.add_node( 1 , 0 , 0 )
model.add_node( 2 , 1 , 0 )
model.add_node( 3 , 1 , 1 )
model.add_node( 4 , 0 , 1 )
# Material and section
model.add_material( 'Steel' , E = 200e9 , v = 0.3 , rho = 7850 )
model.add_shell_section( 'Plate_10mm' , material = 'Steel' , t = 0.01 )
# Add Q4 element
model.add_membrane_q4(
id = 1 ,
node_ids = [ 1 , 2 , 3 , 4 ],
section_name = 'Plate_10mm' ,
state = ConstitutiveModel. PLANE_STRESS
)
# Boundary conditions
model.add_support( node_id = 1 , ux = True , uy = True )
model.add_support( node_id = 2 , ux = True , uy = True )
# Apply load
model.add_load_pattern( 'Tension' )
model.add_nodal_load( node_id = 3 , fx = 10000 , fy = 0 )
model.add_nodal_load( node_id = 4 , fx = 10000 , fy = 0 )
# Solve
model.solve( 'Tension' )
Important: Q4 elements have poor convergence with a single element. Mesh refinement is strongly recommended.
Example: Structured Q4 Mesh
import milcapy as mx
from milcapy.utils.types import ConstitutiveModel
def create_q4_mesh ( model , width , height , nx , ny , section_name , state ):
"""
Create a structured mesh of Q4 elements
Args:
model: Milcapy model
width: Total width
height: Total height
nx: Number of divisions in x
ny: Number of divisions in y
section_name: Section name
state: Constitutive model
"""
dx = width / nx
dy = height / ny
# Create nodes
node_id = 1
node_map = {}
for j in range (ny + 1 ):
for i in range (nx + 1 ):
x = i * dx
y = j * dy
model.add_node(node_id, x, y)
node_map[(i, j)] = node_id
node_id += 1
# Create Q4 elements
elem_id = 1
for j in range (ny):
for i in range (nx):
n1 = node_map[(i, j)]
n2 = node_map[(i + 1 , j)]
n3 = node_map[(i + 1 , j + 1 )]
n4 = node_map[(i, j + 1 )]
model.add_membrane_q4(
id = elem_id,
node_ids = [n1, n2, n3, n4],
section_name = section_name,
state = state
)
elem_id += 1
# Usage
model = mx.Model()
model.add_material( 'Aluminum' , E = 70e9 , v = 0.33 , rho = 2700 )
model.add_shell_section( 'Shell' , material = 'Aluminum' , t = 0.005 )
create_q4_mesh(
model = model,
width = 2.0 ,
height = 1.0 ,
nx = 20 , # 20 elements in x
ny = 10 , # 10 elements in y
section_name = 'Shell' ,
state = ConstitutiveModel. PLANE_STRESS
)
Mesh Quality Guidelines
Aspect Ratio Keep ratio of longest to shortest side < 3:1 for best results.
Distortion Internal angles should stay between 30° and 150°. Avoid highly skewed elements.
Mesh Density Use at least 4-6 elements per dimension for reasonable accuracy.
Transition Gradual mesh refinement. Avoid sudden size changes.
Advantages and Limitations
Advantages
Better than CST triangles
Can model distorted geometries
Efficient for structured meshes
Well-established formulation
Good for general-purpose meshing
Limitations
Poor single-element convergence
Requires refined meshes
Can lock in nearly incompressible materials
Less accurate than Q6, Q6i, or Q8
Cannot capture bending well with coarse mesh
The Q4 element is best suited for refined meshes (many elements). For coarse meshes or single-element accuracy, use Q6, Q6i, or Q8 instead.
When to Use Q4 Elements
Good for:
Refined structured meshes
General-purpose 2D analysis
Complex geometries (with sufficient mesh density)
Preliminary analysis with mesh refinement
Not recommended for:
Single-element patches
Coarse meshes
Bending-dominated problems
Nearly incompressible materials (v ≈ 0.5)
For better accuracy with fewer elements, consider:
Q6 : Adds rotational DOF for drilling resistance
Q6i : Incompatible modes for better bending
Q8 : Higher-order element with excellent single-element convergence
Stress Recovery
Stresses vary within the element. Typically evaluated at:
Gauss points (most accurate)
Element centroid (averaged)
Nodes (extrapolated from Gauss points)
# Stress at element center (ξ=0, η=0)
B_center = element.B( xi = 0 , eta = 0 )
D = element.D()
epsilon = B_center @ u_element
sigma = D @ epsilon # [σx, σy, τxy]