Layouts¶
QDP++ has a single lattice. qdp-jit can hold several lattice layouts at once, typically the fine lattice and the coarse lattices of a multigrid hierarchy. This page covers what a layout is, how coarse layouts are made, the layout stack, and which parts of QDP++ follow the current layout.
What a layout is¶
A LatticeLayout is a lattice size together with its site tables: for every
site, which rank holds it and its linear index on that rank, and the reverse
map from rank and index back to coordinates (see Concepts).
The first layout is set up exactly as in QDP++:
QDP_initialize(&argc, &argv);
multi1d<int> nrow(Nd);
nrow = 16;
nrow[3] = 32;
Layout::setLattSize(nrow);
Layout::create(); // creates the first (fine) layout
LatticeLayout fine = Layout::current();
A LatticeLayout is a small value-type handle; copy it freely. Two handles
compare equal when they refer to the same layout, and since there is only one
layout per lattice size (see below), that is the
same as having the same lattice size. Handles have < and std::hash, so they
can be keys of std::map and std::unordered_map. A default-constructed
LatticeLayout() refers to no layout; its index() is -1.
Layouts are never destroyed before QDP_finalize. A handle stays valid for the
whole program from its point of creation.
Creating coarse layouts¶
There are two ways to get another layout. Both return the layout without changing the current layout.
By blocking an existing layout. layout.coarsen(blocking) divides the
lattice size by blocking in each dimension:
multi1d<int> blocking(Nd);
blocking = 2; // 2^4 blocks
LatticeLayout coarse = fine.coarsen(blocking); // 8 8 8 16
The blocking must divide the lattice size in every dimension, otherwise the program stops with
LatticeLayout::coarsen: blocking 3 1 1 1 does not divide the lattice 4 4 4 4
A blocking of 1 in every dimension returns the layout itself.
By lattice size. Layout::find_or_create(nrow) returns the layout with
lattice size nrow, creating it if there is none yet:
multi1d<int> nrow(Nd);
nrow = 4;
LatticeLayout l = Layout::find_or_create(nrow);
coarsen is a shorthand for find_or_create(lattSize() / blocking). There is
never more than one layout for a given lattice size, so asking for the same
size again, by either function, returns the same layout.
Repeated blocking¶
Because layouts are identified by their lattice size, blocking in steps gives the same layout as blocking in one go:
multi1d<int> bt(Nd), bs(Nd), b(Nd);
bt = 1; bt[3] = 2; // 1 1 1 2
bs = 2; bs[3] = 1; // 2 2 2 1
b = 2; // 2 2 2 2
assert(fine.coarsen(bt).coarsen(bs) == fine.coarsen(b));
A multigrid hierarchy can therefore be built level by level, each level by coarsening the one above it, and fields on a level can be compared or combined with fields made from a different chain of blockings, as long as the lattice sizes agree.
Each layout must fit the node grid¶
All layouts share the machine geometry: the logical grid of ranks, set with
-geom or chosen by QMP when the first layout is created (see
Concepts). Every rank holds part of every
layout. A lattice size therefore fits only if, in every dimension:
- it is divisible by the number of ranks in that dimension, and
- the resulting subgrid extent is a multiple of the minimal extent of the site
layout qdp-jit was built with (
QDP_LAYOUT):
QDP_LAYOUT |
minimal subgrid extent |
|---|---|
lexico |
1 in every dimension |
cb2 (default) |
2 in x, 1 otherwise |
cb3d |
2 in x, 1 otherwise |
This applies to coarse layouts too, and limits how far a lattice can be
coarsened on a given number of ranks. If a layout does not fit, creating it
stops the program, whether through coarsen or find_or_create:
LayoutData::create - problem size 4 4 4 1 does not fit the node grid 1 1 1 2
with a subgrid that is a multiple of 2 1 1 1
This is with the lattice 4 4 4 4 on 2 ranks (-geom 1 1 1 2), blocked by 4
in time: the coarse lattice has 1 site in time, which cannot be split over 2
ranks. With -geom 2 1 1 1 and the cb2 site layout, blocking by 2 in x fails
too: each rank would get 1 site in x, and cb2 needs an even number.
Two earlier checks report the same problem with other messages: a lattice
whose volume is not divisible by the number of ranks stops with
LayoutData::create - problem size not divisible by number of processors,
and one whose size is not a multiple of the minimal subgrid extent (for
example a coarse lattice with 1 site in x on one rank with cb2) stops with
LayoutData::create - Error: problem size not multiple of smallest size allowed
for this type of layout.
Layout::fits_node_grid(nrow) returns whether a lattice size fits, without
creating a layout. Use it to choose a blocking that works on the current number
of ranks.
multi1d<int> nrow = fine.lattSize() / blocking;
if (!Layout::fits_node_grid(nrow))
QDPIO::cerr << "blocking too coarse for this node grid\n";
The layout stack¶
qdp-jit keeps a stack of layouts. The layout on top is the current layout,
Layout::current(). After Layout::create() the stack holds the first layout.
Layout::push(coarse); // coarse is now current
LatticeFermion c; // a field on the coarse layout
Layout::pop(); // fine is current again
push and pop must be balanced. Layout::push(LatticeLayout()) stops the
program (Layout::push called with invalid layout -1), and so does a call of
Layout::current() once the stack has been popped empty. Rather than calling
push and pop by hand, use a LayoutScope, which pops when it
goes out of scope, also when an exception is thrown.
Layouts can be pushed in any order and any number of times. Pushing a layout that is already on the stack is allowed.
A field keeps its layout¶
A field's layout is fixed when the field is constructed (see Construction and copying for where it comes from). Popping the stack does not change the layout of fields created while the layout was current:
{
LayoutScope scope(coarse);
LatticeReal t = zero; // on the coarse layout
...
} // fine is current again; t is gone
LayoutScope scope(coarse);
LatticeReal d = zero; // on the coarse layout
scope.close(); // pop now, d stays alive
assert(d.layout() == coarse);
d[rb[0]] = Real(1.0); // still evaluated on the coarse layout
Double s = sum(d); // coarse volume / 2
Creating a field on a given layout¶
A field can also be created on a layout without touching the stack. The
constructor OLattice(LatticeLayout, Shape) makes a field on the layout and
with the shape you pass, whichever layout and shape are current:
LayoutShapeScope coarse_grid(coarse, 2, 24); // coarse layout, Ns=2, Nc=24
LatticeFermion c; // coarse, Ns=2, Nc=24
LatticeFermion f(fine, Shape(Ns, Nc)); // fine, the configured Ns and Nc
LatticeColorMatrix u(fine, Shape(Ns, Nc)); // fine, shape (1, Nc)
There is no constructor that takes only a layout, on purpose: inside a scope
like the one above, the current shape belongs to the coarse grid, and a fine
field would silently get it. Extents that the type
does not have are set to 1: u above has the shape (1, Nc), since a color
matrix has no spin index. The data format is the current default, as for a
default-constructed field (see Data format).
This is the simplest way to make a field on another layout in the middle of
code that runs on the current one, for example a fine-grid temporary inside a
coarse-grid scope. The fields are ordinary fields: f = psi; with a fine
psi works as usual.
Operations use the field's layout¶
Operations on a field, such as assignment, shift, subsets, random and
reductions, use the field's own layout, not the current one. The current layout
matters only where no field says which layout to use; the
next section lists these places.
Assignment does not move a field to another layout
c = zero; inside a LayoutScope(coarse) does not put c on the coarse
layout if c was constructed on the fine one. Assignment keeps the
destination's layout. Construct the field while the layout you want is
current, or pass the layout to the constructor:
LatticeReal c(coarse, Shape());.
What the current layout affects¶
| What | Uses |
|---|---|
| default-constructed fields | the current layout (and shape) |
fields made with OLattice(layout, shape) |
the layout passed |
the Layout:: query functions |
the current layout |
Layout::latticeCoordinate(mu) |
the current layout |
RNG::setrn, RNG::savern |
the current layout's seed |
random(x) for a scalar x (Real, …) |
the current layout's seed |
random(f) for a lattice field f |
f's layout and its seed |
Set::make, Map::make |
the current layout (see below) |
subset.siteTable(), numSiteTable(), … |
the current layout; pass a layout to choose another |
expressions, shift, subsets in f[s] = … |
the layout of the fields involved |
Lattice coordinates¶
Layout::latticeCoordinate(mu) returns a LatticeInteger on the current
layout, holding each site's coordinate in direction mu. To get coordinates
that match a given field, make the field's layout current first:
LatticeFermion mask_boundary(const LatticeFermion& in, const multi1d<int>& block, int mu)
{
LayoutScope scope(in.layout());
LatticeReal one = 1.0, nil = zero;
LatticeReal m = where((Layout::latticeCoordinate(mu) + 1) % block[mu] == 0, one, nil);
return in * m;
}
Random numbers¶
Each layout has its own random number generator state. When a layout is created, its seed is set to the same default value as the first layout's; seeding one layout does not seed the others. To seed a coarse layout, make it current:
RNG::setrn(seed); // seeds the fine layout only
{
LayoutScope scope(coarse);
RNG::setrn(coarse_seed); // seeds the coarse layout
}
random(f) and gaussian(f) for a lattice field draw from the generator of
f's layout, whichever layout is current. Drawing on a coarse layout leaves
the fine layout's stream unchanged. random(x) for a scalar draws from the
current layout's generator.
Subsets and maps¶
The predefined sets and subsets (all, even, odd, rb, rb3, mcb) and
the nearest-neighbour shifts (shift(f, FORWARD, mu), …) are set up on every
layout when it is created. They work on fields of any layout, with no further
action.
A Set or Map you make yourself is different: Set::make(fn) and
Map::make(fn) compute their tables for the current layout only, and the
function fn sees that layout's coordinates (for example through
Layout::lattSize()). To use one on another layout, call make again with
that layout current. The tables for the other layouts are kept:
Set blocks;
blocks.make(MyBlockFunc()); // tables for the fine layout
{
LayoutScope scope(coarse);
blocks.make(MyBlockFunc()); // and for the coarse layout
}
// blocks[i] now works on fine and on coarse fields
Using a user-made Set or Map on a layout it was not made for stops the
program:
Subset numSiteTable: no site tables for layout 1
Map make_lazy: map not made for layout 1
The site tables of a subset are queried for a given layout with
s.siteTable(layout), s.numSiteTable(layout), s.isElement(layout, i) and
so on. The versions without a layout argument use the current layout.
Reference¶
The Layout namespace¶
Functions marked current act on the current layout.
Setting up the first layout
| Function | Description |
|---|---|
void setLattSize(const multi1d<int>& nrow) |
set the lattice size, before create() |
void create() |
create the layout: site tables, default subsets and shifts, RNG |
void init() |
internal, called by QDP_initialize |
void finalize() |
internal, called by QDP_finalize |
The layout stack and creating layouts
| Function | Description |
|---|---|
LatticeLayout current() |
the layout on top of the stack |
void push(LatticeLayout l) |
make l current |
void pop() |
return to the previous layout |
LatticeLayout find_or_create(const multi1d<int>& nrow) |
the layout with lattice size nrow, created if needed; the stack is unchanged |
bool fits_node_grid(const multi1d<int>& nrow) |
whether a lattice of size nrow fits the node grid |
LatticeLayout from_index(int index) |
the layout with the given index() |
LayoutData& data(int index) |
internal: the layout's data |
Lattice and node geometry (current)
| Function | Description |
|---|---|
const multi1d<int>& lattSize() |
lattice size |
size_t vol() |
lattice volume |
const multi1d<int>& subgridLattSize() |
subgrid size on each rank |
int sitesOnNode() |
subgrid volume |
const multi1d<int>& logicalSize() |
machine geometry |
int numNodes() |
number of ranks |
int nodeNumber() |
this rank |
bool primaryNode() |
whether this is rank 0 |
const multi1d<int>& nodeCoord() |
this rank's coordinates in the node grid |
LatticeInteger latticeCoordinate(int mu) |
each site's coordinate in direction mu |
Site maps (current)
| Function | Description |
|---|---|
int nodeNumber(const multi1d<int>& coord) |
rank that holds the site coord |
int linearSiteIndex(const multi1d<int>& coord) |
linear index of coord on its rank |
int linearSiteIndex(int lexicosite) |
linear index of a lexicographic site number |
multi1d<int> siteCoords(int node, int index) |
coordinates of the site at index on node |
multi1d<int> nodeCoord(const multi1d<int>& coord) |
node-grid coordinates of the rank that holds coord |
int getNodeNumberFrom(const multi1d<int>& node_coord) |
rank at the given node-grid coordinates |
multi1d<int> getLogicalCoordFrom(int node) |
node-grid coordinates of a rank |
I/O node grid (current)
| Function | Description |
|---|---|
bool isIOGridDefined() |
whether an I/O grid is set |
void setIONodeGrid(const multi1d<int>& io_grid) |
set the I/O grid |
int numIONodeGrid() |
number of I/O nodes |
const multi1d<int>& getIONodeGrid() |
the I/O grid |
LatticeLayout¶
The members of a LatticeLayout give the same information for that layout,
whichever layout is current.
| Member | Description |
|---|---|
int index() const |
the layout's number; 0 for the first layout, -1 for none |
const multi1d<int>& lattSize() const |
lattice size |
size_t vol() const |
lattice volume |
const multi1d<int>& subgridLattSize() const |
subgrid size |
int sitesOnNode() const |
subgrid volume |
const multi1d<int>& logicalSize() const |
machine geometry |
int numNodes() const |
number of ranks |
int nodeNumber() const |
this rank |
bool primaryNode() const |
whether this is rank 0 |
int nodeNumber(const multi1d<int>& coord) const |
rank that holds coord |
int linearSiteIndex(const multi1d<int>& coord) const |
linear index of coord on its rank |
multi1d<int> siteCoords(int node, int index) const |
coordinates of a site |
LatticeLayout coarsen(const multi1d<int>& blocking) const |
the layout blocked by blocking |
==, !=, <, std::hash |
comparison, for use as a key |
Fields¶
| Constructor or member | Description |
|---|---|
OLattice() |
a field on the current layout, with the current shape |
OLattice(LatticeLayout layout, const Shape& shape) |
a field on layout with shape, whichever layout and shape are current |
LatticeLayout layout() const |
the field's layout |
The other constructors, and where they take a field's layout from, are on Construction and copying.