Unit management in physical formulas

Hello,

What is in your opinion the best way to manage units in physical formulas with many types? I would want to know real, practical approaches that are reasonable to implement without much overhead. I also do not want to make everything a subtype, as that would defeat the purpose of having distinct units for distinct parameters.

Thanks in advance,
–zen

SI units, single floating-point type.

Well, there is the world of ideas and practical problems. In practice

  • Marginal cases of astronomy or fixed-point embedded targets aside, the problem of physical computations is resolved by SI. All values must be in SI units. Period.
  • Considering engineering, e.g. communication, human-machine interfaces, problem specific languages, middleware, the solution is a subtype, because you need dimensioned values of statically indeterminable units.

So the pattern is always same:

  1. Input dimensioned value, validate dimension, convert to SI.
  2. Deal with SI values internally.
  3. Convert from SI to the expected output unit or dimensioned value.

In short. Untis are for interfacing and never for computations.

I do not understand what you mean. I am using SI units but the problem is with operations and conversions. I will show an example:

type Meters is new Float;
type Seconds is new Float;
type Meters_Per_Second is new Float;
--- etc
type Radians is new Float;
type Degrees is new Float;

Suppose I have this types and similar in order to avoid using Seconds vs Meters where they don’t belong. Frequently, I have to do arithmethic with these to compute something that is another quantity e.g. Meters_Per_Second.
If I made everything a subtype, wouldn’t it preclude from having the type safety benefits?

I haven’t played around with Adas SI units but I remembered that there are some examples you could use for orientation.

Look under the ‘Science’ section in GitHub - ohenley/awesome-ada: A curated list of awesome resources related to the Ada and SPARK programming language · GitHub .

There are 3 libs dealing with this.

Thanks everyone, so

One possibility would use SI Units checked and unchecked, which would have some runtime penalty.

Another possibility would be to use the Dimensionality extension from GNAT, this would you recommend? Or is it better to stick with standard Ada for the most part?

Last, I could use Units of Measurement which (maybe not?) has the same tradeoffs as the first one.

Am I on the right track? What would you choose?

Why not? For example

    subtype Speed is Measure (Velocity);
    Car_Speed : Speed;          -- Only velocities are allowed
    . . .
    Car_Speed := 10.0 * km / h; -- OK
    Car_Speed := A;             -- Illegal, Constraint_Error

This is from a library used in production code.

The software (automation systems) uses units strictly for input and output. For example there is an HMI. For US and the rest of the world it is same but units are switched, e.g. mph vs km/h. There is a configuration language where you can write formulae in dimensioned fashion like

12[A] * sin (Clock * 1[Hz]) -- Computed channel

The unit of Clock is [s]. So the frequency must be in Hz or equivalent.

But all computations are always in SI.

Note also that units are more or less arbitrary thing. Technically you can have just one base unit. Some units like temperature are no “real” physical values. Others are dimensionless. Practically no function can have dimensioned argument. That is why decree, radian, steradian have no dimension.

I slightly disagree with Dmitry here: SI isn’t necessarily the best unit/system to use. There are multiple ways to “run the calculus”, but it really devolves to the ‘what’ in your problem.

To illustrate, consider a system with 9-bit bytes and 36-bit words (I forget the architecture, but it was before the 8-bit byte was quite standardized; it survives in several old standards) —or even the Setun trinary computer— which have native ranges of 2**9/2**36 and 3**X (I don’t recall Setsun’s “bit-length” for words). To properly model the former you would need to either pad-out a record, or use a bit-/trit-vector, or define a integer-range. Which route is ‘best’ depends on what you are going to be doing with it, and importantly the ‘display-type’ (or ‘storage-type’) need not be the same as your ‘native-type’.

To move to a bit more practical to your question, consider the value of 2**48 inches: this gives you approximately Pluto’s orbit (2**48 inches is roughly 97% of Pluto’s apogee distance) — thus, it might be perfectly appropriate to use inches for an orbital tracking system’s native unit, esp. because while not super common 48-bit does appear in some digital signal processing. — Does this mean that the ‘display’ should be in inches? No. You could display in feet, yards, km, or whatever, as that has no (direct) impact on the internal values.

SI is useful, but sometimes inappropriate or cumbersome in the sciences; this is why there’s so many “weird units” — instead of shoehorning in SI, the scientists at the start of these fields were measuring the raw data and thus used forms that were “native” to the problem-space, rather than trying to shove the problem-space into SI.

And that mirrors nicely to Ada: the best way to use Ada is to use the type-system to map your problem-space, and then you use that to solve your problem. — This inherently makes your code more portable if, for example, the compiler cannot generate Type Measure range -2**16..(2**48 - 1) then it is to error out, otherwise generate the appropriate code.

I find the GNAT dimensionality extension the best of all, since it provides the checking that is wanted during compile time. Do you know there is any interest towards standarizing it or something similar?

I think I will try that in case this approach ends up not working out. :hugs:

Taken a look, definitely going to my bookmarks. Thanks :slight_smile: