physical dimensions

in Ada

Abstract:It is shown that Ada is not suited to implementing physical dimensions with its type concept. Therefore a set of Ada packages is constructed which allow dealing with physical quantities without type conversions as far as possible while keeping the advantages of strong typing in critical cases. Basic mathematical functions like the square root and the sine are included as predefined operations of the numeric base types, thus rendering possible the dimensionally correct computation of formulae likex=x_{0}cos(phi)in degrees and radians again without any type conversions.

(See also SI Units - Checked and Unchecked.)

Types in Ada with their inherent strong binding lead, if applied correctly,
to great data safety, i.e. they prevent comparing inadvertently apples
with pears. This conception has proven so successful that languages like
C, which originally had only very limited typing at their disposal, implement
similar ideas in their new standards. Now also physical dimensions have
the property of being combinable only in a very special way, and every
physicist is used to checking equations with respect to dimensional correctness.
So it is tempting to impose this chore upon the compiler by representing
dimensions as types. This, however, is not that easy as can be seen by
observing that indeed meter plus meter results in meter, yet meter times
meter results in meter squared, and Ada operators work type-preserving
(for `A`, `B` of type `T`, the product `A*B`
is also of type `T`). The considerations below will show that Ada
is even absolutely unsuitable to handling dimension checking in assignments
with reasonable expenditure.

The conceptual error lies in the fact that types, which describe mere number representations within some machine, are brought into connection with arbitrarily selected real world objects serving as references for measurements of physical items.

A meter is the length of the so-called Standard Meter, a platinum rod which is preserved in Paris under well-defined environmental conditions. (Once upon a time at least it was defined this way.)

A method will then be presented how to deal with physical dimensions so that mathematical functions are included, trigonometric functions observe dimension, an utmost degree of information can be included within declarations, and type conversions are not needed.

Let physical dimensions be implemented by Ada types:

typeLengthis newFloat;typeTimeis newFloat;typeSpeedis newFloat;

The predefined multiplication and division operators are of no use since
they remain within the type, and what is more, use of types in this naive
way is liable to lead to gross misinterpretations. For error estimation,
the relative error `Delta := Delta_x/x` (a pure number) has to be
declared as a dimensioned variable

Delta, X, Delta_X: Length;

for

Delta := Delta_X/X;

to be compilable, an awful misleading of potential readers of this program!
(It is similar nonsense to call transcendental functions like `exp`
for dimensioned arguments.)

There exist two possibilities to represent the equation `s = vt`
in Ada:

S := Length (V) * Length (T);

or

S := V * T;

the `"*"` operator in the latter case being defined
as follows:

function"*" (Left: Speed; Right: Time)returnLength;

Use of the first way makes the compiler lose the ability to type check
the assignment operation by comparing dimensions on the left and right
sides, which is the very reason for introducing different types for different
dimensions. Use of the second way leads to a plethora of overloaded functions.
Anybody who has ever attempted to define a package of overloaded `"*"`
and `"/"` operators at least for kinematics with only
three dimensions length, time, and mass (let alone for the full SI system
with its seven base dimensions meter, kilogramm, second, ampere, kelvin,
candela, mol) including at least the fourth power in any dimension (computing
the modulus of an acceleration vector leads to the fourth time power in
the denominator, and you would hardly believe what kinds of unusual dimensions
are met with intermediate results) knows what is meant by this aim: The
number of function definitions afforded runs into the hundreds.

One could object that this definition has to be made only once and later-on
the package has only to be *with*ed and *use*d without any need
for the user to care about the package's complexity, but unfortunately
the argument is not fully correct. It takes into account only simple multiplication
and division (by the way: although dimensions of intermediate results depend
on the order the operators are executed in expressions like `nRT/p`,
there is no need to introduce parentheses because operators of the same
precedence level are executed in textual order from left to right). Operations
like exponentiation `a ^{n}` and root extraction

So both methods of dealing with dimensions lead to drawbacks by affording
type conversions. Therefore we look for a method which achieves both, minimization
of type conversions and transfer of semantic information. By the above
it should be clear that some sacrifices have to be made. The sacrifice
we are about to make is dimension checking in assignments if only all variables
belong to a coherent dimension system. This is not a big sacrifice because
this check can only be done for multiplication and division with utmost
expenditure and it cannot be done at all for exponentiation and root extraction.
The gain on the other hand will be that the mathematical functions exist
as predefined operations on the Ada base types so that only __one__
package, the package ** Universe**, has to be

Given a generic mathematical library with all functions needed, we show
the method with the two functions `Sqrt` and `Sin` serving
as examples, as they include all essential features.

generic typeRealis digits<>;packageGeneric_Mathematical_LibraryisPI:constant:= 3.14159;functionSqrt (X: Real)returnReal;functionSin (X: Real)returnReal; -- Cycle 2*PIfunctionSinD (X: Real)returnReal; -- Cycle 360 degrees -- Other functions Illegal_Argument:exception;endGeneric_Mathematical_Library;

This form can easily be obtained if the sine function should be given as

