Building Wind-Load Post-Processing

The building use case turns a pressure time series on a building surface into the engineering deliverables of a wind study: per-floor force and moment coefficients, the modal dynamic response, occupant-comfort accelerations, design load cases, and a multi-direction fan-out over a parametric study.

It composes the v3 recipes and ops (see Data sources, ops, and pipelines (v3 paradigm)); nothing here is high-rise-specific – the same helpers serve low-rise studies. The Python surface lives under cfdmod.building; the modal model it consumes is produced by the importers in Structural Model Import (Dynamics).

The pipeline

A case runs through a fixed sequence of stages, each a thin composition of library recipes:

pressure time series (surface)
  -> Cp                              (cp_from_pressure)
  -> per-floor Cf / Cm               (cf_per_floor, cm_per_floor)
  -> static-equivalent floor loads   (static_floor_loads)          [static]
  -> base loads per direction        (get_global_peaks_by_direction)
  -> floor-load points               (floor_load_source)           [dynamic]
  -> modal dynamic response          (solve_building_response)
  -> floor accelerations + comfort   (floor_accelerations, comfort_limit)
  -> load-case tables                (generate_load_cases)

The static and dynamic branches split after the per-floor coefficients and differ in one important respect: which speed the loads are referenced at. See Reference speeds.

A single case is aggregated into a BuildingCase, which carries the pressure time series, the dynamic pressure and the geometry so the downstream stages read a single value object rather than a pile of loose arrays.

Per-floor coefficients

The building surface is partitioned into floors (a height binning of the triangles), and the pressure coefficient time series is integrated over each floor’s triangles into a per-floor force and moment coefficient. The floor force coefficient is

\[ C_{f,i} = \frac{1}{A_{ref}} \sum_{t \in \text{floor } i} C_{p,t}\, \hat{n}_t\, A_t \]

with the outward normal \(\hat{n}_t\) and triangle area \(A_t\), and the moment coefficient likewise about the floor reference point. Normalisation uses an explicit reference area / volume (nominal_area / nominal_volume), not a per-region bounding-box area, so the coefficients convert back to real-scale forces unambiguously.

Peak estimation

Each deliverable reduces a fluctuating response (acceleration, floor force, displacement) to a single design peak. Three methods are selectable per deliverable (peak_value()):

  • "max" – the observed extreme (optionally of the absolute value).

  • "peak-factor" – Davenport Davenport[1]: \(\hat{x} = \bar{x} + g\,\sigma_x\), with the gust peak factor

    \[ g = \sqrt{2 \ln(\nu T)} + \frac{0.5772}{\sqrt{2 \ln(\nu T)}} \]

    where \(\nu\) is the mean up-crossing rate (taken as the response frequency \(f_0\), in Hz) and \(T\) the averaging duration (default 600 s). The constant 0.5772 is the Euler-Mascheroni constant.

  • "gumbel" – fit a Gumbel distribution to block maxima and read the design fractile off it (more stable than the raw max for short records). See the extreme-value discussion in Statistics.

Reference speeds

Two reference velocities appear in a building case and they are not interchangeable:

  • the simulation inlet \(U_H\), which the pressure coefficient was non-dimensionalised by. BuildingCase carries it as simul_reference_velocity, and dynamic_pressure is built from it.

  • the design \(U_H\) for the wind direction being processed (e.g. the 50-year NBR value), which the structural deliverable must be referenced at.

They differ by \((U_\text{design}/U_\text{simul})^2\) in every load, and because the design speed varies with direction that ratio reshapes the directional envelope rather than merely scaling it. static_floor_loads() therefore takes reference_velocity as a required keyword. The dynamic branch’s floor_load_source() keeps the case dynamic pressure as its default – appropriate there, because the modal response is reported as a ratio – and accepts the same reference_velocity override.

Base loads

The base (global) reaction per wind direction comes from get_global_peaks_by_direction(), which reduces the per-floor loads with global_load_history():

\[ F_x = \sum_i f_{x,i}, \quad F_y = \sum_i f_{y,i}, \quad M_x = -\sum_i f_{y,i}\,z_i, \quad M_y = +\sum_i f_{x,i}\,z_i, \quad M_z = \sum_i m_{z,i} \]

The overturning moments carry the per-floor lever arm \(z_i\) (the floor’s slab level by default, see floor_lever_heights()) and follow the right-hand rule: a horizontal force at height \(z\) contributes \(\mathbf{r} \times \mathbf{F} = (-z F_y,\; +z F_x,\; 0)\). A base moment computed without the lever arm is just a rescaled base force, which is hard to spot by eye in a directional envelope – so the lever arms travel with the load source (in elements.position) and an all-zero ladder is an error rather than a silently zeroed moment.

Dynamic response

