Algorithm 1: Flow Routing: downstream accumulation
Input : Terrain cells with initial discharge (precipitation) and their donors
Output: Updated accumulated discharge
1 for to do
2 foreach cell in parallel do
3 foreach donor of cell do
4 if is a leaf then
5
6 remove edge
7 end
8 else if has a single donor then
9
10 donor of donor of
11 end
12 end
13 if all donors are removed then
14 tag as leaf
15 end
16 end
17 end
With the exception of some cells marked by the outflow mask, stream trees cover all of the terrain, which makes the design of an efficient parallel solution to Equation 1 non-trivial.
Fortunately, this problem can be cast as an instance of Parallel Tree Accumulation, which aims to compute the cumulative sum of quantities in tree nodes, from leaf to root. This can be achieved with straightforward pointer jumping, but this may result in a span due to the required atomic operations. An alternative is to use an Euler tour [SAF05] along the tree edges combined with pointer jumping, which reduces the span to , since the number of edges is less than the number of nodes. However, this method typically requires iterations, as each edge is traversed twice.

Figure 3: A sequence of five diagrams showing the evolution of a flow routing solution over iterations. The first diagram, 'Initial discharge (precipitation)', shows a network of nodes with values (6, 14, 10, 9, 17, 20, 11, 5) and a path of leaf nodes (2, 1, 10, 17, 5) highlighted in blue. The subsequent diagrams, 'Iteration 1', 'Iteration 2', and 'Iteration 3', show the iterative pruning of leaf nodes (green) and rerouting of paths (blue). The final diagram, 'Final discharge', shows the completed network with a final discharge value of 95.
Figure 3: A few iterations of a parallel rake-compress solution to flow routing. On each iteration, leaf nodes (in green) are pruned and recipients of cells with a single donor (in blue) are rerouted.
A still more efficient approach is rake-compress, which combines pointer jumping with leaf pruning to achieve a span of . This method typically needs only iterations, making it faster in practice than the Euler tour. Sevilgen et al. [SAF05] provide a starting point in the form of a general rake-compress solution to graph accumulation problems for distributed architectures. This relies on the observation that leaves constitute half the nodes in a balanced binary tree. So, if we iteratively transfer accumulated discharge from a leaf to its recipient and then prune the leaf and do this in parallel across all leaves, we only need to iterate times where is the number of nodes in the terrain.
In general, river trees might be neither binary nor balanced. In the worst case, where a river tree is composed entirely of single donors, the iterations degrade to . To compensate, we use pointer jumping by re-routing the edges of cells whose donor itself has a single donor (parent). Effectively, the parent (donor) is skipped and an edge is formed directly from the grandparent (donor of the donor). Thereby, worst-case performance is restored to parallel iterations.
As shown in Algorithm 1, with an accompanying example in Figure 3, on each iteration and for each terrain cell , we parse each donor in parallel, with up to 4 donors in our implementation. If is a leaf, we add its discharge to and remove it from the donor matrix. Alternatively, if has only a single donor, we again add its discharge to but in this case reconfigure the donor matrix so that instead points at the donor of . Distinguishing between these cases requires a single lookup in the donor count list, e.g., leaves have a donor count of zero. Straightforward extensions of Algorithm 1 can be used for other flow accumulation functions, including those involving maxima or weighted sums of upstream values.
Note that some care must be taken in the implementation of Algorithm 1, which exhibits potential read-after-write (RAW) hazards when updating the discharge and recipients. An immediate solution is to write the updated and to temporary arrays and copy them back at the end of each iteration. Unfortunately, this forces the unnecessary transfer on each iteration of a large number of cells that are unmodified. Instead, we propose a per-cell ping pong scheme. We first allocate sufficient space to hold the state of two full terrains, and . We then introduce an additional source buffer with one 8-bit integer per cell, initialized to 0. The sign of the integer at cell in the source indicates whether data should be read from and written to , or vice-versa. When cells are updated,
the sign is flipped. To avoid RAW hazards within the same iteration, the remaining bytes of each source entry store the iteration at which was last updated: a thread will check the source to negate the read sign if the stored iteration is the current one. At the end of each iteration, the latest state of each cell is either in or , as indicated by source. Hence, a final merge step is required before returning the result as a single tensor. Incorporating this optimization leads to a speed up on a terrain.