Physical is a Units of Measurement system for the Swift programming language, built on top of (’s) Foundation’s Measurement framework.
The project aims to make use of the developments in Dimensional Analysis and Unit/Measurement research to make writing code more appropriate for both trivial and complex real world calculations. Even if you don’t think you are making such calculations in your code, you probably are. This can be your friend to help maintain readable, debuggable code.
The more real-world code that uses this package, the better we can optimize the whole system. Please file requests, ideally with real code, submit pull requests, and bring up areas that could improvement.
Note: That’s a real unicode arrow character → (U+2192), not ->. It’s utterly unnecessary. If you don’t want to use it or if you use a keyboard layout that doesn’t include it, then you can use the .to() function instead. It’s there if you want it.
Chaining units
To create a Physical object with any dimension, just daisy chain units, possibly with exponents. As well, one can force a unit to be in the denominator via / as below.
There are several (hopefully just the right amount of) ways to write a complicated set of units, and one can choose the ones that fit the situation and readability demands.
var density = 27.grams.centimeters(-3)
density = 27.grams.cubicCentimeters(-1)
density = 27.g.cm(-3)
density = 27.g/.cm(3)
density = 27.grams.perCubicCentimeter
density → .milligramsPerDeciliter
density → .gramsPerLiter
Mixing dimensions
One can compose new kinds of Physical objects through standard algebra.
If the combination of items is impossible, the result is a Physical.notAThing, in analogy to NaN when such an impossible math result happens for floating point numbers. Any further use of this object in more equations infect those results in turn. You can check its status using .isNotAThing just as float point numbers have .isNaN. To find out why a variable has become not a thing, you can check its .errorStack which contains a history of its bad fortune since the first moment it became not a thing.
The ~ operator can be used to test commensurability. x ~ y means: Is x of the same dimension as y.
let force = 4.5.newtons // 4.5 N
let mass = 17.poundsMass // 17 lb
force + mass // Not a Thing
(force + mass) / 7.feet // Not a Thing
(force + mass).isNotAThing // true
(force + mass).errorStack // ["4.5 N + 17 lb"]
force.dimensionalDescription // L¹ M¹ T⁻²
mass.dimensionalDescription // M¹
let acceleration = force / mass // 0.58358 m / s²
acceleration → .gravity // 0.059488 g
acceleration ~ 1.gravity // true
1.gravity.withBasicUnits // 9.81 m / s²
force.withBasicUnits // 4.5 kg m / s²
mass * acceleration // 9.9208 lb m / s²
mass * acceleration → .newtons // 4.5 N
mass * acceleration → .joules // Not a Thing
mass * acceleration * 37.feet → .joules // 50.749 J
Angles and Trig
Trig functions use units, contrary to what you might think [1]. Physical provides both trig and inverse trig functions (normal and hyperbolic) that both wipe out a whole class of bugs (“do I multiply by π and divide by 180?”), but also makes your code, and reasoning about it, greatly improve. These functions are in addition to the standard trig functions and do not conflict with them.
Currently the result of a trig function is a Physical object of unitless (constant) type. If you need to use the Double value, grab its value. E.g., sin(φ).value. This allows inverse trig functions to return Physical objects with units radian, without interferring with existing trig functions in the Swift standard library.
[1] In fairness, other approaches are being considered, but for now this is the situation.
75° // 75 °
sin(75°) // 0.96593
asin(sin(75°)) // 1.309 rad
75° → .radians // 1.309 rad
asin(sin(75°)) → .degrees // 75 °
let θ₁ = (2.π/5).radians // 1.2566 rad
let θ₂ = θ1 → .degrees // 72 °
let θ₃ = θ1 → .revolutions // 0.2 rev
sin(θ₁) // 0.95106
sin(θ₂) // 0.95106
sin(θ₃) // 0.95106
Note: The degree symbol is ° (⇧⌥8 on a US Qwerty keyboard) and not º (⌥0).
Arrays, Ramps, Indices
Physical can work to describe whole arrays at once, also providing acceleration on calculations done on them for free. As well, a ramp function is included, akin to Numpy’s linspace function. One can treat arrays an n-dimensional vectors as well, with support for rotating (for now) 2-d arrays.
Exponents are of a special TieredNumber type, that is alternately an integer, a rational or of floating point value, and will gracefully degrade as needed. This allows equations to recover integer or rational exponents, providing better unit matching and accuracy of results.
let x = 4.76.meters
x^5 // 2,443.6 m⁵
(x^5) ^ 0.2 // 4.76 m
(x^5) ^ 0.2 → .yards // 5.2056 yd
let y = x ^ (3.0/7) // 1.9517 m^(3/7)
y^7 // 107.85 m³
y^7 → .cubicInches // 6.5814e6 in³
x^π // 134.51 m^3.141592653589793
(x^π) ^ (1/π) // 4.76 m^1.0
(x^π) ^ (1/π) → .yards // Not a Thing
(Note in the last example that the Double exponent value meant that m^1.0 wasn’t exact, and so wasn’t commensurate with length.)
Strong typing
One can optionally (a pun!) use strong typing. If one wraps a Physical object with a strong type, the result is an optional. This is done with analogy to how Int(...) and Double(...) etc are optionals in the Swift standard library.
One can create a literal strong type, using the two argument init, Length(value, unit: .meters) (etc) which guarantees unit-correctness and does not produce an optional. A compile-time error will result if the type is selected incorrectly.
To use a strongly typed object with a dynamically-typed Physical object, one need only extract the physical content. (There is room for improvement here.)
Physical
Physical is a Units of Measurement system for the Swift programming language, built on top of (’s) Foundation’s Measurement framework.
The project aims to make use of the developments in Dimensional Analysis and Unit/Measurement research to make writing code more appropriate for both trivial and complex real world calculations. Even if you don’t think you are making such calculations in your code, you probably are. This can be your friend to help maintain readable, debuggable code.
The more real-world code that uses this package, the better we can optimize the whole system. Please file requests, ideally with real code, submit pull requests, and bring up areas that could improvement.
Highlights:
For a recent overview of the project, see this talk on the framework given at 360iDev 2022.
License
Physical is released under the MIT license.
Installation:
If using Xcode, open a workspace or project and: File → Add Packages… → put in the URL of this project.
If editing your
Package.swift
by hand, add in this dependency info:Once you have either of the above set, just
import Physical
atop any file that you would like.Dimensions
Physical extends the units supported by the Measurement framework’s dimension set:
and adds these dimensions as well:
In addition, the entire system is customizable and expandable.
Examples
Basic Idea: Extending numbers
Simple creation and composition of units. One can then convert results into a more appropriate unit.
Note: That’s a real unicode arrow character → (U+2192), not ->. It’s utterly unnecessary. If you don’t want to use it or if you use a keyboard layout that doesn’t include it, then you can use the
.
.to()
function instead. It’s there if you want itChaining units
To create a Physical object with any dimension, just daisy chain units, possibly with exponents. As well, one can force a unit to be in the denominator via
/
as below.Various means of expression
There are several (hopefully just the right amount of) ways to write a complicated set of units, and one can choose the ones that fit the situation and readability demands.
Mixing dimensions
One can compose new kinds of Physical objects through standard algebra.
If the combination of items is impossible, the result is a
Physical.notAThing
, in analogy toNaN
when such an impossible math result happens for floating point numbers. Any further use of this object in more equations infect those results in turn. You can check its status using.isNotAThing
just as float point numbers have.isNaN
. To find out why a variable has become not a thing, you can check its.errorStack
which contains a history of its bad fortune since the first moment it became not a thing.The
~
operator can be used to test commensurability.x ~ y
means: Isx
of the same dimension asy
.Angles and Trig
Trig functions use units, contrary to what you might think [1]. Physical provides both trig and inverse trig functions (normal and hyperbolic) that both wipe out a whole class of bugs (“do I multiply by π and divide by 180?”), but also makes your code, and reasoning about it, greatly improve. These functions are in addition to the standard trig functions and do not conflict with them.
Currently the result of a trig function is a Physical object of unitless (
constant
) type. If you need to use the Double value, grab its value. E.g.,sin(φ).value
. This allows inverse trig functions to return Physical objects with units radian, without interferring with existing trig functions in the Swift standard library.[1] In fairness, other approaches are being considered, but for now this is the situation.
Note: The degree symbol is
°
(⇧⌥8
on a US Qwerty keyboard) and notº
(⌥0
).Arrays, Ramps, Indices
Physical can work to describe whole arrays at once, also providing acceleration on calculations done on them for free. As well, a
ramp
function is included, akin to Numpy’slinspace
function. One can treat arrays an n-dimensional vectors as well, with support for rotating (for now) 2-d arrays.Unit exponents
Exponents are of a special
TieredNumber
type, that is alternately an integer, a rational or of floating point value, and will gracefully degrade as needed. This allows equations to recover integer or rational exponents, providing better unit matching and accuracy of results.(Note in the last example that the Double exponent value meant that m^1.0 wasn’t exact, and so wasn’t commensurate with length.)
Strong typing
One can optionally (a pun!) use strong typing. If one wraps a Physical object with a strong type, the result is an optional. This is done with analogy to how
Int(...)
andDouble(...)
etc are optionals in the Swift standard library.One can create a literal strong type, using the two argument init,
Length(value, unit: .meters)
(etc) which guarantees unit-correctness and does not produce an optional. A compile-time error will result if the type is selected incorrectly.To use a strongly typed object with a dynamically-typed Physical object, one need only extract the
physical
content. (There is room for improvement here.)Constants and formulas
Physical has a rich set of constants with both their units and presently known precision.
Extensions to regular numbers
A number of enhancements to float point numbers have been included.
Note: Both π and √ are typable on most keyboard layouts. (On a US Qwerty layout, they’re
⌥p
and⌥v
.)Note: Non-ASCII characters all have ASCII equivalents, for those stuck on projects with 20th-century restrictions and Asciitarians.