Object-oriented Scientific Computing Library: Version 0.910
Arrays, Vectors, Matrices and Tensors

Introduction

The O2scl library uses a standard nomenclature to distinguish a couple different concepts. The word "array" is always used to refer to C-style arrays, i.e. double[]. If there are two dimensions in the array, it is a "two-dimensional array", i.e. double[][] . The word "vector" is reserved generic objects with array-like semantics, i.e. any type of object or class which can be treated similar to an array in that it has an function operator[] which gives array-like indexing. Thus arrays are vectors, but not all vectors are arrays. There are a couple specific vector types defined in O2scl like ovector and uvector . The STL vector std::vector<double> is also a vector type in this language. The word "matrix" is reserved for the a generic object which has matrix-like semantics and can be accessed using either operator() or two successive applications of operator[] (or sometimes both). The O2scl objects omatrix and umatrix are matrix objects, as is a C-style two-dimensional array, double[][] . The header files are named in this spirit also: functions and classes which operate on generic vector types are in vector.h and functions and classes which work only with C-style arrays are in array.h . The word "tensor" is used for a generic object which has rank n and then has n associated indices. A vector is just a tensor of rank 1 and a matrix is just a tensor of rank 2.

Most of the classes in O2scl which use vectors and/or matrices are designed so that they can be used with any appropriately-defined vector or matrix types. This is a major part of the design goals for O2scl and most of the classes are compatible with matrix and vector objects from GSL, TNT, MV++, uBlas, and Blitz++.

The first index of a matrix type is defined always to be the index associated with "rows" and the second is associated with "columns". The O2scl matrix output functions respect this notation as well, so that all of the elements of the first row is sent to the screen, then all of the elements in the second row, and so on. With this in mind, one can make the distinction between "row-major" and "column-major" matrix storage. C-style two-dimensional arrays are "row-major" in that the elements of the first row occupy the first locations in memory as opposed "column-major" where the first column occupies the first locations in memory. It is important to note that the majority of the classes in O2scl do not care about the details of the underlying memory structure, so long as two successive applications of operator[] (or in some cases operator(,) ) works properly. The storage format used by omatrix and umatrix is row-major, and there are no column-major matrix classes in O2scl (yet).

Matrix indexing with [][] vs. (,)

While vector indexing with operator[] is very compatible with almost any vector type, matrix indexing is a bit more difficult. There are two options: assume matrix objects provide an operator[] method which can be applied twice, i.e. m[i][j], or assume that matrix elements should be referred to with m(i,j). Most of the O2scl classes use the former approach so that they are also compatible with two-dimensional arrays. However, there are sometimes good reasons to want to use operator() for matrix-intensive operations from linear algebra. For this reason, some of the functions given in the linalg directory are specified in two forms: the first default form which assumes [][], and the second form with the same name, but in a namespace which has a suffix _paren and assumes matrix types use (,).

Vector types for template classes

Many of the classes and functions are designed to be generic with regard to vector types, permitting the user to use any type which has a suitably defined operator[] method (or sometimes operator() method ). In order to do this, some classes also require the ability to allocate new vectors, and for this reason they have three different templated vector types:

The typical requirement is that one must be able to write something like

    alloc_t ao;
    alloc_vec_t vec;
    ao.allocate(vec,5);

to allocate a new vector vec of length 5. In addition, to provide exception safety, either the objects of type alloc_vec_t must either free its memory in its destructor, or it must be taken care of in the destructor of the alloc_t object. The native O2scl vector classes provide this kind of functionality by default, so some typical choices for these template types are

An example demonstrating how this works is given in the Multi-dimensional solver example.

Rows and columns vs. x and y

Sometimes its useful to think about the rows and columns in a matrix as referring to a elements of a grid, and the matrix indices refer to points in a grid in $ (x,y) $. It might seem intuitive to think of a matrix as A[ix][iy] where ix and iy are the $ x $ and $ y $ indices because the ordering of the indices is alphabetical. However, it is useful to note that because functions like matrix_out print the first "row" first rather than the first column, a matrix constructed as A[ix][iy] will be printed out with x on the "vertical axis" and y on the "horizontal axis", so it is sometimes useful to store data in the form A[iy][ix] (for example, in the two dimensional interpolation class, twod_intp). In any case, all classes which take matrix data as input will document how the matrix ought to be arranged.

Generic vector functions

There are a couple functions which operate on generic vectors of any type in vector.h . They perform sorting, summing, rotating, copying, and computations of minima and maxima. For more statistically-oriented operations, see also vec_stats.h .

