Physical Units

The main obstacle to developing some classes for physical units is the lack of naming standardization and, where there is standardization, the difficulty of using these names in a Java context.

A colleague, Denis Garneau of IBM, and I have been trying to come up with an implementation of physical units in Java for some time now, and we have come up with a partial list of requirements, based partly on our experience with business data types. One requirement that Denis is very keen on is that we must take as much advantage as possible of Java's compile-time checking. Suppose we build an elaborate type-checking mechanism that is only available at run-time - your system might run beautifully for years because it seldom executes a particular path in the code, and then crash unexpectedly in the middle of the night!   Although this provides a lot of flexibility, from a practical point of view it does not seem to us to be acceptable.

On the other hand, there is no doubt that the run-time approach avoids a number of problems, some of which will be described in what follows. There is a very complete run-time package called "Java Addition to Default Environment" (described in JScience - Java Tools and Libraries for the Advancement of Sciences), developed by J-M. Dautelle, in collaboration with the JSR-108 experts. This provides a large number of units and sophisticated dimensional analysis - all at run-time. He is obviously an accomplished mathematician, unlike myself! The units supported by this package are grouped into two classes: SI and NonSI. SI also contains symbols for the 20 SI prefixes (positive and negative powers of 10). It is certainly convenient to be able to ask for the dimensions of, say, a coulomb, at run-time. However, as stated above, a strong argument can be made for a compile-time facility, even if not as sophisticated a one from a mathematical point of view. I am not comfortable with the idea that an error in the use of units may not be detected until the spacecraft is actually in the air, or beyond it!

One obvious problem with either type of facility is that one cannot support all the units in use worldwide, so we need a way for an application to add its own unit definitions. It seems to make sense to partition symbols by dimension, rather than lumping all non-SI symbols into a single class. When one collects all the colloquial measures in use in just a single language, it becomes rather an unwieldy collection. As J-M. Dautelle points out, the American and British measures are often different, e.g. ton, gallon, cup, and that is supposed to be the same language!

In what follows, I will be using very simple Java - hopefully someone who is more versed in the language will run with this and improve it!

Distance

Let's start with a basic dimension: distance. The approach I propose applies to just about all physical units - and of course so do the problems.  Anyway, to get us started, I suggest two classes relating to the distance dimension: Distance and DistanceUnit. These in turn extend two abstract classes: Dimension and Unit, respectively.

Dimension is an abstract class with the following instance variables:
 
  
  double scalar;                     // value in reference units                           
  double units;                      // value in units (Unit unit)
  Unit unit;                         // reference to an object of class Unit                 
 

It has two methods: toString(), which has the obvious function, and showInUnits().

We will hold a physical dimension in two forms (for performance reasons): a "reference" unit, which all values of that dimension will be converted into by their constructors, and an amount in the unit in which the data was originally created (on the assumption that the user will probably want to see it in that form at some point).   E.g. if the user entered a distance as "6 inches", s/he may want to see it in that form again, not 15.24 cm.

Unit is an abstract class with three instance variables:
 
  String name;                       // e.g. "Angstrom" 
  double multFactor;                 // e.g.  1E-10  
  double addFactor = 0.0;            // only used for temperatures                            
 

and no methods.

Distance will extend Dimension, providing a number of methods, and in addition adding definitions of a number of useful distance units, defined as public static, using the DistanceUnit class.  Each such definition gives a public name for the unit, and specifies how to convert it to the reference unit.  Here is a possible set of distance units:
 
  
  public static final DistanceUnit m = new DistanceUnit("m",1.0);// this is the reference Unit
  public static final DistanceUnit in = new DistanceUnit("in",0.0254);
  public static final DistanceUnit km = new DistanceUnit("km",1.0e+3);
  public static final DistanceUnit mile = new DistanceUnit("mile",1609.0);
  public static final DistanceUnit angstrom = new DistanceUnit("angstrom",1.0e-10);
  public static final DistanceUnit AU = new DistanceUnit("AU",1.5e+11);
  public static final DistanceUnit parsec = new DistanceUnit("parsec",3.08e+16);

