Indexing

Table of Contents

  1. Introduction
    1. Indexing Syntax
    2. Dimension-Position
    3. Subscript
    4. Elemental Index
  2. Index
    1. Shape-Preserving
    2. Vector-Flip
    3. Full-index
    4. Cross-product-index
  3. Indirect Indexing
    1. Indirect Shape-Preserving Indexing
    2. Indirect Cross-Product Indexing
    3. Indirect Full Indexing
    4. How Indirect Indexing Works

Introduction

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:

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

Indexing Syntax

NAP syntax specifies that indexing is implied by two adjacent NAOs, with the base array on the left and the index on the right. Thus it is not necessary to parenthesise an index that is simply a constant or variable-name. However parentheses may make the code clearer to humans, who are likely to be familiar with languages where this is required.

This syntax means that the above example can be rewritten without parentheses as:

% [nap "vector 2"]
9
It 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

Dimension-Position

A dimension-position is a scalar value defining the position along a dimension. Fractional values are valid and represent positions between the array elements. Values at non-integral positions are estimated using n-dimensional linear interpolation. The following demonstrates this (continuing the above example):
% [nap "vector 2.5"]
6.5
Note 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 using ordinary one-dimensional linear interpolation.

If n is the dimension-size and p the position, then 0 ≤ p < n. Values between n-1 and n are defined by treating position n as equivalent to 0. This gives wraparound useful with cyclic dimensions such as longitude. Thus

% [nap "vector 3.1"]
3.8
Note 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

Subscript

Dimension-positions are always specified via subscripts. A subscript is similar to a dimension-position except that there are no size limits. If s is the subscript and n is the dimension-size, then the dimension-position p is defined by s%n, the remainder after dividing s by n.

Thus in our example subscript 6 is treated as 6%4 = 2. So we get

% [nap "vector 6"]
9
It 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

Elemental Index

An elemental index is a vector of rank subscripts, specifying the subscripts of an element of an array. The following example creates a matrix 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

An index is an array defining one or more elemental indices. The following table lists the four types, which are explained in the sections below:
Index Type Rank of Indexed Array
shape-preserving 1
vector-flip 1
full 2 or more
cross-product 2 or more

Shape-Preserving

Shape-preserving indexing is used to index a vector. The shape of the result is the same as that of the index. The following example shows how the previously defined variable vector can be indexed by
  • a scalar to produce a scalar
  • a vector to produce a vector
  • a matrix to produce a matrix:
    % $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
    

    Vector-Flip

    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:
    -Inf
    
    It 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
    

    Full-index

    A full-index is an array specifying a separate elemental index for every element of the result. The shape of the index is the shape of the result with r (the rank of the indexed array) appended. Each row of the index contains a vector of r elements defining an elemental index.

    The following example shows how the previously defined variable mat can be indexed by

  • a vector to produce a scalar
  • a matrix to produce a vector
    % $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.

    Cross-product-index

    A cross-product-index is a boxed vector containing rank elements pointing to scalars, vectors, nulls and flips. The cross-product combination of this vector defines the elemental indices of the indexed array.

    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)", where n is the dimension-size. Scalar operands produce no corresponding dimension in the result. A flip (dimension reversal) is normally represented by the niladic "-" 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
    

    Indirect Indexing

    It is often more natural to index via coordinate variables rather than subscripts. For example, consider a matrix with latitude and longitude coordinate variables. One could specify an element directly using subscripts such as "row 3, column 5". One could also specify an interpolated point directly using subscripts such as "row 3.5, column 5.2". However many users would prefer to specify latitude and longitude (values of the coordinate variables) rather than specify row and column. Indirect indexing simplifies such indexing via coordinate variables. The following three sections correspond to the three types of index: shape-preserving, cross-product and full.

    Indirect Shape-Preserving Indexing

    The following table defines indirect shape-preserving indexing of any vector v via any array c containing coordinate variable values:

    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.7
    
    Indirect 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)".

    Indirect Cross-Product Indexing

    The syntax for a general cross-product-index (involving direct and/or indirect indexing) is:
    [@[@]]expr, [@[@]]expr, [@[@]]expr, …
    where expr is an expression (which may need to be enclosed in parentheses).

    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

  • unit of degC (°C).
  • rows corresponding to latitudes 10°N, 20°N and 30°N
  • columns corresponding to longitudes 110°E, 120°E, 130°E and 140°E
    % 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 140
    
    The 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)"]
    29
    
    In 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.9
    
    Now 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.9
    
    Say we want to estimate temperatures on a grid with
  • latitudes 19°N, 20°N and 21°N
  • longitudes 121°E, 122°E 123°E and 124°E
    Naming the new matrix 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 124
    
    Why 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
  • wrap around to allow interpolation across longitude 180
  • data-type f32

    Indirect Full Indexing

    A full index can be preceded by the unary "@" 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
    

    How Indirect Indexing Works

    Indirect indexing uses ancillary NAOs linked to index NAOs using their link slots. These ancillary NAOs contain integers with value
    • 0 for direct indexing
    • 1 for indirect indexing using "@"
    • 2 for indirect indexing using "@@".

    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().

    Author: Harvey Davies       © 2002, CSIRO Australia.       Legal Notice and Disclaimer
    CVS Version Details: $Id: indexing.html,v 1.5 2004/11/11 22:34:23 dav480 Exp $