RasmahRasmah

Units

Overview

Every number in a physics problem means something — a length, a force, a temperature — and a number with the wrong meaning is a silent disaster. The classic example is the 1999 Mars Climate Orbiter: one team reported a thruster impulse in pound-force·seconds while another read it in newton·seconds, and the spacecraft burned up because the two units never met.

Rasmah prevents this class of mistake by carrying physical dimensions on every quantity. A length is not just 0.5; it is 0.5 of a metre. The system then refuses to do something physically meaningless — you cannot add a length to a pressure — and it converts between units for you so you can work in millimetres, tonnes, and megapascals, or in inches and pounds-force, while the solver always sees clean SI values.

This chapter introduces the three ideas behind Rasmah's units:

  1. a dimension — the pattern of base-quantity exponents that tells you what a number measures;
  2. a quantity — a value tagged with a dimension, always stored in SI;
  3. a unit system — the choice of units used only at the boundary, to read bare numbers and to render results.

Dimensions: what a number measures

Dimensional analysis says that any mechanical quantity is built from a small handful of base quantities. Rasmah tracks the seven SI base quantities — length $L$, mass $M$, time $T$, electric current $I$, thermodynamic temperature $\Theta$, amount of substance $N$, and luminous intensity $J$ — plus plane angle. Every other quantity is a product of powers of these, so a dimension is just the list of exponents. Pressure is force per area,

\[\text{pressure} = \frac{M\,L\,T^{-2}}{L^{2}} = M\,L^{-1}\,T^{-2},\]

so its dimension is the exponent tuple $(L, M, T) = (-1, 1, -2)$. Rasmah lets those exponents be rational, which is what makes fractional dimensions such as fracture toughness ($\text{Pa}\cdot\sqrt{\text{m}} = M\,L^{-1/2}\,T^{-2}$) representable.

Rasmah.Units.DimensionsType
Dimensions(exps)

The 8 base-quantity exponents that define a physical dimension, in the order (length, mass, time, electric current, thermodynamic temperature, amount of substance, luminous intensity, plane angle).

Dimensional analysis expresses every quantity as a product of base-quantity powers. A dimension is that pattern of powers: pressure is $M\,L^{-1}\,T^{-2}$, so its exponent tuple is $(L, M, T, I, \Theta, N, J, \text{angle}) = (-1, 1, -2, 0, 0, 0, 0, 0)$. The exponents are rational, so fractional dimensions (e.g. fracture toughness $\text{Pa}\cdot\sqrt{\text{m}} = M\,L^{-1/2}\,T^{-2}$) are representable. Plane angle is tracked only for display (radians/degrees); solvers treat it as a plain number.

Arguments

  • exps: an 8-tuple of integer (or rational) exponents, in the base-quantity order above.

Returns

A Dimensions tag. The common dimensions are pre-computed constants — Rasmah.D_LENGTH, Rasmah.D_MASS, Rasmah.D_PRESSURE, Rasmah.D_FORCE, Rasmah.D_ENERGY, … — and Rasmah.DIMENSIONLESS is the all-zero tuple.

Example

julia> Dimensions((-1, 1, -2, 0, 0, 0, 0, 0)) == Rasmah.D_PRESSURE
true
source

The most common dimensions are pre-computed as constants — Rasmah.D_LENGTH, Rasmah.D_MASS, Rasmah.D_PRESSURE, Rasmah.D_FORCE, Rasmah.D_ENERGY, and so on — and Rasmah.DIMENSIONLESS is the all-zero tuple.

Rasmah.D_PRESSURE == Rasmah.D_MASS * Rasmah.D_LENGTH / Rasmah.D_TIME^2
false

Quantities: a value with a dimension

A Quantity is a number carrying its dimension. The stored value is always in SI base units — the units you type are converted to SI the moment the quantity is built. This one invariant is what keeps the solver fast and the AD path clean: units are a facade, and the compute kernel only ever sees plain Float64.

Rasmah.Units.QuantityType
Quantity(x, dim::Dimensions)
Quantity(x, u::UnitDef)
Quantity(x::Real)

A number tagged with physical dimensions. The stored value val is always in SI base units; dim is the Dimensions tag.

A physical quantity is a magnitude times a unit, $q = v \cdot [q]$. Rasmah splits the two: the magnitude v is kept in SI base units, and the unit's meaning — the pattern of base-quantity exponents — is carried by the dimension. Keeping the magnitude SI-canonical is what lets the solver and the AD path stay in plain Float64 while the boundary layer still knows what each number measures.

