Skip to main content

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

Element Formulation

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

B = Ia @ Γ @ dN
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:
  1. Gauss points (most accurate)
  2. Element centroid (averaged)
  3. 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]

Build docs developers (and LLMs) love