Catalog · How the catalog works
adimRT
Units of Measurement Debugging for FreePascal
- ★ 1 on GitHub
Anyone may press Interesting, signed in or not. It helps others find this project.
README
ADimRT
ADimRT is a Free Pascal library for run-time dimensional analysis. It associates each numerical value with its physical dimension and carries that information through calculations, so errors such as adding a length to a time are detected where they occur.
The library is intended for scientific, engineering and educational software that manipulates physical quantities. Its notation remains close to an ordinary formula:
distance := 5000 * m;
duration := 2 * hr;
speed := distance / duration;speed now contains both its numerical value and the resulting length/time
dimension. It can later be converted to or displayed in any compatible unit.
ADimRT was inspired by DimPas.
What the library provides
- Run-time validation of physical dimensions during arithmetic, comparison, conversion and mathematical operations.
- Real and complex scalar quantities.
- Dynamically sized real and complex vector quantities.
- Dynamically sized square real and complex matrix quantities, including common linear-algebra operations.
- Dimensioned vectors, bivectors, trivectors and multivectors in the Clifford
algebras
Cl(2,0)(plane),Cl(3,0)(space) andCl(1,3)(spacetime). - Hundreds of ready-to-use SI, derived, scaled and non-SI units.
- SI decimal prefixes from quetta (
10^30) to quecto (10^-30). - Physical constants represented as dimensioned quantities.
- Dimension-aware powers, roots, trigonometric functions, exponentials and logarithms.
- Compact and verbose formatting, selectable prefixes, numerical precision and optional tolerances.
- Correct affine conversion of absolute Celsius and Fahrenheit temperatures, with separate units for temperature intervals.
Dimensional checks are performed at run time, not at compile time. Invalid
Pascal syntax and type errors are still handled by the compiler; incompatible
physical dimensions raise EDimensionError when the expression is evaluated.
Getting started
ADimRT requires Free Pascal and the FCL. Lazarus is optional, but the repository
includes adimpack.lpk for adding the library as a Lazarus project dependency.
For a command-line Free Pascal project, make the ADimRT units visible in the
compiler unit path.
Most applications only need the main unit:
uses
ADim;Use ADimMath as well if the application needs to name EDimensionError or use
the underlying mathematical types directly. When both units are present, put
ADim after ADimMath so ADimRT's dimension-aware overloads take precedence:
uses
ADimMath, ADim;First calculation
program SpeedExample;
{$mode objfpc}{$H+}
uses
ADim;
var
Distance: TRealQuantity;
Duration: TRealQuantity;
Speed: TRealQuantity;
begin
Distance := 5000 * m;
Duration := 2 * hr;
Speed := Distance / Duration;
WriteLn('Speed: ', MeterPerHourUnit.ToString(Speed, [pKilo]));
end.Output:
Speed: 2.5 km/hThe decimal separator used by formatted output follows Free Pascal's
DefaultFormatSettings; the examples in this document assume ..
The expression may mix compatible units. ADimRT converts factored units such as hours and kilometres to a coherent internal SI value, while preserving the physical dimension.
Constructing quantities
Multiply a number by a unit or by one of its aliases:
Length := 1.2 * km;
ElapsedTime := 250 * ms;
Force := 12 * N;
Angle := 30 * deg;
Temperature := 20 * degC;Common units generally have:
- a descriptive unit record, such as
MeterUnitorNewtonUnit; - a readable alias, such as
MeterorNewton; - a symbol alias, such as
morN.
Some prefixed units are also exposed directly as dimensioned constants, for
example km, mm, nm, ms and kJ. The complete set is listed in the
units of measurement reference and in the
generated API documentation.
Assigning a plain real value to TRealQuantity creates a dimensionless
quantity. Attach a unit whenever the value represents a physical measurement.
Dimensional rules
ADimRT applies the usual rules of dimensional analysis:
| Operation | Rule |
|---|---|
| Addition and subtraction | Both operands must have the same dimension. |
| Multiplication | Dimensions are multiplied and their exponents are added. |
| Division | Dimensions are divided and their exponents are subtracted. |
Min, Max and tolerant comparison |
The quantities must be dimensionally compatible. |
| Integer powers | The value and every dimensional exponent are raised together. |
| Roots | The dimension must admit the requested exact root. |
Sin, Cos, Tan, etc. |
The argument must have the dimension of an angle. |
Exp, Log10, Log2, LogN, Power |
Arguments that mathematically require it must be dimensionless. |
For example:
Area := Length * Width;
Radius := SquareRoot(Area / Pi);
// Raises EDimensionError when evaluated: length and time cannot be added.
Invalid := 10 * m + 3 * s;Use SquarePower, CubicPower, QuarticPower, QuinticPower and
SexticPower instead of applying an unrestricted floating-point exponent to a
dimensioned value. Matching root functions are available from square through
sixth root. A root is rejected when the quantity's dimension cannot be divided
exactly by that order.
Conversion and output
A unit record validates compatibility and extracts the numerical value in the requested unit:
Length := 1.2 * km;
ValueInMeters := MeterUnit.ToFloat(Length);
ValueInKilometers := MeterUnit.ToFloat(Length, [pKilo]);
WriteLn('Value in metres: ', ValueInMeters:0:1);
WriteLn('Value in kilometres: ', ValueInKilometers:0:1);
WriteLn('Length: ', MeterUnit.ToString(Length));
WriteLn('Length in km: ', MeterUnit.ToString(Length, [pKilo]));Output:
Value in metres: 1200.0
Value in kilometres: 1.2
Length: 1200 m
Length in km: 1.2 kmToFloat returns a plain numerical value. ToString appends the unit symbol,
whereas ToVerboseString uses the unit name. Overloads allow the caller to
control floating-point formatting and to include a tolerance:
Length := 10.5 * mm;
Tolerance := 0.2 * mm;
WriteLn('Length: ', MeterUnit.ToString(Length, Tolerance, 5, 5, [pMilli]));Output:
Length: 10.5 ± 0.2 mmFor a compound unit, the prefix array follows the order of the unit factors.
For example, [pKilo, pNone] selects kilometres per second for a unit whose
factors are metre and second. Passing [] uses the unit's default prefixes.
Complex scalars, vectors and matrices can be extracted with ToComplex,
ToVector and ToMatrix, respectively, and Clifford quantities with
ToMultivector, ToVector, ToBivector, ToTrivector and ToQuadrivector.
ToString and ToVerboseString, with or without a precision, are available
for every quantity type. The tolerance overloads, which show a result as
value ± tolerance, are specific to real quantities: for a complex, vector,
matrix or Clifford quantity it would not be clear which component the
tolerance refers to. A conversion to an incompatible unit raises
EDimensionError; it never silently reinterprets the value.
A unit may also appear on the left of a scalar quantity, for example
m / (2*s) is 0.5 m/s.
Temperatures and temperature intervals
Celsius and Fahrenheit are affine scales: an absolute temperature needs both a scale and a zero-point offset. ADimRT therefore distinguishes an absolute temperature from a temperature difference.
Temperature := 20 * degC;
WriteLn('Temperature: ', KelvinUnit.ToString(Temperature, 5, 2, []));
DeltaTemperature := 18 * deltaDegF;
WriteLn('Temperature interval: ', DeltaDegreeCelsiusUnit.ToString(DeltaTemperature));Output:
Temperature: 293.15 K
Temperature interval: 10 Δ°CUse these units for absolute values:
K/KelvinUnitdegC/DegreeCelsiusUnitdegF/DegreeFahrenheitUnit
Use deltaDegC, deltaDegF, DeltaDegreeCelsiusUnit and
DeltaDegreeFahrenheitUnit for differences, increments and tolerances. A
temperature interval is scaled but never receives an absolute-temperature
offset. Subtracting two absolute temperatures produces a compatible interval.
Supported numerical quantities
Scalars
TRealQuantity stores a Double value and a physical dimension, with
Abs, Norm, SquaredNorm, Conjugate and Reciprocal.
TComplexQuantity provides the same dimensional behaviour for complex values,
with conjugate, norm, squared-norm, argument and null tests.
Vectors
TRealVectorQuantity and TComplexVectorQuantity are dynamically sized and
require all coefficients to share one physical dimension. They support indexed
access, addition, subtraction, scalar products, norms, normalization,
reciprocals and tolerant comparison. Real vectors also provide
three-dimensional cross products; complex vectors provide conjugation.
A scalar quantity times a plain vector is a vector quantity, so
BohrRadius * V gives a vector in metres.
Matrices
TRealMatrixQuantity and TComplexMatrixQuantity represent dynamically sized
square matrices whose coefficients share one dimension. Available operations
include matrix arithmetic, determinant, trace, norm, rank, transpose, inverse,
linear-system solving and eigenvalue/eigenvector calculation, with dedicated
routines for symmetric (SymmetricEigenpairs) and Hermitian
(HermitianEigenpairs) matrices. Real matrices can be tested for
orthogonality; complex matrices support conjugation, conjugate transpose,
Hermitian and unitary checks. As for vectors, a scalar quantity times a plain
matrix is a matrix quantity.
The dimension of a result follows the operation. For example, the determinant
of an order-n matrix has the coefficient dimension raised to n, while an
inverse matrix has the reciprocal dimension.
Clifford algebras
Three geometric algebras are available, each with its own unit for the basis blades:
| Algebra | Unit | Basis | Quantity types |
|---|---|---|---|
Cl(2,0), plane |
ADimCL2 |
e1, e2, e12 |
TCL2VecQuantity, TCL2BivecQuantity, TCL2MultivecQuantity |
Cl(3,0), space |
ADimCL3 |
e1 .. e123 |
TCL3VecQuantity, TCL3BivecQuantity, TCL3TrivecQuantity, TCL3MultivecQuantity |
Cl(1,3), spacetime |
ADimCL13 |
g0 .. g0123, g0^2 = +1 |
TCL13VecQuantity .. TCL13QuadrivecQuantity, TCL13MultivecQuantity |
A program uses the unit of the algebra it needs, next to ADim:
uses
ADim, ADimCL3;
var
Radius, Force: TCL3VecQuantity;
Torque: TCL3BivecQuantity;
begin
Radius := 2*e1*m;
Force := 3*e3*N;
Torque := Radius.Wedge(Force);
WriteLn('Torque: ', NewtonMeterUnit.ToString(Torque));
end.Output:
Torque: (+6e13) N∙mThese types combine physical dimensions with geometric products and operations such as dot, wedge and cross products, dual, reverse, inverse, projection, rejection, reflection and rotation. This is useful when orientation is part of the physical model, for example directed lengths, oriented areas, torques, rotations and Lorentz transformations.
All the components of a quantity share one dimension. In spacetime, a
four-vector is therefore written with homogeneous components, for example an
event as (c*t)*g0 + x*g1 in metres or a four-momentum as (E/c)*g0 + p*g1.
The spacetime algebra (STA) turns special relativity into geometry. A
spaceship leaves the Earth at 0.6c: how much time has the clock on board
measured after 5 s of Earth time?
uses
ADim, ADimCL13, Math;
var
EarthTime: TRealQuantity;
Event, ShipEvent: TCL13VecQuantity;
Boost: TCL13MultivecQuantity;
Rapidity: double;
begin
// After 5 s the ship is at x = 0.6 c t: the event is c t g0 + x g1, in metres
EarthTime := 5*s;
Event := (SpeedOfLight*EarthTime)*g0 + (0.6*SpeedOfLight*EarthTime)*g1;
// The interval (ct)^2 - x^2 is the same in every frame: the proper time is Norm/c
WriteLn('Proper time from the interval: ', SecondUnit.ToString(Event.Norm/SpeedOfLight, 5, 0, []));
// The same result in the ship frame, reached with the boost rotor
// R = cosh(phi/2) + sinh(phi/2) g01, where tanh(phi) = v/c
Rapidity := ArcTanh(0.6);
Boost := Cosh(Rapidity/2) + Sinh(Rapidity/2)*g01;
ShipEvent := Event.Rotate(Boost);
WriteLn('Proper time in the ship frame: ', SecondUnit.ToString(ShipEvent.Dot(1*g0)/SpeedOfLight, 5, 0, []));
end.Output:
Proper time from the interval: 4 s
Proper time in the ship frame: 4 sIn the ship frame the event lies on the time axis (x' = 0, up to rounding),
so its time component is the proper time.
In the plane, Cl(2,0) handles the phasors of AC circuits without complex
numbers: a phasor is a vector and a reactance is a bivector, because
multiplying by e12 turns a vector by 90 degrees. A series RL circuit with
R = 30 Ω and X_L = ωL = 40 Ω carries an rms current of 2 A; find the
voltage, the active power and the phase shift:
uses
ADim, ADimCL2;
var
Current, Voltage: TCL2VecQuantity;
Resistance, ActivePower: TRealQuantity;
Reactance: TCL2BivecQuantity;
PowerFactor: double;
begin
Current := 2*e1*A; // reference phasor
Resistance := 30*ohm;
Reactance := 40*e12*ohm; // inductive: the voltage leads the current
Voltage := Current*Resistance + Current*Reactance;
WriteLn('Voltage phasor: ', VoltUnit.ToString(Voltage));
WriteLn('Voltage (rms): ', VoltUnit.ToString(Voltage.Norm));
// The dot product of the phasors is the active power
ActivePower := Voltage.Dot(Current);
WriteLn('Active power: ', WattUnit.ToString(ActivePower));
PowerFactor := ScalarUnit.ToFloat(ActivePower/(Voltage.Norm*Current.Norm));
WriteLn('Power factor: ', PowerFactor:0:2);
WriteLn('Phase shift: ', DegreeUnit.ToString(ArcCos(PowerFactor), 4, 0, []));
end.Output:
Voltage phasor: (+60e1 +80e2) V
Voltage (rms): 100 V
Active power: 120 W
Power factor: 0.60
Phase shift: 53.13 degThe wedge product Current.Wedge(Voltage) is the reactive power, 160 e12
in volt-amperes, and the product of the norms is the apparent power, 200 VA.
The algebras come from the Clifford library. Conventions:
Inverseis the multiplicative inverse,X * X.Inverse = X.Inverse * X = 1; the dimension is inverted. Dividing by a non-invertible value raises a floating-point exception.Involuteis the grade involution andConjugatethe Clifford conjugate; both preserve the dimension.Dotis the "fat" dot product andDualisX * I, withIthe unit pseudoscalar. In 3D,a.Cross(b)equals-(a.Wedge(b)).Dual.X.Rotate(R)isR X ~R, the rotation by the rotorR, and keeps the type ofX;X.Rotation(A, B)rotates by twice the angle fromAtoB.SquaredNormis<X ~X>, which in spacetime is the invariant interval and can be negative;NormisSqrt(Abs(SquaredNorm)).Extractkeeps the selected components, for examplevc1,vc2for vectors orbc12for bivectors.- Widening to a multivector is implicit; narrowing is explicit, for example
TCL3VecQuantity(M). - A plain value converts implicitly to a dimensionless quantity, so a rotor is
written
R := Cos(a/2) - Sin(a/2)*e12, and a real quantity times a basis blade or a value is a quantity, for example(c*t)*g0 + x*g1.
Units, prefixes and physical constants
TUnit represents a coherent unit with its dimension, symbol and names.
TFactoredUnit adds a conversion factor for units such as hours, degrees,
electronvolts and other scaled or non-SI units. Celsius and Fahrenheit have
special affine unit types because a multiplicative factor alone is not enough.
The TPrefix enumeration covers:
quetta, ronna, yotta, zetta, exa, peta, tera, giga, mega, kilo, hecto,
deca, none, deci, centi, milli, micro, nano, pico, femto, atto, zepto,
yocto, ronto, quectoThe main unit also exports dimensioned physical constants, including the speed of light, Planck and reduced Planck constants, elementary charge, electron mass, Bohr radius, vacuum permittivity and many others. Because constants carry their own dimensions, they participate in the same validation as application values.
Example using the predefined constants:
Radius := (SquarePower(ReducedPlanckConstant) / ElectronMass) /
(CoulombConstant * SquarePower(ElectronCharge));
WriteLn('Bohr radius: ', MeterUnit.ToString(Radius, 4, 4, []));Output:
Bohr radius: 5.292E-11 mDisabling dimensional checks
Defining the compiler symbol ADIMOFF removes dimensional checking: quantity
types become aliases of their underlying real, complex, vector, matrix or
Clifford types. This mode is intended for a validated performance-sensitive
build where the loss of all run-time unit protection is an explicit choice.
The same source compiles in both modes: every operation of a quantity type has a counterpart on the underlying type. Two points need care:
- With
ADIMOFF, the methods ofTRealQuantity(Norm,SameValue, ...) come from theDoublehelper ofADimMath. Free Pascal activates only the last helper in scope, so listADimMathafterSysUtilsin theusesclause, or enable{$modeswitch multihelpers}. - Code compiled with
ADIMOFFmust not depend onEDimensionErrorbeing raised.
Keep checks enabled while developing and testing physical calculations.
Further documentation and examples
- Generated API reference
- Worked examples
- Units of measurement
- Mechanics
- Fluid mechanics
- Electricity and magnetism
- Heat and thermodynamics
- Waves and hearing
- Light and vision
- Relativity
- Quantum mechanics
The physics pages collect equations and examples by subject. adimtest.pas is
a collection of runnable exercises that show how the library is used, including
unit conversion, tolerances, affine temperatures, quantum-mechanical relations,
vectors, matrices and Clifford algebras.
Author
Melchiorre Caruso (melchiorrecaruso@gmail.com)
License
ADimRT is distributed under the MIT License.
The unit adimlapack.pas contains translations to Pascal of routines of
LAPACK, distributed under the LAPACK license
reproduced in COPYING.LAPACK. Redistributions in binary form
must reproduce that notice in their documentation.

