Example 5: Dense DataΒΆ
The FleCSI data model provides several different storage types. A storage type is a formal term that implies a particular logical layout to the data of types registered under it. The logical layout of the data provides the user with an interface that is consistent with a particular view of the data. In this example, we focus on the dense storage type.
Logically, dense data may be represented as a contiguous array, with indexed access to its elements. The dense storage type interface is similar to a C array or std::vector, with element access provided through the () operator, i.e., if var has been registered as a dense data type, it may be accessed like: var(0), var(1), etc.
In the fields example (04-fields), we were actually using the dense storage type to represent an array of double defined on the cells of our specialization mesh. This example is an extension that demonstrates using user-defined types as dense field data.
This example uses a user-defined struct as the dense data type. The struct_type_t is defined in the types.h file:
#pragma once
#include <flecsi/data/dense_accessor.h>
namespace types {
using namespace flecsi;
// This is the definition of the struct_type_t type.
struct struct_type_t {
double a;
size_t b;
double v[3];
}; // struct_type_t
// Here, we define an accessor type to use in the task signatures in our
// example. Notice that the base type "dense_accessor" takes four
// parameters. The first parameter is the type that has been registered on
// the associated index space. The other three parameters specify the
// privileges with which the corresponding data will be accessed.
template<
size_t SHARED_PRIVILEGES>
using struct_field = dense_accessor<struct_type_t, rw, SHARED_PRIVILEGES, ro>;
} // namespace types
Aside from using a struct type, this example of registering data is identical to registering a fundamental type, e.g., double or size_t.
The code for this example can be found in dense.cc:
#include <iostream>
#include<flecsi/tutorial/specialization/mesh/mesh.h>
#include<flecsi/data/data.h>
#include<flecsi/execution/execution.h>
#include "types.h"
using namespace flecsi;
using namespace flecsi::tutorial;
using namespace types;
flecsi_register_data_client(mesh_t, clients, mesh);
flecsi_register_field(mesh_t, types, f, struct_type_t, dense, 1, cells);
namespace example {
// This task initializes the field of struct_type_t data. Notice that
// nothing has changed about the iteration logic over the mesh. The only
// difference is that now the dereferenced values are struct instances.
void initialize_field(mesh<ro> mesh, struct_field<rw> f) {
for(auto c: mesh.cells(owned)) {
f(c).a = double(c->id())*1000.0;
f(c).b = c->id();
f(c).v[0] = double(c->id());
f(c).v[1] = double(c->id())+1.0;
f(c).v[2] = double(c->id())+2.0;
} // for
} // initialize_field
flecsi_register_task(initialize_field, example, loc, single);
// This task prints the struct values.
void print_field(mesh<ro> mesh, struct_field<ro> f) {
for(auto c: mesh.cells(owned)) {
std::cout << "cell id: " << c->id() << " has value: " << std::endl;
std::cout << "\ta: " << f(c).a << std::endl;
std::cout << "\tb: " << f(c).b << std::endl;
std::cout << "\tv[0]: " << f(c).v[0] << std::endl;
std::cout << "\tv[1]: " << f(c).v[1] << std::endl;
std::cout << "\tv[2]: " << f(c).v[2] << std::endl;
} // for
} // print_field
flecsi_register_task(print_field, example, loc, single);
} // namespace example
namespace flecsi {
namespace execution {
void driver(int argc, char ** argv) {
auto m = flecsi_get_client_handle(mesh_t, clients, mesh);
// The interface for retrieving a data handle now uses the
// struct_type_t type.
auto f = flecsi_get_handle(m, types, f, struct_type_t, dense, 0);
flecsi_execute_task(initialize_field, example, single, m, f);
flecsi_execute_task(print_field, example, single, m, f);
} // driver
} // namespace execution
} // namespace flecsi