Indexing is the process of extracting elements from arrays. NAP extends this concept to the estimation (using interpolation) of values between the elements.
An index can appear:
set value takes an an argument that specifies
which elements are to be modified
nap_get hdf and nap_get netcdf,
specifying positions within a file
NAP provides powerful indexing (subscripting) facilities. The subscript origin is 0 (as in other aspects of Tcl such as lists). The rightmost dimension is the least significant (varies fastest). Here is a simple example of a vector indexed by a scalar:
% nap "vector = {2 -5 9 4}"
::NAP::14-14
% [nap "vector(2)"]
9
This syntax means that the above example can be rewritten without parentheses as:
% [nap "vector 2"] 9It also means that any non-scalar expression (including a constant of course) can be indexed, as shown by:
% [nap "{2 -5 9 4} 2"]
9
% [nap "({2 -5 9 4} + 10) 2"]
19
% [nap "vector 2.5"] 6.5Note that the dimension-position
2.5
is halfway between
2
(corresponding to the value 9)
and
3
(corresponding to the value 4).
Thus the value is estimated to be
0.5 * 9.0 + 0.5 * 4.0 = 4.5 + 2.0 = 6.5
If n is the dimension-size and p the position, then
% [nap "vector 3.1"] 3.8Note that the dimension-position
3.1
is 10% of the distance between
3
(corresponding to the value 4)
and
4
(equivalent to
0
and corresponding to the value 2).
Thus the value is estimated to be
0.9 * 4.0 + 0.1 * 2.0 = 3.6 + 0.2 = 3.8
%n,
Thus in our example subscript 6 is treated as
6%4 = 2.
So we get
% [nap "vector 6"] 9It also means that negative values can be use to index backward from the end, as shown by:
% [nap "vector(-1)"] 4 % [nap "vector(-2)"] 9 % [nap "vector(-3)"] -5
mat and illustrates the use of
elemental indices to extract individual elements.
% nap "mat = {{1.5 0 7}{2 -4 -9}}"
::NAP::60-60
% $mat
1.5 0.0 7.0
2.0 -4.0 -9.0
% [nap "mat {0 1}"]
0
% [nap "mat {1 -1}"]
-9
%
% [nap "mat {0.5 1.5}"]
-1.5
The value corresponding to the index
{0.5 1.5}
is estimated, using bilinear interpolation, to be
0.25 * 0.0 + 0.25 * 7.0 + 0.25 * (-4.0) + 0.25 * (-9.0)
= -1.5
| Index Type | Rank of Indexed Array |
|---|---|
| shape-preserving | 1 |
| vector-flip | 1 |
| full | 2 or more |
| cross-product | 2 or more |
vector
can be indexed by
% $vector
2 -5 9 4
% [nap "vector(2)"] all
::NAP::57-57 i32 MissingValue: -2147483648 References: 0 Unit: (NULL)
Value:
9
% [nap "vector({2 2.5 2})"] all
::NAP::61-61 f32 MissingValue: NaN References: 0 Unit: (NULL)
Dimension 0 Size: 3 Name: (NULL) Coordinate-variable: (NULL)
Value:
9 6.5 9
% [nap "vector({
{1 0 2.5}
{-1 2 1}
})"] all
::NAP::67-67 f32 MissingValue: NaN References: 0 Unit: (NULL)
Dimension 0 Size: 2 Name: (NULL) Coordinate-variable: (NULL)
Dimension 1 Size: 3 Name: (NULL) Coordinate-variable: (NULL)
Value:
-5.0 2.0 6.5
4.0 9.0 -5.0
The shape-preserving property means one can use a vector to
define a mapping.
The following example maps 0 to 4, 1 to 1, 2 to 9 and 3 to 4:
% [nap "{4 1 9 4} {
{2 1 2 0}
{3 3 0 1}
}"]
9 1 9 4
4 4 4 1
The following example uses the same technique to implement
a simple substitution cipher
(mapping space to R, A to X, B to B, C to T, … as shown)
to encrypt the message
"HELLO WORLD"
as
"A HHVREVZHC"
which is then decrypted.
% nap "plain = ' ABCDEFGHIJKLMNOPQRSTUVWXYZ'" ::NAP::63-63 % nap "cipher = 'RXBTC MUAFGWHYIVJKZDLNOEPQS'" ::NAP::64-64 % [nap "plain((plain @@ cipher)(plain @@ 'HELLO WORLD'))"]; # encrypt A HHVREVZHC % [nap "cipher((cipher @@ plain)(cipher @@ 'A HHVREVZHC'))"]; # decrypt HELLO WORLD
It is often necessary to reverse the order of elements in a vector. One could use shape-preserving indexing, as in:
% [nap "{2 4 6 8}(3 .. 0)"]
8 6 4 2
NAP provides the niladic operator
"-"
to specify such reversal (or flipping).
(A niladic operator is one without any operands.)
Thus one can simplify the above example to:
% [nap "{2 4 6 8}(-)"]
8 6 4 2
Such an index of a vector, consisting of just
"-", is called a vector-flip.
Note that
cross-product-indexing
also allows the niladic
"-"
to specify flipping of one or more dimensions.
What does the niladic
"-"
generate?
Let's see:
% [nap "-"] all ::NAP::62-62 f32 MissingValue: NaN References: 0 Unit: (NULL) Value: -InfIt generates a scalar 32-bit NAO with the value negative infinity! Indexing treats such a NAO as meaning "flip". So the above indexing example could also (but less conveniently) be written as:
% [nap "{2 4 6 8}(-1if32)"]
8 6 4 2
The following example shows how the previously defined variable
mat
can be indexed by
% $mat
1.5 0.0 7.0
2.0 -4.0 -9.0
% [nap "mat {0.5 1.5}"] all
::NAP::148-148 f32 MissingValue: NaN References: 0 Unit: (NULL)
Value:
-1.5
% [nap "mat {
{0.5 1.5}
{0 1}
{-1 -1}
}"] all
::NAP::157-157 f32 MissingValue: NaN References: 0 Unit: (NULL)
Dimension 0 Size: 3 Name: (NULL) Coordinate-variable: (NULL)
Value:
-1.5 0 -9
Note that shape-preserving indexing is similar to applying full indexing
to a vector (if this were allowed).
The shape-preserving-index is the hypothetical full-index reshaped to omit the
final redundant dimension of size 1.
A cross-product-index is usually defined using the operator
",".
This allows the left and/or right operand to be omitted and such null (missing)
operands are treated as
0..(n-1)",
-" operator,
which is equivalent to
(n-1)..0".
The following examples again use the previously defined variable
mat.
We begin by repeating the first full-index example above and then we provide the
cross-product-index equivalent:
% $mat
1.5 0.0 7.0
2.0 -4.0 -9.0
% [nap "mat({0.5 1.5})"] all
::NAP::196-196 f32 MissingValue: NaN References: 0 Unit: (NULL)
Value:
-1.5
% [nap "mat(0.5,1.5)"] all
::NAP::204-204 f32 MissingValue: NaN References: 0 Unit: (NULL)
Value:
-1.5
The next example shows how the previously defined variable
mat
can be indexed by the cross-product of two vectors to produce a matrix,
then provides the equivalent full-index:
% $mat
1.5 0.0 7.0
2.0 -4.0 -9.0
% [nap "mat({1 0},{2 0 -1 0})"] all
::NAP::174-174 f64 MissingValue: NaN References: 0 Unit: (NULL)
Dimension 0 Size: 2 Name: (NULL) Coordinate-variable: (NULL)
Dimension 1 Size: 4 Name: (NULL) Coordinate-variable: (NULL)
Value:
-9.0 2.0 -9.0 2.0
7.0 1.5 7.0 1.5
% [nap "mat({
{{1 2}{1 0}{1 -1}{1 0}}
{{0 2}{2 0}{2 -1}{2 0}}
})"] all
::NAP::180-180 f64 MissingValue: NaN References: 0 Unit: (NULL)
Dimension 0 Size: 2 Name: (NULL) Coordinate-variable: (NULL)
Dimension 1 Size: 4 Name: (NULL) Coordinate-variable: (NULL)
Value:
-9.0 2.0 -9.0 2.0
7.0 1.5 7.0 1.5
The following example illustrates the effect of a null operand to
",".
It also shows the difference between a scalar operand and a single-element vector
containing the same value.
% $mat
1.5 0.0 7.0
2.0 -4.0 -9.0
% [nap "mat(1,)"] all
::NAP::209-209 f64 MissingValue: NaN References: 0 Unit: (NULL)
Dimension 0 Size: 3 Name: (NULL) Coordinate-variable: (NULL)
Value:
2 -4 -9
% [nap "mat({1},)"] all
::NAP::213-213 f64 MissingValue: NaN References: 0 Unit: (NULL)
Dimension 0 Size: 1 Name: (NULL) Coordinate-variable: (NULL)
Dimension 1 Size: 3 Name: (NULL) Coordinate-variable: (NULL)
Value:
2 -4 -9
The following examples show how the niladic "-" operator
is used to flip (reverse) dimensions:
% $mat
1.5 0.0 7.0
2.0 -4.0 -9.0
% [nap "mat(,-)"]
7.0 0.0 1.5
-9.0 -4.0 2.0
% [nap "mat(-,)"]
2.0 -4.0 -9.0
1.5 0.0 7.0
% [nap "mat(-,-)"]
-9.0 -4.0 2.0
7.0 0.0 1.5
% [nap "mat(0,-)"]
7 0 1.5
% [nap "mat(-,{2 0 0})"]
-9.0 2.0 2.0
7.0 1.5 1.5
The following example creates a rank-3 array a3d with shape
{2 2 3},
then extracts all of row 0 from both layers:
% nap "a3d = {
{
{9 1 4}
{0 8 7}
}{
{2 3 5}
{9 6 0}
}
}"
::NAP::215-215
% $a3d all
::NAP::215-215 i32 MissingValue: -2147483648 References: 1 Unit: (NULL)
Dimension 0 Size: 2 Name: (NULL) Coordinate-variable: (NULL)
Dimension 1 Size: 2 Name: (NULL) Coordinate-variable: (NULL)
Dimension 2 Size: 3 Name: (NULL) Coordinate-variable: (NULL)
Value:
9 1 4
0 8 7
2 3 5
9 6 0
% [nap "a3d(,0,)"] all
::NAP::220-220 i32 MissingValue: -2147483648 References: 0 Unit: (NULL)
Dimension 0 Size: 2 Name: (NULL) Coordinate-variable: (NULL)
Dimension 1 Size: 3 Name: (NULL) Coordinate-variable: (NULL)
Value:
9 1 4
2 3 5
| Syntax | Value |
|---|---|
v(@c)
|
v(coordinate_variable(v)@c)
|
v(@@c)
|
v(coordinate_variable(v)@@c)
|
For example, suppose we have temperatures at two-hourly intervals from time 1000 to 1600 as follows:
% nap "t = {20.2 21.6 24.9 22.7}"
::NAP::159-159
% $t set coord "10 .. 16 ... 2"
We could estimate temperatures every hour during this period as follows:
% [nap "t(coordinate_variable(t)@(10..16))"] value 20.2 20.9 21.6 23.25 24.9 23.8 22.7Indirect indexing allows us to omit the left argument of operators "
@"
and
"@@"
in such expressions.
This enables the above expression to be simplified as follows:
% [nap "t(@(10..16))"] value 20.2 20.9 21.6 23.25 24.9 23.8 22.7
Note that this syntax does not allow indirect indices such as that in "t(3+@@12)".
Instead we have to use binary "@@" as in
"t(3+coordinate_variable(t)@@12)".
@[@]]expr,
[@[@]]expr,
[@[@]]expr,
…
The following table defines indirect cross-product indexing. It shows how the subscript for dimension d of array a is calculated from (vector or scalar) v:
| Syntax | Subscript Value |
|---|---|
@v
|
coordinate_variable(a,d)@v
|
@@v
|
coordinate_variable(a,d)@@v
|
The following creates a 3×4 matrix
temperature
that will be used to demonstrate indirect indexing.
It has
degC (°C).
% nap "temperature = f32{
{31.5 37.2 32.9 34.0}
{25.1 25.2 29.0 21.9}
{20.5 21.2 21.0 19.9}
}"
::NAP::72-72
% $temperature set unit degC
% nap "latitude = f32{10 20 30}"
::NAP::76-76
% $latitude set unit degrees_north
% nap "longitude = f32(110 .. 140 ... 10)"
::NAP::86-86
% $longitude set unit degrees_east
% $temperature set coo latitude longitude
The following verifies that the main NAO and its coordinate variables are as expected:
% $temperature all ::NAP::72-72 f32 MissingValue: NaN References: 1 Unit: degC Dimension 0 Size: 3 Name: latitude Coordinate-variable: ::NAP::76-76 Dimension 1 Size: 4 Name: longitude Coordinate-variable: ::NAP::86-86 Value: 31.5 37.2 32.9 34.0 25.1 25.2 29.0 21.9 20.5 21.2 21.0 19.9 % ::NAP::76-76 all ::NAP::76-76 f32 MissingValue: NaN References: 2 Unit: degrees_north Dimension 0 Size: 3 Name: (NULL) Coordinate-variable: (NULL) Value: 10 20 30 % ::NAP::86-86 all ::NAP::86-86 f32 MissingValue: NaN References: 2 Unit: degrees_east Dimension 0 Size: 4 Name: (NULL) Coordinate-variable: (NULL) Value: 110 120 130 140The following illustrates the use of both direct and indirect indexing to display the value of
29 in row 1 and column 2:
% [nap "temperature(1,2)"] 29 % [nap "temperature(@20, @130)"]; # latitude=20 longitude=130 29 % [nap "temperature(@@20, @@130)"] 29 % [nap "temperature(1, @130)"] 29In this case there is a point exactly corresponding to 20°S, 130°E, so the operators
@ and @@ give the same result.
Let us try the point 21°S, 138°E, which is not a grid point:
% [nap "temperature(@21, @138)"] 23 % [nap "temperature(@@21, @@138)"] 21.9Now we get different results for the two operators. Operator
@ gives a value estimated using bilinear interpolation.
Operator @@ gives the data value at the nearest row (1) and column (3).
If the unary operators @ and @@ did not exist we would have to
use their binary equivalents as follows:
% nap "interpolated_row = coordinate_variable(temperature,0) @ 21" ::NAP::96-96 % $interpolated_row 1.1 % nap "interpolated_col = coordinate_variable(temperature,1) @ 138" ::NAP::103-103 % $interpolated_col 2.8 % [nap "temperature(interpolated_row, interpolated_col)"] 23 % nap "nearest_row = coordinate_variable(temperature,0) @@ 21" ::NAP::112-112 % $nearest_row 1 % nap "nearest_col = coordinate_variable(temperature,1) @@ 138" ::NAP::119-119 % $nearest_col 3 % [nap "temperature(nearest_row, nearest_col)"] 21.9Say we want to estimate temperatures on a grid with
region_temperature, this can be done as follows:
% nap "region_temperature = temperature(@(19 .. 21), @(121 .. 124))" ::NAP::147-147 % $region_temperature all ::NAP::147-147 f32 MissingValue: NaN References: 1 Unit: degC Dimension 0 Size: 3 Name: latitude Coordinate-variable: ::NAP::145-145 Dimension 1 Size: 4 Name: longitude Coordinate-variable: ::NAP::146-146 Value: 26.699 26.998 27.297 27.596 25.580 25.960 26.340 26.720 25.140 25.480 25.820 26.160 % ::NAP::145-145 all ::NAP::145-145 i32 MissingValue: -2147483648 References: 1 Unit: degrees_north Dimension 0 Size: 3 Name: (NULL) Coordinate-variable: (NULL) Value: 19 20 21 % ::NAP::146-146 all ::NAP::146-146 i32 MissingValue: -2147483648 References: 1 Unit: degrees_east Dimension 0 Size: 4 Name: (NULL) Coordinate-variable: (NULL) Value: 121 122 123 124Why has the new longitude coordinate-variable been converted to data-type
f32?
NAP recognises degrees_east as a special unit implying longitude characteristics
such as
f32
@"
or "@@" operator to give indirect indexing.
However, unlike cross-product indexing, this operator applies to all dimensions.
The following example shows how full indexing can be used to produce the same values
as those in the above example of cross-product indexing.
% [nap "temperature({{1 2}{1.1 2.8}})"]; # direct indexing
29 23
% [nap "temperature(@{{20 130}{21 138}})"]
29 23
% [nap "temperature(@@{{20 130}{21 138}})"]
29 21.9
@" @@".
The unary "@" and "@@" operators simply create a copy of
their operand and attach to it such an ancillary NAO.
Another process in which indirect full indices are created by attaching such
an ancillary NAO, is
the function invert_grid().