Physical Scalars and Plotting Tools in Scala

#1

Overview

An open-source scalar package and associated software tools have been developed in the Scala programming language, including plotting tools based on the free GRACE plotting package. The scalar package represents physical scalars and can help to prevent errors involving physical units in engineering and scientific computation. The scalar package includes a complete implementation of the standard SI metric system of units and many common non-metric units. The design also allows users to easily define a specialized or reduced set of physical units for any particular application or domain. The scalar package can be used in two different modes: one mode provides unit compatibility checking but is slower, and the other mode bypasses the compatibility checks but is much faster and still prevents the most common type of unit error. Switching between the two modes requires no changes in the user’s code, making it convenient and usable with no significant performance penalty for even the most computationally intensive applications.

User Manual

A user manual is provided in the file scalar-guide.pdf.

License

This software is released under the NASA Open Source Agreement Version 1.3.

#2

Nice work!

Do you plan to elaborate the documentation?:

  • description of the internals
  • discussion of related approaches

The checking overhead would not be necessary if it could be left to the compiler. There have been attempts in this direction with Scala typeclasses if I recall correctly, but I think these had limitations.

Over 2 decades ago I made a Java compiler with support of dimensional checking; during compilation it erased the information. It worked fine and without run time overhead, but with some limitations; e.g., you could not make arrays with elements of varying dimensionality.

Shortly afterwards Grant Petty published a Fortran library for dimension checking without that limitation, but with the run time overhead that you may then expect. Full flexibility without runtime cost may be impossible to achieve.

On a high level, formulas in physics are not really about units; they are more about the physical dimensions such as distance, time, speed, acceleration. I miss that level of abstraction a bit in your approach. My extension supported definitions such as

dimension Distance(meter)
dimension Time(second)
dimension Speed = Distance/Time
val s: Double*Speed = 3.8 * meter/second

Instead of syntax in the latter line I would now prefer phrases such as Double[Speed] or Speed[Double]. I think that typeclasses could do most of the work that the dimension checking part of my compiler did, but just not all of it. Maybe a relative small change in the Scala compiler could change that.

References:

#3

Thanks, Andre. My approach to the problem is a spinoff of my work in air traffic control automation. It bothered me that scalar physical quantities are normally represented as numbers without explicit units. I’ve seen the errors where minutes get mistaken for seconds, degrees for radians and such. Sometimes those errors aren’t detected for a long time – like after a research paper is published – whoops! (I’m referring to a colleague here, of course, not me!) I originally developed my approach in Python, then I reimplemented it in Scala several years ago.

That is some interesting work you did 20 years ago. I skimmed your paper and may take a closer look at it later. It would be nice to have phusical dimensions represented as types and compatibility checks at compile time, but that is very difficult and beyond my level of expertise.

Jesper Nordenberg had that in his Metascala package a few years ago. As brilliant as it was how he used the Scala type system, however, his approach left a lot to be desired. Even though the unit compatibility checks were done by the compiler, the runtime performance was still very slow (by a factor of over 20 IIRC). Moreover, the actual physical units seemed to be a secret known only to the compiler. I couldn’t figure out how to get it to print unit information.

My approach has limitations, but I’m sure you will agree that it is preferable to raw dimensionless numbers.

My approach does not stop the user from defining multiple base units for length, for example. This could actually be considered a feature, I guess. (At one point I thought about defining separate length units for horizontal distance and altitude, since they are rarely combined in ATC, but I never actually did it.)

One of the most common errors in engineering and scientific computation involves mistaking degrees for radians. Using my scheme, one would write sin(30 * deg) to get the sin of 30 degrees. That is of course just a convenient way to do the standard conversion that is routinely done – but often forgotten. A while back, I decided that the trig functions should reject any argument that is not explicitly converted to radians. But radians are dimensionless, and trying to enforce a named unit for it caused other problems (relating to the implicit conversion of a Double to a dimensionless Scalar). I eventually abandoned that effort to force the user to explicitly convert the trig arguments to radians.

In any case, let me know if you have any other suggestions or ideas.

#4

On using 30*deg style units.

This is close to an implementation detail. Forgive me if it is out of place here.

Have you considered
Degreeamount
Or
amount
:Radian

Degree & Radian (trait&object). The trait would store the amount in a final, public val. This matches the type/category of the sets. Both have a normal form, normalization can be weakly evaluated:

A standard standard type can be defined to hold the mutators & functors.

trait Quantity( final val quantity ) { final def toScalar=quantity; def normalize: Quantity }
trait Angular( q:double ) extends Quantity(q) { def toScalar:double }
final class AngularDegree(} extends Angular {}
final class AngularRadian(val quantity:double) extends Angular {}
sealed trait AngularMonad[D<:Angular,R<:Angular] {
   def toDegree(q:double):D=new AngularDegree(q)
   def toDegree(final def toDegree(angle:D}:D = angle):AngularDegree=angle
   def toRadian(angle:R)}:D = angle):AngularDegree=???
   def *:(q:double,u:Angular):Angular=???

  def sin(angle:D):D=???
  def sin=???
  ....
}
object AngularMonad extends AngularMonad[AngularDegree,AngularRadian] { ...}
object AngularDegree extends AngularDegree {
  def apply( q:double ):AngularDouble= new AngularDegree(q:double)
  def *:(q:double):AngularDouble=apply(q)
}

And so on. I typed this in directly. I’m sure there are errors. I hope this gets the general idea across. Types, functors/mutators, monads, simple “*” methods for construction. This will be fast enough for everything less than super computing problems. Depending on the codes, it gives a place to optimize if need be. Move everything out of the type traits and into the final classes, that kind of thing.

