Skip to content

Commit 7109232

Browse files
committed
Reuse the neighbour search between Get and the neighbour getters
Get (and GetHost) now accept a LookupTableNeighbours structure in which they store the search they performed. Giving that same structure to GetNeighbours or GetNeighboursIndx makes them reuse this search instead of walking the table a second time, and they search it again as usual as soon as different coordinates are requested. The structure is held by the caller rather than by the lookup table, so that it is thread-private when it is declared inside an idefix_for: a lookup table is shared by all of the threads of a loop, and can hence not cache anything itself. The existing Get is left untouched.
1 parent e58ff2d commit 7109232

3 files changed

Lines changed: 350 additions & 9 deletions

File tree

doc/source/programmingguide.rst

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -676,6 +676,46 @@ For instance, the 2D table ``example2D.csv`` above, interpolated in (x=2.1, y=3.
676676
The same methods without the ``Host`` suffix can be called from within an ``idefix_for`` loop, the arrays being then
677677
local to the loop.
678678

679+
Both ``Get`` and the methods above search the table for the cell surrounding the requested coordinates. When the
680+
interpolated value *and* its neighbours are needed for the same coordinates, this search can be performed only once,
681+
by handing a ``LookupTableNeighbours<nDim>`` structure to ``Get``: the search it stores is then reused by
682+
``GetNeighbours`` and ``GetNeighboursIndx`` instead of being done a second time.
683+
684+
.. code-block:: c++
685+
686+
idefix_for("loop",0, 10, KOKKOS_LAMBDA (int i) {
687+
real x[2];
688+
x[0] = 2.1;
689+
x[1] = 3.5;
690+
691+
// The search performed by Get is stored in neighbours...
692+
LookupTableNeighbours<2> neighbours;
693+
real value = csv.Get(x, neighbours);
694+
695+
// ... and is reused by the two methods below, which do not search the table again
696+
real xN[4];
697+
real dataN[4];
698+
int idx[2];
699+
int dataIdx[4];
700+
csv.GetNeighbours(x, neighbours, xN, dataN);
701+
csv.GetNeighboursIndx(x, neighbours, idx, dataIdx);
702+
});
703+
704+
The very same methods are available on the host, as ``GetHost``, ``GetNeighboursHost`` and ``GetNeighboursIndxHost``.
705+
The structure gives access to the search itself, ``neighbours.idx[n]`` being the index of the left neighbour along
706+
the dimension ``n``, and ``neighbours.delta[n]`` the elementary ratio used to weight the two neighbours of that
707+
dimension.
708+
709+
.. note::
710+
The stored search is only reused when the coordinates it was computed for are the ones being requested. Calling
711+
``GetNeighbours`` or ``GetNeighboursIndx`` with different coordinates simply searches the table again, and updates
712+
the structure accordingly, so that a ``LookupTableNeighbours`` can be reused from one point to the next.
713+
714+
.. warning::
715+
The structure is declared by the caller, and not by the lookup table itself: when it is used inside an
716+
``idefix_for`` loop, it should be declared *inside* the loop, so that each thread has its own copy. A lookup table
717+
is shared by all of the threads of a loop, and can therefore not store anything of the sort itself.
718+
679719
.. note::
680720
When the table is interpolated in function space (see above), ``xN`` and ``dataN`` are returned in the original
681721
space of the table, i.e. ``invFunc`` is applied to the values which are stored in ``xin`` and ``data``. The

src/utils/lookupTable.hpp

Lines changed: 160 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,34 @@ struct LookupTableExp {
3131
}
3232
};
3333