Construct with the string macro (10u"mm"), from a unit (Quantity(10, u"mm")), or from a raw SI value and a dimension (Quantity(0.5, Rasmah.D_LENGTH)).

Arguments

  • x: the magnitude. A bare Real is taken as SI; with u it is read in u's unit.
  • dim::Dimensions: the physical dimension (a bare Real is dimensionless).
  • u::UnitDef: the unit to interpret x in (converted to SI on entry).

Returns

A Quantity whose val is in SI base units.

Example

julia> 10u"mm"
0.01 m

julia> 2u"m" * 3u"s"
6.0 m·s

julia> ustrip(10u"mm")
0.01
source

Quantities behave like the numbers underneath, with the dimensions doing the bookkeeping. Adding or subtracting requires equal dimensions; multiplying and dividing combine them; powers, sqrt, abs, and comparisons all propagate or check them:

2u"m" * 3u"s"
6.0 m·s
10u"m" / 2u"s"
5.0 m/s
sqrt(4u"m^2")
2.0 m
1u"m" + 2u"m"
3.0 m

The same type checks protect you everywhere: an attempt to add a length to a pressure throws a DimensionError instead of producing a meaningless number.

Rasmah.Units.DimensionErrorType
DimensionError

Error thrown when an operation between quantities with incompatible dimensions is attempted (e.g. adding a length to a mass).

source

Unit literals and parsing

Units are written with the u"…" string macro, which understands the full SI prefix family and compound expressions:

Rasmah.Units.@u_strMacro
@u_str(str)

String macro for unit literals: u"mm" returns a UnitDef (magnitude 1); a value is attached by juxtaposition — 10u"mm" is 10 * u"mm", handled by *(Number, UnitDef), so affine temperature scales (25u"°C") convert correctly.

Example

julia> u"mm"
mm

julia> 25u"°C"
298.15 K
source
10u"mm"
0.01 m
25u"°C"
298.15 K

The affine temperature scales (°C, °F, °R) carry an offset as well as a scale, which is why 25u"°C" is 298.15 K — the same absolute temperature, not 25 of anything.

For programmatic use the same parser is exposed as uparse (returns the dimension, the SI factor, and the offset) and uparse_quantity (returns a Quantity):

Rasmah.Units.uparseFunction
uparse(str) -> (dim::Dimensions, factor::Float64, offset::Float64)

Parse a unit expression ("mm", "kg/m^3", "N*m", "W/(m·K)", "°C") into its dimension, its SI magnitude (the factor a value in that unit is multiplied by to get the SI value), and an additive offset (nonzero only for the temperature scales °C/°F, which are only valid as standalone units).

source
Rasmah.Units.uparse_quantityFunction
uparse_quantity(x, str) -> Quantity

Parse the unit string str and return x as a Quantity expressed in that unit (e.g. uparse_quantity(10, "mm")).

source
uparse_quantity(10, "mm")
0.01 m

The unit system