Native matrix and vector types

The rest of this section in the User's guide is dedicated to describing the O2scl implementations of vector, matrix, and tensor types.

Vectors and matrices are designed using the templates ovector_tlate and omatrix_tlate, which are compatible with gsl_vector and gsl_matrix. Vectors and matrices with unit stride are provided in uvector_tlate and umatrix_tlate. The most commonly used double-precision versions of these template classes are ovector, omatrix, uvector and umatrix, and their associated "views" (analogous to GSL vector and matrix views) which are named with a _view suffix. The classes ovector_tlate and omatrix_tlate offer the syntactic simplicity of array-like indexing, which is easy to apply to vectors and matrices created with GSL.

The following sections primarily discuss the operation objects of type ovector. The generalizations to objects of type uvector, omatrix, and the complex vector and matrix objects ovector_cx, omatrix_cx, uvector_cx, and umatrix_cx are straightforward.

Vector and matrix views

Vector and matrix views are provided as parents of the vector and matrix classes which do not have methods for memory allocation. As in GSL, it is simple to "view" normal C-style arrays or pointer arrays (see ovector_array), parts of vectors (ovector_subvector), and rows and columns of matrices (omatrix_row and omatrix_col). Several operations are defined, including addition, subtraction, and dot products.

Vector and matrix typedefs

Several typedefs are used to give smaller names to often used templates. Vectors and matrices of double-precision numbers all begin with the prefixes ovector and omatrix. Integer versions begin with the prefixes ovector_int and omatrix_int. Complex versions have an additional "_cx", e.g. ovector_cx. See ovector_tlate.h, ovector_cx_tlate.h, omatrix_tlate.h, and omatrix_cx_tlate.h.

Unit-stride vectors

The uvector_tlate objects are naturally somewhat faster albeit less flexible than their finite-stride counterparts. Conversion to GSL vectors and matrices is not trivial for uvector_tlate objects, but demands copying the entire vector. Vector views, operators, and the nomenclature is similar to that of ovector.

Memory allocation

Memory for vectors can be allocated using ovector::allocate() and ovector::free(). Allocation can also be performed by the constructor, and the destructor automatically calls free() if necessary. In contrast to gsl_vector_alloc(), ovector::allocate() will call ovector::free(), if necessary, to free previously allocated space. Allocating memory does not clear the recently allocated memory to zero. You can use ovector::set_all() with a zero argument to clear a vector (and similarly for a matrix).

If the memory allocation fails, either in the constructor or in allocate(), then the error handler will be called, partially allocated memory will be freed, and the size will be reset to zero.

Although memory allocation in O2scl vectors is very similar to that in GSL, the user must not mix allocation and deallocation between GSL and O2scl .

Get and set

Vectors and matrices can be modified using ovector::get() and ovector::set() methods analogous to gsl_vector_get() and gsl_vector_set(), or they can be modified through ovector::operator[] (or ovector::operator() ), e.g.

    ovector a(4);
    a.set(0,2.0);
    a.set(1,3.0);
    a[2]=4.0;
    a[3]=2.0*a[1];

If you want to set all of the values in an ovector or an omatrix at the same time, then use ovector::set_all() or omatrix::set_all().

Range checking

Range checking is performed depending on whether or not O2SCL_NO_RANGE_CHECK is defined. It can be defined in the arguments to ./configure upon installation to turn off range checking. Note that this is completely separate from the GSL range checking mechanism, so range checking may be on in O2scl even if it has been turned off in GSL. Range checking is used primarily in the vector, matrix, and tensor get() and set() methods.

To see if range checking was turned on during installation (without calling the error handler), use lib_settings_class::range_check().

Note that range checking in O2scl code is most often present in header files, rather than in source code. This means that range checking can be turned on or off in user-defined functions separately from whether or not it was used in the library classes and functions.

Shallow and deep copy

Copying O2scl vectors using constructors or the = operator is performed according to what kind of object is on the left-hand side (LHS) of the equals sign. If the LHS is a view, then a shallow copy is performed, and if the LHS is a ovector, then a deep copy is performed. If an attempt is made to perform a deep copy onto a vector that has already been allocated, then that previously allocated memory is automatically freed. The user must be careful to ensure that information is not lost this way, even though no memory leak will occur.

For generic deep vector and matrix copying, you can use the template functions vector_copy(), matrix_copy(). These would allow you, for example, to copy an ovector to a std::vector<double> object. These functions do not do any memory allocation so that must be handled beforehand by the user.

