Data Model
FleCSI provides a data model that integrates with the task and kernel abstractions to provide easy registration and access to various data types with automatic dependency tracking.
Example 1: Global data
Global fields are used to store global variables/objects that can be
accessed by any task. For the common case of only one value for each field, it
is natural to use the data::single
layout.
template<typename T>
using single = field<T, data::single>;
const single<double>::definition<global> gfield;
To create a topology instance, declare a topology slot and then use allocate
with an argument appropriate to the topology called a coloring.
In general, a coloring describes the structure of a topology and its distribution among colors.
The global topology is a special case that does not actually use colors; its “coloring” is simply a count of values for each field.
Writing to a global field requires a single task launch.
void
init(double v, single<double>::accessor<wo> gv) {
gv = v;
}
void
print(single<double>::accessor<ro> gv) {
flog(trace) << "global value: " << gv << std::endl;
}
void
advance(control_policy &) {
topo::global::slot gtopo;
gtopo.allocate(1);
const auto v = gfield(gtopo);
execute<init>(42.0, v);
execute<print>(v);
} // advance()
Example 2: Index data
A field on an index
topology stores one value for each color.
using namespace flecsi;
template<typename T>
using single = field<T, data::single>;
const single<std::size_t>::definition<topo::index> ifield;
void
init(single<std::size_t>::accessor<wo> iv) {
flog(trace) << "initializing value on color " << color() << " of " << colors()
<< std::endl;
iv = color();
}
void
print(single<std::size_t>::accessor<ro> iv) {
flog(trace) << "index value: " << iv << " (color " << color() << " of "
<< colors() << ")" << std::endl;
}
void
advance(control_policy &) {
topo::index::slot custom_topology;
custom_topology.allocate(4);
execute<init>(ifield(custom_topology));
execute<print>(ifield(custom_topology));
} // advance()
Example 3: Dense data
A dense field is a field defined on a dense topology index space. In this example we allocate a pressure field on the cells index space of the canonical topology.
const field<double>::definition<canon, canon::cells> pressure;
One can access the field inside of the FleCSI task by passing
topology and field accessors with access permissions (wo/rw/ro).
The canonical
topology is a very simple specialization of the unstructured
core topology.
It illustrates the use of the mpi_coloring
type, which applies a specialization-defined rule for specifying a coloring.
Here, a file is the source of the mesh (for purposes of illustration).
The resulting coloring is used to initialize two meshes canonical
and cp
, and the copy
task operates on both of them at once using a low-level accessor.
The init
and print
tasks, by contrast, use a topology accessor as a parameter that provides access to the structure of the mesh via the entities
function.
void
init(canon::accessor<ro> t, field<double>::accessor<wo> p) {
std::size_t off{0};
for(const auto c : t.cells()) {
p[c] = (off++) * 2.0;
} // for
} // init
void
copy(field<double>::accessor<ro> src, field<double>::accessor<wo> dest) {
auto s = src.span();
std::copy(s.begin(), s.end(), dest.span().begin());
}
void
print(canon::accessor<ro> t, field<double>::accessor<ro> p) {
std::size_t off{0};
for(auto c : t.cells()) {
flog(info) << "cell " << off++ << " has pressure " << p[c] << std::endl;
} // for
} // print
void
advance(control_policy &) {
canon::slot canonical, cp;
canon::mpi_coloring c("test.txt");
canonical.allocate(c);
cp.allocate(c);
auto pf = pressure(canonical), pf2 = pressure(cp);
execute<init>(canonical, pf);
execute<copy>(pf, pf2);
execute<print>(cp, pf2);
} // advance()