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()