Dimensional Analysis in Programming Languages (2018)
gmpreussner.com
gmpreussner.com
That's not what the Buckinham pi theorem states. The Buckingham pi theorem is a statement about the functional dependencies of dimensionally homogeneous equations. https://en.wikipedia.org/wiki/Buckingham_%CF%80_theorem
I guess the writer mirrored some language on Wikipedia which indicated that the physical laws being independent of the choice of units is a consequence of the theorem.
Anyway, I don't mean to detract from what otherwise appears to be an interesting and comprehensive summary of dimensional homogeneity checking in programming languages.
Length distance = 100 mph * 3 hr;
Force f = 5.2 kg m/s/s; // same as 5.2 N
Mass infant = 9 lb + 8.71 oz;
[1] https://github.com/manifold-systems/manifold/tree/master/man...We used Emacs-Lisp to generate a collection of C++ classes with all of the arithmetic operaters that were allowed, and only those. "friend" is your friend for this in C++.
We mostly used floating point to represent values, but could also use integers with scale factors (automatically applied in the operators), which is nearly equivalent to fixed point.
For things where vectors made sense, 1d, 2d and 3d vector types were available. So Position2d and Position3d.
Absolute and relative values were distinguished: You could substract two absolutes to get a relative, but you couldn't add two absolutes. You could add a relative to an absolute to get an absolute.
And for time intervals we used wraparound arithmetic and comparisons, like the Linux kernel has the time_before() macro, so absolute times could continue "growing" without overflow in a running game.
Using the classes looked like a bit like this:
Position3d p1, p2;
Direction3d d = p2 - p1;
Absolute_Time t1, t2;
Time_Interval t = t2 - t1;
Velocity3d v;
object.set_position(p1 + t * v);
These types were used pervasively throughout all the game engine, gameplay logic, "AI" and so on.The constraints provided great type-checking and effectively documented a lot of code as well.
This being a game engine, they had to be fast. So they were all inline methods, in classes containing just a number, or up to 3 numbers for vectors.
Using GNU C++ this compiled down to the same machine code as if we hand-coded the arithmetic in the traditional way; we checked. So there was no runtime cost.
It was very satisfying to use.
julia> 1u"kg" == 1000u"g" # Equivalence implies unit conversion
true
Java: Length distance = 100 mph * 3 hr; julia> using Unitful.DefaultSymbols
julia> 50m / s
50 m s^-1 const m = u"m"
50m
There's actually nothing special in the compiler for unit implementation. This is just using the fact that `2x` is valid Julia code, where now x is something that overloads a dispatch to turn the number into a number with units. By default it uses string macros to keep the namespace clear, but you can fill the namespace with whatever you want.And it works throughout the ecosystem, so you can even use it in linear algebra and things like ODE solves. Here's an example of solving an ODE with units:
https://tutorials.juliadiffeq.org/html/type_handling/03-unit...
unitcalc 50stones kg
unitcalc 1lightyear nm
unitcalc 7fortnights decades
unitcalc 99mi/sec fathom/month
unitcalc 3.14radians/fortnight degrees/ms
unitcalc 0.2W eV/century
unitcalc 0.2J/ns ton_TNT/month
Pint is excellent. If you put in units that need a dimension to convert, the exception says what is missing, it could ask for that value, but havent figured it out yet; obviously dont want to parse the exception message.The framework to tell it physics exists, so stuff like:
unitcalc 0.2eV femtograms
should be possible.There are many terms in an engineering project that are unitless. Having a system to automatically check the units on the remaining scalars is cute, but doesn't do anything to prevent you from improperly performing operations that relate efficiency and power factor by accident, for example.
Multi-input, multi-output control systems regularly assemble state vectors out of terms that have mixed units. You can easily have amps, volts, meters per second, and radians per second in the same mathematical vector.
I have yet to see a compile-time unit-checking scheme that adequately addressed those issues.
The whole point is that it is an accident. Its exactly the kind of accident that embedding units into your type system is supposed to prevent. I just find that the kind of trivial examples that folks support this way are just that - trivial examples. They don't scale to the kinds of problems that I am faced with solving.
I miss them in the languages I use (Python, TypeScript). I would love to have e.g. "percent" or "probability" type. So that when "100 percent === 1 probability", with proper autocasting. Same for quite a few other qualities (e.g. logit -(softmax)-> probabilities).
Before I get "just use Haskell" or something in that line. IMHO for anything practical, ecosystem >>> language syntax and stuff. So the question is: are there any solid Python or TypeScript approaches for these systems?
Units != dimensions.
E.g. angle in radians, degrees and full rotations differs by a factor. It is a typical, and common, mistake that would benefit from such system.
No percentages are unitless. If they were, you could substitute them without consequence, like applying an interest rate percentage in place of a mortality rate pecentage or an alcohol-to-water percentage.
Angles, exponents, counts all fit in units, just more complex unit expressions.
With mathematics libraries in a language like Ada your angles will usually be one type, your distances another, and the rate of change another, and so on.
Personally I would view such a thing as a cool demo (or a wild abuse of operators) but I could easily imagine it becoming infuriatingly difficult to analyze/debug if operations started returning outlandish units (`print(x/y) => 1 gallon/light-year`).
My favorite tool for doing informal dimensional analysis is the `units` command-line utility. It is surprisingly adept. (It's a part of BSD and built into mac)
$ units
You have: meters/s
You want: furlongs/fortnight
* 6012.8848
/ 0.00016630952 >>> display(lightsecond)
>>> v = c / 3
>>> h / electron.mass / v
(3.019×10⁻²⁰ ± 2.277×10⁻²⁸)ls <length, distance>
>>> potrzebie / bushel
62.23m⁻² <vehicle efficiency>
>>> print(f"The speed of light is about {c:.5e}")
The speed of light is about c = 2.99792e+08m·s⁻¹
>>> momentum
mass * length / time
>>> print(flow)
L³·T⁻¹
There's no explicit support for probability, but you can roll your own quick with some support >>> prob = Dimension.new(3.4, "probability", "P", "chance")
>>> chance = Unit(prob)
>>> percent = Unit(chance / 100, "%")
>>> display(percent)
>>> chance / 3
33.33%
[0]: https://github.com/yunruse/noether import astropy.units as u
import astropy.constants as const
m1 = 1.0 * const.M_sun
m2 = 150 * u.lb
F = const.G*m1*m2 / (1.0 * u.au ** 2)
print F.to('newton')
where it has auto-converted the solar mass and the other mass to commensurate units, and then reduced the force into newtons.If you get the units wrong (e.g., use the wrong "g", or forget a mass when multiplying), it will be unable to convert to newtons and throw an exception.
As you supposed, this proved to be a big benefit in a scientific code. Units checking removed a lot of worry when combining quantities from across the simulation, e.g., you didn't have to worry about how you were storing distances. A Matlab code shadowed some of the Python functions, and the difference in complexity/reliability was apparent.
$ units "firkin furlong per fortnight"
Definition: 5.6659503e-06 m^4 / s
$A system of units is hobbled without including the necessary counts of objects/measurement events, which is open-ended. Mass per person is different than mass per chicken or per 787, for example.
The SI system isn't perfect, but expressing at least some dimensional information to be checked by machine feels like a step in the right direction.
Author clearly hadn't tried multiplying a user-input time value with a time constant (like milliseconds) in Go, a strongly typed language