34+
// Neighbours of a point of a lookup table, as they are found by the search performed by Get:
35+
// idx[n] is the index of the left neighbour along the dimension n, and delta[n] is the elementary
36+
// ratio used to weight the two neighbours of that dimension.
37+
// Get (and GetHost) fill this structure when it is given as an argument, and GetNeighbours and
38+
// GetNeighboursIndx then reuse the search it contains instead of performing it a second time
39+
// (which is only done when they are called with the same coordinates: they search the table again
40+
// as usual when a different x is requested).
41+
// This structure is kept by the caller, so that it is thread-private when it is declared inside
42+
// an idefix_for loop. The lookup table itself is shared by all of the threads of a loop, and can
43+
// therefore not be used to store anything of the sort.
44+
template <const int kDim>
45+
struct LookupTableNeighbours {
46+
real x[kDim]; // coordinates for which the neighbours below were computed
47+
int idx[kDim]; // index of the left neighbour along each dimension
48+
real delta[kDim]; // elementary ratio between the two neighbours of each dimension
49+
bool valid{false}; // whether the neighbours above have been successfully computed
50+
51+
// Check whether this structure already holds the neighbours of the coordinates xIn
52+
KOKKOS_INLINE_FUNCTION
53+
bool Matches(const real xIn[kDim]) const {
54+
if(!valid) return(false);
55+
for(int n = 0 ; n < kDim ; n++) {
56+
if(x[n] != xIn[n]) return(false);
57+
}
58+
return(true);
59+
}
60+
};
61+
3462
template <const int kDim, class TFunc = LookupTableLog, class TInvFunc = LookupTableExp>
3563
class LookupTable {
3664
public:
@@ -276,6 +304,59 @@ class LookupTable {
276304
return(value);
277305
}
278306

307+
// Fill "neighbours" with the neighbours of x, unless it already holds them (in which case the
308+
// table is not searched again). This is what allows Get, GetNeighbours and GetNeighboursIndx to
309+
// share a single search when they are called successively with the same coordinates.
310+
template<typename Tint, typename Treal>
311+
KOKKOS_INLINE_FUNCTION
312+
void SearchNeighbours(const real x[kDim], Tint &dimensions, Tint &offset, Treal &xin,
313+
LookupTableNeighbours<kDim> &neighbours) const {
314+
// Nothing to do if the neighbours of these very coordinates are already known
315+
if(neighbours.Matches(x)) return;
316+
317+
neighbours.valid = GetIndices(x, dimensions, offset, xin, neighbours.idx, neighbours.delta);
318+
for(int n = 0 ; n < kDim ; n++) {
319+
neighbours.x[n] = x[n];
320+
}
321+
}
322+
323+
// Generic getter which stores in "neighbours" the elements of the table it used, so that a
324+
// subsequent call to GetNeighbours or GetNeighboursIndx with the same coordinates does not
325+
// search the table again
326+
template<typename Tint, typename Treal>
327+
KOKKOS_INLINE_FUNCTION
328+
real Get(const real x[kDim], Tint &dimensions, Tint &offset, Treal &xin, Treal &data,
329+
LookupTableNeighbours<kDim> &neighbours) const {
330+
SearchNeighbours(x, dimensions, offset, xin, neighbours);
331+
332+
if(!neighbours.valid) return(NAN);
333+
334+
// Do a linear interpolation from the neightbouring points to get our value.
335+
real value = 0;
336+
337+
// loop on all of the vertices of the neighbours
338+
for(unsigned int n = 0 ; n < (1 << kDim) ; n++) {
339+
real weight = 1.0;
340+
for(unsigned int m = 0 ; m < kDim ; m++) {
341+
unsigned int myBit = 1 << m;
342+
// If bit is set, we're doing the right vertex, otherwise we're doing the left vertex
343+
if((n & myBit) > 0) {
344+
// We're on the right
345+
weight = weight*neighbours.delta[m];
346+
} else {
347+
// We're on the left
348+
weight = weight*(1-neighbours.delta[m]);
349+
}
350+
}
351+
value = value + weight*data(GetDataIndex(dimensions, neighbours.idx, n));
352+
}
353+
354+
// The interpolation was performed on func(data), so we transform the result back
355+
if(interpolateInFuncSpace) value = invFunc(value);
356+
357+
return(value);
358+
}
359+
279360
// Generic getter for the neighbours used by the interpolation, for all kinds of input arrays.
280361
// On output, xN[2*n] and xN[2*n+1] are the coordinates bracketing x[n] along the dimension n,
281362
// and dataN[v] is the data at the vertex v of these neighbours (see GetDataIndex for the
@@ -286,19 +367,30 @@ class LookupTable {
286367
KOKKOS_INLINE_FUNCTION
287368
void GetNeighbours(const real x[kDim], Tint &dimensions, Tint &offset, Treal &xin, Treal &data,
288369
real xN[2*kDim], real dataN[1 << kDim]) const {
289-
int idx[kDim];
290-
real delta[kDim];
370+
LookupTableNeighbours<kDim> neighbours;
371+
GetNeighbours(x, dimensions, offset, xin, data, neighbours, xN, dataN);
372+
}
373+
374+
// Same as above, but the search is stored in (and reused from) "neighbours": the table is only
375+
// searched again when "neighbours" does not already hold the neighbours of x, e.g. because it
376+
// was filled by a previous call to Get with these very same coordinates.
377+
template<typename Tint, typename Treal>
378+
KOKKOS_INLINE_FUNCTION
379+
void GetNeighbours(const real x[kDim], Tint &dimensions, Tint &offset, Treal &xin, Treal &data,
380+
LookupTableNeighbours<kDim> &neighbours,
381+
real xN[2*kDim], real dataN[1 << kDim]) const {
382+
SearchNeighbours(x, dimensions, offset, xin, neighbours);
291383

292-
if(!GetIndices(x, dimensions, offset, xin, idx, delta)) {
384+
if(!neighbours.valid) {
293385
for(int n = 0 ; n < 2*kDim ; n++) xN[n] = NAN;
294386
for(unsigned int n = 0 ; n < (1 << kDim) ; n++) dataN[n] = NAN;
295387
return;
296388
}
297389

298390
// Coordinates of the neighbours along each dimension
299391
for(int n = 0 ; n < kDim ; n++) {
300-
xN[2*n] = xin(offset(n) + idx[n]);
301-
xN[2*n+1] = xin(offset(n) + idx[n]+1);
392+
xN[2*n] = xin(offset(n) + neighbours.idx[n]);
393+
xN[2*n+1] = xin(offset(n) + neighbours.idx[n]+1);
302394
if(interpolateInFuncSpace) {
303395
xN[2*n] = invFunc(xN[2*n]);
304396
xN[2*n+1] = invFunc(xN[2*n+1]);
@@ -307,7 +399,7 @@ class LookupTable {
307399

308400
// Data on each vertex of the neighbours
309401
for(unsigned int n = 0 ; n < (1 << kDim) ; n++) {
310-
dataN[n] = data(GetDataIndex(dimensions, idx, n));
402+
dataN[n] = data(GetDataIndex(dimensions, neighbours.idx, n));
311403
if(interpolateInFuncSpace) dataN[n] = invFunc(dataN[n]);
312404
}
313405
}
@@ -321,16 +413,30 @@ class LookupTable {
321413
KOKKOS_INLINE_FUNCTION
322414
void GetNeighboursIndx(const real x[kDim], Tint &dimensions, Tint &offset, Treal &xin,
323415
int idx[kDim], int dataIdx[1 << kDim]) const {
324-
real delta[kDim];
416+
LookupTableNeighbours<kDim> neighbours;
417+
GetNeighboursIndx(x, dimensions, offset, xin, neighbours, idx, dataIdx);
418+
}
419+
420+
// Same as above, but the search is stored in (and reused from) "neighbours", exactly like the
421+
// GetNeighbours variant above
422+
template<typename Tint, typename Treal>
423+
KOKKOS_INLINE_FUNCTION
424+
void GetNeighboursIndx(const real x[kDim], Tint &dimensions, Tint &offset, Treal &xin,
425+
LookupTableNeighbours<kDim> &neighbours,
426+
int idx[kDim], int dataIdx[1 << kDim]) const {
427+
SearchNeighbours(x, dimensions, offset, xin, neighbours);
325428

326-
if(!GetIndices(x, dimensions, offset, xin, idx, delta)) {
429+
if(!neighbours.valid) {
327430
for(int n = 0 ; n < kDim ; n++) idx[n] = -1;
328431
for(unsigned int n = 0 ; n < (1 << kDim) ; n++) dataIdx[n] = -1;
329432
return;
330433
}
331434

435+
for(int n = 0 ; n < kDim ; n++) {
436+
idx[n] = neighbours.idx[n];
437+
}
332438
for(unsigned int n = 0 ; n < (1 << kDim) ; n++) {
333-
dataIdx[n] = GetDataIndex(dimensions, idx, n);
439+
dataIdx[n] = GetDataIndex(dimensions, neighbours.idx, n);
334440
}
335441
}
336442

@@ -369,6 +475,51 @@ class LookupTable {
369475
void GetNeighboursIndxHost(const real x[kDim], int idx[kDim], int dataIdx[1 << kDim]) const {
370476
GetNeighboursIndx(x, dimensionsHost, offsetHost, xinHost, idx, dataIdx);
371477
}
478+
479+
// Getter on device, which stores the neighbours it used in "neighbours". Giving that same
480+
// structure to GetNeighbours or GetNeighboursIndx below then avoids searching the table twice.
481+
KOKKOS_INLINE_FUNCTION
482+
real Get(const real x[kDim], LookupTableNeighbours<kDim> &neighbours) const {
483+
return(Get(x, dimensionsDev, offsetDev, xinDev, dataDev, neighbours));
484+
}
485+
486+
// Getter on Host, which stores the neighbours it used in "neighbours"
487+
KOKKOS_INLINE_FUNCTION
488+
real GetHost(const real x[kDim], LookupTableNeighbours<kDim> &neighbours) const {
489+
return(Get(x, dimensionsHost, offsetHost, xinHost, dataHost, neighbours));
490+
}
491+
492+
// Getter for the neighbours used by the interpolation, on device, reusing the search stored in
493+
// "neighbours" when it was performed for these very same coordinates
494+
KOKKOS_INLINE_FUNCTION
495+
void GetNeighbours(const real x[kDim], LookupTableNeighbours<kDim> &neighbours,
496+
real xN[2*kDim], real dataN[1 << kDim]) const {
497+
GetNeighbours(x, dimensionsDev, offsetDev, xinDev, dataDev, neighbours, xN, dataN);
498+
}
499+
500+
// Getter for the neighbours used by the interpolation, on Host, reusing the search stored in
501+
// "neighbours" when it was performed for these very same coordinates
502+
KOKKOS_INLINE_FUNCTION
503+
void GetNeighboursHost(const real x[kDim], LookupTableNeighbours<kDim> &neighbours,
504+
real xN[2*kDim], real dataN[1 << kDim]) const {
505+
GetNeighbours(x, dimensionsHost, offsetHost, xinHost, dataHost, neighbours, xN, dataN);
506+
}
507+
508+
// Getter for the indices of the neighbours, on device, reusing the search stored in
509+
// "neighbours" when it was performed for these very same coordinates
510+
KOKKOS_INLINE_FUNCTION
511+
void GetNeighboursIndx(const real x[kDim], LookupTableNeighbours<kDim> &neighbours,
512+
int idx[kDim], int dataIdx[1 << kDim]) const {
513+
GetNeighboursIndx(x, dimensionsDev, offsetDev, xinDev, neighbours, idx, dataIdx);
514+
}
515+
516+
// Getter for the indices of the neighbours, on Host, reusing the search stored in
517+
// "neighbours" when it was performed for these very same coordinates
518+
KOKKOS_INLINE_FUNCTION
519+
void GetNeighboursIndxHost(const real x[kDim], LookupTableNeighbours<kDim> &neighbours,
520+
int idx[kDim], int dataIdx[1 << kDim]) const {
521+
GetNeighboursIndx(x, dimensionsHost, offsetHost, xinHost, neighbours, idx, dataIdx);
522+
}
372523
};
373524

374525
template <int kDim, class TFunc, class TInvFunc>

0 commit comments

Comments
 (0)