Alexandria 2.31.0
SDC-CH common library for the Euclid project
Loading...
Searching...
No Matches
GridContainer module

The GridContainer module provides a highly configurable, multi-dimensional grid, which provides extended functionality related with the information about its axes (in an efficient way). Note that this module is NOT a mathematical module. The provided GridContainer is closer to a collection structure than a mathematical grid and no mathematical functionality is provided.

This page contains multiple code examples. To improve readability, the following two lines are assumed for each example:

using namespace Euclid::GridContainer;
using namespace std;
STL namespace.

Note that these lines are used to make the example code more readable and they will introduce all the symbols of the GridContainer and std namespaces in the global namespace, so they must be sparingly used.

At the bottom of this page can be found the entire code of the examples as a single file.

GridContainer data model

The GridContainer data model consists of three concepts, the GridAxis class, which contains information of a grid axis, the GridCellManager, which handles the grid cell values and the GridContainer class, which combines everything together. The following sections describe these concepts and provide examples of how to use them.

GridAxis class

Each axis of a GridContainer can be seen as a collection of (zero based) indexed knots (knot_0, knot_1, knot_2 etc). The index of each knot represents the coordinate of the grid cells and the value of the knot represents the world value of the axis. For example, an axis representing the wavelength might have the following knots:

Knot Index Knot Value
0 3000
1 3500
2 4000
3 4500
4 5000

Using the above axis, to access the grid cells which have wavelength equal with 4000, the wavelength axis coordinate must be 2.

Creating GridAxis objects

Constructing a GridAxis object which represents the above axis can be done using the following code:

vector<int> knot_values {3000, 3500, 4000, 4500, 5000};
GridAxis<int> wavelength_axis {"Wavelength", move(knot_values)};
Provides information related with an axis of a GridContainer.
Definition GridAxis.h:49

In the code above one can make the following observations:

  • To support axes with knots of different types, the GridAxis class gets this type as a template parameter. In the example above the type chosen is int (GridAxis<int>). To use knots of string type, the definitions should be GridAxis<string>. Note that user defined types are also supported.
  • The first parameter of the GridAxis constructor is a string which describes the axis name. The axes of a GridContainer must have unique names.
  • The values of the knots must be given during construction. This is because the GridAxis class is immutable and further modifications are not allowed.
  • The number of knots is not given explicitly, but it is inferred from the vector containing the knot values.

Using GridAxis objects

Retrieving the axis information from an GridAxis object is trivial. The knot values can be accessed either by using an iterator or directly, by using the brackets ([]) operator, as demonstrated in the following example:

// Getting the axis name
cout << "Axis name : " << wavelength_axis.name() << "\n";
// Getting the number of knots
cout << "Number of knots : " << wavelength_axis.size() << "\n";
// Accessing a knot value directly using its (zero based) index
cout << "Second knot value : " << wavelength_axis[1] << "\n";
// Accessing the knots using the iterator
cout << "All knot values : ( ";
for (auto& knot_value : wavelength_axis) {
cout << knot_value << " ";
}
cout << ")\n";

Output:

Axis name : Wavelength
Number of knots : 5
Second knot value : 3500
All knot values : ( 3000 3500 4000 4500 5000 )

GridCellManager interface

The GridContainer class does not implement in itself a data structure to hold the array of grid cell values. Instead, it delegates this task to a GridCellManager**, the type of which is provided as a template parameter to the GridContainer class (with signatures such as GridContainer<GridCellManager, ...>, where the additional template parameters are related to the grid axes, see GridContainer class below).

Existing container classes can be used as the GridCellManager of a GridContainer, with the simplest example being the std::vector. For example, a GridContainer defined as GridContainer<vector<int>,...> will use internally a vector to hold and manage the grid cell integer values.

The usage of custom GridCellManagers requires a better understanding of the GridContainer module in total, so it is postponed for later in this document (section Using a custom GridCellContainer).

GridContainer class

As explained earlier, the GridContainer class is combining together the concepts of the GridCellManager (which keeps the GridContainer data) and the information of the corresponding axes (described as GridAxis objects).

Creating GridContainer objects

Each GridContainer object has a fixed parameter space, meaning that, once created, its axes cannot be modified. For this reason, the GridAxis objects representing the GridContainer axes must be created beforehand. The following code demonstrates how to create a three dimensional grid:

// Create the axes descriptions
GridAxis<int> int_axis {"Integers", {1, 2, 3, 4, 5}};
GridAxis<double> double_axis {"Doubles", {0.1, 0.2}};
GridAxis<string> string_axis {"Strings", {"one", "two", "three"}};
// Create a grid with integer cell values
GridContainer<vector<int>, int, double, string> grid {int_axis, double_axis, string_axis};
Representation of a multi-dimensional grid which contains axis information.

The first template parameter of the GridContainer class defines the GridCellManager type that the GridContainer will use. In the example above this type is vector<int>, which shows that the GridContainer will keep integer values and it will use a vector as the GridCellManager.

The rest of the template parameters define the types of the axes world values. In our example the first axis has integer knots, the second double knots and the third strings. Note that the axes knots can be of any type, even of user defined types. The axes given as parameters to the constructor must be of the correct type, otherwise the compilation will fail. Note that in the example above, the vector for keeping the grid data is automatically created by the constructor, by using the GridCellManagerTraits::factory() method.

The second way of creating Grids is when one already has a GridContainer instance and want to create a second GridContainer with the same parameter space (axes). This is a common operation, so it is supported by the GridContainer API as demonstrated by the following code:

GridContainer<vector<bool>, int, double, string> bool_grid {grid.getAxesTuple()};
const std::tuple< GridAxis< AxesTypes >... > & getAxesTuple() const
Returns a tuple containing the information of all the grid axes.

The code above creates a new grid of boolean cells, with the same axes like the parameter. Note that only the axes number and types need to match between the two grids. The new grid GridCellManager can be of any type.

Retrieving GridContainer information

The GridContainer class provides a range of methods for retrieving its information, as demonstrated by the following code:

// Get the grid dimensionality
cout << "Dimensionality: " << grid.axisNumber() << "\n";
// Get the total number of cells of the grid
cout << "Total number of cells: " << grid.size() << "\n";
// Retrieve information about the third axis
auto& third_axis = grid.getAxis<2>();
// Print the axis information
cout << "Third axis name: " << third_axis.name() << "\n";
cout << "Third axis knots: ";
for (auto& knot : third_axis) {
cout << knot << " ";
}

Output:

Dimensionality: 3
Total number of cells: 30
Third axis name: Strings
Third axis knots: one two three

Note that the axes information is retrieved by the GridContainer::getAxis() method, which returns a reference to an GridAxis object. The axis to get the information for is specified with an integer template parameter representing its (zero based) index.

Direct cell access

The GridContainer cells can be directly accessed based on their coordinates, by using the parenthesis operator. This operator returns a reference to the cell value which can be used both for reading and writing:

// Set the value of a specific cell
grid(2, 0, 1) = 50;
// Read the value of a specific cell
cout << grid(2, 0, 1) << "\n";

Note that the parenthesis operator will not perform any bound checks to the given indices. If any of the indices is out of bounds will result to undefined behavior. For cases the indices are not guaranteed to be in bounds, the alternative method at() should be used (with the implied performance cost), which behaves the same way like the parenthesis operator, but it throws an Elements::Exception in the case any of the indices is out of bounds:

size_t coord = .....;
try {
// Set the value of a specific cell
grid.at(coord, 0, 1) = 50;
// Read the value of a specific cell
cout << grid.at(coord, 0, 1) << "\n";
} catch (Elements::Exception e) {
cout << e.what() << "\n";
}

In the above example if the coord variable has a value less than the first axis size the code will print the cell value. If it is out of bounds, it will print the message of the exception.

Accessing the grid cells by using the parenthesis operator and the at() method is very useful for accessing a single cell of which the coordinates are known at the time of the call. This access though includes an overhead of the coordinates translation, so the use of the GridContainer iterator (described bellow) is recommended for any case a big number of cells is accessed.

GridContainer iterator

The second way to access the GridContainer cells, both for reading and writing, is by using the GridContainer iterator. This method is the most efficient because it does not imply any overhead related with axes management. For example, the following code is almost as fast as accessing directly the vector keeping the values:

// Set all the grid values
int counter {0};
for (auto& cell : grid) {
cell = ++counter;
}
// Print all the grid cells
cout << "GridContainer cells: ";
for (auto& cell : grid) {
cout << cell << " ";
}

Output:

GridContainer cells: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

The order the cells are iterated is such so that the first axis varies the fastest and the last the slowest. Note that using the iterator for accessing the grid cells is the most efficient way, but it does not provide any information about the axes of each cell. This information can be retrieved by using the special methods of the GridContainer::iterator when needed (which implies some computing overhead):

// Print detailed information for each cell
for (auto iter=grid.begin(); iter!=grid.end(); ++iter) {
cout << "Cell (" << iter.axisValue<0>() << ", " << iter.axisValue<1>()
<< ", " << iter.axisValue<2>() << "): " << *iter << "\n";
}

Output:

Cell (1, 0.1, one): 1
Cell (2, 0.1, one): 2
Cell (3, 0.1, one): 3
Cell (4, 0.1, one): 4
Cell (5, 0.1, one): 5
Cell (1, 0.2, one): 6
Cell (2, 0.2, one): 7
Cell (3, 0.2, one): 8
Cell (4, 0.2, one): 9
Cell (5, 0.2, one): 10
Cell (1, 0.1, two): 11
Cell (2, 0.1, two): 12
Cell (3, 0.1, two): 13
Cell (4, 0.1, two): 14
Cell (5, 0.1, two): 15
Cell (1, 0.2, two): 16
Cell (2, 0.2, two): 17
Cell (3, 0.2, two): 18
Cell (4, 0.2, two): 19
Cell (5, 0.2, two): 20
Cell (1, 0.1, three): 21
Cell (2, 0.1, three): 22
Cell (3, 0.1, three): 23
Cell (4, 0.1, three): 24
Cell (5, 0.1, three): 25
Cell (1, 0.2, three): 26
Cell (2, 0.2, three): 27
Cell (3, 0.2, three): 28
Cell (4, 0.2, three): 29
Cell (5, 0.2, three): 30

The methods provided by the iterator for accessing the axes information are the GridContainer::iterator::axisIndex(), which returns the related coordinate, and the GridContainer::iterator::axisValue(), which returns the world value of the axis knot. Both methods receive the axis (zero based) index as an integer template parameter. Note that the overhead of the second method is higher than the one of the first, so it should be avoided in cases performance is an issue.

GridContainer slicing

The GridContainer module provides very efficient iteration over slices of a GridContainer. This can be done with two ways:

Slicing using iterators

The GridContainer::iterator::fixAxisByIndex() and GridContainer::iterator::fixAxisByValue() methods can be used for slicing, as demonstrated by the following code:

// Get an iterator and fix the third axis
auto iter = grid.begin();
iter.fixAxisByValue<2>("two");
// Print detailed information for each cell
do {
cout << "Cell (" << iter.axisValue<0>() << ", " << iter.axisValue<1>()
<< ", " << iter.axisValue<2>() << "): " << *iter << "\n";
} while (++iter != grid.end());

Output:

Cell (1, 0.1, two): 11
Cell (2, 0.1, two): 12
Cell (3, 0.1, two): 13
Cell (4, 0.1, two): 14
Cell (5, 0.1, two): 15
Cell (1, 0.2, two): 16
Cell (2, 0.2, two): 17
Cell (3, 0.2, two): 18
Cell (4, 0.2, two): 19
Cell (5, 0.2, two): 20