functionSin (X, Cycle: Real)returnReal;

In a kind of creative act, base types are produced from package `Generic_Mathematical_ Library`
possessing all derivable operations that are desired.

withGeneric_Mathematical_Library; -- and what else is desired -- to be derivablepackageBig_Bangis typePrimeval_Floatis digitsproject_defined;packagePrimeval_Mathematical_Libraryis newGeneric_Mathematical_Library (Primeval_Float); -- Take subprograms out of the instantiation to make them -- derivable operations on type Primeval_Float:functionSqrt (X: Primeval_Float)returnPrimeval_FloatrenamesPrimeval_Mathematical_Library.Sqrt;functionSin (X: Primeval_Float)returnPrimeval_FloatrenamesPrimeval_Mathematical_Library.Sin;functionSinD (X: Primeval_Float)returnPrimeval_FloatrenamesPrimeval_Mathematical_Library.SinD; -- Take out objects that are not derivable: PI:constant:= Primeval_Mathematical_Library.PI; Illegal_Argument:exception renamesPrimeval_Mathematical_Library.Illegal_Argument; -- A type without mathematical operations:typePrimeval_Float_Without_Mathematicsis digitsproject_defined;endBig_Bang;

The precision of the base types `Primeval_Float` and `Primeval_Float_Without_ Mathematics`
is determined by project needs. Both may have different precision, or there
may be several Floating point and fixed point types with different precisions
and with and without mathematical functions. This only adds some more overloaded
functions, but is without any influence on the method presented here.

Out of the Big Bang evolves the Universe containing all desired types,
operations, exceptions, and constants. TheBig Bang itself is no longer
visible, i.e. it is strictly forbidden to include this package in any others
*with* list. Unfortunately, there is no syntax rule in Ada to force
observance of this requirement. Here programmer compliance and code inspection
by those responsible is asked for.

We come to the following agreement:

Within this project, all computation is done in only one coherent system of physical dimensions.

Here we choose the SI system. We define an Ada type, and for all dimensions which are frequently needed there are subtypes:

typeSI_Unitis newBig_Bang.Primeval_Float;subtypeMeterisSI_Unit;subtypeSecondisSI_Unit;subtypeMeter_Per_SecondisSI_Unit;subtypeNewtonisSI_Unit; -- kg*m/s**2subtypeRadianisSI_Unit; -- 1subtypeWithout_UnitisSI_Unit; -- 1 ...

In this way, we can include semantic information within variable declarations.

g: Meter_Per_Second_2 := 9.81; t: Second := 10.0; s: Meter := 0.5 * g * t**2;

*It is the programmer's own responsibility that this formula is physically
correct, no type checking is done!*

If all of the above subtypes were true types, the assignment would be marred with type conversions without guarantee of correctness. So where is the advantage?

s := 0.5 * Meter(g) * Meter(t)**2;

The subtype `Without_Unit` is thought to be used only for truely
dimensionless quantities, i.e. for quantities with dimension 1:

Alpha:constantWithout_Unit := e**2 / (4.0 * PI * eps_0 * h_bar * c);

Although `Radian` and `Without_Unit` physically are the
same, use of the name Radian conveys the meaning of an angle.

The type `SI_Unit` itself is only to be used in cases where the
variable possesses some dimension different from 1, but there is no subtype
defined for it:

Dioptry: SI_Unit; -- 1/m

Indication of the dimension in declarations of this kind (as is done in the example) could be introduced as a requirement.

For input and output other units may be needed. All of those other units are denoted by types of their own (respectively subtypes in case of other coherent unit systems), and there are conversion functions for each of them to the corresponding SI unit.

typeKilometeris newBig_Bang.Primeval_Float_Without_Mathematics;functionKilometer_To_Meter (km: Kilometer)returnMeter;functionMeter_To_Kilometer ( m: Meter )returnKilometer;typeNautical_Unitis newBig_Bang. Primeval_Float_Without_Mathematics;subtypeNautical_MileisNautical_Unit;subtypeHourisNautical_Unit;subtypeKnotsisNautical_Unit; -- NMi/hfunctionNautical_Mile_To_Meter (NMi: Nautical_Mile)returnMeter;functionMeter_To_Nautical_Mile (m : Meter)returnNautical_Mile;functionHour_To_Second (h: Hour )returnSecond;functionSecond_To_Hour (s: Second)returnHour;functionKnots_To_Meter_Per_Second (kts: Knots)returnMeter_Per_Second;functionMeter_Per_Second_To_Knots (mps: Meter_Per_Second)returnKnots;

The type without mathematics was chosen deliberately because computation
shall __not__ be done in these types. In the critical case of unit mixing,
strong typing thus guarantees the correct use of a coherent unit system.

*Type conversions in Ada style*

Kts: Knots := 10.0; v : Meter_Per_Second := Meter_Per_Second (Kts); -- Nonsense

*are also strictly forbidden.*

Again and unfortunately, a syntax rule to prevent this does not exist.

