Array.h

Classes

Array -- A templated N-D Array class with variable origin (full description)

template<class T> class Array : public Cleanup

Interface

Public Members
Array()
Array(const IPosition &shape)
Array(const IPosition &shape, const IPosition &origin)
Array(const Array<T> &other)
virtual ~Array()
void cleanup()
void set(const T &value)
void apply(T (*function)(T))
void apply(T (*function)(const T &))
virtual void reference(Array<T> &other)
virtual Array<T> &operator=(const Array<T> &other)
Array<T> &operator=(const T &value)
Array<T> &operator= (const ROMaskedArray<T> &marray)
Array<T> copy() const
void unique()
Array<T> reform(const IPosition &shape, const IPosition &origin) const
Array<T> nonDegenerate()
virtual void resize(const IPosition &newShape)
virtual void resize(const IPosition &newShape, const IPosition &newOrigin)
T &operator()(const IPosition &)
const T &operator()(const IPosition &) const
Array<T> operator()(const IPosition &start, const IPosition &end)
Array<T> operator()(const IPosition &start, const IPosition &end, const IPosition &inc)
ROMaskedArray<T> operator() (const LogicalArray &mask) const
MaskedArray<T> operator() (const LogicalArray &mask)
ROMaskedArray<T> operator() (const ROMaskedLogicalArray &mask) const
MaskedArray<T> operator() (const ROMaskedLogicalArray &mask)
uInt nrefs() const
uInt ndim() const
uInt nelements() const
virtual Bool ok() const
Bool conform(const Array<T> &other) const
Bool conform(const ROMaskedArray<T> &other) const
IPosition origin() const
IPosition shape() const
IPosition end() const
T *getStorage(Bool &deleteIt)
const T *getStorage(Bool &deleteIt) const
void putStorage(T *&storage, Bool deleteAndCopy)
void freeStorage(const T *&storage, Bool deleteIt) const
static uInt arrayVersion()
rtti_dcl_mbrf_p1(Array<T>, Cleanup)
Protected Members
Array(uInt, const Block<Int> &)
void validateConformance(const Array<T> &) const
void validateIndex(const IPosition &) const
void copyVec(uInt n, T *to, uInt toStride, const T *from, uInt fromStride) const
void Array<T>::copyScalar(uInt n, T *to, uInt toStride, T value) const

Description

Array is a templated, N-dimensional, Array class. The origin is variable, but by default indices are zero-based. This Array class is the base class for specialized Vector, Matrix, and Cube classes.

Indexing into the array, and positions in general, are given with IPosition (essentially a vector of integers) objects. That is, an N-dimensional array requires a length-N IPosition to define a position within the array. Unlike C, indexing is done with (), not []. Also, the storage order is the same as in FORTRAN, i.e. memory varies most rapidly with the first index.

                                        // axisLengths = [1,2,3,4,5]
    IPosition axisLengths(5, 1, 2, 3, 4, 5); 
    Array<Int> ai(axisLengths);         // ai is a 5 dimensional array of
                                        // integers; indices are 0-based
                                        // => ai.nelements() == 120
    IPosition origin(5); origin = -1;   //    origin = [-1,-1,-1,-1,-1]
    Array<Int> ai2(axisLengths, origin);// The first element is at index
                                        // "origin".
    IPosition zero(5); zero = 0;        // [0,0,0,0,0]
    //...
    ai(zero) = ai2(origin);             // Copy first element of ai2 to ai1
    
Indexing into an N-dimensional array is relatively expensive. Normally you will index into a Vector, Matrix, or Cube. These may be obtained from an N-dimensional array by creating a reference, or by using an ArrayIterator. The "shape" of the array is an IPosition which gives the length of each axis, and the "origin" of the array is an IPosition which gives the index of the first array element ([0,0,0...] by default).

An Array may be standalone, or it may refer to another array, or to part of another array (by refer we mean that if you change a pixel in the current array, a pixel in the referred to array also changes, i.e. they share underlying storage).

