Structural Load from Simulation¶
Wind-induced base shear force coefficients (\(C_{fx}\), \(C_{fy}\)) and the base torsional moment coefficient (\(C_{Mz}\)) are the primary inputs for the structural design of a building. Because they are dimensionless, they are independent of wind speed and can be combined with any design dynamic pressure to obtain the actual loads. This guide shows how to integrate the pressure coefficient \(c_p\) over the building surface to obtain time series of these coefficients.
Important
This workflow requires the \(c_p\) field to be already available in the body HDF5 file. Follow the Pressure Coefficient Measurement guide first to compute and store \(c_p\).
Definitions¶
Force Coefficient¶
The force coefficient is the dimensionless ratio between the net aerodynamic force and the product of dynamic pressure and a nominal area. Starting from the per-face force:
the force coefficient in a given direction is obtained by summing over all \(N\) faces and normalising by the nominal area \(A_\text{nom}\):
The same applies for \(C_{fy}\). Note that the dynamic pressure \(q\) cancels out - the force coefficient depends only on \(c_p\) and the geometry.
Symbol |
Quantity |
Notes |
|---|---|---|
\(c_{p_i}(t)\) |
Pressure coefficient on face \(i\) |
Time series from the previous step |
\(A_i\) |
Area of face \(i\) |
Computed from the triangle vertices |
\(\hat{n}_i\) |
Outward unit normal of face \(i\) |
Points away from the solid into the fluid |
\(A_\text{nom}\) |
Nominal (reference) area |
User-defined; see below |
The negative sign accounts for the convention that positive \(c_p\) means compression (pressure pushing into the surface), while the outward normal points away from it.
Moment Coefficient¶
The torsional moment coefficient \(C_{Mz}\) about a reference point \(O\) (typically the centroid of the building footprint at ground level) is normalised by the nominal volume \(V_\text{nom}\):
where \(\vec{r}_i = \vec{x}_{\text{centroid},i} - \vec{x}_O\) is the position vector from \(O\) to the centroid of face \(i\).
Note
The choice of reference point \(O\) affects the moment values. For structural design the standard choice is the geometric centre of the building footprint at ground level, because this is where the base reactions are evaluated. If you need moments about a different point (e.g. a floor slab), adjust the coordinates of \(O\) accordingly.
Nominal Area and Volume¶
The nominal area \(A_\text{nom}\) and nominal volume \(V_\text{nom}\) are user-defined reference quantities used for normalisation. For a rectangular building of width \(b\), depth \(d\), and height \(h\):
Computing Load Coefficients¶
The script below reads the body HDF5 file (which must already contain the cp/ group from the Pressure Coefficient Measurement step), extracts the mesh geometry from the XDMF descriptor, and integrates \(c_p\) over the building surface at every timestep to produce time series of \(C_{fx}\), \(C_{fy}\), and \(C_{Mz}\).
Prerequisites¶
Before running the script, make sure you have:
Run the
compute_cp.pyscript from the Pressure Coefficient Measurement guide. The body HDF5 file must contain thecp/group.Both the HDF5 and XDMF files for the body surface export in the same directory.
Python packages: h5py and NumPy. Install them with
pip install h5py numpyif not already available.
Parameters¶
Edit the top of the script to match your simulation:
Parameter |
Description |
|---|---|
|
Coordinates \((x, y, z)\) of the point about which the torsional moment is computed. Typically the centroid of the building footprint at ground level. |
|
Reference area (m^2) for force coefficient normalisation. |
|
Reference volume (m^3) for moment coefficient normalisation. |
|
Path to the body HDF5 file (with the |
|
Path to the body XDMF file (used to read mesh topology and vertex coordinates). |
Script¶
Download compute_base_loads.py
import h5py
import numpy as np
import xml.etree.ElementTree as ET
from pathlib import Path
# ---------------------------------------------------------------
# Parameters - edit these to match your simulation
# ---------------------------------------------------------------
# Base point - the point about which the torsional moment is computed.
# Typically the centroid of the building footprint at ground level.
base_point = np.array([0.0, 0.0, 0.0]) # (x, y, z) in metres
# Nominal area and volume for normalisation.
# For a rectangular building of width b, depth d, and height h:
# nominal_area = b * h
# nominal_volume = b * d * h
# Example for a building of 40x40x160
nominal_area = 40*160 # m^2
nominal_volume = 40*40*160 # m^3
# File paths - adjust to the location of your downloaded files
base = Path(".")
body_h5 = base / "bodies.body_Cp body.h5"
body_xdmf = base / "bodies.body_Cp body.xdmf"
output_csv = base / "base_load_coefficients.csv"
# ---------------------------------------------------------------
# ----- Step 1: read mesh from XDMF / HDF5 -----
print("Reading mesh geometry from XDMF ...")
tree = ET.parse(body_xdmf)
root = tree.getroot()
domain = root.find("Domain")
collection = domain.find("Grid")
first_grid = collection.findall("Grid")[0]
# Topology (triangle connectivity)
topo_elem = first_grid.find("Topology")
topo_data = topo_elem.find("DataItem")
topo_ref = topo_data.text.strip() # e.g. "file.h5:/path"
topo_h5_file, topo_h5_path = topo_ref.split(":")
# Geometry (vertex coordinates)
geom_elem = first_grid.find("Geometry")
geom_data = geom_elem.find("DataItem")
geom_ref = geom_data.text.strip()
geom_h5_file, geom_h5_path = geom_ref.split(":")
# Resolve HDF5 paths relative to the XDMF directory
xdmf_dir = body_xdmf.parent
topo_h5_path_full = xdmf_dir / topo_h5_file
geom_h5_path_full = xdmf_dir / geom_h5_file
with h5py.File(topo_h5_path_full, "r") as f:
triangles = f[topo_h5_path][:] # (N_tri, 3) - vertex indices
with h5py.File(geom_h5_path_full, "r") as f:
vertices = f[geom_h5_path][:] # (N_vert, 3) - xyz coordinates
# ----- Step 2: compute face normals, areas, and centroids -----
print("Computing face normals, areas, and centroids ...")
v0 = vertices[triangles[:, 0]]
v1 = vertices[triangles[:, 1]]
v2 = vertices[triangles[:, 2]]
edge1 = v1 - v0
edge2 = v2 - v0
cross = np.cross(edge1, edge2) # (N_tri, 3)
# Area = 0.5 * |cross product|
area = 0.5 * np.linalg.norm(cross, axis=1) # (N_tri,)
# Unit outward normal
normals = cross / (2.0 * area[:, np.newaxis]) # (N_tri, 3)
# Face centroid = mean of three vertices
centroids = (v0 + v1 + v2) / 3.0 # (N_tri, 3)
# Area-weighted normal vectors (A_i * n_hat_i)
area_normals = area[:, np.newaxis] * normals # (N_tri, 3)
# Lever arm from base point to each face centroid
lever_arm = centroids - base_point[np.newaxis, :] # (N_tri, 3)
n_tri = len(triangles)
print(f" {n_tri} triangles")
print(f" Nominal area = {nominal_area} m^2")
print(f" Nominal volume = {nominal_volume} m^3")
# ----- Step 3: read Cp and integrate force / moment coefficients -----
print("Reading Cp and integrating load coefficients ...")
with h5py.File(body_h5, "r") as f:
if "cp" not in f:
raise RuntimeError(
"No 'cp' group found in the body HDF5 file. "
"Run compute_cp.py first to add pressure coefficients."
)
cp_keys = sorted(f["cp"].keys(), key=lambda k: float(k[1:]))
n_steps = len(cp_keys)
# Pre-allocate output arrays
times = np.empty(n_steps)
Cfx = np.empty(n_steps)
Cfy = np.empty(n_steps)
CMz = np.empty(n_steps)
for i, key in enumerate(cp_keys):
cp = f[f"cp/{key}"][:] # (N_tri,)
times[i] = float(key[1:]) # strip the leading 't'
# Force coefficient per face: cf_i = -cp_i * A_i * n_hat_i / A_nom
# The negative sign accounts for pressure acting inward
# on the surface (positive cp = compression = inward force).
# Summing over all faces gives the global force coefficient.
Cfx[i] = -(cp * area_normals[:, 0]).sum() / nominal_area
Cfy[i] = -(cp * area_normals[:, 1]).sum() / nominal_area
# Torsional moment coefficient:
# CMz = sum( rx * cfy_i - ry * cfx_i ) / V_nom
fx_per_face = -cp * area_normals[:, 0]
fy_per_face = -cp * area_normals[:, 1]
CMz[i] = (
lever_arm[:, 0] * fy_per_face
- lever_arm[:, 1] * fx_per_face
).sum() / nominal_volume
if (i + 1) % 100 == 0 or (i + 1) == n_steps:
print(f" {i + 1}/{n_steps} timesteps", end="\r")
print(f"\nIntegrated {n_steps} timesteps over {n_tri} faces.")
# ----- Step 4: write results to CSV -----
print(f"Writing results to {output_csv} ...")
header = "time,Cfx,Cfy,CMz"
data = np.column_stack([times, Cfx, Cfy, CMz])
np.savetxt(output_csv, data, delimiter=",", header=header, comments="",
fmt="%.6e")
print("Done.")
print(f" Cfx range: [{Cfx.min():.4f}, {Cfx.max():.4f}]")
print(f" Cfy range: [{Cfy.min():.4f}, {Cfy.max():.4f}]")
print(f" CMz range: [{CMz.min():.4f}, {CMz.max():.4f}]")
Output¶
The script writes a CSV file (base_load_coefficients.csv) with the following columns:
Column |
Description |
|---|---|
|
Simulation time (s) |
|
Force coefficient in the \(x\)-direction (along-wind) |
|
Force coefficient in the \(y\)-direction (across-wind) |
|
Torsional moment coefficient about the \(z\)-axis |
Note
The same integration approach can be used to obtain the remaining components (\(C_{fz}\), \(C_{Mx}\), \(C_{My}\)) if needed. The script can be extended by summing the corresponding area-normal and cross-product components.
The CSV can be loaded into ParaView, a spreadsheet, or any plotting tool to visualise the time series alongside the building surface.
Load coefficient time series (\(C_{fx}\), \(C_{fy}\), \(C_{Mz}\)) plotted in ParaView alongside the building surface coloured by \(c_p\).¶
Interpreting the Results¶
Sign Convention¶
The sign of each component follows the right-hand rule applied to the simulation coordinate system. In a typical AeroSim setup where \(x\) is the along-wind direction:
\(C_{fx} > 0\): net force pushes the building in the \(+x\) (downwind) direction - this is the along-wind drag coefficient.
\(C_{fy}\): across-wind force coefficient, which oscillates around zero for a symmetric building in smooth flow but can have a nonzero mean under turbulent or asymmetric conditions.
\(C_{Mz}\): torsional moment coefficient about the vertical axis. A nonzero mean \(C_{Mz}\) indicates an asymmetric pressure distribution (e.g. from irregular building geometry or surrounding buildings).
Statistical Quantities¶
From the time series you can extract the standard statistics used in wind engineering:
Mean (\(\overline{C_f}\), \(\overline{C_M}\)): the time-averaged coefficient, used for static design checks.
Peak (\(\hat{C_f}\), \(\hat{C_M}\)): the maximum instantaneous coefficient, used for ultimate limit state checks.
RMS (\(\sigma_{C_f}\), \(\sigma_{C_M}\)): the standard deviation of the fluctuating component, used for fatigue and dynamic response assessments.
Obtaining Dimensional Loads¶
To convert the dimensionless coefficients into forces (N) and moments (N.m) for a specific design condition, multiply by the design dynamic pressure and the corresponding reference quantity:
where the design dynamic pressure is:
This separation between the aerodynamic coefficient (from the simulation) and the design wind speed (from the wind code or site-specific study) is the standard practice in wind engineering. The same coefficient time series can be reused for different wind speed scenarios without rerunning the simulation.
Appendix: Geometric Quantities from the Mesh¶
The force and moment integrals require three geometric properties per triangle. The script computes them directly from the vertex coordinates stored in the XDMF/HDF5 files. This section documents the formulas for reference.
Face normal and area. For a triangle with vertices \(\vec{v}_0\), \(\vec{v}_1\), \(\vec{v}_2\):
Face centroid. The centroid of the triangle is the arithmetic mean of its vertices:
Lever arm. The position vector from the base point to the face centroid:
Tip
All these quantities are static - they depend only on the mesh and need to be computed once. Only the \(c_p\) time series changes at each timestep.