Improvements in the Quantity Framework

Nick Lariviere, Wolfram Research Inc.

(RowDefault[, (Spacer1), , (Spacer1), ])  #WolframTechConf

ABSTRACT

This talk provides and overview of the improvements and optimizations made to Quantity and related functions in the Wolfram Language, such  as  recent  updates  to  the  conversion  and  arithmetic  frameworks, as  well  as  a  discussion  of  updates  in  the handling of temperatures .

Quantity and Units in the Wolfram Language

A physical quantity is a material or systemic property that can be quantifiably measured, such as mass, length, time or temperature.

In the Wolfram Language, quantities are represented using the form:

Quantity[value, unit]

Unit expressions may be strings(e.g. “Meters”), arithmetic combinations of string (“Feet”^2/”Seconds”), mixed units (MixedUnit[{“Hours”, “Minutes”, “Seconds”}]), or a number of qualified variations thereof(e.g. DatedUnit).

Core Unit Operations

Quantities and units are supported throughout much of the Wolfram Language, but the fundamental unit operations that are commonly used are:

validation (KnownUnitQ, QuantityQ)

dimensionality checks (UnitDimensions, CompatibleUnitQ)

conversion (UnitConvert, CurrencyConvert, CommonUnits)

Unit Validation

Validation of units is the most fundamental operation in the framework, as nearly ever operation needs to quickly check if an arbitrary combination of units is valid.

Benchmarks on improvements

Since Version 13.0 unit validation has seen an improvement of 2 orders of magnitude in performance.

(Legended[, (SwatchLegend["Version 14", "Version 13"])])

Speed is key

Why isn’t this a trivial lookup?

KnownUnitQ["Meters"]

KnownUnitQ[(((("MesopotamianGesh")^(5/3)) "Micromicroliters" "ProtonElementaryViscosity")/((("Micriteslas")^(2/3)) (("RomanLibras")^0.2)))]