Tall, flexible buildings resonate with the fluctuating wind load, so the response is not simply the static application of the peak load. The building is solved as a modal single-degree-of-freedom (SDOF) system per mode (solve_building_response()):

  1. The per-floor load coefficients are dimensionalised into a floor-load PointsDataSource (per-floor \(f_x\), \(f_y\), \(m_z\)) by the case dynamic pressure and reference area (floor_load_source()).

  2. Each mode shape is mass-normalized to unit generalized mass, \(\sum_i M_i\,(\phi_{x,i}^2 + \phi_{y,i}^2 + (R_i\,\phi_{rz,i})^2) = 1\), the precondition the modal solver assumes.

  3. The floor loads are projected onto each mode to a generalized modal load, the modal SDOF equation

    \[ \ddot{q}_j + 2\,\xi_j\,\omega_j\,\dot{q}_j + \omega_j^2\,q_j = p_j(t) \]

    is solved in time with damping ratio \(\xi_j\) and natural frequency \(\omega_j\), and the modal coordinates are recomposed into per-floor displacements (\(d_x\), \(d_y\)) and torsional rotation (\(r_z\)). The solver is the closed-form piecewise-linear (Nigam-Jennings) recurrence, exact for a load interpolated linearly between samples.

    Before trusting the response, cross-check the load record against vortex-shedding physics with check_vortex_shedding(): the across-wind spectral peak must imply a physical Strouhal number for the plan section. It is the only check on the record that does not depend on the case configuration agreeing with itself.

    The load must arrive on a physical time axis. The pressure stage writes a dimensionless time normalised by its own characteristic length, and that same length – not the base dimension used by the force normalisation – must re-dimensionalise it; see DimensionalData. De-normalising with the wrong length rescales the whole record, moving the forcing spectrum off the natural frequencies and changing the resonant amplification while every array shape and unit still looks right.

  4. The response is reported as per-floor displacements plus static-equivalent floor forces / moments (\(f_{eq,x}\), \(f_{eq,y}\), \(m_{eq,z}\)) that the structural engineer applies statically, and as per-floor horizontal accelerations at an off-centre occupant point (floor_accelerations()).

The natural periods, per-floor mass, radius of gyration, centre of mass and mode shapes come from the structural model (BuildingStructuralData); see Structural Model Import (Dynamics) for how that model is imported from TQS or Eberick.

Occupant comfort

Occupant comfort is judged by comparing the peak horizontal acceleration at the top floor against a standard’s acceptance limit. Three criteria are supported (comfort_limit()); all return the limit in SI \(m/s^2\) (convert to milli-g with mps2_to_milli_g()).

NBR 6123 (serviceability limit, sec. 9.6.2), a decreasing power law in the fundamental sway frequency \(f_0\) Nbr[2]:

\[ a_{lim} = 0.01\, k_c\, f_0^{-0.445} \quad [m/s^2] \]

with \(k_c = 4.08\) (residential) or \(6.12\) (commercial / office), valid over \(0.06 \le f_0 \le 1.00\) Hz at a 1-year return period.

Melbourne and Palmer (1992) serviceable-acceleration curve, a function of \(f_0\) and the return period \(R\) in years Melbourne and Palmer[3]:

\[ a = \sqrt{2 \ln(f_0 T)}\,\left(0.68 + \frac{\ln R}{5}\right)\, e^{-3.65 - 0.41 \ln f_0} \quad [m/s^2] \]

with \(T = 600\) s, valid over \(0.06 < f_0 < 1.0\) Hz and \(0.5 < R < 10\) years.

NBCC occupant-comfort criterion, a flat (frequency-independent) limit at a 10-year return period: 15 milli-g residential, 25 milli-g office / commercial.

Design load cases

For the structural hand-off, the multi-direction response is reduced to per-floor load-case tables (generate_load_cases(), written with save_load_case_tables()):

  • Effective-load envelopes – per-floor \(F_x\) / \(F_y\) / \(M_z\), one column per wind direction, for min / max (the signed per-floor envelopes) and peak (the governing envelope, whichever of min / max has the larger mean magnitude per direction and axis).

  • Companion load cases – for each principal axis the critical direction is selected and the accompanying loads on the other axes are carried along, in the form the structural software (e.g. Eberick) ingests.

Multi-direction fan-out

Real consulting cases process many wind directions (and bodies, and Cp configurations). The v3 building path solves one case at a time; the fan-out driver (run_fanout() with a FanoutPlan) maps every (direction, body, config) key through the whole Cp -> per-floor Cf/Cm -> dynamic-response chain and collects the results in a Container – the exact shape the multi-direction reducers (cfdmod.dynamics.cases) and the load-case tables above consume.

Note

The examples/high_rise/ suite in the repository is the reference layout: thin notebooks, one per stage, orchestrating the cfdmod.building helpers end-to-end and writing versioned debug / deliverable output roots.