*3.1. Simultaneous Calculation of Value and Uncertainty*

Section 2.2 explained that the calculation of components of uncertainty can be handled using the chain rule for partial differentiation when a measurement model is expressed in stages. GTC extends this further by decomposing stage model expressions into very basic operations, such as ×, ÷, sin(), exp(), etc. This is effectively using a computational technique called automatic differentiation [11]. Arithmetic operator overloading and a library of mathematical functions for uncertain numbers are used to automate decomposition of mathematical expressions into simple steps and then to evaluate the value and components of uncertainty at each step.

All the basic uncertain-number functions and arithmetic operations defined in GTC are either univariate or bivariate. For a univariate function, *fh*(*z*), Equation (7) reduces to the following:

$$
\mu\_j(y\_h) = \frac{\partial f\_h}{\partial z} \mu\_j(z) \tag{9}
$$

and for a bivariate function, *fh*(*z*1, *z*2), Equation (7) becomes the following.

$$u\_j(y\_h) = \frac{\partial f\_h}{\partial z\_1} u\_j(z\_1) + \frac{\partial f\_h}{\partial z\_2} u\_j(z\_2) \,. \tag{10}$$

Thus, for example, the uncertain-number trigonometric sine function can be handled as follows. If the value of an uncertain-number input is *z*, then the value of the uncertainnumber result is *yh* = sin(*z*). Furthermore, if there are two components of uncertainty associated with the input, *u*1(*z*) and *u*2(*z*), then the two corresponding components of

