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 mostleaf_capacityof 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:
SmoothFMMover 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
Functions
| Name | Description |
|---|---|
comoving_fields | Energy, potential and field with the boxes attached to the centre of mass. |
direct_energy | Exact pair energy, same units as :attr:SmoothFMM.energy. |
direct_fields | Exact 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 = rootmembershipsmethod#
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) ** 2orderattributeinstance 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 = treezattributeinstance attribute#
z = np.asarray(pos[0], dtype=float) + 1j * np.asarray(pos[1], dtype=float)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.ndarrayDirect-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.