The DistanceUnit class has (at least) two constructors:
 
Constructors
public DistanceUnit (String, double) 
public DistanceUnit constructor specifying display String and multiple of reference unit
public DistanceUnit (String, DistanceUnit, double) 
public DistanceUnit constructor specifying display String, DistanceUnit and multiple of that unit

Here you will see the standardization problem.  My colleague points out that the SI units have standard abbreviations, and these could certainly be used as variable names, except for the cases where the units are derived.  However, many measures in common use are not only not standard, but also language-dependent. For instance, we cannot make "foot" a world-wide standard as the corresponding French term is "pied". Some symbols could be written using Unicode characters, as these are acceptable within Java identifiers, but this is somewhat unwieldy if these terms are going to be in common use. For instance, angstrom, shown in the example, is more correctly \u00C5ngstr\u00F6m, which represents "Ångström". I suggest that a practical compromise is to use the DistanceUnit class, to define "locally defined" units for an application - some of these might eventually become de facto standards, assuming that the concept of using units instead of dimensionless values catches on!

Note that the above definitions provide both a Java variable name, and a String for input/output, which would presumably support a wider choice of characters.

The DistanceUnit class only provides the constructor that allows objects like the ones shown above to be built - it has no other methods.

Here is a preliminary list of constructors and methods for the Distance class - as in all OO applications, I'm sure real-life applications will see the need for additional ones.  Among the constructors you will notice one that has no unit defined - this is convenient when doing arithmetic with units, but it is defined as non-public, so it cannot be used outside the class.

Constructors for Distance:
 
Constructors
public Distance (double,DistanceUnit) 
public Distance constructor with value initialized from double and DistanceUnit
Distance (double) 
non-public Distance constructor with value initialized from double using reference unit

Initial set of methods (all public):
 