Vector and matrix arithmetic

Several operators are available as member functions of the corresponding template:

Vector_view unary operators:

Matrix_view unary operators:

Binary operators like addition, subtraction, and matrix multiplication are also defined for ovector, uvector, and related objects. The generic template for a binary operator, e.g.

    template<class vec_t> vec_t &operator+(vec_t &v1, vec_t &v2);

is difficult because the compiler has no way of distinguishing vector and non-vector classes. At the moment, this is solved by creating a define macro for the binary operators. In addition to the predefined operators for native classes, the user may also define binary operators for other classes using the same macros. For example,

    O2SCL_OP_VEC_VEC_ADD(o2scl::ovector,std::vector<double>,
    std::vector<double>)

would provide an addition operator for ovector and vectors from the Standard Template Library. A full list of the operators which are defined by default and the associated macros are detailed in the documentation for vec_arith.h.

The GSL BLAS routines can also be used directly with ovector and omatrix objects.

Note that some of these arithmetic operations succeed even with non-matching vector and matrix sizes. For example, adding a 3x3 matrix to a 4x4 matrix will result in a 3x3 matrix and the 7 outer elements of the 4x4 matrix are ignored.

Converting to and from GSL forms

Because of the way ovector is constructed, you may use type conversion to convert to and from objects of type gsl_vector.

    ovector a(2);
    a[0]=1.0;
    a[1]=2.0;
    gsl_vector *g=(gsl_vector *)(&a);
    cout << gsl_vector_get(g,0) << " " << gsl_vector_get(g,1) << endl;

Or,

    gsl_vector *g=gsl_vector_alloc(2);
    gsl_vector_set(0,1.0);
    gsl_vector_set(1,2.0);
    ovector &a=(ovector &)(*g);
    cout << a[0] << " " << a[1] << endl;

This sort of type-casting is discouraged among unrelated classes, but is permissible here because ovector_tlate is a descendant of gsl_vector. In particular, this will not generate "type-punning" warnings in later gcc versions. If this bothers your sensibilities, however, then you can use the following approach:

    ovector a(2);
    gsl_vector *g=a.get_gsl_vector();

The ease of converting between these two kind of objects makes it easy to use gsl functions on objects of type ovector, i.e.

    ovector a(2);
    a[0]=2.0;
    a[1]=1.0;
    gsl_vector_sort((gsl_vector *)(&a));
    cout << a[0] << " " << a[1] << endl;

Converting from STL form

To "view" a std::vector<double>, you can use ovector_array

    std::vector<double> d;
    d.push_back(1.0);
    d.push_back(3.0);
    ovector_array aa(d.size,&(d[0]));
    cout << aa[0] << " " << aa[1] << endl;

However, you should note that if the memory for the std::vector is reallocated (for example because of a call to push_back()), then a previously created ovector_view will be incorrect.

push_back() and pop() methods

These two functions give a behavior similar to the corresponding methods for std::vector<>. This will work in O2scl classes, but may not be compatible with all of the GSL functions. This will break if the address of a ovector_tlate is given to a GSL function which accesses the block->size parameter instead of the size parameter of a gsl_vector. Please contact the author of O2scl if you find a GSL function with this behavior.

Vector and matrix views

Views are slightly different than in GSL in that they are now implemented as parent classes. Effectively, this means that vector views are just the same as normal vectors, except that they have no memory allocation functions (because the memory is presumably owned by a different object or scope). The code

    double x[2]={1.0,2.0};
    gsl_vector_view_array v(2,x);
    gsl_vector *g=&(v.vector);
    gsl_vector_set(g,0,3.0);
    cout << gsl_vector_get(g,0) << " " << gsl_vector_get(g,1) << endl;

can be replaced by

    double x[2]={1.0,2.0};
    ovector_array a(2,x);
    a[0]=3.0;
    cout << a << endl;

Passing ovector parameters

It is often best to pass an ovector as a const reference to an ovector_base, i.e.

    void function(const ovector_base &a);

If the function may change the values in the ovector, then just leave out const

    void function(ovector_base &a);

This way, you ensure that the function is not allowed to modify the memory for the vector argument.

If you intend for a function (rather than the user) to handle the memory allocation, then some care is necessary. The following code

class my_class {
  int afunction(ovector &a) {
    a.allocate(1);
    // do something with a
    return 0;
  }
};