Warning One way one array can reference another is through the copy constructor. While this might be what you want, you should probably use the reference() member function to make it explicit. The copy constructor is used when arguments are passed by value; normally functions should not pass Arrays by value, rather they should pass a reference or a const reference. On the positive side, returning an array from a function is efficient since no copying need be done. Later releases of the array classes might have the copy constructor actually make a copy -- comments solicited.
Aside from the explicit reference() member function, a user will most commonly encounter an array which references another array when he takes an array slice (or section). A slice is a sub-region of an array (which might also have a stride: every nth row, every mth column, ...).
    IPosition lengths(3,10,20,30);
    Array<Int> ai(lengths);         // A 10x20x30 cube
    Cube<Int> ci;
    //...
    ci.reference(ai1);              // ci and ai now reference the same
                                    // storage
    ci(0,0,0) = 123;                // Can use Cube indexing
    ci.xyPlane(2) = 0;              //     and other member functions
    IPosition zero(3,0,0,0);
    assert(ai(zero) == 123);        // True because ai, ci are references
    //...
    Array<Int> subArray;
    IPosition blc(3,0,0,0), trc(3,5,5,5);
    subArray.reference(ai(blc, trc));
    subArray = 10;                  // All of subArray, which is the
                                    // subcube from 0,0,0 to 5,5,5 in
                                    // ai, has the value 10.
    
While the last example has an array slice referenced explicitly by another array variable, normally the user will often only use the slice as a temporary in an expresion, for example:
   Array<Complex> array;
   IPosition blc, trc, offset;
   //...
   // Copy from one region of the array into another
   array(blc, trc) = array(blc+offset, trc+offset);

The Array classes are intended to operate on relatively large amounts of data. While they haven't been extensively tuned yet, they are relatively efficient in terms of speed. Presently they are not space efficient -- the overhead is about 15 words. While this will be improved (probably to about 1/2 that), these array classes are not appropriate for very large numbers of very small arrays. The Block class may be what you want in this circumstance.

Element by element mathematical and logical operations are available for arrays (defined in aips/ArrayMath.h and aips/ArrayLogical.h). Because arithmetic and logical functions are split out, it is possible to create an Array (and hence Vector etc) for any type T that has a default constructor, assignment operator, and copy constructor. In particular, Array works.

Array is derived from Cleanup, so if an exception is thrown storage from the array will be reclaimed. When "real" exceptions are available, this will no longer be required.

If compiled with the preprocessor symbol AIPS_DEBUG symbol, array consistency ("invariants") will be checked in most member functions, and indexing will be range-checked. This should not be defined for production runs.

A tutorial for the ArrayClasses is available in the "AIPS++ Programming Manual."

Tip Most of the data members and functions which are "protected" should likely become "private".

Member Description

Array()

Result has dimensionality of one, but nelements is zero. Perhaps instead it should be a dimension zero, length zero array.

Array(const IPosition &shape)

Create an array of the given shape, i.e. after construction array.ndim() == shape.nelements() and array.shape() == shape. The origin of the Array is zero.

Array(const IPosition &shape, const IPosition &origin)

Create an array with a given shape and origin.

Array(const Array<T> &other)

After construction, this and other reference the same storage.

virtual ~Array()

Frees up storage only if this array was the last reference to it.

void cleanup()

This functions is used by the exception handling mechanism we have defined. It merely calls the destructor. When real exceptions are available it will be unnecessary.

void set(const T &value)

Set every element of the array to "value." Also could use the assignment operator which assigns an array from a scalar.

void apply(T (*function)(T))

Apply the function to every element of the array. This modifies the array in place.

This version takes a function which takes a T and returns a T.

void apply(T (*function)(const T &))

Apply the function to every element of the array. This modifies the array in place.

This version takes a function which takes a const T reference and returns a T.

virtual void reference(Array<T> &other)

After invocation, this array and other reference the same storage. That is, modifying an element through one will show up in the other. The arrays appear to be identical; they have the same shape and origin.

virtual Array<T> &operator=(const Array<T> &other)

Copy the values in other to this. If the array on the left hand side has no elements, then it is resized to be the same size as as the array on the right hand side. Otherwise, the arrays must conform (same shapes, but origins do not need to be the same).

    IPosition shape(2,10,10);     // some shape
    Array<Double> ad(shape);
    //...
    Array<Double> ad2;            // N.B. ad2.nelements() == 0
    ad2 = ad;                     // ad2 resizes, then elements
                                  //     are copied.
    shape = 20;
    Array<Double> ad3(shape);
    ad3 = ad;                     // Error: arrays do not conform
    