Let us now consider the mathematical functions. For `Sqrt` there
is not much to say. Since the function `Sqrt` is known with the
type `SI_Unit`, the statement

t := Sqrt (2.0 * s / g);

compiles. Trigonometric functions are more complicated. This will be shown by means of the sine function. The procedure for the cosine and tangent and their inverses will then be obvious. We want both of the following equations to be representable in Ada

x = x_{0}sin (phi) x = x_{0}sin (omega t)

`phi` being measured in degrees, `omega` in Hertz, i.e.
the argument in the second line is dimensionless respectively has the dimension
radian.

The second case is relatively simple. As in the case of `Sqrt`,
the correct `Sin` function is known with the type `SI_Unit`
so that we can write

X := X0 * Sin (Omega * T);

What is disturbing is that also the wrong function `SinD` which
interpretes `(omega t)` in degrees is an operation on the type `SI_Unit`.
There are several options to cope with this situation. Firstly one might
say that the existence of this function was not disturbing at all and rather
might show up useful; it is kept deliberately. Secondly, one could destroy
it by hiding the derived (and therefore implicitely declared) function
by a new explicitly declared homograph which when called raises an exception
`Illegal_Function_Call`. Thirdly, the unwelcome function is hidden
as well, but now by renaming it to the correct `Sin` function, thus
making `Sin` and `SinD` identical.

For dealing with angles measured in degrees, a new type is introduced (like for the other non SI units), but this time by deriving it from the type with the mathematical functions:

typeDegreeis newBig_Bang.Primeval_Float;

Now there exist implicitly declared functions

functionSin (X: Degree)returnDegree;functionSinD (X: Degree)returnDegree;

only the second of which is correct. Again there are the three options
mentioned above, but now only the last one seems sensible since the information
about computing in degrees is already conveyed by the argument type so
that later-on it will easily be forgotten that only the `SinD` function
leads to the correct result. If degree is defined at all as a type of its
own, then `Sin` must consistently work in degrees. So `Sin`
is mapped onto `SinD` by a renaming declaration. Consistency within
the abundance of overloaded trigonometric functions created in this way
requires that also in the case of an argument of type radian the last option
is used. We thus arrive at the result:

The kind of trigonometric function (degree versus radian) is solely determined by the argument type.

The functions `Sin` and `SinD` always produce the same
effect. Preferably there will be a requirement to only use `Sin`,
the alias `SinD` is disallowed.

We are not yet at the end of the journey for the results of `Sin`
and `SinD` are still `Degree` rather than `SI_Unit`.
Since these functions do not disturb, they are kept; additionally a new
set of functions with the desired parameter and result profile is declared:

functionSin (X: Degree)returnSI_Unit;functionSinD (X: Degree)returnSI_UnitrenamesSin;

(The second declaration is a consequence of the rule that there are both names denoting identical functions.) Now we have reached the aim of being able to compile the statement

X := X0 * SIN (Phi);

The price to pay for this achievement, namely the abundance of overloaded functions, seems adequate since all of them are uniquely discriminated by their profile

functionF (X: Radian)returnSI_Unit;functionF (X: Degree)returnSI_Unit;functionF (X: Degree)returnDegree ;functionF_1 (X: SI_Unit)returnRadian;functionF_1 (X: SI_Unit)returnDegree;functionF_1 (X: Degree )returnDegree;

(with `F_1` denoting the inverse function).

A remark concerning the direct visibility of all declarations in `Universe`
is due. Many projects impose restrictions on *use* clauses or even
disallow them at all, and they do so for good reasons. Since the package
`Universe`, however, provides the very basis for everything else
(as is indicated by the name), it can be looked at as residing on the same
level as the predefined Ada package `Standard` representing the
Ada "Universe". For this reason, the *use* clause should
be allowed in this case. This could even become the requirement that `Universe`
only be present in context clauses in the form of a *with* clause
combined with a *use* clause.

See excerpts of the specification
and body of package `Universe`.

We come to the conclusion that in spite of the seeming complexity because
of heavy function overloading these packages are proven, especially for
spheric trigonometry to convert between geographic and UTM coordinates.
They lead to the following advantages:

- Code becomes much simpler.
- There is only one package needed in the
*with*list, package`Universe`, rather than a set of packages with one providing the types and several others providing mathematical functions for different types, as is normally the case. (The*use*clause for package`Universe`must be allowed.) - Use of a coherent unit system is forced in expressions and assignments.
- The physical dimension forms part of the variable declaration.
- The trigonometric functions observe dimensions: A sine working on degrees results in SI units.
- Ada type conversions
`type_mark (expression)`are not needed; rather they are forbidden.

There is one service restriction:

- Checks in assignments concerning physical dimensions are not performed.

Definitions of SI units, historical overviews and further pertinent information can be found at the National Institute of Standards and Technology (NIST).

This is the translation of a version that is older than the current
German version [published in
*Ada Aktuell 1.1 (March 1993)*] and hence not as elaborate.
Especially references to other works can be found there.

A ready to use version can be downloaded here.

Contents | ||

Inhaltsverzeichnis |