Slices and Ranges in NMath Matrices

NMath employs the data-view design pattern by distinguishing between data, and the different ways mathematical objects such as vectors and matrices view the data. For example, a contiguous array of numbers in memory might be viewed by one object as the elements of a vector, while another object might view the same data as the elements of a matrix, laid out row by row. Many different objects might share a given block of data. The data-view pattern has definite advantages for both storage efficiency and performance.

Combined with Slices and Ranges, the data-view pattern also offers a very rich set of matrix and vector manipulation semantics. We sometimes review or optimize customer code, and under-use or mis-use of Slices and Ranges is one of the most common things we find.

Creating Slices and Ranges

Slice and Ranges simply provide a way to specify a subset on non-negative integers with constant spacing, which you can then use as an indexing object into matrices and vectors. The difference between a Slice and a Range is only in how you specify the integer subset. You construct a Slice object by specifying

  • a beginning index
  • the total number of indices
  • a step increment, or stride

For example, to create a slice for the indices { 2, 4, 6, 8, 10 }, specify a start of 2, 5 total elements, and a stride of 2, like so:

Slice s = new Slice( 2, 5, 2 );

You construct a Range object by specifying:

  • a beginning index
  • an ending index
  • a stride

Thus, to create a range for the indices { 2, 4, 6, 8, 10 }, specify a starting point of 2, a stopping point of 10, and a stride of 2:

Range r = new Range( 2, 10, 2 );

But suppose you want to address the elements in a vector v from the third element to the last? You could do this by creating a Range like so:

Range r = new Range( 2, v.Length - 1, 1 );

but this is rather cumbersome. As a convenience, therefore, NMath provides the Position enumeration which lists different view positions of underlying data. You can use values in the Position enumeration in conjunction with ranges and slices to create abstract subsets. The precise meaning of an abstract subset is only determined when an indexing object is applied to a particular matrix or vector. The enumerated values are:

  • Position.Start indicates the starting position.
  • Position.MidPoint indicates the midpoint position, rounded down for data structures with an even number of elements.
  • Position.End indicates the ending position.

For instance, this code creates two ranges that could be used to specify the odd and even elements of a vector:

Range evenElements = new Range( Position.Start, Position.End, 2 );
Range oddElements = new Range( 1, Position.End, 2 );

The static All property on Slice and Range returns a new object indexing all elements:

Slice allElements = Slice.All;

Using Slices and Ranges

Now let’s look at how Slice and Range are used to create new views of matrix and vector data. The NMath vector and matrix classes provide standard indexing operators for getting and setting element values. Thus, v[i] always returns the ith element of vector v’s view of the data, and A[i,j] always returns the element in the ith row and jth column of matrix A’s view of the data. The indexers are also overloaded to accept Slice and Range indexing objects. For instance, this uses a Slice to create a new view of a vector’s data:

DoubleVector v = new DoubleVector( 10, 1, 1 );
Slice first3Elements = new Slice( 0, 3 );
DoubleVector u = v[first3Elements];

Both u and v reference the same data.

Using a Range object, this code creates a new 3×3 matrix view of the top left portion of an 8×8 matrix:

DoubleMatrix A = new DoubleMatrix( 8, 8 );
Range topLeft = new Range( 0, 3 );
DoubleMatrix B = A[ topLeft, topLeft ];

Both A and B reference the same data.

You can use negative strides too. Here is the matrix A, reversed.

Slice rowSlice = new Slice( A.Rows-1, A.Rows, -1 );
Slice colSlice = new Slice( A.Cols-1, A.Cols, -1 );
DoubleMatrix ARev = A[rowSlice,colSlice];

Any indexer or method that returns a view of the data referenced by a vector or matrix can be used to modify the values of the object:

DoubleMatrix A = new DoubleMatrix( 5, 5, 2);
Slice s = new Slice( 0, 2 );
A[s,s] = new DoubleMatrix( 2, 2, 1);

You can also use the Set() method to set elements to a specified value:

Slice col = new Slice( 3, 2 );
Slice row = Slice.All;
A.Set( col, row, 0 );

As a final example, here we change the contents of matrix A to alternate values 0 and 1 – like a checker board.

int numEvenElts = rows % 2 == 0 ? rows/2 : rows/2 + 1;
int numOddElts = rows/2;
Slice evenElts = new Slice( 0, numEvenElts, 2 );
Slice oddElts = new Slice( 1, numOddElts, 2 );
A.Set( evenElts, oddElts, 0 );
A.Set( evenElts, evenElts, 1 );
A.Set( oddElts, evenElts, 0 );
A.Set( oddElts, oddElts, 1 );

Ken

Leave a Reply

Your email address will not be published. Required fields are marked *

Top