![]() |
Object-oriented Scientific Computing Library: Version 0.910
|
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).
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 (,)
.
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:
vec_t
- the generic vector typealloc_t
- a vector allocation typealloc_vec_t
- a vector type which can be allocated with an object of type alloc_t
and can be passed as an argument of type vec_t
.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
vec_t
= ovector_base, alloc_t
= ovector_alloc, and alloc_vec_t
= ovectorvec_t
= uvector_base, alloc_t
= uvector_alloc, and alloc_vec_t
= uvectorvec_t
= double[n]
, alloc_t
= array_alloc<double[n]>
, and alloc_vec_t
= double[n]
vec_t
= double *
, alloc_t
= pointer_alloc<double>
, and alloc_vec_t
= double *
An example demonstrating how this works is given in the Multi-dimensional solver example.
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 . It might seem intuitive to think of a matrix as
A
[ix][iy] where ix
and iy
are the and
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.
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 .
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 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.
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.
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 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 .
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 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.
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.
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.
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;
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.
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.
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;
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
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.
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.
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.
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.
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
ovector_const_view v1; const ovector_view v2; const ovector_const_view v3;
Explicitly,
ovector_const_view
is a view of a const vector, the view may change, but the vector may not.const ovector_const_view
is a const view of a const vector, the view may not point to a different vector and the vector may not change.const ovector_view
is a const view of a normal vector, the view may not change, but the vector can. 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.
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.
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.
Documentation generated with Doxygen. Provided under the GNU Free Documentation License (see License Information).