SetAttributes[timing, HoldAll];
timing[eval_] := MapAt[UnitConvert[Quantity[#, "Seconds"], "Microseconds"] &, RepeatedTiming[eval], 1]

randomString[] := StringJoin[RandomChoice[CharacterRange["a", "z"], RandomInteger[{4, 20}]]]

randomString[]

MemberQ comparison

With[{example=Echo@randomString[]},
timing[MemberQ[list,example]]]

With[{example=Echo@randomString[]},
timing[PrintableASCIIQ[example]]]

KnownUnitQ

With[{example=Echo@randomString[]},
timing[KnownUnitQ[example]]]

With[{example=Echo@randomString[]},
timing[StringQ[example]]]

Compatibility and Unit Dimensions

Compatibility and physical dimension checks are the most fundamental checks that can be performed on validated units, and have been optimized along side validation in recent versions.

Benchmarks on improvements

Since Version 13.0 dimensionality checks have seen an improvement of 1-2 orders of magnitude in performance.

Column[{Row[{,}],(SwatchLegend["Version 14", "Version 13"])}]

UnitDimensions

UnitDimensions gives the physical dimensions associated with a quantity or unit:

UnitDimensions["Feet"]

UnitDimensions["Pascals"]

Note: IndepdendentUnits have unique unit dimensions, so it is possible to expand the set of unit dimensions infinitely, but in practice there are only a small set of those which are commonly used.

Since there is a finite* set of physical dimensions, a units dimensions can also be represented with a vector of powers:

QuantityUnits`$UnitDimensionAssociation["Feet"]

QuantityUnits`$UnitDimensionAssociation["Pascals"]

Dimension positions are fixed, and while trailing zeros are dropped, preceding zeros need to be included:

QuantityUnits`$UnitDimensionAssociation["Amperes"]

Each element in the vector corresponds to a specific physical dimension:

QuantityUnits`$UnitDimensionList[[;;10]]

CompatibleUnitQ

Unit compatibility is determined by unit dimension, so compatibility checks can simply compare the vector of dimensions:

QuantityUnits`$UnitDimensionAssociation["Volts"]

For a multiplication of units the sum of the vectors is performed, and for power it’s multiplication(as the vectors represent powers in a given dimension):

unit=(("Pounds" "Acres")/("Amperes" (("Hours")^3)));

((Plus@@unit)/.Power[s_String,n_]:>n*s)/.s_String:>PadRight[QuantityUnits`$UnitDimensionAssociation[s],4]

CompatibleUnitQ[(("Pounds" "Acres")/("Amperes" (("Hours")^3))),"Volts"]

Unit Conversion

Conversion between units is the most common non-trivial operation involving units, and has also recently been optimized for Version 14.

Benchmarks on improvements

Since Version 13.0 dimensionality checks have seen an improvement of 1-2 orders of magnitude in performance.

(Legended[, (SwatchLegend["Version 14", "Version 13"])])

Parts of Conversion

Unit conversion has three fundamental elements that are needed to do any conversion:

fundamental value (scale)

zero value

base unit

Conversion to base units

For conversion to base units, we need only to look up the information for a single unit and do basic arithmetic:

{a1,a0,base}=QuantityUnits`$FundamentalUnitValuePairs["Feet"]

Function[(#*a1)+a0][10]

UnitConvert[Quantity[10,"Feet"],"SIBase"]

For non-zero based units the operation is the same:

{a1,a0,base}=QuantityUnits`$FundamentalUnitValuePairs["DegreesCelsius"]

Function[(#*a1)+a0][10]

UnitConvert[Quantity[10,"DegreesCelsius"],"SIBase"]

Conversion between units

For conversion from one unit to another, we need the scale and zero value for each unit:

{b1,b0,base}=QuantityUnits`$FundamentalUnitValuePairs["DegreesCelsius"]

{c1,c0,base}=QuantityUnits`$FundamentalUnitValuePairs["DegreesFahrenheit"]

Function[(c1 (# + c0) - b1 b0) / b1][10]

UnitConvert[Quantity[10,"DegreesFahrenheit"],"DegreesCelsius"]

Temperatures vs Temperature Differences

Temperatures requires special care because temperatures are typically expressed in scales(e.g. Celsius or Fahrenheit) with an arbitrary choice of zero, while differences of temperatures are standard quantities with a well-defined zero values.

The Wolfram Language quantity framework has always distinguished between “DegreesCelsius” as a unit of temperature from “DegreesCelsiusDifference” as a unit of temperature difference, but in Version 13 we unified both have unit dimensions of “TemperatureUnit”(where previously there was also a “TemperatureDifferenceUnit” dimension)

Temperature

Temperature units represent the hotness of matter and are measured using a thermometer that uses a scale such as Fahrenheit, Celsius or Kelvin. Common instances of temperature may be measuring ambient temperature, the body temperature of a patient or the boiling point of a substance. Temperature scales have a zero value that may or may not correspond to absolute zero.

Temperature Difference

Temperature differences represent the relative distance between temperatures, also measured using a temperature scale. Temperature differences are typically used to indicate a change in temperature, or the difference between two temperatures, and thus do not typically directly reference absolute zero.

Plot3D[Sin[x y],{x,0,4},{y,0,4},ImageSize->400]

Kelvins

Temperatures expressed in Kelvin are equivalent to differences with respect to absolute zero, and therefore there is no need to separate units of temperature and temperature difference in Kelvin.

Note: previously “KelvinsDifference” was a distinct unit, but now is just an alias for “Kelvins”:

Quantity[3,"KelvinsDifference"]

QuantityUnit[%]

Temperature Conversions

The distinction between temperatures and temperature differences has implications for unit conversion between the two.

T <-> T

Temperatures may be freely converted from one scale to another, such as converting from Fahrenheit to Celsius or Kelvins using UnitConvert:

UnitConvert[(Quantity[3., "°F", degrees Fahrenheit, "DegreesFahrenheit"]),"DegreesCelsius"]

UnitConvert[(Quantity[3., "°F", degrees Fahrenheit, "DegreesFahrenheit"]),"Kelvins"]

ΔT <-> ΔT

Temperature differences may also be freely converted:

UnitConvert[(Quantity[8.2, (Subscript[, "Δ"]^("◦"))"C", Celsius degrees difference, "DegreesCelsiusDifference"]),"DegreesFahrenheitDifference"]

Note: since both Kelvin and Degrees Celsius Difference have a zero base, and use the same conversion ratio, converting between the two does not change the value:

UnitConvert[(Quantity[12, (Subscript[, "Δ"]^("◦"))"C", Celsius degrees difference, "DegreesCelsiusDifference"]),"Kelvins"]

T -> ΔT

Temperatures may be converted into temperature differences, in which case the temperature differences is interpreted as differences from absolute zero:

Note: a warning message is provided in such cases:

UnitConvert[(Quantity[3., "°F", degrees Fahrenheit, "DegreesFahrenheit"]),"DegreesFahrenheitDifference"]

This is equivalent to first converting to Kelvins and then converting into a temperature difference:

UnitConvert[(Quantity[3., "°F", degrees Fahrenheit, "DegreesFahrenheit"]),"Kelvins"]

UnitConvert[%,"DegreesFahrenheitDifference"]

Another way to understand conversion of temperatures into temperature differences is as a difference with absolute zero:

UnitConvert[(Quantity[3., "°F", degrees Fahrenheit, "DegreesFahrenheit"]),"DegreesCelsiusDifference"]

UnitConvert[(Quantity[3., "°F", degrees Fahrenheit, "DegreesFahrenheit"])]-(Quantity[0, "K", kelvins, "Kelvins"])

%==%%

ΔT !-> T

However, temperature differences cannot be directly converted to temperatures:

Note: in such cases an error message is provided.

UnitConvert[(Quantity[3., (Subscript[, "Δ"]^("◦"))"C", Celsius degrees difference, "DegreesCelsiusDifference"]),"DegreesFahrenheit"]

The equivalent operation would be to converted the temperature difference first to Kelvins, and then convert the value in Kelvins to temperature:

UnitConvert[(Quantity[3., (Subscript[, "Δ"]^("◦"))"C", Celsius degrees difference, "DegreesCelsiusDifference"]),"Kelvins"]

UnitConvert[%,"DegreesFahrenheit"]

Temperature Arithmetic

For arithmetic operations like addition, the distinction between temperature and temperature difference units impacts the resulting quantity arithmetic.

Temperature difference units may be added to a temperature to produce a temperature, temperature difference units may be added to produce a temperature difference or temperatures may be added to produce a temperature.

Addition

T + ΔT

One common arithmetic operation is to add a temperature difference to a temperature:

(Quantity[3, "°C", degrees Celsius, "DegreesCelsius"])+(Quantity[3, (Subscript[, "Δ"]^("◦"))"C", Celsius degrees difference, "DegreesCelsiusDifference"])

This works regardless of temperature scale:

(Quantity[3., "°C", degrees Celsius, "DegreesCelsius"])+(Quantity[3, (Subscript[, "Δ"]^("◦"))"F", Fahrenheit degrees difference, "DegreesFahrenheitDifference"])

ΔT + ΔT

The sum of temperature difference units will be a temperature difference:

(Quantity[3.`, (Subscript[, "Δ"]^("◦"))"C", Celsius degrees difference, "DegreesCelsiusDifference"])+(Quantity[3, (Subscript[, "Δ"]^("◦"))"C", Celsius degrees difference, "DegreesCelsiusDifference"])

(Quantity[3., (Subscript[, "Δ"]^("◦"))"C", Celsius degrees difference, "DegreesCelsiusDifference"])+(Quantity[3, (Subscript[, "Δ"]^("◦"))"F", Fahrenheit degrees difference, "DegreesFahrenheitDifference"])

T + T

When adding two temperature quantities, the result will be the sum of the values including the difference from absolute zero. The result of summing two temperatures is equivalent to converting those temperatures to Kelvin and then summing, with results canonically given in Kelvin:

(Quantity[3., "°C", degrees Celsius, "DegreesCelsius"])+(Quantity[3, "°C", degrees Celsius, "DegreesCelsius"])

The previous operation is equivalent to first converting the temperatures to Kelvin before summing:

UnitConvert@(Quantity[3., "°C", degrees Celsius, "DegreesCelsius"])+UnitConvert@(Quantity[3, "°C", degrees Celsius, "DegreesCelsius"])

Subtraction

T - T

Temperature differences represent the distance between two temperatures, and thus the result of subtracting one temperature from another is a temperature difference:

(Quantity[3, "°C", degrees Celsius, "DegreesCelsius"])-(Quantity[3, "°C", degrees Celsius, "DegreesCelsius"])

(Quantity[9., "°C", degrees Celsius, "DegreesCelsius"])-(Quantity[30, "°F", degrees Fahrenheit, "DegreesFahrenheit"])

ΔT -  ΔT

Subtracting one temperature difference from another results in a temperature difference:

(Quantity[5, (Subscript[, "Δ"]^("◦"))"C", Celsius degrees difference, "DegreesCelsiusDifference"])-(Quantity[2, (Subscript[, "Δ"]^("◦"))"C", Celsius degrees difference, "DegreesCelsiusDifference"])

T - ΔT

Temperature differences may also be subtracted from a temperature to produce another temperature:

(Quantity[20, "°C", degrees Celsius, "DegreesCelsius"])-(Quantity[5, (Subscript[, "Δ"]^("◦"))"C", Celsius degrees difference, "DegreesCelsiusDifference"])

(Quantity[20, "°C", degrees Celsius, "DegreesCelsius"])-(Quantity[5., (Subscript[, "Δ"]^("◦"))"F", Fahrenheit degrees difference, "DegreesFahrenheitDifference"])

ΔT - T

Subtracting a temperature from a temperature difference will also produce a temperature, one that is equivalent to the inverted sign of that temperature difference subtracted from the temperature.

(Quantity[5, (Subscript[, "Δ"]^("◦"))"C", Celsius degrees difference, "DegreesCelsiusDifference"])-(Quantity[20, "°C", degrees Celsius, "DegreesCelsius"])

-1((Quantity[20, "°C", degrees Celsius, "DegreesCelsius"])-(Quantity[5, (Subscript[, "Δ"]^("◦"))"C", Celsius degrees difference, "DegreesCelsiusDifference"]))

Multiplication

Multiplication of temperatures is equivalent to adding temperatures and is canonically given in Kelvin:

2*(Quantity[3., "°F", degrees Fahrenheit, "DegreesFahrenheit"])

2*UnitConvert[(Quantity[3., "°F", degrees Fahrenheit, "DegreesFahrenheit"]),"Kelvins"]

Because some temperature scales’ zero values do not represent absolute zero, multiplication by a negative number will not migrate the sign into the temperature quantity.

-Quantity[3.,"DegreesCelsius"]

Adding the result to a temperature will produce a temperature difference:

Quantity[3,"DegreesCelsius"]+%

The sign remains outside of the temperature quantity, even for negative temperature values:

-2Quantity[-3,"DegreesCelsius"]

Division

Division by temperature units will produce a quantity equivalent to the temperature converted to Kelvin before division, with results canonically given in Kelvin:

1/(Quantity[1., "°C", degrees Celsius, "DegreesCelsius"])

Division by a temperature is equivalent to division by that same value in Kelvin:

1/UnitConvert[(Quantity[1., "°C", degrees Celsius, "DegreesCelsius"])]

Division of one temperature by another will result in a numeric ratio given by the value of both temperatures in Kelvin:

(Quantity[10, "K", kelvins, "Kelvins"])/(Quantity[10., "°C", degrees Celsius, "DegreesCelsius"])

Compound Units

Temperatures in compound units will be automatically normalized to Kelvin, with an accompanying warning message:

Quantity[3.,"DegreesFahrenheit"/"Meters"]

This is equivalent to division of the quantities themselves:

(Quantity[3., "°F", degrees Fahrenheit, "DegreesFahrenheit"])/(Quantity[1, "m", meter, "Meters"])

This is also equivalent to conversion to Kelvin prior to division:

UnitConvert[(Quantity[3., "°F", degrees Fahrenheit, "DegreesFahrenheit"])]/(Quantity[1, "m", meter, "Meters"])

Additional Resources

Units tech notes

Temperature units tutorial