uncertainty associated with the result are *u*1(*yh*) = *c u*1(*z*) and *u*2(*yh*) = *c u*2(*z*), where *c* = *∂ fh*/*∂z* = cos(*z*) is the derivative in (9).

## *3.2. Unique Identifiers*

GTC algorithms track the identity of elementary uncertain numbers representing influence factors. The subscript *i* that appears on the terms for components of uncertainty, *ui*(*y*), is the same as the subscript appearing on the influence quantities, *Xi*, in model Equation (1).

$$\boldsymbol{Y} = f(X\_1, X\_2, \dots) \dots$$

Software and digital records must somehow keep track of these *i*'s, even when measurements are carried out in stages, at different locations and at different times. GTC uses a simple tuple of integers as an identifier format. The first integer is kept fixed for a given session while the second integer takes the value of a counter that is incremented each time an elementary uncertain number is created. To ensure that these identifiers are unique in time and space, the first integer is a Universally Unique Identifier (UUID) formatted as a 128-bit integer.

This identifier format reveals nothing about the influence quantity, although identifiers can be arranged in order, which improves the performance of some algorithms. Would a more sophisticated type of identifier be useful? For the purposes of data processing, the only requirement is uniqueness. Nevertheless, GTC already allows text labels to be associated with nodes (used as labels for influence quantities in uncertainty budgets) and a planned enhancement to GTC will allow information about influence quantities to be held in a manifest and indexed by unique identifiers. Such a manifest could accompany a digital record of uncertain-number data. This could address any need for additional metadata about influence quantities, without the burden of minting and configuring more specialised digital objects designed to access information on the internet [12].

#### *3.3. Node Classes*

During a GTC calculation, most of the uncertain numbers that are created may be regarded as temporary objects and can be garbage-collected almost immediately. If this is not performed in large problems, the demands on memory can seriously limit performance. To address this, GTC only holds essential information about influence quantities, and information about certain intermediate results as required. This essential information is kept in small node objects, allowing the memory occupied by larger uncertain-number objects to be reclaimed when not required.

There are two classes of node: A Leaf is associated with elementary uncertain numbers and a Node is associated with uncertain numbers representing intermediate results; Leaf is a subclass of Node (Figure 4). A Leaf is created whenever an elementary uncertain number is declared. The information encapsulated includes the following: a value of standard uncertainty; a number of degrees of freedom; a Boolean flag, which identifies objects declared to be independent; a string label, for display purposes; a unique identifier; and two Python collection objects. One of these is the dictionary correlation, which holds values of *r*(*xi*, *xj*) for calculations such as Equation (11). The other is the ensemble set, which is used to identify other nodes associated with an ensemble of closely related elementary uncertain numbers. Ensembles are used in the calculation of degrees of freedom described in Section 2.1 (see also Appendix A.1).

While a Leaf is created for every elementary uncertain number, there is no need to create nodes at every stage of a calculation. When intermediate components of uncertainty are required (for an intermediate result of particular significance or when an uncertain number will be stored), the function result() is used to create a new Node (as shown below in Section 3.5 and later in Appendix A.3).

**Figure 4.** A UML diagram for node classes. There are two types of node: Node and Leaf. Leaf nodes are associated with elementary uncertain real numbers, while objects of the parent class Node may be associated with intermediate uncertain-number results. The uid attribute can be used to sort nodes. The first integer in uid is a common UUID value for all the nodes created in a Python session, while the second integer enumerates the nodes created in that session.

#### *3.4. Propagating Uncertainty*

During uncertainty propagation, components of uncertainty must be evaluated at each step. The cumulative effect of these computations can dominate execution time and the demands on memory can be high. Moreover, the overhead of looking up correlation coefficient values for pairs of inputs in Equation (3) during the final calculation of a standard uncertainty is inefficient.

To address this, information about components of uncertainty is stored in several sequences in uncertain-number objects (u\_components, d\_components and i\_components in Figure 5). The elements in these sequences consist of a component of uncertainty paired with a node that holds information about the corresponding influence quantity (see Figure 4).


**Figure 5.** A UML class diagram for an uncertain real number. Vector objects contain sequences of pairs (the Node and Leaf classes in Figure 4). The Vectors u\_components and d\_components hold components of uncertainty for independent and dependent influences, respectively. The Vector i\_components holds components of uncertainty with respect to designated intermediate results. The private node attribute will refer to a Leaf when an UncertainReal object is elementary, or to a Node when the object is an intermediate result, but otherwise, the attribute is not assigned.

During calculations, an uncertain number is created at every step. The components of uncertainty are evaluated by weighting the components of uncertainty for the inputs to the step and combining these weighted components when common influences are involved. By keeping the elements in sequences ordered, this process can be handled efficiently by stepping along the sequences and identifying any common influences. The ordering is established by the uid attribute of node objects.

For example, consider the multiplication of a pair of uncertain numbers, *fh*(*Z*1, *Z*2) = *Z*<sup>1</sup> *Z*2. Suppose the component-of-uncertainty sequence for the first argument contains the following elements, where only the first four digits of the UUID integer are shown:

$$\begin{aligned} (7953...,1) &: 3, \\ (7953...,2) &: -1, \\ (7953...,4) &: 15, \\ (7953...,6) &: -5 \end{aligned}$$

and the component-of-uncertainty elements for the second argument are as follows.

(7953..., 1) : −1, (7953..., 3) : 2, (7953..., 4) : 2, (7953..., 5) : 12

To obtain the component-of-uncertainty sequence for the product, components in the first sequence are weighted by the value of the second argument and components in the second sequence are weighted by the value of the first argument. The weighted components of common influences are added together (in this case, there is a pair of common influences identified by (7953 ... , 1) and (7953 ... , 4)). Thus, if the value of the first argument is 5 and the value of the second is 10, the components of uncertainty for the product are as follows.

$$\begin{aligned}(7953...'1)&: 25, \\ (7953...'2)&: -10, \\ (7953...,3)&: 10, \\ (7953...4)&: 160, \\ (7953...,5)&: 60, \\ (7953...,6)&: -50\end{aligned}$$

When evaluating Equation (3), the number of terms to be summed grows in proportion to the square of the number of input arguments. Moreover, a value of *r*(*xi*, *xj*) is needed for every pair of inputs. However, in practice, there are very few non-trivial correlation coefficients assigned ( when *i* = *j*, *r*(*xi*, *xj*) = 1 and usually *r*(*xi*, *xj*) = 0, when *i* = *j*). Therefore, not only is the overhead of looking up correlation coefficients unnecessary but many terms in the double sum are zero. To streamline this calculation, the data for independent and dependent influences are separated in two different component-of-uncertainty sequences (u\_components and d\_components, in Figure 5). When elementary uncertain numbers are defined, it will be known whether correlation coefficients will be associated with the inputs; thus, dependent and independent influences can be identified and separated. The evaluation of Equation (3) can then be handled more efficiently. For instance, if there are independent estimates *X*1, ··· , *XK* and dependent estimates *XK*+1, ··· , *Xl*, the calculation of Equation (3) can be expressed as follows:

$$u(y) = \left[\sum\_{i=1}^{K} u\_i^2(y) + 2\sum\_{i=K+1}^{l} \sum\_{j=i+1}^{l} u\_i(y) \, r(\mathbf{x}\_i, \mathbf{x}\_j) \, u\_j(y)\right]^{1/2},\tag{11}$$

where the double sum is now only over dependent terms.

#### *3.5. Intermediate Results*

In some problems, the interpretation of results is complicated by a large number of influences and, hence, a large number of components of uncertainty. A succinct and often more intuitive presentation can sometimes be obtained, without sacrificing rigour, by reporting the sensitivity of a final result to uncertainty in intermediate results. This is implemented in GTC by using another sequence for components of uncertainty with respect to designated intermediate results ( i\_components in Figure 5).

To initialise the process of calculating an intermediate component of uncertainty, function result() must be applied to an uncertain number. This seeds a new element in i\_components. Thereafter, propagation occurs, as before, by weighting the intermediate components of stage inputs by the partial derivatives of the stage function. The elements in the sequence i\_components are also node-value pairs, but a Node rather than a Leaf is used (see Figure 4).

The electrical network example can be used to illustrate the use of intermediate results. Suppose the current through the series network is measured as 1.0000 mA, with a standard uncertainty of 0.0010 mA. The resistance of the resistor in the middle of the network, and a breakdown of the contributions to uncertainty in that value, is obtained simply from the following.

```
I = ureal(1E-3,0.001E-3,label="I")
display( (V_20-V_10)/I, "R2" )
```
The results are as follows.

```
R2: 259.73(36)
I: 0.2597279999999999
E_rel: 0.20778240000000003
E_rnd_1: 0.1
E_rnd_2: 0.1
E_off: 0.0
```
However, to see how resistance depends on voltage and current measurements, we declare the voltage difference to be an intermediate result and report an uncertainty budget in terms of only the current and voltage.

```
V_20_V_10 = result(V_20-V_10,label="V_20-V_10")
for l,u in rp.budget(V_20_V_10/I,influences=[I,V_20_V_10]):
print( "{}: {}".format(l,u) )
```
The results are as follows:

```
I: 0.2597279999999999
V_20-V_10: 0.2513434418276316
```
which shows that the contribution to uncertainty from the current measurement is comparable to the contribution from the voltage measured across the resistor. This was not so obvious from the complete list of uncertainty components obtained earlier.