returns Methods
Distance add (Distance) 
add one distance to another, giving result in reference units
Distance subtract (Distance) 
subtract one distance from another, giving result in reference units
boolean eq (Distance) 
return true if one distance is equal to another
boolean ne   (Distance 
return true if one distance is not equal to another
boolean gt (Distance) 
return true if one distance is greater than another
boolean ge (Distance) 
return true if one distance is greater than or equal to another
boolean lt (Distance) 
return true if one distance is less than another
boolean le (Distance) 
return true if one distance is less than or equal to another
Distance multiply (double) 
multiply a distance by a scalar, giving a distance
Distance divide (double) 
divide a distance by a scalar, giving a distance
Area multiply (Distance) 
multiply one distance to another, giving an area
Volume multiply (Area) 
multiply a distance by an area, giving a volume
Speed divide (TimeInterval)
divide a distance by a time interval, giving a speed
TimeInterval divide (Speed)
divide a distance by a speed, giving a time interval

This may not be a complete set of methods, but it should be enough to give you a flavour of what I'm driving at.   Notice that these methods seem to fall into groups:

Here is an example of how such units might be referenced. It seems a bit wordy to me - maybe there are Java facilities that I am not familiar with that would simplify this...?  And my distances are totally arbitrary!  Of course, in real life, the "trip leg" information would be read from a database.


    final DistanceUnit klik = new DistanceUnit("klik", Distance.km, 1.0);
 
    Trip trip = new Trip(2);  // specify number of legs
 
    trip.addLeg(0, new TripLeg("YKK", "NYC", new Distance(1200.0, klik)));          
 
    trip.addLeg(1, new TripLeg("NYC", "LAX", new Distance(5000.0, Distance.km))); 
    
    Distance totDist = new Distance(0.0, Distance.mile);
    
    for (int i = 0; i < trip.tripleg.length; i++) {

           totDist = totDist.add(trip.tripleg[i].getDist());

           }
    
    System.out.println(totDist.showInUnits(Distance.km,2));  // 2 is no. of decimal places
        

Notice that we have used the DistanceUnit class to define a "local usage" unit called a "klik", defined as 1.0 kilometres.

Mass and Weight

Now, we can set up a similar system for "mass", defining grams, kilos, pounds, etc., but some really interesting problems start to show up. Let us define both mass and weight as separate classes, which makes sense based on popular usage. In general, I would measure the weight of a bag of tomatoes, rather than its mass, and clearly, on the surface of the Earth the values are the same for most practical purposes, so let us use "weight" for things like groceries, gravel, nails, etc., and keep "mass" for scientific applications. Even the units in common use are the same, except for units explicitly introduced for scientific reasons, such as the "slug" (14.59 kg mass). And you can define a set of units and methods for both classes, without having them interfere at all. But, from a dimension point of view, "mass" and "weight" are very different: "weight" is a force, and its dimensions are "mass" times "acceleration" (md/t2). So, for completeness, at first I thought we should add a bunch of methods connecting these dimensions, e.g.
 
returns Methods
Weight multiply (Acceleration)
multiply a mass by an acceleration, giving a weight

However, in this case we would want 1 kilo mass to convert to 1 kilo weight, so we must force the acceleration to be 1 - i.e. 1 gravity. In fact, we all know that the relationship depends on what planet we are on, or, more generally, what gravitational field we are in. So let us also define a class called GFactor, with values such as "nullG" (0.0), "earthG" (1.0), "moonG" (0.17), etc. Now we can define a more pragmatic relationship between mass and weight, defining a method in the Mass class, as follows:
 
returns Methods
Weight calcWeight (GFactor)
determine the weight for this mass, given a G factor 

and its converse (in the Weight class):
 
returns Methods
Mass calcMass (GFactor)
determine the mass for this weight given a G factor 

In fact, we can keep Acceleration, with the same dimensions as GFactor, but with a different reference unit - Acceleration would use 1 metre/sec2, while GFactor would use 1 "G", which is defined to be 9.80665 metres/sec2.

"Compound" Dimensions

I came across similar patterns of methods in all the physical dimensions I have looked at so far.  However, when we start to look at what might be called "compound dimensions" (e.g. speed, pressure, volume), where the units are usually expressed using multiplication or division, we run into the problem of what to name the unit variables.

Let us consider Speed, whose dimension is d/t (distance over time). In English, "miles per hour" is usually abbreviated "mph", but "m" is the standard abbreviation for "metres". Let us accept "mph" as an "ad hoc" abbreviation (and possibly also "kps"), but we still need a convention for arbitrary combinations of units. In the case of divisions, we could allow ...Per..., but this really violates the requirement for language-independence (although Latin is pretty close to a "lingua franca", especially in the scientific community). So we need a notation that is as language-independent as possible, but conforms to Java naming conventions for variables.

Now the Java convention for variable names allows alpha characters, including Unicode alpha characters from other (non-Roman) character sets, numbers in the non-initial position, underscore and dollar-sign. So I would like to put forward this humble suggestion (please feel free to throw brickbats):

Here is a sample of some units for the Speed class (in the String value, we have more characters to choose from, so we can use oblique stroke for divisions, and the caret for exponentiation):
 
  
        public static final SpeedUnit m_sec$n1 = new SpeedUnit("m/sec",1.0); 
            // reference Unit
        public static final SpeedUnit mph = new SpeedUnit("mph",1609.0 / 3600.0);
        public static final SpeedUnit kps = new SpeedUnit("kps",10e-3);
        public static final SpeedUnit km_sec$n1 = new SpeedUnit("km/sec",10e-3);

Some corresponding units for Acceleration:
 
  
        public static final AccelerationUnit m_sec$n2 = 
            new AccelerationUnit("m/sec^2",1.0);
        public static final AccelerationUnit km_sec$n2 = 
            new AccelerationUnit("km/sec^2",10e-3);

Temperature

This uses the addFactor value defined in the abstract class Unit. I am not sure if other dimensions require this, but temperatures certainly do. This factor provides an offset to be used when converting temperatures to the reference unit (Celsius or Centigrade).
 
 I will try to add more as I figure it out!
3,889 visits