`

When a method for fixing an axis is called, the iterator moves to the next cell with the given axis coordinate (or stays where it is if the current cell has this coordinate). Increasing further the iterator will move it only through the cells of the slice.

The functions for fixing an iterators axes can be chained to fix more than one axes. For example, if in the previous code we had used:

iter.fixAxisByValue<2>("two").fixAxisByIndex<0>(2).fixAxisByValue<1>(.1);

we would get the following output:

Cell (3, 0.1, two): 13

The order in which the axes are fixed is not important, but keep in mind that if the current cell does not have the requested axis coordinate the iterator is shifted. For this reason it is best to fix the iterator axes right after retrieving the iterator with the GridContainer::begin() method. Fixing the same axis for a second time (moving to a different slice) is not allowed and an exception is thrown.

Direct Grid slicing

The second way to slice a grid is to use the methods gixAxisByValue() and fixAxisByIndex() of the GridContainer class itself. These methods will return a GridContainer object which represents the slice of the original grid. For example:

// Get a slice of the grid by fixing the third axis
auto slice = grid.fixAxisByValue<2>("two");
// Print detailed information for the slice
cout << "Slice size: " << slice.size() << "\n";
for (auto iter=slice.begin(); iter!=slice.end(); ++iter) {
cout << "Cell (" << iter.axisValue<0>() << ", " << iter.axisValue<1>()
<< ", " << iter.axisValue<2>() << "): " << *iter << "\n";
}

Output:

Slice size: 10
Cell (1, 0.1, two): 11
Cell (2, 0.1, two): 12
Cell (3, 0.1, two): 13
Cell (4, 0.1, two): 14
Cell (5, 0.1, two): 15
Cell (1, 0.2, two): 16
Cell (2, 0.2, two): 17
Cell (3, 0.2, two): 18
Cell (4, 0.2, two): 19
Cell (5, 0.2, two): 20

Note that the grid representing the slice has the same number of axes with the original grid, but the fixed axis has a single value:

// The original grid and the slice have the same dimensionality
cout << "Original dimensionality: " << grid.axisNumber() << "\n";
cout << "Slice dimensionality: " << slice.axisNumber() << "\n";
// The slice fixed axis has a single value
auto& fixed_axis = slice.getAxis<2>();
cout << "Fixed axis name: " << fixed_axis.name() << "\n";
cout << "Fixed axis knots: ";
for (auto& knot : fixed_axis) {
cout << knot << " ";
}

Output:

Original dimensionality: 3
Slice dimensionality: 3
Fixed axis name: Strings
Fixed axis knots: two

The functions for slicing a grid can be chained to fix more than one axes. For example:

auto slice2 = grid.fixAxisByValue<2>("two").fixAxisByIndex<0>(2).fixAxisByValue<1>(.1);
cout << "Slice size: " << slice2.size() << "\n";
for (auto iter=slice2.begin(); iter!=slice2.end(); ++iter) {
cout << "Cell (" << iter.axisValue<0>() << ", " << iter.axisValue<1>()
<< ", " << iter.axisValue<2>() << "): " << *iter << "\n";
}

Output:

Slice size: 1
Cell (3, 0.1, two): 13

There are two important notes for when using grid slices. The first is that both the original grid and the slice use the same underlying data. This means that any modifications on the slice cells will be reflected to the original grid and vice versa. For example:

cout << "Original cell before: " << grid(2, 0, 1) << "\n";
// Modify the first element of the slice2
*(slice2.begin()) = -15;
// The modification is reflected to the original grid
cout << "Original cell after: " << grid(2, 0, 1) << "\n";

Output:

Original cell: 13
Original cell: -15

The second important note is that the const versions of the fixAxisByIndex() and fixAxisByValue() return an object of type const GridContainer. This is necessary because otherwise the returned slice could be used for modifying the original (constant) grid (because they share the same underlying data). This, with combination that the GridContainer does not provide a copy constructor, makes it impossible to store the result to a new GridContainer instance (the move constructor cannot be used because of the constness of the returned type). The solution to this problem is to use a const reference to store the result:

// A constant reference to the grid
const auto& const_grid = grid;
// The following two lines will not compile. They will fail with a message saying
// that the GridContainer copy constructor is deleted.
auto const_slice1 = const_grid.fixAxisByValue<2>("two"); // WRONG - DONT DO THAT
const GridContainer<vector<int>, int, double, string> const_slice2
= const_grid.fixAxisByValue<2>("two"); // WRONG - DONT DO THAT
// The following two lines do not create new objects. They only create references
// to the temporary, which works fine. (Note the &)
auto& const_slice1 = const_grid.fixAxisByValue<2>("two"); // CORRECT - works fine
const GridContainer<vector<int>, int, double, string>& const_slice2
= const_grid.fixAxisByValue<2>("two"); // CORRECT - works fine
GridContainer< GridCellManager, AxesTypes... > fixAxisByValue(const axis_type< I > &value)
Returns a slice of the grid based on an axis value.

GridContainer I/O

To be able to import and export GridContainer objects, the GridContainer module provides by default support for binary boost serialization. This support is enabled by including the GridContainer/serialize.h file:

Default GridContainer Serialization

For the simple case that a vector is used as a GridCellManager and the cell type, as well as all the axes types, are boost serializable, serialization of the grids can work out-of-the-box:

stringstream stream {};
gridBinaryExport(stream, grid);
auto stream_grid = gridBinaryImport<GridType>(stream);
void gridBinaryExport(std::ostream &out, const GridContainer< GridCellManager, AxesTypes... > &grid)
Exports to the given output stream the given grid.
Definition serialize.h:103

where GridType is the type of the variable grid.

To be more flexible, the GridContainer module provides GridContainer serialization to generic streams. To make the example more readable a string stream is used, but it can be easily replaced with file streams to support permanent storage, or by socket streams to transfer grids via the network.

Non-Default Axis Serialization

The above example works without any input from the user because all the axes have types which are boost serializable. If an axis had a user defined type the compilation would fail, as boost would not know how to serialize its values. This is easily fixed by implementing the required methods to make the user defined type serializable. More information can be found in the boost serialization documentation, which can be found here.

Custom GridCellManager Serialization

Similarly with the axes types, the type of the cells of a grid must be boost serializable, otherwise it will not be possible to serialize the grid. Note that the GridCellManager itself does not need to be serializable, just the values it manages. By default serialization is disabled for all the GridCellManagers. To enable serialization for a specific GridCellManager, except of defining the related boost::serialization methods, the related specialization of the GridCellManagerTraits must have the enable_boost_serializable flag set to true.

By default the GridContainer module enables serialization only for vectors of types which are boost serializable.

Generating a Table

A GridContainer can be unfolded into an Alexandria Table, which can, in turn, be serialized either into plain text or FITS files. However, keep in mind that this is operation has a single direction: there is no functionality provided to load a GridContainer from a Table. However, it can be useful for debugging purposes to serialize a GridContainer into a format easy to read from other tools or languages (i.e. astropy's Table)

To be able to export a Table, you need to include first GridContainer/GridContainerToTable.h

When you want to transform a Grid, you need to make sure the axes and cell types can be translated to the types supported by Row::cell_type, and given a name. This is done via the specialization of two set of traits: GridAxisToTable and GridCellToTable.

By default, Alexandria provides two specializations of GridAxisToTable:

Note that only one column can be generated for each axis, using the name assigned to the axis.

For GridCellToTable, a default specialization for any kind of scalar - integer and floating point - is provided. By default, the associated column name will be value.

For composed cell values, as it would be SourceCatalog::Photometry, you will need to provide a custom specialization. For instance:

Note that addColumnDescriptions uses the first value on the grid as a model for every other cell. This code assumes that all instances look alike.

Specializing the GridContainer API

From the examples above can be seen that the API of the GridContainer module is quite verbose. This is because of the flexibility the API provides, which leads in lengthy template definitions. In real life applications though, there are many cases that a set of (different cell type) grids is required for a fixed parameter space. The following is a suggestion of how to simplify the use of the GridContainer API for these cases.

Defining the axes order

To avoid the error prone use of integers as axes indices for all the methods requiring them as a template parameter, an enumeration can be used, so more meaningful names will replace them:

enum {
INT_AXIS = 0,
DOUBLE_AXIS = 1,
STRING_AXIS = 2
};

Note that an anonymous plain enum has been used. This is because we want to be able to use the conversion of the enumeration name to integer directly, without having to perform casts (as is required by the enum class).

Defining a GridContainer specialization

To avoid repetition of the lengthy GridContainer declarations a specialized alias can be used:

template <typename CellType>
using MyGridContainer = GridContainer<vector<CellType>, int, double, string>;

The above definition assumes that all the grids will use a vector as a GridCellManager and allows only for further customization of the grid cells type. If different GridCellManagers are to be used, the following (more generic) definition can be used:

template <typename GridCellManager>

Using the specialized types

The following code demonstrates how the code using the above specializations looks like. Note that the same examples like the previous sections are used, to make easier the comparison.

// Create the GridAxis objects (same as before)
GridAxis<int> int_axis {"Integers", {1, 2, 3, 4, 5}};
GridAxis<double> double_axis {"Doubles", {0.1, 0.2}};
GridAxis<string> string_axis {"Strings", {"one", "two", "three"}};
// Create a grid with integer data
MyGridContainer<int> grid {int_axis, double_axis, string_axis};
// Print all the nodes of the string axis
cout << "String axis knots:";
for (auto& knot : grid.getAxis<STRING_AXIS>()) {
cout << " " << knot;
}
cout << "\n";
// Get an iterator and fix two axes
auto iter = grid.begin();
iter.fixAxisByValue<STRING_AXIS>("two").fixAxisByIndex<INT_AXIS>(3);
// Print some info about each cell
do {
cout << "Double axis coord: " << iter.axisIndex<DOUBLE_AXIS>() << "\n";
cout << "Double axis knot value: " << iter.axisValue<DOUBLE_AXIS>() << "\n";
} while (++iter != grid.end());
// Write the grid in a string stream and then read it back
stringstream stream {};
gridBinaryExport(stream, grid);
auto stream_grid = myBinaryImport<int>(stream);
// Create a grid to keep strings
MyGridContainer<string> string_grid = {grid.getAxesTuple()};

Using a custom GridCellContainer

The CellManagerTraits interface

To achieve maximum flexibility, the GridContainer never use directly the GridCellManager** instance itself. Instead, it uses the GridCellManagerTraits class, which provides an interface to manipulate the grid cell values. In the case of a std::vector<int> GridCellManager, the GridContainer will make calls to a GridCellManagerTraits<std::vector<int>>.

The GridCellManagerTraits must implement the following interfaces:

The default implementation of the GridCellManagerTraits will redirect all the calls to the GridCellManager. If the type used as GridCellManager provides all the required functions, it can be used directly.

The module also provides a specialization for all the std::vector types. This specialization behaves the same way like the default one, but it enables the boost serialization flag.

The following sections describe how to specialize the GridCellManagerTraits for types which are not providing the required API.

A custom GridCellManager type

As an example of a type which can be used as a GridCellManager but it does not follow the GridCellManagerTraits API, imagine that we want to use as GridCellManager an old-style class which uses pointers to handle an area in the memory:

template <typename T>
class MemoryManager {
public:
MemoryManager(size_t size) {
m_size = size;
m_start = reinterpret_cast<T*>(malloc(m_size * sizeof(T)));
}
~MemoryManager() {
free(m_start);
}
size_t getSize() {
return m_size;
}
T* getPointerToStart() {
return m_start;
}
private:
size_t m_size;
T* m_start;
};

As can be seen above, the MemoryManager class uses malloc to allocate enough memory for the required number of objects, and frees it when it is destroyed. It provides direct access to the memory by returning a pointer to the beginning of it (note that the above implementation is incomplete and only for demonstration purposes). The MemoryManager class is not compliant with the GridCellManagerTraits interface, so, if we want to use it as a GridCellManager, we need to provide a traits specialization.

Specializing the GridCellManagerTraits

The GridCellManagerTraits defines the behavior a GridCellManager type must provide, so it can be used by the GridContainer class. In other words, it provides the interface of the GridCellManager for the GridContainer to use. This is done so a type which provides a different interface can be used as a GridCellManager, by just defining a specialization of the GridCellManagerTraits. Note that the GridCellManagerTraits default behavior is to delegate all the operations to the type used as a GridCellManager. If the type is compliant with this interface, there is no need for defining the GridCellManagerTraits specialization.

The specialization of the GridCellManagerTraits for our MemoryManager type can be done with the following code:

namespace GridContainer {
template<typename T>
struct GridCellManagerTraits<MemoryManager<T>> {
typedef T data_type;
typedef T* iterator;
static unique_ptr<MemoryManager<T>> factory(size_t s) {
return unique_ptr<MemoryManager<T>>{new MemoryManager<T>{s}};
}
static size_t size(const MemoryManager<T>& m) {
return m.getSize();
}
static iterator begin(MemoryManager<T>& m) {
return m.getPointerToStart();
}
static iterator end(MemoryManager<T>& m) {
return m.getPointerToStart() + m.getSize();
}
static const bool enable_boost_serialize = false;
};
} // End of GridContainer namespace
T begin(T... args)
T end(T... args)
constexpr double m
Class used by the GridContainer to access the different CellManagers.

Note that it is not obligatory to implement all the above, but only what is actually being used. Furthermore, there is no requirement for the GridCellManager to actually keep the data in the memory. An implementation can provide an iterator which will calculate the data on the fly. Such types of GridCellManagers are too application specific, so they are not described further here.

Full example code

#include <iostream>
#include <vector>
#include <sstream>
using namespace Euclid::GridContainer;
using namespace std;
// /////////////////////////////////////
// Definitions for custom GridCellManager
// /////////////////////////////////////
template <typename T>
class MemoryManager {
public:
MemoryManager(size_t size) {
m_size = size;
m_start = reinterpret_cast<T*>(malloc(m_size * sizeof(T)));
}
~MemoryManager() {
free(m_start);
}
size_t getSize() {
return m_size;
}
T* getPointerToStart() {
return m_start;
}
private:
size_t m_size;
T* m_start;
};
namespace Euclid {
namespace GridContainer {
template<typename T>
struct GridCellManagerTraits<MemoryManager<T>> {
typedef T data_type;
typedef T* iterator;
static unique_ptr<MemoryManager<T>> factory(size_t s) {
return unique_ptr<MemoryManager<T>>{new MemoryManager<T>{s}};
}
static size_t size(const MemoryManager<T>& m) {
return m.getSize();
}
static iterator begin(MemoryManager<T>& m) {
return m.getPointerToStart();
}
static iterator end(MemoryManager<T>& m) {
return m.getPointerToStart() + m.getSize();
}
static const bool enable_boost_serialize = false;
};
} // End of GridContainer namespace
} // End of Euclid namespace
// /////////////////////////////////////////
// End of custom GridCellManager definitions
// /////////////////////////////////////////
// ///////////////////////////////////////////////////
// Definitions for specializing the GridContainer API
// ///////////////////////////////////////////////////
enum {
INT_AXIS = 0,
DOUBLE_AXIS = 1,
STRING_AXIS = 2
};
template <typename CellType>
using MyGridContainer = GridContainer<vector<CellType>, int, double, string>;
template <typename T>
MyGridContainer<T> myBinaryImport(istream& in) {
return gridBinaryImport<vector<T>, int, double, string>(in);
}
// ///////////////////////////////////////////////////
// End of definitions for specializing the GridContainer API
// ///////////////////////////////////////////////////
int main() {
// /////////////////////////////////////////
// Creating GridAxis objects
// /////////////////////////////////////////
cout << "\nCreating GridAxis objects\n";
vector<int> knot_values {3000, 3500, 4000, 4500, 5000};
GridAxis<int> wavelength_axis {"Wavelength", move(knot_values)};
// /////////////////////////////////////////
// Using GridAxis objects
// /////////////////////////////////////////
cout << "\nUsing GridAxis objects\n";
// Getting the axis name
cout << "Axis name : " << wavelength_axis.name() << "\n";
// Getting the number of knots
cout << "Number of knots : " << wavelength_axis.size() << "\n";
// Accessing a knot value directly using its (zero based) index
cout << "Second knot value : " << wavelength_axis[1] << "\n";
// Accessing the knots using the iterator
cout << "All knot values : ( ";
for (auto& knot_value : wavelength_axis) {
cout << knot_value << " ";
}
cout << ")\n";
// /////////////////////////////////////////
// Using custom GridCellManager
// /////////////////////////////////////////
cout << "\nUsing custom GridCellManager\n";
GridContainer<MemoryManager<double>, int> custom_dm_grid { wavelength_axis };
// /////////////////////////////////////////
// Creating GridContainer objects
// /////////////////////////////////////////
cout << "\nCreating GridContainer objects\n";
// Create the axes descriptions
GridAxis<int> int_axis {"Integers", {1, 2, 3, 4, 5}};
GridAxis<double> double_axis {"Doubles", {0.1, 0.2}};
GridAxis<string> string_axis {"Strings", {"one", "two", "three"}};
// Create a grid with integer data
GridContainer<vector<int>, int, double, string> grid {int_axis, double_axis, string_axis};
// Create grid with same parameter space
GridContainer<vector<bool>, int, double, string> bool_grid {grid.getAxesTuple()};
// /////////////////////////////////////////
// Retrieving GridContainer information
// /////////////////////////////////////////
cout << "\nRetrieving GridContainer information\n";
// Get the grid dimensionality
cout << "Dimensionality: " << grid.axisNumber() << "\n";
// Get the total number of cells of the grid
cout << "Total number of cells: " << grid.size() << "\n";
// Retrieve information about the third axis
auto& third_axis = grid.getAxis<2>();
// Print the axis information
cout << "Third axis name: " << third_axis.name() << "\n";
cout << "Third axis knots: ";
for (auto& knot : third_axis) {
cout << knot << " ";
}
// /////////////////////////////////////////
// Direct cell access
// /////////////////////////////////////////
cout << "\nDirect cell access\n";
// Set the value of a specific cell
grid(2, 0, 1) = 50;
// Read the value of a specific cell
cout << "GridContainer cell (2, 0, 1):" << grid(2, 0, 1) << "\n";
// Calling at() with in bounds coordinates
size_t coord = 1;
try {
// Set the value of a specific cell
grid.at(coord, 0, 1) = 50;
// Read the value of a specific cell
cout << "GridContainer cell (" << coord << ", 0, 1):" << grid.at(coord, 0, 1) << "\n";
} catch (Elements::Exception e) {
cout << e.what() << "\n";
}
// Calling at() with out of bounds coordinates
coord = 20;
try {
// Set the value of a specific cell
grid.at(coord, 0, 1) = 50;
// Read the value of a specific cell
cout << "GridContainer cell (" << coord << ", 0, 1):" << grid.at(coord, 0, 1) << "\n";
} catch (Elements::Exception e) {
cout << e.what() << "\n";
}
// /////////////////////////////////////////
// GridContainer iterator
// /////////////////////////////////////////
cout << "\nGridContainer iterator\n";
// Set all the grid values
int counter {0};
for (auto& cell : grid) {
cell = ++counter;
}
// Print all the grid cells
cout << "GridContainer cells: ";
for (auto& cell : grid) {
cout << cell << " ";
}
cout << '\n';
// Print detailed information for each cell
for (auto iter=grid.begin(); iter!=grid.end(); ++iter) {
cout << "Cell (" << iter.axisValue<0>() << ", " << iter.axisValue<1>()
<< ", " << iter.axisValue<2>() << "): " << *iter << "\n";
}
// /////////////////////////////////////////
// GridContainer slicing
// /////////////////////////////////////////
cout << "\nGridContainer slicing\n";
// Get an iterator and fix the third axis
cout << "One axis fixed\n";
auto iter = grid.begin();
iter.fixAxisByValue<2>("two");
// Print detailed information for each cell
do {
cout << "Cell (" << iter.axisValue<0>() << ", " << iter.axisValue<1>()
<< ", " << iter.axisValue<2>() << "): " << *iter << "\n";
} while (++iter != grid.end());
cout << "Three axes fixed\n";
iter = grid.begin();
iter.fixAxisByValue<2>("two").fixAxisByIndex<0>(2).fixAxisByValue<1>(.1);
// Print detailed information for each cell
do {
cout << "Cell (" << iter.axisValue<0>() << ", " << iter.axisValue<1>()
<< ", " << iter.axisValue<2>() << "): " << *iter << "\n";
} while (++iter != grid.end());
// /////////////////////////////////////////
// Default GridContainer Serialization
// /////////////////////////////////////////
cout << "\nDefault GridContainer Serialization\n";
stringstream stream {};
gridBinaryExport(stream, grid);
auto stream_grid = gridBinaryImport<vector<int>, int, double, string>(stream);
// /////////////////////////////////////////
// Using specialized GridContainer types
// /////////////////////////////////////////
cout << "\nUsing specialized GridContainer types\n";
// Create a grid with integer data
MyGridContainer<int> spec_grid {int_axis, double_axis, string_axis};
// Print all the nodes of the string axis
cout << "String axis knots:";
for (auto& knot : spec_grid.getAxis<STRING_AXIS>()) {
cout << " " << knot;
}
cout << "\n";
// Get an iterator and fix two axes
auto iter2 = spec_grid.begin();
iter2.fixAxisByValue<STRING_AXIS>("two").fixAxisByIndex<INT_AXIS>(3);
// Print some info about each cell
do {
cout << "Double axis coord: " << iter2.axisIndex<DOUBLE_AXIS>() << "\n";
cout << "Double axis knot value: " << iter2.axisValue<DOUBLE_AXIS>() << "\n";
} while (++iter2 != spec_grid.end());
// Write the grid in a string stream and then read it back
gridBinaryExport(stream, spec_grid);
auto stream_spec_grid = myBinaryImport<int>(stream);
// Create a grid to keep strings
MyGridContainer<string> string_grid = {stream_spec_grid.getAxesTuple()};
}
T move(T... args)
constexpr double e
constexpr double s