Miscellaneous methods and definitions

Making contiguous arrays

In general, allocating arrays requires one to know the size of all dimensions of the array, whereas de-allocating requires you to know the dimensions all but the inner dimension size.

The canonical way to allocate and deallocate arrays (for e.g. floats) in C++ is as follows:

float* array_1d = new float[number_of_elements];
delete[] array_1d;

This is totally fine for 1 dimensional arrays. It can also be easily extended to higher dimensional arrays. In 2D, the implementation would look something like this:

float** array_2d = new float*[number_of_rows];
for(int i = 0; i < number_of_rows; ++i)
    array_2d[i] = new float[number_of_columns];

Again, this is fine for 2 dimensional arrays. But not totally. What happens here is that basically we run the code from the previous code block for 1D arrays as many times as we have rows, so we allocate number_of_rows 1D arrays. This does however not guarantee that all these arrays are contiguous in memory. They can be all over the place. This might impact performance of the finite difference code.

What we want, is that the entire 2D (or higher D) array is in one piece in the computers memory. To create space for the entire array, one can simply allocate a 1D array with as many elements as the higher order dimensions array would be. This helps, because 1D arrays are guaranteed to be contiguous.

float* fake_2d_which_is_actually_1d = new float[number_of_rows * number_of_columns];
delete[] fake_2d_which_is_actually_1d;

However, this breaks the nice interface we would normally have for higher order arrays:

fake_2d_which_is_actually_1d[0][5]; // Does not work
fake_2d_which_is_actually_1d[0 + 5 * number_of_rows]; // Does actually work

Here we see the need for linear indexing when we create arrays like this. To re-enable the ‘nice’ behaviour of 2D arrays, one can still create a 2D array of the required type, and do the pointer arithmetic manually, s.t. it refers to the locations in the large 1D array. That is what the allocate_array() functions do. They are implemented for 1 through 4 dimensional arrays.

Example usage

Below is an example on how to create, access and destroy a 1D array of float. Any other type can be used, since the definitions are templated.

float *t;
int nt = 100;
allocate_array(t, nt);

t[50] = 3.0;

deallocate_array(t); // Remember to deallocate, otherwise you get memory leaks.

The same thing, but in 4 dimensions is given below.

float ****t;
int nt = 100;
int nx = 100;
int ny = 100;
int ns = 100;
allocate_array(t, nt, nx, ny, ns);

t[50][25][74][1] = 3.0;

deallocate_array(t); // Remember to deallocate, otherwise you get memory leaks.

Allocation and deallocation functions

template<class T>
void allocate_array(T *&pointer, int dim1)
template<class T>
void deallocate_array(T *&pointer)
template<class T>
void allocate_array(T **&pointer, int dim1, int dim2)
template<class T>
void deallocate_array(T **&pointer)
template<class T>
void allocate_array(T ***&pointer, int dim1, int dim2, int dim3)
template<class T>
void deallocate_array(T ***&pointer)
template<class T>
void allocate_array(T ****&pointer, int dim1, int dim2, int dim3, int dim4)
template<class T>
void deallocate_array(T ****&pointer)

Parse functions

template<class T>
void parse_string_to_vector(std::basic_string<char> string_to_parse, std::vector<T> *destination_vector)

Function to parse strings containing lists to std::vectors.

Parses any string in the format {a, b, c, d, …} to a vector of int, float, double. Only types that can be cast to floats are supported for now. This is due to the usage of strtof(); no template variant is used as of yet. The input string is allowed to be trailed by a comment leading with a semicolon. Items are appended to the passed vector.

Parameters
  • T – Arbitrary numeric type that has a cast to and from float.

  • string_to_parse

    Input string of the form “{<item1>, <item2>, <item3>,

    … } ; comment “.

  • destination_vector – Pointer to an (not necessarily empty) vector of suitable type.

void parse_string_to_nested_int_vector(std::basic_string<char> string_to_parse, std::vector<std::vector<int>> *destination_vector)

Function to parse strings containing 2d integer lists to std::vector<std::vector<int>>. Sublists do not need to be of the same length.

Parameters
  • string_to_parse – Input string of the form “{{<item1.1>, <item1.2>, < item1.3>, …}, {<item2.1>, <item2.2>, …}, … } ; comment “.

  • destination_vector – Pointer to an (not necessarily empty) std::vector<std::vector<int>>.