Fields / Geometry and storage

A field is more than a vector

The same 96 physical values can occupy 96 or 288 storage slots. The distinction matters at every boundary.

First six experiments · 2 of 6

Download runnable lesson (.zip)

Includes the model, inputs, required headers and a short run guide.

A Field connects a global domain, an owned index box, physical coordinates, and storage. Padding supports neighbouring-value calculations without changing which physical values the rank owns.

Geometry belongs to the field

For grid index \(i\), physical position is \(x=x_0+ih\). Our global x origin is \(-1\), spacing is \(1/4\), and the owned box starts at global index four. Its first x coordinate is therefore zero, even though its local index is zero.

const auto domain = pfc::domain::create(pfc::GridSize{{8, 6, 4}},
                                        pfc::PhysicalOrigin{{-1.0, 0.0, 0.0}},
                                        pfc::GridSpacing{{0.25, 0.5, 1.0}});
const auto box = pfc::Box3i::from_bounds({4, 0, 0}, {7, 5, 3});
pfc::data::Field<double> compact(domain, box, 0);
pfc::data::Field<double> padded(domain, box, 1);
auto polynomial = [](double x, double y, double z) { return 1 + x + y * y + z; };
compact.apply(polynomial);
padded.apply(polynomial);

Both fields receive \(u(x,y,z)=1+x+y^2+z\) at every owned point. apply supplies physical coordinates, including the owned box’s global offset. No separate coordinate array or rank-dependent correction is needed.

Count values and count slots

The owned box has dimensions \(4\times6\times4\), hence 96 values. A halo width of one makes the padded extents \(6\times8\times6\), hence 288 slots. Its owned origin appears at storage offset

\[ 1+6\cdot1+(6\cdot8)\cdot1=55. \tag{1}\]

padded(0,0,0) and padded.data()[0] therefore name different cells. The former is an owned value; the latter is in the padding. The executable compares all owned values between compact and padded representations and prints the sizes and Equation 1.

Values can have components

Geometry and storage do not require scalar physics. For example:

pfc::data::Field<std::array<double, 3>> positions(domain, box, 0);
positions.apply([](double x, double y, double z) { return std::array{x, y, z}; });
const auto first = positions(0, 0, 0);

This is a field of three-component C++ values. It demonstrates container generality; it does not imply that a scalar FFT or a scalar stepper accepts the same component type without an adapter.

Run, inspect, then change the padding

openpfc run model.cpp input.json --launcher=none

The empty JSON leaves these intentionally small constants in model.cpp. Predict the reported storage size and origin offset for halo width two, then make that edit. The owned count and physical initial condition should be unchanged. Update the independent expected storage checks after doing the arithmetic yourself.

The program does not fill meaningful ghost values or calculate a stencil. Padding allocates space; a halo exchange or boundary treatment supplies its contents. The heat lesson shows that next step. The Field header defines the ownership and indexing contracts. The complete C++ model and starting input are available with this lesson.