Array<T> &operator=(const T &value)

Set every element of this array to "value". In other words, a scalar behaves as if it were a constant conformant array.

Array<T> &operator= (const ROMaskedArray<T> &marray)

Copy to this those values in marray whose corresponding elements in marray's mask are True.

Thrown Exceptions

Array<T> copy() const

This makes a copy of the array and returns it. This can be useful for, e.g. making working copies of function arguments that you can write into.

    void someFunction(const Array<Int> &arg)
    {
        Array<Int> tmp(arg.copy());
        // ...
    }
    
Note that since the copy constructor makes a reference, if we just created used to copy constructor, modifying "tmp" would also modify "arg". Clearly another alternative would simply be:
    void someFunction(const Array<Int> &arg)
    {
        Array<Int> tmp;
        tmp = arg;
        // ...
    }
    
which likely would be simpler to understand. (Should copy() be deprecated and removed?)

void unique()

Make a copy of this

This ensures that this array does not reference any other storage.

Tip When a section is taken of an array with non-unity strides, storage can be wasted if the array which originally contained all the data goes away. unique() also reclaims storage. This is an optimization users don't normally need to understand.
        IPosition shape(...), blc(...), trc(...), inc(...);
        Array<Float> af(shape);
        inc = 2; // or anything > 1
        Array<Float> aSection.reference(af(blc, trc, inc));
        af.reference(anotherArray);
        // aSection now references storage that has a stride
        // in it, but nothing else is. Storage is wasted.
        aSection.unique();

Array<T> reform(const IPosition &shape, const IPosition &origin) const

It is occasionally useful to have an array which access the same storage appear to have a different shape and origin. For example, Turning an N-dimensional array into a Vector. This will work when before.nelements() == after.nelements() and if this array refers to other storage, the increment on all axes must be one (guaranteed, for example, by unique()).

    IPosition squareShape(2,5,5);
    Array<Float> square(squareShape);
    IPosition lineShape(1,25);
    Vector<Float> line(square.reform(lineShape));
    // "square"'s storage may  now be accessed through Vector "line"
    

Array<T> nonDegenerate()

This member function returns an Array reference which has all degenerate (length==1) axes removed and the dimensionality reduced appropriately. If nelements==0 returns a dimension 1 0-length array. Otherwise returns an array with all length-1 axes remoeved.

virtual void resize(const IPosition &newShape)
virtual void resize(const IPosition &newShape, const IPosition &newOrigin)

Make this array a different shape. Presently the old values are not copied over to the new array.

const T &operator()(const IPosition &) const

Access a single element of the array. This is relatively expensive. Extensive indexing should be done through one of the Array specializations (Vector, Matrix, Cube). If AIPS_DEBUG is defined, index checking will be performed.

T &operator()(const IPosition &)

Access a single element of the array. This is relatively expensive. Extensive indexing should be done through one of the Array specializations (Vector, Matrix, Cube). If AIPS_DEBUG is defined, index checking will be performed.

Array<T> operator()(const IPosition &start, const IPosition &end, const IPosition &inc)

Ret a reference to an array which extends from "start" to end."

Along the ith axis, every inc[i]'th element is chosen.

Array<T> operator()(const IPosition &start, const IPosition &end)

Ret a reference to an array which extends from "start" to end."

MaskedArray<T> operator() (const LogicalArray &mask)

The array is masked by the input LogicalArray. This mask must conform to the array, but it does not need to have the same origin.

Return a ROMaskedArray.

ROMaskedArray<T> operator() (const LogicalArray &mask) const

The array is masked by the input LogicalArray. This mask must conform to the array, but it does not need to have the same origin.

MaskedArray<T> operator() (const ROMaskedLogicalArray &mask)

The array is masked by the input ROMaskedLogicalArray. The mask is effectively the AND of the internal LogicalArray and the internal mask of the ROMaskedLogicalArray. The ROMaskedLogicalArray must conform to the array, but it does not need to have the same origin.

Return a ROMaskedArray.

ROMaskedArray<T> operator() (const ROMaskedLogicalArray &mask) const

The array is masked by the input ROMaskedLogicalArray. The mask is effectively the AND of the internal LogicalArray and the internal mask of the ROMaskedLogicalArray. The ROMaskedLogicalArray must conform to the array, but it does not need to have the same origin.