is confusing because the user may have already allocated memory for a. To avoid this, you may want to ensure that the user sends an empty vector. For example,

class my_class {
  int afunction(ovector &a) {
    if (a.get_size()>0 && a.is_owner()==true) {
      O2SCL_ERR("Unallocated vector not sent to afunction().",1);
      return 1;
    } else {
      a.allocate(1);
      // do something with a
      return 0;
    }
  }
};

In lieu of this, it is often preferable to use a local variable for the storage and offer a get() function,

class my_class {
protected:
  ovector a;
public:
  int afunction() {
    a.allocate(1);
    // do something with a
    return 0;
  }
  int get_result(const ovector_view &av) { av=a; return 0; }
};

The O2scl classes run into this situation quite frequently, but the vector type is specified through a template

template<class vec_t> class my_class {
protected:
  vec_t a;
public:
  int afunction(vec_t &a) {
    // do something with a
    return 0;
  }
};

where the drawback is that the user must perform the allocation.

Vectors and operator=()

An "operator=(value)" method for setting all vector elements to the same value is not included because it leads to confusion between, ovector_tlate::operator=(const data_t &val) and ovector_tlate::ovector_tlate(size_t val) For example, after implementing operator=() and executing the following

    ovector_int o1=2;
    ovector_int o2;
    o2=2;

o1 will be a vector of size two, and o2 will be an empty vector!

To set all of the vector elements to the same value, use ovector_tlate::set_all(). Because of the existence of constructors like ovector_tlate::ovector_tlate(size_t val), the following code

    ovector_int o1=2;

still compiles, and is equivalent to

    ovector_int o1(2);

while the code

    ovector_int o1;
    o1=2;

will not compile. As a matter of style, ovector_int o1(2); is preferable to ovector_int o1=2; to avoid confusion.

Matrix structure

The matrices from omatrix_tlate are structured in exactly the same way as in GSL. For a matrix with 2 rows, 4 colums, and a "tda" or "trailing dimension" of 7, the memory for the matrix is structured in the following way:

    00 01 02 03 XX XX XX
    10 11 12 13 XX XX XX
    

where XX indicates portions of memory that are unreferenced. The tda can be accessed through, for example, the method omatrix_view_tlate::tda(). The get(size_t,size_t) methods always take the row index as the first argument and the column index as the second argument. The matrices from umatrix_tlate have a trailing dimension which is always equal to the number of columns.

Reversing the order of vectors

You can get a reversed vector view from ovector_reverse_tlate, or uvector_reverse_tlate. For these classes, operator[] and related methods are redefined to perform the reversal. If you want to make many calls to these indexing methods for a reversed vector, then simply copying the vector to a reversed version may be faster.

Const-correctness with vectors

Vector objects, like ovector, can be const in the usual way. However because vector views, like ovector_view are like pointers to vectors, there are two ways in which they can be const. The view can point to a const vector, or the view can be a 'const view' which is fixed to point only to one vector (whether or not the vector pointed to happens to be const). This is exactly analogous to the distinction between const double *p, double * const p, and const double * const p. The first is a non-const pointer to const data, the second is a const pointer to non-const data, and the third is a const pointer to const data. The equivalent expressions in O2scl are

Explicitly,

A reference of type ovector_base is often used as a function argument, and can hold either a ovector or a ovector_view. The important rule to remember with ovector_base is that, a const reference, i.e.

    const ovector_base &v;

is always a const reference to data which cannot be changed. A normal ovector_base reference does not refer to const data.

Vector and matrix output

Both the ovector_view_tlate and omatrix_view_tlate classes have an << operator defined for each class. For writing generic vectors to a stream, you can use vector_out() which is defined in vector.h . Pretty matrix output is performed by global template functions matrix_out(), matrix_cx_out_paren(), and matrix_out_paren() which are defined in columnify.h since they internally use a columnify object to format the output.

Tensors

Some preliminary support is provided for tensors of arbitrary rank and size in the class tensor. Classes tensor1, tensor2, tensor3, and tensor4 are rank-specific versions for 1-, 2-, 3- and 4-rank tensors. For n-dimsional data defined on a grid, tensor_grid provides a space to define a hyper-cubic grid in addition to the the tensor data. This class tensor_grid also provides simple n-dimensional interpolation of the data defined on the specified grid.

 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines

Documentation generated with Doxygen. Provided under the GNU Free Documentation License (see License Information).

Get Object-oriented Scientific Computing
Lib at SourceForge.net. Fast, secure and Free Open Source software
downloads.