Skip to content
Projects

tree_d_3d.smooth

A line-charge FMM with smooth cell membership.

Forces are the exact gradient of a smooth potential energy, and mutual pair forces are exactly antisymmetric.

This is a NumPy prototype for method research, not a fast code: everything is complex arithmetic on small arrays, and it is meant for a few thousand particles. It separates two things the production tree ties together:

  • a :class:BoxTree: a static set of square cells (split until a cell holds at most leaf_capacity of the positions it was built from). The cells can be kept across time steps, so the interaction lists depend on nothing that moves;
  • a :class:SmoothFMM over that tree for given positions and charges.

Membership. A particle belongs to every leaf whose box, grown by band, contains it, with a weight w_L(z) that is the tensor product of quintic smoothsteps across the leaf’s edges. The weights form a partition of unity on any quadtree leaf partition when band is at most half the smallest leaf width. The interaction of particles i and j is split over all their membership pairs (routes) (L, L') with weight w_L(z_i) w_L'(z_j); each route uses the exact kernel if (L, L') is a near-field pair and the symmetric multipole-to-local expansion of the accepted ancestor pair otherwise. band = 0 is the ordinary tree (hard membership). Softening is compact (a smooth charge cloud of radius softening), so the far field of a softened particle is exactly the point charge’s and the tree solves the same model as the direct sum.

Energy and forces. In the units of :mod:tree_d_3d.flat_tree (potential of a line charge -2 q ln r, field 2 q d / r^2, no k_e),

U = -2 sum_routes w_m w_m' q_i q_j Phi(z_i - z_j),
F_i = -dU/dx_i = 2 q_i sum_{m: i in m} [ w_m G_m + Phi_m grad w_m ],

where Phi_m and G_m are the potential and its gradient at z_i seen through membership m (local expansion of its leaf plus its near-field partners). The second term, potential times the gradient of the weight, is what makes F the exact gradient; without it (or with band = 0) the energy is not conserved when a particle changes leaf.

Classes

NameDescription
BoxTreeSquare cells of a quadtree over root, split to hold leaf_capacity points.
SmoothFMMPotential energy and exact-gradient forces of line charges on a :class:BoxTree.

Functions

NameDescription
comoving_fieldsEnergy, potential and field with the boxes attached to the centre of mass.
direct_energyExact pair energy, same units as :attr:SmoothFMM.energy.
direct_fieldsExact potential and field, same units as :meth:SmoothFMM.fields.

BoxTreeclass#

class BoxTree(pos: np.ndarray, root: Quad, *, leaf_capacity: int = 16)

Square cells of a quadtree over root, split to hold leaf_capacity points.

centerattributeinstance attribute#

center = np.array(center)

childattributeinstance attribute#

child = np.array(child)

depthattributeinstance attribute#

depth = np.array(depth)

halfattributeinstance attribute#

half = np.array(half)

leavesattributeinstance attribute#

leaves = np.flatnonzero(self.child < 0)

min_leaf_halfattributeinstance attribute#

min_leaf_half = float(self.half[self.leaves].min())

parentattributeinstance attribute#

parent = np.array(parent)

rootattributeinstance attribute#

root = root

membershipsmethod#

def memberships(z: np.ndarray, band: float)

(particle, leaf, weight, dw/dx, dw/dy) for every particle-leaf pair with w > 0.

SmoothFMMclass#

class SmoothFMM(tree: BoxTree, pos: np.ndarray, charge: np.ndarray, *, order: int = 12, theta: float = 0.5, band: float = 0.0, symmetric: bool = True, softening: float = 0.0)

Potential energy and exact-gradient forces of line charges on a :class:BoxTree.

bandattributeinstance attribute#

band = float(band)

eps2attributeinstance attribute#

eps2 = float(softening) ** 2

orderattributeinstance attribute#

order = int(order)

qattributeinstance attribute#

q = np.asarray(charge, dtype=float)

symmetricattributeinstance attribute#

symmetric = bool(symmetric)

thetaattributeinstance attribute#

theta = float(theta)

treeattributeinstance attribute#

tree = tree

zattributeinstance attribute#

z = np.asarray(pos[0], dtype=float) + 1j * np.asarray(pos[1], dtype=float)

energyproperty#

energy: float

U = -2 sum over unordered routes, in the units of flat_tree.

fieldsmethod#

def fields() -> tuple[np.ndarray, np.ndarray]

Potential (N,) and field (2, N) such that F_i = q_i E_i = -dU/dx_i.

potential_atmethod#

def potential_at(points: np.ndarray) -> np.ndarray

Direct-sum potential at external points, for checks (no tree).

comoving_fieldsfunction#

def comoving_fields(tree: BoxTree, pos: np.ndarray, charge: np.ndarray, mass: np.ndarray, center: np.ndarray, **options) -> tuple[float, np.ndarray, np.ndarray]

Energy, potential and field with the boxes attached to the centre of mass.

The box set of tree is used in a frame whose origin follows the centre of mass c(x), i.e. U(x) = U_tree(x - (c(x) - center)). U is then translation invariant, so its exact gradient has zero net force: the gradient of the shift subtracts the mass-weighted net force of the smooth-membership terms. With this, both energy (smooth U) and momentum (Noether) are conserved by a symplectic integrator. center is where the centre of mass was when the boxes were built.

direct_energyfunction#

def direct_energy(pos, charge, softening=0.0)

Exact pair energy, same units as :attr:SmoothFMM.energy.

direct_fieldsfunction#

def direct_fields(pos, charge, softening=0.0)

Exact potential and field, same units as :meth:SmoothFMM.fields.