Code

Unit conversion pitfalls in civil engineering scripts

Why a script that “converges” can still be wrong by 10⁹ — mixed millimetres and metres, MPa versus pascals, and density versus unit weight.

Civil scripts fail more often from mixed units than from the solver. Finite-element engines, including OpenSees, are unitless: they will happily return a displacement of 1e6 “something” if E, I, and L do not share a system. Spreadsheets hide the same bug behind a cell format.

This article is not a full units library. It shows the failure modes that show up in structural and BIM scripts, and a small checklist that catches them before you trust a number.

Prerequisites

  • Python 3
  • The cantilever closed form δ = P L³ / (3 E I) (elementary strength of materials)
  • Willingness to print both the mixed-unit result and the coherent-unit result in the same run

The trap: three numbers that look fine

Suppose a steel cantilever, 4 m long, 100 mm × 100 mm square section, E = 200 GPa, tip load 1 kN. A rushed script often looks like this:

E = 200_000      # "MPa" in the comment, used as if it were N and mm
I = 8.333e6      # mm⁴  (100 mm × 100 mm rectangle)
L = 4.0          # metres  — the silent mix
P = 1_000        # newtons

delta_mixed = P * L**3 / (3.0 * E * I)
print(delta_mixed)

Every literal is a real quantity someone wrote on a drawing. Together they are not a model. L³ is in m³ while EI is in N·mm² if you took the comment seriously — or worse, the comment is ignored and the values are just floats.

Two coherent systems that must agree

Stay inside one system, then convert the displacement at the end.

Newton–millimetre (E in N/mm², which is MPa; I in mm⁴; L in mm; P in N; δ in mm):

E_nmm = 200_000.0       # N/mm²
I_nmm = 100.0 * 100.0**3 / 12.0  # mm⁴
L_nmm = 4_000.0         # mm
P_n = 1_000.0           # N
delta_mm = P_n * L_nmm**3 / (3.0 * E_nmm * I_nmm)

Newton–metre (SI):

E_si = 2.0e11           # Pa
I_si = I_nmm * 1e-12    # mm⁴ → m⁴
L_si = 4.0              # m
delta_m = P_n * L_si**3 / (3.0 * E_si * I_si)
delta_mm_from_si = delta_m * 1_000.0
rel = abs(delta_mm - delta_mm_from_si) / abs(delta_mm_from_si)
print(f"δ = {delta_mm:.6f} mm (N-mm system)")
print(f"δ = {delta_mm_from_si:.6f} mm (SI, converted)")
print(f"relative difference = {rel:.3e}")
if rel > 1e-9:
    raise SystemExit("unit systems disagree — stop")

If the two systems differ, you do not have a rounding issue. You have a conversion factor wrong (usually 10³, 10⁶, 10⁹, or 10¹² on I).

A third run that intentionally keeps L in metres with E and I in millimetre units should differ from the coherent result by many orders of magnitude. Keep that broken call in a test named test_mixed_mm_m_is_detected so the pitfall stays visible.

Other civil classics

Density versus unit weight

Concrete around 2400 kg/m³ is not 2400 kN/m³. Unit weight is γ = ρ g. Using 9.81 m/s² gives roughly 23.5 kN/m³. Using 2400 in a load pattern that expects kN/m³ is an error of about two orders of magnitude. The reverse — treating 25 kN/m³ as kg/m³ in a mass matrix — is just as bad.

US customary scripts add g = 32.2 ft/s² versus 9.81 m/s², and kips versus pounds. Do not convert g “by eye.”

Section properties

I in mm⁴ to m⁴ is a factor of 10⁻¹², not 10⁻⁶. Area mm² to m² is 10⁻⁶. Mixing those two factors is how a column “works” in a notebook and buckles in the real unit check.

BIM and IFC

IFC files carry a project length unit. A quantity stored as a float without applying ifcopenshell.util.unit (or the equivalent) is not a metre just because your script variable is named length_m. That is the next tutorial.

Solvers with no unit system

OpenSees, many research codes, and a large fraction of in-house Python will not warn you. A first elastic model that matches P L³ / (3 E I) in one documented unit set is the cheapest regression test you can add.

Validation

  1. Round-trip: convert mm → m → mm on L, I, and δ independently; each round-trip should hit relative error at machine precision for these scales.
  2. Order of magnitude: a 4 m steel stick, 100 mm square, 1 kN at the tip is a small millimetre-to-centimetre deflection, not metres and not nanometres. If the printout is 1e5 or 1e-12, believe the units, not the solver.
  3. Independent system: N-mm versus SI must agree after converting δ.
  4. Do not accept an LLM-generated factor of 1000 without writing the dimension of I on paper.

Limitations

  • This page does not replace a units library (pint, unyt, or a project-wide enum). Those tools still fail if you attach the wrong unit at the boundary (IFC, CSV, a GUI).
  • Thermal units, US customary structural units (kip, kip·in²), and geotechnical kPa versus ksf are only sketched. Add tests for the unit pairs your office actually mixes.
  • Self-weight, load factors, and code combinations are not unit conversions. Do not fold them into the same helper.
  • Numerical agreement between two Python expressions is not a substitute for a cited beam formula or a solver verification.

Professional context

If a calculation package does not state the unit system on the same page as the result, reviewers cannot check it. Put the unit set in the script header, in the log line next to every printed displacement, and in the verification note you keep with the model.

References

  • BIPM. The International System of Units (SI). Current brochure — names and prefixes, not a structural code.
  • Timoshenko, S. P., and Gere, J. M. Mechanics of Materials. Cantilever result used as the referee expression.
  • OpenSeesPy documentation: the engine does not convert units for you. https://openseespydoc.readthedocs.io/