Array.h

Classes

StorageInitPolicy -- A global enum used by some Array constructors. (full description)
Array -- A templated N-D Array class with zero origin (full description)
Global Functions -- General global functions for Arrays. (full description)

enum StorageInitPolicy

COPY
COPY is used when an internal copy of the storage is to be made. The array is NOT responsible for deleting the external storage.
TAKE_OVER
TAKE_OVER is used to indicate that the Array should just use the external storage (i.e., no copy is made). The Array class is now responsible for deleting the storage (hence it must have come from a call to new[]).
SHARE
Share means that the Array will just use the pointer (no copy), however the Array will NOT delete it upon destruction.

Description

Synopsis

StorageInitPolicy is used in functions where an array is formed from a shape and an ordinary pointer. This enum should be in Array but that causes gcc to be unhappy.

template<class T> class Array

Interface

Public Members
Array()
explicit Array(const IPosition &shape)
Array(const IPosition &shape, const T &initialValue)
Array(const Array<T> &other)
Array(const IPosition &shape, T *storage, StorageInitPolicy policy = COPY)
Array(const IPosition &shape, const T *storage)
virtual ~Array()
virtual void assign (const Array<T>& other)
void set(const T &value)
void apply(T (*function)(T))
void apply(T (*function)(const T &))
void apply(const Functional<T,T> &function)
virtual void reference(Array<T> &other)
virtual Array<T> &operator=(const Array<T> &other)
Array<T> &operator=(const T &value)
Array<T> &operator= (const MaskedArray<T> &marray)
Array<T> copy() const
void unique()
template <class U> void tovector(vector<T, U> &out) const
Array<T> reform(const IPosition &shape) const
Array<T> nonDegenerate(uInt startingAxis=0, Bool throwIfError=True)
const Array<T> nonDegenerate(uInt startingAxis=0, Bool throwIfError=True) const
void nonDegenerate(Array<T> &other, uInt startingAxis=0, Bool throwIfError=True)
Array<T> nonDegenerate(const IPosition& ignoreAxes)
const Array<T> nonDegenerate(const IPosition& ignoreAxes) const
void nonDegenerate(Array<T> &other, const IPosition &ignoreAxes)
Array<T> addDegenerate(uInt numAxes)
const Array<T> addDegenerate(uInt numAxes) const
virtual void resize()
virtual void resize(const IPosition &newShape)
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)
Array<T> operator()(const Slicer&)
MaskedArray<T> operator() (const LogicalArray &mask) const
MaskedArray<T> operator() (const LogicalArray &mask)
MaskedArray<T> operator() (const MaskedLogicalArray &mask) const
MaskedArray<T> operator() (const MaskedLogicalArray &mask)
uInt nrefs() const
uInt ndim() const
uInt nelements() const
virtual Bool ok() const
Bool conform (const Array<T> &other) const
Bool conform (const MaskedArray<T> &other) const
const IPosition &shape() const
IPosition endPosition() const
Bool contiguousStorage() const
T* data()
T* const data() const
const IPosition& steps() 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
virtual void takeStorage(const IPosition &shape, T *storage, StorageInitPolicy policy = COPY)
virtual void takeStorage(const IPosition &shape, const T *storage)
static uInt arrayVersion()
iterator begin()
const_iterator begin() const
const T* const end() const
contiter cbegin()
const_contiter cbegin() const
const contiter cend()
const const_contiter cend() const
Protected Members
virtual void doNonDegenerate(Array<T> &other, const IPosition &ignoreAxes)
void makeSteps()
void validateConformance(const Array<T> &) const
void validateIndex(const IPosition &) const
Bool isStorageContiguous() const
void setEndIter()
See Also
Array general global functions -- General global functions for Arrays.
Array IO -- Input/output operators for Arrays.
Array binary IO -- Simple binary input/output for Arrays.
ArrayIterator -- Iterate an Array cursor through another Array.
ReadOnlyArrayIterator -- Iterate a const Array cursor through a const Array.
Array logical operations -- Logical operations for Arrays.
Array mathematical operations -- Mathematical operations for Arrays.
LogicalArray -- Logical valued Arrays.
LogicalArray forwards -- Forward declarations for LogicalArrays.
LogicalCube -- Logical valued Cubes.
LogicalMatrix -- Logical valued Matrices.
LogicalVector -- Logical valued Vectors.
MaskedArray logical operations -- Logical operations for MaskedArrays, and between MaskedArrays and Arrays.
MaskedArray mathematical operations -- Mathematical operations for MaskedArrays, and between MaskedArrays and Arrays.
MaskedLogicalArray -- Masked LogicalArrays.
MaskedLogicalArray forwards -- Forward declarations for MaskedLogicalArrays.
MaskedArray general global functions -- General global functions for MaskedArrays, and between MaskedArrays and Arrays.

