### 14.3 Defining Nonlinear Units ¶

Some unit conversions of interest are nonlinear; for example, temperature conversions between the Fahrenheit and Celsius scales cannot be done by simply multiplying by conversion factors.

When you give a linear unit definition such as ‘`inch 2.54 cm`’ you are providing information that `units` uses to convert values in inches into primitive units of meters. For nonlinear units, you give a functional definition that provides the same information.

Nonlinear units are represented using a functional notation. It is best to regard this notation not as a function call but as a way of adding units to a number, much the same way that writing a linear unit name after a number adds units to that number. Internally, nonlinear units are defined by a pair of functions that convert to and from linear units in the database, so that an eventual conversion to primitive units is possible.

Here is an example nonlinear unit definition:

    tempF(x) units=[1;K] domain=[-459.67,) range=[0,) \
                (x+(-32)) degF + stdtemp ; (tempF+(-stdtemp))/degF + 32

A nonlinear unit definition comprises a unit name, a formal parameter name, two functions, and optional specifications for units, the domain, and the range (the domain of the inverse function). The functions tell `units` how to convert to and from the new unit. To produce valid results, the arguments of these functions need to have the correct dimensions and be within the domains for which the functions are defined.

The definition begins with the unit name followed immediately (with no spaces) by a ‘`(`’ character. In the parentheses is the name of the formal parameter. Next is an optional specification of the units required by the functions in the definition. In the example above, the ‘`units=[1;K]`’ specification indicates that the ‘`tempF`’ function requires an input argument conformable with ‘`1`’ (i.e., the argument is dimensionless), and that the inverse function requires an input argument conformable with ‘`K`’. For normal nonlinear units definition, the forward function will always take a dimensionless argument; in general, the inverse function will need units that match the quantity measured by your nonlinear unit. Specifying the units enables `units` to perform error checking on function arguments, and also to assign units to domain and range specifications, which are described later.

Next the function definitions appear. In the example above, the ‘`tempF`’ function is defined by

    tempF(x) = (x+(-32)) degF + stdtemp

This gives a rule for converting ‘`x`’ in the units ‘`tempF`’ to linear units of absolute temperature, which makes it possible to convert from tempF to other units.

To enable conversions to Fahrenheit, you must give a rule for the inverse conversions. The inverse will be ‘`x(tempF)`’ and its definition appears after a ‘`;`’ character. In our example, the inverse is

    x(tempF) = (tempF+(-stdtemp))/degF + 32

This inverse definition takes an absolute temperature as its argument and converts it to the Fahrenheit temperature. The inverse can be omitted by leaving out the ‘`;`’ character and the inverse definition, but then conversions to the unit will not be possible. If the inverse definition is omitted, the `--check` option will display a warning. It is up to you to calculate and enter the correct inverse function to obtain proper conversions; the `--check` option tests the inverse at one point and prints an error if it is not valid there, but this is not a guarantee that your inverse is correct.

With some definitions, the units may vary. For example, the definition

    square(x)       x^2

can have any arbitrary units, and can also take dimensionless arguments. In such a case, you should not specify units. If a definition takes a root of its arguments, the definition is valid only for units that yield such a root. For example,

    squirt(x)       sqrt(x)

is valid for a dimensionless argument, and for arguments with even powers of units.

Some definitions may not be valid for all real numbers. In such cases, `units` can handle errors better if you specify an appropriate domain and range. You specify the domain and range as shown below:

    baume(d) units=[1;g/cm^3] domain=[0,130.5] range=[1,10] \
             (145/(145-d)) g/cm^3 ; (baume+-g/cm^3) 145 / baume

In this example the domain is specified after ‘`domain=`’ with the two numerical endpoints given in brackets. No spaces can appear before the ‘`=`’. In accord with mathematical convention, square brackets indicate a closed interval (one that includes its endpoints), and parentheses indicate an open interval (one that does not include its endpoints). An interval can be open or closed on one or both ends; an interval that is unbounded on either end is indicated by omitting the limit on that end. For example, a quantity to which decibel (dB) is applied may have any value greater than zero, so the range is indicated by ‘`(0,)`’:

    decibel(x) units=[1;1] range=(0,) 10^(x/10); 10 log(decibel)

If the domain or range is given, the second endpoint must be greater than the first. The intervals given for the domain and range must be numbers and cannot include units or any calculations.

The domain and range specifications can appear independently and in any order along with the units specification. The values for the domain and range endpoints are attached to the units given in the units specification, and if necessary, the parameter value is adjusted for comparison with the endpoints. For example, if a definition includes ‘`units=[1;ft]`’ and ‘`range=[3,)`’, the range will be taken as 3 ft to infinity. If the function is passed a parameter of ‘`900 mm`’, that value will be adjusted to 2.9527559 ft, which is outside the specified range. If you omit the units specification from the previous example, `units` can not tell whether you intend the lower endpoint to be 3 ft or 3 microfurlongs, and can not adjust the parameter value of 900 mm for comparison. Without units, numerical values other than zero or plus or minus infinity for domain or range endpoints are meaningless, and accordingly they are not allowed. If you give other values without units, then the definition will be ignored and you will get an error message.

Although the units, domain, and range specifications are optional, it’s best to give them when they are applicable; doing so allows `units` to perform better error checking and give more helpful error messages. Giving the domain and range also enables the `--check` option to find a point in the domain to use for its point check of your inverse definition.

You can make synonyms for nonlinear units by providing both the forward and inverse functions; inverse functions can be obtained using the ‘`~`’ operator. So to create a synonym for ‘`tempF`’ you could write

    fahrenheit(x) units=[1;K] tempF(x); ~tempF(fahrenheit)

This is useful for creating a nonlinear unit definition that differs slightly from an existing definition without having to repeat the original functions. For example,

    dBW(x)     units=[1;W] range=[0,) dB(x) W ;  ~dB(dBW/W)

If you wish a synonym to refer to an existing nonlinear unit without modification, you can do so more simply by adding the synonym with appended parentheses as a new unit, with the existing nonlinear unit—without parentheses—as the definition. So to create a synonym for ‘`tempF`’ you could write

    fahrenheit()  tempF

The definition must be a nonlinear unit; for example, the synonym

    fahrenheit()  meter

will result in an error message when `units` starts.

You may occasionally wish to define a function that operates on units. This can be done using a nonlinear unit definition. For example, the definition below provides conversion between radius and the area of a circle. This definition requires a length as input and produces an area as output, as indicated by the ‘`units=`’ specification. Specifying the range as the nonnegative numbers can prevent cryptic error messages.

    circlearea(r) units=[m;m^2] range=[0,)   pi r^2 ; sqrt(circlearea/pi)