Examples

C++ Examples

There are three major classes in exafmm-t:

  • Body<T>: The class for bodies (particles).

  • Node<T>: The class for nodes in the octree.

  • Fmm: The FMM class.

The choice of template parameter T depends on the data type of the potential: T should be set to real_t for real-valued kernels (ex. Laplace and modified Helmholtz), and set to complex_t for complex-valued kernels (ex. Helmholtz).

exafmm-t uses double precision by default, i.e., real_t and complex_t are mapped to double and std::complex<double> respectively. If you want to use single precision, you should still use real_t and complex_t in your code, and add -DFLOAT to your compiler flags which predefines the macro FLOAT as true.

All exafmm-t’s types, classes and functions are in exafmm_t namespace. API documentation can be found in the last section.

Let’s solve a Laplace N-body problem as an example, we first need to create sources and targets. Here we create 100,000 sources and targets that are randomly distributed in a cube from -1 to 1. Their type Bodies is a STL vector of Body.

using exafmm_t::real_t;
std::random_device rd;
std::mt19937 gen(rd());  // random number generator
std::uniform_real_distribution<> dist(-1.0, 1.0);
int ntargets = 100000;
int nsources = 100000;

exafmm_t::Bodies<real_t> sources(nsources);
for (int i=0; i<nsources; i++) {
  sources[i].ibody = i;
  sources[i].q = dist(gen);        // charge
  for (int d=0; d<3; d++)
    sources[i].X[d] = dist(gen);   // location
}

exafmm_t::Bodies<real_t> targets(ntargets);
for (int i=0; i<ntargets; i++) {
  targets[i].ibody = i;
  for (int d=0; d<3; d++)
    targets[i].X[d] = dist(gen);   // location
}

Next, we need to create an FMM instance fmm for Laplace kernel, and set the order of expansion and ncrit. We use the former to control the accuracy and the latter to balance the workload between near-field and far-field.

int P = 8;         // expansion order
int ncrit = 400;   // max number of bodies per leaf
exafmm_t::LaplaceFmm fmm(P, ncrit);

We can then build and balance the octree. The variable nodes represents the tree, whose type is Nodes, a STL vector of Node. To facilitate creating lists and evaluation, we also store a vector of leaf nodes - leafs and a vector of non-leaf nodes - nonleafs. Their type NodePtrs is a STL vector of Node*.

exafmm_t::get_bounds(sources, targets, fmm.x0, fmm.r0);
exafmm_t::NodePtrs<real_t> leafs, nonleafs;
exafmm_t::Nodes<real_t> nodes = exafmm_t::build_tree<real_t>(sources, targets, leafs, nonleafs, fmm);
exafmm_t::balance_tree<real_t>(nodes, sources, targets, leafs, nonleafs, fmm);

Next, we can build lists and pre-compute invariant matrices.

exafmm_t::init_rel_coord();        // compute all possible relative positions of nodes for each FMM operator
exafmm_t::set_colleagues(nodes);   // find colleague nodes
exafmm_t::build_list(nodes, fmm);  // create list for each FMM operator
fmm.M2L_setup(nonleafs);           // an extra setup for M2L operator

Finally, we can use FMM to evaluate potentials and gradients

fmm.upward_pass(nodes, leafs);
fmm.downward_pass(nodes, leafs);

After the downward pass, the calculated potentials and gradients are stored in the leaf nodes of the tree. You can compute the error in L2 norm by comparing with direct summation:

std::vector<real_t> error = fmm.verify(leafs);
std::cout << "potential error: " << error[0] << "\n"
          << "gradient error:  " << error[1] << "\n";

Other examples can be found in examples/cpp folder.

Python Examples

For simplicity, the name of our Python package is just exafmm. It has a separate module for each kernel: exafmm.laplace, exafmm.helmholtz and exafmm.modified_helmholtz.

Compare with C++ interface, exafmm-t’s Python interface only exposes high-level APIs. Now, the steps for tree construction, list construction and pre-computation are merged into one function called setup(). Also, the evaluation now only requires to call one function evalute(). Below are Python examples on Jupyter notebooks.