Copyright (C) 1998 John Salmon
Permission is granted to make and distribute verbatim copies of
this manual provided the copyright notice and this permission notice
are preserved on all copies.
@raisesections
The oct-trees described in this manual are hierachical, adaptive data structures that allow for rapid searching of spatial data. They are motivated by a desire to perform large-scale, numerically intensive algorithms such as Barnes-Hut, Greengard-Rokhlin and relatives, but it is hoped that they are general enough to be used in other pursuits. The prefix "oct" refers to the fact that eight sub-cells arise from dividing a three-dimensional cube is in half in each dimension. The oct-tree libraries described here are NOT restricted to three dimensions. One and two-dimensional trees are also supported, and higher dimensions could be supported with a small amount of additional code. The data structures should more properly be called @ifnottex 2**d-tree, but that is both too hard to render in text, and too hard to pronounce. Since the majority of applications to-date have been in three-dimensions, we will continue to call the data structures oct-trees, and occasionally refer to "the eight daughter cells", and allow the reader to re-interpret for his own preferred dimensionality.
Oct-trees are a generalization of binary trees, which have been part of the repertoire of computer science since the very early days. They are also closely related to k-d trees and other data structures. The goal of this library is to make oct-trees available in a potentially distributed memory, parallel computing context. The library attempts to separate the "physics" from the "computer science", and to perform all the necessary bookkeeping so that users can focus on the physics (loosely defined) of their application without worrying about the bookkeeping details of managing a large, irregular parallel data structure. Out-of-core computation is also supported on one or more processors, so extremely large data structures can be managed by moderate computational resources.
For the purposes of this library, oct-trees are defined by a collection of points inside a rectangular volume. Conceptually, the volume is subdivided hierarchically into eight sub-subvolumes (called cells) by dividing it in half in each dimension. Each cell is further subdivided recursively, so that an infinite hierachy results. Thus, there is one cell at level 0, eight at level 1, 64 at level 2, and more generally, @ifnottex 8**l at level l. A set of points and an occupancy parameter, m, defines a subset of this infinite hierarchy by the simple rule:
A cell is part of the oct-tree defined by the point set @ifnottex P_i and the occupancy parameter, m, if and only if m or more of the points in the set are contained within the cell.
This particular tree definition is motivated by the primary use of the oct-trees in this library for locating neighbors and groups of neighbors in large particle simulations. Other applications (e.g., adaptive mesh refinement) might be better served by a somewhat different different definition. It is worth noting that in our oct-trees the refinement level of neighboring cells is not restricted in any way. Two adjacent cells may be at arbitrary levels of the hierarchy, depending on the local density of points in the defining point-set.
The design of the library is guided by the observation that modern supercomputers are
sib\_group
data
structure rather than individual cells
reflects this design choice.
The oct-tree library uses a specialized coordinate system that provides a unified way to refer to cells of the tree and to locations in three-dimensional space. The coordinate system is based on long integers keys (typically 64-bit) which are constructed by interleaving the bits of a more conventional cartesian representation. Floating point coordinates are avoided in the internal representations used by the oct-tree library because of their susceptibility to roundoff error, underflow, etc. However, utility functions are provided which allow for easy conversion between floating point and key-based coordinates.
At level L of the tree we have a @ifnottex 2**L x 2**L x 2**L array of (potential) cells. We can label each of these cells by a cartesian triple of L-bit binary values. For example, the L=3 level of a two-dimensional quad-tree is shown in Figure???.
The Morton order key for each cell in Figure??? is obtained by interleaving the bits of the cell's cartesian coordinate and prepending a one-bit to the beginning. The Morton order keys are shown in Figure ???. Keys defined this way are very useful for identifying cells in a hierarchy. They have the following useful properties:
The Morton-ordered keys have another interesting property. They are almost continuous. That is, the path defined by the the sequence of keys from 0 to @ifnottex 2**Ld - 1, stays in one region of space for a long time and occasionally undergoes long "jumps" to another part of the domain. The line in Figure?? shows the path mapped out by increasing Morton-order keys. This continuity property is actually extremely useful. Locality of references in computer memory generally leads to good cache performance and can be exploited to minimize network and disk bandwidth as well.
In fact, there is another ordering that is even better than Morton ordering for prerserving locality. The Peano-Hilbert ordering is actually a continuous mapping from the one-dimensional key-space into the d-dimensional cartesian space. That is, the path defined by the sequence of Peano-Hilbert keys from 0 to @ifnottex 2**Ld - 1, always moves between adjacent cells. The Peano-Hilbert mapping also satisifies the same three criteria mentioned above for Morton ordering. The Peano-Hilbert keys for the two-dimensional space are shown in Figure???.
The tree library works with either Morton keys Peano-Hilbert keys. Conversion routines are provided that allow for conversion from cartesian integer coordinates to and from Morton and Peano-Hilbert keys.
The header file <key.h>
defines the type Key_t
and the
constant KEYBITS
and contains prototypes for a the functions
that operate on Key_t
s.
unsigned long int
values.
Key_t
.
if the corresponding predicate is true for the two keys.
These functions typically require a specification of the dimensionality of the underlying problem, i.e., the ndim argument should be 3 for oct-trees, 2 for quad-trees, etc.
All key conversion routines (including the definition of bbox_t
are in the header file <keycvt.h>
. Key Conversion functions
are logically layered on top of the Key Arithmetic functions.
That is, they use the functions provided by <key.h>
to perform
all necessary manipulations of Key_t
objects. There is nothing
special about these conversion functions. They provide a convenient
way of converting from floating-point or integer coordinate systems
to Key_t
s, but application codes are free to
generate Key_t
s objects in any way they wish.
KeyFromInts
call.
KeyFromInts
, but generate a Peano-Hilbert key instead.
KeyFromInts
, but input is a Peano-Hilbert key instead.
Conversion from a Cartesian floating point coordinate system to
to Key_t
s requires an intermediate conversion to a
Cartesian integer system. One way to define such conversions
is with respect to a bounding box in the floating point system.
Integer coordinates are defined by dividing the bounding box into
INT_MAX
equal intervals in each cartesian direction.
Typically, the application program will define a UniverseBbox
and do all float to key conversions with respect to it.
int ndim
float rmin[ndim]
float sz[ndim]
These members are exposed, and may be manipulated by application code.
Several functions are defined to support common operations on tbbox
s.
Two functions are provided to convert between cartesian integer and
floating point representations. Applications may use these in conjunction
with KeyFromInts
and IntsFromKey
, or they may wish to use
the more succinct (but less flexible) GenerateKeys
instead.
CellBBFromKey
and GenerateKeys
, which selects between the
two supported methods of ordering the key space. Accordingly, it may
take on one of the two values:
MORTON_ORDER
PEANO_ORDER
IntsFromFloats
and then the appropriate one of KeyFromInts
or
KeyFromPHInts
, depending on the value of ordering.
Return KeyInt(0x0)
if thef coordinates are outside the bounding
box specified by universe.
Key_t
to a cartesian floating
point coordinate system.
DISCUSSION!!! motivation, etc. Swapping policy, PgId, lack of coherence.
Only one paging system may be operational in a program. It is neither reentrant nor thread-safe.
tmpnam
. In this case, unlink
is called immediately
after the file is opened. On Unix systems, this makes the file
invisible to directory searches, but it remains open until processes
that have it open (e.g., the process that called PgInit
) terminate.
There is only "namespace" for all the pages used by a program - no matter
whether they are used by libraries, user code, etc. PgAlloc
and
PgFree
provide a mechanism whereby different subsystems can
coordinate their allocation and deallocation of PgId
s without
interference.
PgId
that refers to an unused page in the calling process.
PgAlloc
in the calling process,
or if it has already been freed.
PgRef
, and passed
as an argument to PgUnref
, PgRefef
and PgAddr
.
PgHndl
object which
can be used for subsequent accesses to the page. Increment the reference
count associated with the page. Every effort has been
made to optimize the (hopefully) common case in which the page is alredy
in memory, in which case, PgRef
is very fast. If the page is
not in memory, PgRef blocks until either I/O system (either disk
or network) delivers the page. This may be up to several milliseconds for
a page on disk. If the page is on a remote processor, it will not be
returned until the remote processor calls PagePoll
, or until it
also blocks on a non-local PgRef
.
PgRef
call.
A page will not move in primary storage until PgUnref
is called,
so the pointer returned by PgAddr
is valid (at least) until
PgUnref
is called. PgAddr
does nothing more than return
a field from the opaque PgHndl
structure. It is inlined where
possible.
PgHndl
, and for some reason we want to guarantee that it
stays around, but cannot avoid calling PgUnref
(e.g., you're inside
a callback in a tree traversal).
PgUnref
.
PG_CLEAN
PG_DIRTY
PgAddr
will remain valid at least until PgUnref
is called.
The application tells the library whether the contents of the page were
modified via the dirtied argument. When the space in the primary
working set is reclaimed, the library checks whether any of the accumulated
PgUnrefs
specified PG_DIRTY, and if so, the new data is
written back to disk storage. Otherwise, the writeback is unnecessary
and is skipped.
PG_STAT_REMOTE
PG_STAT_INMEM
PG_STAT_REFED
PG_STAT_DIRTY
PgUnref
s have declared the page to be dirty
since it was last written to disk.
The paging library maintains several counters which record
cumulative information about the status of its internal data structures.
In a future release, this set will be more complete, and the
values will be fields in a PgInfo
structure.
PgInit
.
PgRef
found its target page already in memory.
The paging library is polled. It does not start a separate thread to respond to remote requests. Therefore, if your code is must respond to remote requests, then it must occasionally poll for them. These two routines do that.
PagePollTillDone
. This function acts as a barrier, but
the caller continues to respond to remote page requests while waiting
for their peers to reach the barrier.
oocptr
is a structure containing two
fields. Its allows one to store information about out-of-core objects
that are smaller than an individual page.
As with void
pointers
in C, the intepretation of the data is left to the caller. The fields are:
PgId pgid
unsigned int offset
PgHndl
that should be passed
to PgUnref
when the pointer is no longer needed.
OOCFollow is implemented (where possible) as an inline function
equivalent to the following:
INLINE void *OOCFollow(oocptr ooc, PgHndl *hndl){ *hndl=PgRef(ooc.pgid); return (void *)( ((char *)PgAddr(*hndl)) + ooc.offset ); }
Since an oct-tree is defined by a point-set, it is natural to construct a tree by specifying a point-set. Points are added one at a time, and each point may be associated with arbitrary user-data when it is inserted into the tree. Exactly which cells exist in the tree are implicit in this scheme. That is, the application program does not explicitly identify the cells of the tree. The cells are constructed by the library. Whenever a cell is constructed callback functions (registered when the tree was initialized) are invoked so that the application can associate data with each cell in the tree.
This section describes programmer-visible data types declared in
<tree17.h>
tree_t
is an argument to all of the tree
construction functions.
sib_group
represents a cubical (or square in two dimensions, etc.)
volume of space.
The following function pointer typedefs are used as for callback routines that the user must supply to the tree building functions.
A typedef of a function pointer with prototype: size_t (*makeIntermediateCell_t) ( Key_t key, int ntbody, const _tbody **tbodyptrs, int ncell, const _cellDataIntermediate intermediates, _cellDataIntermediate result)
The following 'pseudo-typedefs' are provided.
The pseudo-typedefs are actually `#define' macro definitions of the form:
#ifndef _tbody #define _tbody void #endif
These are used as argument types in callback functions passed to the
tree building routines. They exist to work around an annoying feature
of ANSI C compilers with strict type checking.
If the programmer inserts his own #definition
for a pseudo-typedef ahead of the #include "tree17.h"
then
it is possible to declare the callback functions with correct, user-specific
arguments. For example, one can write:
#define _tbody mybody #define _cellDataIntermediate cofm #include "tree17.h" size_t computeCofm(Key_t, int, const mybody **, int, const cofm **, cofm *);
and computeCofm
can be assigned to a makeIntermediateCell_t
without generating spurious warnings about mismatched types. Without the
pre-definition of _tbody
and _cellDataIntermediate
, warnings
would be generated because a function that takes a cofm *
argument
is not strictly equivalent to a function that takes a void *
argument.
This section describes the functions available for building oct-trees.
@lowersections
This document was generated on 2 November 1998 using the texi2html translator version 1.51.