Description

Array is a templated, N-dimensional, Array class. The origin is zero, 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
    Array<Int> ai2(axisLengths);        // The first element is at index 0
    IPosition zero(5); zero = 0;        // [0,0,0,0,0]
    //...
    
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.

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.

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

Integrate into the Lattice hierarchy

  • Factor out the common functions (shape etc) into a type-independent base class.

    Member Description

    Array()

    Result has dimensionality of zero, and nelements is zero.

    explicit 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 T &initialValue)

    Create an array of the given shape and initialize it with the initial value.

    Array(const Array<T> &other)

    After construction, this and other reference the same storage.

    Array(const IPosition &shape, T *storage, StorageInitPolicy policy = COPY)

    Create an Array of a given shape from a pointer.

    Array(const IPosition &shape, const T *storage)

    Create an Array of a given shape from a pointer. Because the pointer is const, a copy is always made.

    virtual ~Array()

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

    virtual void assign (const Array<T>& other)

    Assign the other array to this array. If the shapes mismatch, this array is resized.

    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.

    void apply(const Functional<T,T> &function)

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

    This version applies a functional.

    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.

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

        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
        
    Note that the assign function can be used to assign a non-conforming array.

    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 MaskedArray<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();
    

    template <class U> void tovector(vector<T, U> &out) const

    Create an STL vector from an Array. The created vector is a linear representation of the Array memory. See Vector for details of the operation and its reverse (i.e. creating a Vector from a vector), and for details of definition and instantiation.

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

    It is occasionally useful to have an array which access the same storage appear to have a different shape. For example, turning an N-dimensional array into a Vector.
    When the array data are contiguous, the array can be reshaped to any form as long as the number of elements stays the same. When not contiguous, it is only possible to remove or add axes with length 1.

        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(uInt startingAxis=0, Bool throwIfError=True)
    const Array<T> nonDegenerate(uInt startingAxis=0, Bool throwIfError=True) const
    void nonDegenerate(Array<T> &other, uInt startingAxis=0, Bool throwIfError=True)
    Array<T> nonDegenerate(const IPosition& ignoreAxes)
    const Array<T> nonDegenerate(const IPosition& ignoreAxes) const
    void nonDegenerate(Array<T> &other, const IPosition &ignoreAxes)

    These member functions remove degenerate (ie. length==1) axes from Arrays. Only axes greater than startingAxis are considered (normally one wants to remove trailing axes. The first two of these function return an Array reference with axes removed. The last of these functions returns a reference to the 'other' array with degenerated axes removed.
    Unless throwIfError is False, an exception will be thrown if startingAxis exceeds the array's dimensionality.
    The functions with argument ignoreAxes do not consider the axes given in that argument.

    Caution When the two functions returning void throw are invoked on a derived object (e.g. Matrix), an exception is thrown if removing the degenerate axes from other does not result in a correct number of axes.

    Array<T> addDegenerate(uInt numAxes)
    const Array<T> addDegenerate(uInt numAxes) const

    These member functions return an Array reference with the specified number of extra axes, all of length one, appended to the end of the Array. Note that the reform function can also be used to add extra axes.

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

    Make this array a different shape. Presently the old values are not copied over to the new array. Resize without argument is equal to resize(IPosition()).

    T &operator()(const IPosition &)
    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.

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

    Get 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)

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

    Array<T> operator()(const Slicer&)

    Get a reference to an array using a Slicer.

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

    The array is masked by the input LogicalArray. This mask must conform to the array.

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

    The array is masked by the input MaskedLogicalArray. The mask is effectively the AND of the internal LogicalArray and the internal mask of the MaskedLogicalArray. The MaskedLogicalArray must conform to the array.

    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 MaskedArray<T> &other) const

    Are the shapes identical?

    const IPosition &shape() const

    The length of each axis.

    IPosition endPosition() const

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

    Bool contiguousStorage() const

    Are the array data contiguous? If they are not contiguous, getStorage (see below) needs to make a copy.

    T* data()
    T* const data() const

    Get a pointer to the beginning of the array. Note that the array may not be contiguous.

    const IPosition& steps() const

    Return steps to be made if stepping one element in a dimension. This is the 'physical' step, thus it also works correctly for non-contiguous arrays. E.g. data() + steps(0) gives the second element of the first axis.

    T *getStorage(Bool &deleteIt)
    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 freeStorage or 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.

    virtual void takeStorage(const IPosition &shape, const T *storage)

    Replace the data values with those in the pointer storage. The results are undefined is storage does not point at nelements() or more data elements. After takeStorage() is called, unique() is True.

    Since the pointer is const, a copy is always taken.

    virtual void takeStorage(const IPosition &shape, T *storage, StorageInitPolicy policy = COPY)

    Replace the data values with those in the pointer storage. The results are undefined is storage does not point at nelements() or more data elements. After takeStorage() is called, unique() is True.

    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.

    iterator begin()
    const_iterator begin() const
    const T* const end() const

    Define the STL-style iterators. It makes it possible to iterate through all data elements of an array.

      Array<Int> arr(shape);
      for (Array<Int>::iterator iter=arr.begin(); iter!=arr.end(); iter++) {
        *iter += 1;
      }
    

    Get the begin iterator object for any array.

    contiter cbegin()
    const_contiter cbegin() const
    const contiter cend()
    const const_contiter cend() const

    Define the STL-style iterators. It makes it possible to iterate through all data elements of an array.

      Array<Int> arr(shape);
      for (Array<Int>::iterator iter=arr.begin(); iter!=arr.end(); iter++) {
        *iter += 1;
      }
    

    virtual void doNonDegenerate(Array<T> &other, const IPosition &ignoreAxes)

    Remove the degenerate axes from the Array object. This is the implementation of the nonDegenerate functions. It has a different name to be able to make it virtual without having the "hide virtual function" message when compiling derived classes.

    void makeSteps()

    Make the indexing step sizes.

    void validateConformance(const Array<T> &) const
    void validateIndex(const IPosition &) const
    Bool isStorageContiguous() const

    Various helper functions.

    void setEndIter()

    Set the end iterator.


    General global functions for Arrays. (source)

    Interface

    uInt ArrayVolume(uInt Ndim, const Int *Shape)
    uInt ArrayIndexOffset(uInt Ndim, const Int *Shape, const Int *Origin, const Int *Inc, const IPosition &Index)
    uInt ArrayIndexOffset(uInt Ndim, const Int *Shape, const Int *Inc, const IPosition &Index)
    Bool conform2 (const Array<T> &left, const Array<U> &right)

    Description

    Review Status

    Programs:
    Tests:
    • tArray

    Prerequisite

    Synopsis

    These are generally useful global functions which operate on all Arrays.

    Member Description

    uInt ArrayVolume(uInt Ndim, const Int *Shape)

    What is the volume of an N-dimensional array. Shape[0]*Shape[1]*...*Shape[N-1]. An Array helper function.

    uInt ArrayIndexOffset(uInt Ndim, const Int *Shape, const Int *Origin, const Int *Inc, const IPosition &Index)
    uInt ArrayIndexOffset(uInt Ndim, const Int *Shape, const Int *Inc, const IPosition &Index)

    What is the linear index into an "Ndim" dimensional array of the given "Shape", "Origin", and "Increment" for a given IPosition Index. An Array helper function.

    Bool conform2 (const Array<T> &left, const Array<U> &right)

    Test conformance for two arrays of different types. Are the shapes identical?