#5

About 10 years ago I did some work on radar emitter analysis. In C++ I created a class Angle, with methods

sin, cos, tan
asin, acos, atan, atan2
fromDegrees, toDegrees
fromRadians, toRadians

About half of these methods were static.
It worked really well; it excluded any confusion with angle units.

#6

My Java extension worked very well in a chemical simulation that I made; there was no run-time penalty because it worked with erasure; however it had its limitations.

There are 3 separate issues IMO:

  1. the features of dimensional checking support
  2. the notation
  3. the implementation

Maybe it is best to forget about the implementation FTTB, and concentrate what features would be desirable, and their appearance.

E.g., maybe you would like to be able to subtype physical dimensions, e.g. to separate length units for horizontal distance and altitude, like you wrote.

#7

Your Angle class sounds interesting. Where can I find the code of find out more about it?

Correct me if I am wrong, but I was under the impression that ANY user-defined type will be much slower than the builtin float type (i.e., “Double”). That is why my approach is to just swap my Scalar class for Double for performance. The trick is that the “swap” does not require any change to user code. It is done by swapping one source file and recompiling, or swapping a jar file.

As I explained earlier, I wanted to shadow and wrap the basic trig functions with my own equivalent functions that force the user to explicitly declare radians. So for example, sin(0.35) would throw an exception, but sin(0.35 * rad) would compute the sin of 0.35 radians. And sin(35 * deg) would compute the sin of 35 degrees, of course. That would catch many degree/radian errors. But my implementation caused other problems with implicit conversion from Double to Scalar, so I abandoned it.

Again, I would like to see your Angle class. Thanks.

#8

I can’t find the code back of my C# Angle class, but it was pretty straightforward.
In Scala you could have such a class with the scalar angle in radians.
Methods sin, cos, tan, toDegrees, toRadians are then straightforward.
A companion object would then contain asin, acos, atan, atan2, fromDegrees, fromRadians.

Angle would thus be a wrapper so it has a run time overhead.
Maybe you could program it in such a way that this wrapper will be erased.
I would like to see that if anyone manages to make it.

The dimensionality checking system in my Java compiler only worked with primitive numeric types; not with boxed numbers. It applied erasure, like is done with List[double], so without any performance penalty.
You would typically write double*Distance, but double[Distance] might have been nicer.

#9

Sounds like it would be very straightforward to make this a value class.

class Angle private (val toRadians: Double) extends AnyVal {
  def sin = math.sin(toRadians)
  def cos = math.cos(toRadians)
  def tan = math.tan(toRadians)
  def toDegrees = math.toDegrees(toRadians)
}
object Angle {
  def fromRadians(rad: Double) = new Angle(rad)
  ...
}

If you wanted even better erasure guarantees you could make it an opaque type with very little extra work, once that feature is available.

#10

I don’t understand what this class accomplishes. As far as I can tell, the “toRadians” argument might as well just be called x. What am I missing? How does this help to prevent the user from forgetting to convert from degrees to radians? Thanks.

#11

It’s what André was describing, unless I misunderstood him. It helps the user by providing an API which works with Angle instead of Double. The only way to construct an Angle is by calling fromRadians or fromDegrees (the Angle constructer itself should have been private of course, I fixed this now) which accomplishes more or less the same thing as the a * Degrees or a * Radians API.

#12

Thank you Jasper; that looks good.

Additional useful features would be addition and subtraction with other Angles,
and multiplication and division by scalars.

#13

Hi Russ,

nice work. It would be interesting to know how this relates / compares to squants.](https://www.squants.com/).)

#14

Thanks for pointing out squants. I think I saw it a while back, but I just reviewed the documentation again. Here is my take on it.

It is a nice design and has the advantage of compile-time checking of unit compatibility. However, I’ll bet it is much slower than plain Douibles (by a factor of 10 to 20 or more). I would have to do some testing to confirm that, but I don’t have time for that. If I am right, then squants is useful only for applications that don’t require much speed. That severely limits its applicability.

The designers of squants could possibly do what I did and make a “Doubles” version that can be swapped for the regular version (by swapping a jar file or by swapping one source file and recompiling). I don’t know if that is possible with their design, but if it is I think it would be well worth doing because it would extend the range of applicability to computationally intensive applications.

Other than that, I prefer my syntax to theirs because it is more natural, but that’s just a personal preference.

#15

Assumption check: you know about AnyVal, right? That’s imperfect, but works fine performance-wise under some circumstances, and provides at least some type safety.

More importantly: do you know about opaque types, which are coming in upcoming versions of Scala? Those will be providing strong type safety with zero runtime overhead…

#16

Thanks for that information. Yes, I am aware of value classes and opaque types, but it is not yet clear to me how or whether they can be used for this problem. If anyone knows, please pass it along. Thanks.

#17

FYI, I have upgraded this project to use Scala 2.13.0.

In the process, I also realized that some of my earlier discussion in this thread was not quite right. I had said that I tried and failed to come up with a scheme to prevent errors involving passing angles in degrees to trig functions that take them in radians. I should give myself more credit. After reviewing my code, I realized that I do have a method for that. In the full-checking mode, if you try to pass an angle as a standard Double (or Float), it will throw an exception.

For example, sin(23.5) will throw an Exception. To calculate the sin of 23.5 degrees, one would use sin(23.5*deg). For an angle that is already scaled in radians, one would pass it as a Scalar explicitly in radians, such as sin(0.24*rad) for example. This requirement forces the user to explicitly specify degrees or radians, thus preventing potential errors that have been known to go unnoticed for extended periods of time.

I also added a short section in the user guide to explain this feature.