The UnitSystem decides two things: how bare numbers are interpreted when they cross the API boundary (a bare length is read in the system's length unit), and how quantities are rendered for display. Base units (length, mass, time, …) and derived display units (force, pressure, stress, energy, …) may be set independently, so a system can be non-consistent — millimetres for length with newtons for force and megapascals for stress, the way structural engineers actually work.

Rasmah.Units.UnitSystemType
UnitSystem(; length, mass, time, current, temperature, amount, luminous,
           angle, force, pressure, stress, energy, power, velocity, density,
           viscosity, frequency)

The units used to interpret bare numbers at the API boundary and to render Quantity values for display. Base and derived units may be given as a UnitDef, a Quantity (u"mm"), or a unit name ("mm"). Derived display units (force, stress, …) are independent of the base units, so they may be a non-consistent set (e.g. mm + N + MPa). When a derived unit is omitted it is derived from the base units. SI is the canonical internal system regardless of these choices.

source

The active system is a global setting, queried and changed with:

Rasmah.Units.set_unitsystem!Function
set_unitsystem!(us::UnitSystem) -> UnitSystem
set_unitsystem!(; length="mm", mass="tonne", ...)

Set the active unit system. The keyword form keeps the current system's other units and changes only those named.

source
unitsystem().length
m
set_unitsystem!(MM_TONNE_S)
10u"mm"
10.0 mm
set_unitsystem!(SI_UNITS)
10u"mm"
0.01 m

Three ready-made systems cover the common cases:

Rasmah.Units.MM_TONNE_SConstant
MM_TONNE_S

The millimetre–tonne–second unit system (structural FEA): length mm, mass tonne, force N, pressure/stress MPa, energy mJ.

source
Rasmah.Units.IN_LBF_SConstant
IN_LBF_S

The US customary inch–pound–second unit system (structural FEA): length inch, mass lbm, force lbf, stress ksi, energy in·lbf, power hp, temperature °F.

source

Converting and stripping

Because the SI value never changes, converting is just a change of display:

Rasmah.Units.ustripFunction
ustrip(q) -> Number
ustrip(u, q) -> Number

Strip the units from a Quantity (returning its SI value), or convert it to the value expressed in unit u. Bare numbers are returned unchanged.

This is the choke point into the solver: every geometry, material, and load entry point calls it on the way in, so the compute kernel and the AD path only ever see raw SI Float64.

Example

julia> ustrip(10u"mm")
0.01

julia> ustrip(u"mm", 10u"m")
10000.0
source
Rasmah.Units.udimFunction
udim(x) -> Dimensions

The Dimensions of a Quantity, UnitDef, or bare number (dimensionless).

source
ustrip(10u"mm")
0.01
uconvert(u"mm", 10u"m")
10000.0
udim(10u"mm") == Rasmah.D_LENGTH
true

ustrip is also the choke point into the solver: every geometry, material, and load entry point strips its Quantity inputs to SI Float64 on the way in, so the compute kernel and the AD path never see a Quantity.

Display: units, names, and significant figures

Rendering a quantity in the active system's preferred unit is done by quantity_string; si_unit_string decomposes a raw dimension back into base units:

Rasmah.Units.quantity_stringFunction
quantity_string(q) -> String

Render a quantity in the active unit system's preferred display unit.

Example

julia> quantity_string(2u"m")
"2.0 m"

julia> si_unit_string(Rasmah.D_PRESSURE)
"kg/(m·s^2)"
source
Rasmah.Units.si_unit_stringFunction
si_unit_string(d::Dimensions) -> String

The SI base-unit string of a dimension (e.g. "kg/(m·s²)" for pressure, "" for dimensionless).

source
quantity_string(2u"m")
"2.0 m"
si_unit_string(Rasmah.D_PRESSURE)
"kg/(m·s^2)"

For a nicer scale, simplify re-expresses a quantity at the power-of-1000 prefix whose mantissa lies in $[1, 1000)$, so 1000 µm becomes 1.0 mm:

Rasmah.Units.simplifyFunction
simplify(q::Quantity) -> ScaledQuantity

Re-express q at the power-of-1000 SI prefix whose mantissa lies in [1, 1000), so simplify(1000u"µm") is 1.0 mm and simplify(1500u"m") is 1.5 km. Dimensions without a prefixable SI unit (angle, compound units) are returned in their base unit unchanged. Mass prefixes attach to the gram.

Example

julia> simplify(1000u"µm")
1.0 mm

julia> simplify(1500u"m")
1.5 km

julia> simplify(2500u"N")
2.5 kN

julia> simplify(1000u"kg")
1.0 Mg
source
Rasmah.Units.ScaledQuantityType
ScaledQuantity(value, unit)

A quantity re-expressed at a chosen unit scale, returned by simplify. ustrip gives the SI value; Quantity(::ScaledQuantity) converts back.

source
simplify(1000u"µm")
1.0 mm
simplify(1500u"m")
1.5 km
simplify(2500u"N")
2.5 kN
simplify(1000u"kg")
1.0 Mg

Mass is special: prefixes attach to the gram (the kilogram is the SI base unit but already carries a prefix), which is why 1000 kg simplifies to 1.0 Mg.

The number of significant digits used when rendering — and the full spelled-out name and one-line description of a unit — are exposed as:

Rasmah.Units.sigfigsFunction
sigfigs() -> Union{Nothing,Int}

The number of significant digits used to render Quantity values (see quantity_string). nothing means full precision.

source
Rasmah.Units.set_sigfigs!Function
set_sigfigs!(n)

Set the number of significant digits used to render Quantity values. Pass nothing for full precision (the default), or a positive integer for significant-figure rounding (e.g. set_sigfigs!(6)).

source
Rasmah.Units.unit_nameFunction
unit_name(x) -> String

The full (spelled-out) name of a unit. Accepts a UnitDef, a unit Quantity (u"mm"), a Symbol, or a unit name String:

unit_name(u"mm")       == "millimeter"
unit_name(u"GPa")      == "gigapascal"
unit_name(u"kg/m^3")   == "kilogram per cubic meter"
unit_name(u"W/(m·K)")  == "watt per meter kelvin"
unit_name(u"°C")       == "degree Celsius"
source
unit_name(u"mm") == "millimeter"
true
unit_name(u"GPa") == "gigapascal"
true
unit_info(u"mm") == "mm — millimeter — dimension: m"
true

Significant figures are display-only — they never change the stored SI value — and they also absorb the tiny floating-point residue of imperial/affine conversions:

set_sigfigs!(6)
uconvert(u"°F", 100u"°C")
211.99999999999994
set_sigfigs!(nothing)

Units through the pipeline

Because the invariant is SI inside, units at the boundary, units thread through the whole pipeline without ever reaching the solver. Geometry constructors accept Quantity arguments, and geometric measurements come back tagged:

volume(cylinder(1, 2))
6.283185307179586 m^3
volume(box(2u"mm", 3u"mm", 4u"mm"))
2.4e-8 m^3

Materials expose each property with its physical dimension:

Rasmah.Materials.quantityFunction
quantity(m, sym::Symbol) -> Quantity

The value of a material property as a Quantity with its SI unit (for display).

source
Rasmah.Materials.property_dimensionFunction
property_dimension(sym::Symbol) -> Dimensions

The physical dimension of a material property accessor (e.g. property_dimension(:youngs_modulus) is pressure). Throws KeyError for an unknown property.

source
Rasmah.Materials.property_dimensionsFunction
property_dimensions() -> Dict{Symbol,Dimensions}

A copy of the physical-dimension table mapping each property accessor symbol to its Dimensions (e.g. :youngs_modulus → pressure).

source
si_unit_string(property_dimension(:youngs_modulus))
"kg/(m·s^2)"
si_unit_string(property_dimension(:density))
"kg/m^3"

And a solved field — or a load applied to it — knows its own dimension, so results and boundary conditions can be checked and rendered correctly:

Rasmah.field_dimensionFunction
field_dimension(physics, field::Symbol) -> Dimensions

The physical dimension of a solved field (e.g. field_dimension(Elasticity(), :u) is length). Falls back to dimensionless for unknown fields.

source
Rasmah.load_dimensionFunction
load_dimension(physics, field::Symbol) -> Dimensions

The physical dimension of a load/Neumann term on a field (a force for :u, a heat flux for :T, a charge density for , …).

source
si_unit_string(field_dimension(Elasticity(), :u))
"m"
si_unit_string(load_dimension(Elasticity(), :u))
"m·kg/s^2"
si_unit_string(field_dimension(HeatConduction(), :T))
"K"

A solved field is returned as a QuantityVector (or QuantityMatrix), a vector of values sharing one dimension; ustrip gives back the raw SI vector:

Rasmah.Units.QuantityVectorType
QuantityVector(data, dim)

A vector of quantities sharing one dimension, returned by unit-aware entry points. ustrip(v) gives the raw SI Vector; v[i] gives a scalar Quantity.

source
Rasmah.Units.QuantityMatrixType
QuantityMatrix(data, dim)

A matrix of quantities sharing one dimension (e.g. a solved vector-valued field, one column per degree of freedom). ustrip(m) gives the raw SI Matrix; m[i, j] gives a scalar Quantity.

source
v = QuantityVector([1.0, 2.0, 3.0], Rasmah.D_LENGTH)
3-element QuantityVector{Float64}:
 1.0 m
 2.0 m
 3.0 m
ustrip(v)
3-element Vector{Float64}:
 1.0
 2.0
 3.0

A worked example: a pressure vessel

Let's put it together. Build a cylinder of radius $1\,\text{m}$ and height $2\,\text{m}$ and check its volume against $V = \pi r^{2} h$:

volume(cylinder(1, 2))
6.283185307179586 m^3
volume(cylinder(1, 2)) ≈ π * 1^2 * 2
true

The same solid in millimetres gives the same physical volume; the number just changes with the unit:

volume(cylinder(1000u"mm", 2000u"mm"))
6.283185307179586 m^3
ustrip(volume(cylinder(1000u"mm", 2000u"mm"))) ≈ 2π
true

because $1000\,\text{mm} = 1\,\text{m}$ and $V = \pi \cdot 1^{2} \cdot 2 = 2\pi$ in SI, and $2\pi \cdot (\text{a unit of volume})$ is the same amount of material however you print it.

Finally, the safety net: a dimensional slip is caught at the boundary, not in the solver. Ask for the volume of a shape in newtons and Rasmah refuses to even build the quantity, because a volume is $L^{3}$ and a newton is $M\,L\,T^{-2}$.

Next steps

Units are the vocabulary the rest of Rasmah speaks in. Continue to Materials, where every property is a Quantity with a physical dimension, and to the FEM chapters, where boundary conditions and solved fields carry their units into visualization. If you are coming from the geometry chapters, head back to Modeling operations for the remaining geometry, or on to Meshing.