uInt nrefs() const

The number of references the underlying storage has assigned to it. It is 1 unless there are outstanding references to the storage (e.g., through a slice). Normally you have no need to do this since the arrays handle all of the references for you.

uInt ndim() const

The dimensionality of this array.

uInt nelements() const

How many elements does this array have? Product of all axis lengths.

virtual Bool ok() const

Check to see if the Array is consistent. This is about the same thing as checking for invariants. If AIPS_DEBUG is defined, this is invoked after construction and on entry to most member functions.

Bool conform(const Array<T> &other) const
Bool conform(const ROMaskedArray<T> &other) const

Are the shapes identical? The origins do NOT need to be the same. Binary operations will "line up" the arrays at their origins, so as long as the shapes are the same the Arrays conform.

IPosition origin() const

The IPosition of the first array element. All zero is the default. When zero based, max_index(i) == shape(i) - 1.

IPosition shape() const

The length of each axis.

IPosition end() const

A convenience function: end(i) = origin(i) + shape(i) - 1; i.e. this is the IPosition of the last element of the Array.

const T *getStorage(Bool &deleteIt) const

Generally use of this should be shunned, except to use a FORTRAN routine or something similar. Because you can't know the state of the underlying data layout (in particular, if there are increments) sometimes the pointer returned will be to a copy, but often this won't be necessary. A boolean is returned which tells you if this is a copy (and hence the storage must be deleted). Note that if you don't do anything unusual, getStorage followed by putStorage will do the deletion for you (if required). e.g.:

    Array<Int> a(shape); ...
    Bool deleteIt; Int *storage = a.getStorage(deleteIt);
    foo(storage, a.nelements()); a.puStorage(storage, deleteIt);
    // or a.freeStorage(storage, deleteIt) if a is const.
    
NB: However, if you only use getStorage, you will have to delete the pointer yourself using freeStorage().

It would probably be useful to have corresponding "copyin" "copyout" functions that used a user supplied buffer. Note that deleteIt is set in this function.

T *getStorage(Bool &deleteIt)

Generally use of this should be shunned, except to use a FORTRAN routine or something similar. Because you can't know the state of the underlying data layout (in particular, if there are increments) sometimes the pointer returned will be to a copy, but often this won't be necessary. A boolean is returned which tells you if this is a copy (and hence the storage must be deleted). Note that if you don't do anything unusual, getStorage followed by putStorage will do the deletion for you (if required). e.g.:

    Array<Int> a(shape); ...
    Bool deleteIt; Int *storage = a.getStorage(deleteIt);
    foo(storage, a.nelements()); a.puStorage(storage, deleteIt);
    // or a.freeStorage(storage, deleteIt) if a is const.
    
NB: However, if you only use getStorage, you will have to delete the pointer yourself using freeStorage().

It would probably be useful to have corresponding "copyin" "copyout" functions that used a user supplied buffer. Note that deleteIt is set in this function.

void putStorage(T *&storage, Bool deleteAndCopy)

putStorage() is normally called after a call to getStorage() (cf). The "storage" pointer is set to zero.

void freeStorage(const T *&storage, Bool deleteIt) const

If deleteIt is set, delete "storage". Normally freeStorage calls will follow calls to getStorage. The reason the pointer is "const" is because only const pointers are released from const arrays. The "storage" pointer is set to zero.

static uInt arrayVersion()

Array version for major change (used by ArrayIO). enum did not work properly with cfront 3.0.1), so replaced by a static inline function. Users won't normally use this.

rtti_dcl_mbrf_p1(Array<T>, Cleanup)

Macro to define the typeinfo member functions.

Array(uInt, const Block<Int> &)
void validateConformance(const Array<T> &) const
void validateIndex(const IPosition &) const

Various helper functions that should be deleted.

void copyVec(uInt n, T *to, uInt toStride, const T *from, uInt fromStride) const

A helper function to copy one vector to another, both of which might have strides. This is probably useful enough that it should be made available more publicly?

void Array<T>::copyScalar(uInt n, T *to, uInt toStride, T value) const

Copy a scalar value into a carray with a given stride.


Copyright © 1995 Associated Universities Inc., Washington, D.C.