Unit of measure conversions are a constant concern in scientific code. Most well written scientific domain kernels should be unit un-aware because the equations of nature are generally unit invariant: momentum is mass times velocity whether velocity is in meters per second or furlongs per fortnight. But there are always important places where the actual values matter: water boils at 100 degrees Celsius. Therefore one typically assumes a set of canonical units in the computational domain to make the programming more straightforward. It’s also more efficient and numerically stable to only translate units on the boundaries of the computation domain, rather than littering them throughout.
But users of such software often think in different units. They may prefer Fahrenheit, or simply just units with more intuitive magnitudes (millions of barrels instead of cubic meters). So where should we use and build unit conversion code? The typical division of labor in the simulation bounded context of a scientific application looks like
(compute data structures & algorithms) (modeling business objects) (UI presentation)
During model construction, there are often validation rules that need to be applied to ensure the model makes physical sense. For instance, an ocean bottom temperature could certainly never be below about -4C or it would be ice. Here we likely prefer to use standard units during domain logic execution, but error messages may need to be “localized” in the UI presentation layer for user-preferred units.
However before we decide simply to continue using canonical units, we must be careful to check whether our persistent domain model should be unit-aware. An important question to ask is whether the exact unit from user input should be recorded. There may be legal requirements to store measurement in particular units.
If we always convert to canonical units we may suffer from rounding errors. A user enters “5 feet”, we convert it to “1.524 meters”, then when we show it to him again, it may look like “4.999999932 feet” depending on the numerical accuracy of the conversions at different tiers of the system. (These automatic conversions could also introduce unnecessary ‘edits’ or numeric drift as objects are converted to and from unit systems during their traversal through the UI or persistence layers.) One option would be to store the value plus the unit of user input in persistent form, however this should only be pursued if it’s worth the considerable overhead.
If the domain itself must be unit aware, then it’s likely the case that the domain’s value objects should provide unit inquiry and conversion functions directly, such as
var sourceRock = myModel.SourceRock;// sourceRock.Temperature = 200;// sourceRock.UnitCatalog == UnitCatalogs.Sivar ImperialSourceRock = sourceRock.UnitConvert(imperialUnitCatalog);// imperialSourceRock.Temperature = 392// imperialSourceRock.UnitCatalog = UnitCatalogs.Imperial
But for most applications, this is completely unnecessary overhead in the business or calculation modules. The answer is usually that unit conversion should be pushed all the way into the UI layer. Here several factors come together to make a solution technically simple and pleasing to the user.
Transparent unit conversion in the two-way bindings of an MVVM model such as knockout bindings will make the code easier to write. Business object representations can be bound directly to edit templates. The user will see transparently computed numbers in their text boxes, and the business objects will only be updated if the user types in a new number, avoiding inadvertent rounding errors or numeric drift.
The issue of seeing “funny numbers” that are caused by unit conversions is typically solved in the UI layer by only showing numbers — particularly inputs — to a fixed number of significant digits anyway. Rounding may well cause someone’s “5 feet” to actually be “4.9999932 feet”, but if we show data to 5 significant digits, then it will look like “5 feet” again. Clearly this sort of destructive update is not acceptable for the compute or modeling tiers, but it usually fine for the UI layer. Users prefer seeing “5.387 million barrels” over “5.387345928582E+6” anyway.
Another issue we typically run into is input validation. If the water temperature must be above -4.2C, but the user is working in Fahrenheit, we must require that their input is actually above 24.44F. Range validators must be converted in the UI tier, and messages updated appropriately. Here again rounding errors can raise their ugly head. Suppose that the temperature was input only to the nearest degree. Do we accept 24 degrees? This is the correct rounding of 24.44F, but is actually below the domain minimum of -4.2C. If we assume that the modeling constraints may not be violated, we have no choice but to restrict the input to 25F or higher.
How we manage units, associate them with particular fields on our business objects, and the mechanics of converting them will be covered in a future article.