Using the Python and Julia bindings
Both bindings wrap the C interface and require the shared
library libpvfmm.so — build it first and point the PVFMM environment
variable at the directory containing it (see Python interface and
Julia interface for installation details).
Python: particle FMM
Based on python/examples/example1.py. For a single process just run
python example1.py; to use several ranks, launch with
mpirun -n 2 python -m mpi4py example1.py and pass an mpi4py communicator.
import numpy as np
import pvfmm
N = 20000
src_pos = np.random.rand(3 * N) # coordinates in [0,1]^3, AoS layout
src_den = np.random.rand(3 * N) - 0.5 # Biot-Savart: 3 values per source
trg_pos = np.random.rand(3 * N)
fmm = pvfmm.FMMParticleContext(
box_size=-1, # <= 0: free space
max_points=1000, # max points per leaf
multipole_order=10, # accuracy (even)
kernel=pvfmm.FMMKernel.BiotSavartPotential,
# comm omitted: uses the world communicator from the library, so mpi4py is
# not needed. Pass comm=MPI.COMM_WORLD (from mpi4py) to run across ranks.
)
trg_val = fmm.evaluate(src_pos, src_den, None, trg_pos) # with setup
trg_val = fmm.evaluate(src_pos, src_den, None, trg_pos, setup=False) # densities only
The example verifies the result against a direct sum compiled with numba.
Python: volume FMM
Based on python/examples/example2.py (Stokes velocity). The source density
callback must be a C function pointer; numba.cfunc produces one without
writing C:
import numba
import pvfmm
@numba.cfunc(numba.types.void(
numba.types.CPointer(numba.types.double), # coord (3n)
numba.types.int64, # n
numba.types.CPointer(numba.types.double), # out (n * data_dim)
numba.types.voidptr), nopython=True) # ctx
def fn_input_C(coord_, n, out_, ctx):
coord = numba.carray(coord_, n * 3)
out = numba.carray(out_, n * 3)
# ... evaluate the density ...
fmm = pvfmm.FMMVolumeContext(multipole_order, cheb_deg,
pvfmm.FMMKernel.StokesVelocity, comm)
tree = pvfmm.FMMVolumeTree.from_function(
cheb_deg, 3, fn_input_C.ctypes, None, trg_coord, comm,
tol=1e-6, max_pts=100, periodic=False, init_depth=0)
trg_val = tree.evaluate(fmm, loc_size=len(trg_coord) // 3)
The coefficient path mirrors the C example: prepare density values on the
tensor-product Chebyshev nodes of chosen leaves, convert with
pvfmm.nodes_to_coeff, build with FMMVolumeTree.from_coefficients, and
read the result back with tree.get_coefficients() / tree.get_values().
Julia: particle FMM
Based on julia/test/reference_comparison.jl:
ENV["PVFMM"] = "/path/to/dir-containing-libpvfmm"
using PVFMM
N = 10000
src_pos = rand(3N); src_den = rand(3N) .- 0.5; trg_pos = rand(3N)
# comm omitted: the world communicator is obtained from the library, so no
# MPI.jl dependency is needed. Pass MPI.COMM_WORLD explicitly if you use MPI.jl.
ctx = FMMParticleContext(-1.0, 1000, 10, PVFMM.StokesVelocity)
trg_val = evaluate(ctx, src_pos, src_den, nothing, trg_pos)
trg_val = evaluate(ctx, src_pos, src_den, nothing, trg_pos; setup=false)
Per-axis periodicity is selected with the boundary keyword (e.g.
boundary=PVFMM.PX with box_size > 0). The volume-FMM path
(FMMVolumeContext, from_function / from_coefficients, evaluate,
get_values) follows the Python API one-to-one — see Julia interface.