tree_d_3d.flat_tree
The flat quadtree and direct sums: free-space electrostatics for charges constrained to the xy plane.
Uses the three-dimensional softened Coulomb kernel, not the logarithmic Green’s function of 2D Poisson. No periodic images, conducting walls, collisions, radiation, or relativistic effects are included.
The simulation stores particles as arrays and evaluates fields with the
kernels tree_d_3d.kernels.<name> (quadtree_quadrants,
evaluate_quadtree, fmm_m2l, …): a flat quadtree (:class:FlatQuadtree) or, for small systems,
an exact direct sum. The object tree :class:ReferenceQuadtree is the readable
reference implementation of the same algorithm and is kept for inspection and
tests.
Each kernel has a Pyccel, a NumPy and a CUDA version with the same arguments, and
runs the one matching its arrays. :class:FlatQuadtree and :func:direct_field
work on arrays of cunumpy’s active backend: on NumPy the Pyccel versions run (the
NumPy versions without Pyccel), on CuPy the CUDA versions, with the launch sizes
given here; their results are arrays of that backend. The simulation keeps its
particles on the host (each :class:ChargedBody is a view of a column) and hands them
to the field solver, which returns to the host.
Per-particle arrays with a component axis are component-major: positions,
targets and the fields at them have shape (2, N) (array[0] is x of every
particle); per-particle scalars (charge, phi, exclude) are (N,);
the tree-node arrays (node_center (nodes, 2), node_p, moments…)
are node-major.
Attributes
| Name | Description |
|---|---|
MAX_ORDER | No description. |
Classes
| Name | Description |
|---|---|
FlatNode | Read-only view of one node of a :class:FlatQuadtree. |
FlatQuadtree | Array quadtree for point charges in a plane or for line charges. |
Functions
| Name | Description |
|---|---|
direct_field | Exact potential (M,) and field (2, M) at targets (2, M), without k_e. |
MAX_ORDERattributemodule attribute#
MAX_ORDER = 64FlatNodeclass#
class FlatNode(tree: FlatQuadtree, index: int)Read-only view of one node of a :class:FlatQuadtree.
childrenproperty#
children: tuple[FlatNode | None, ...]countproperty#
count: intdipoleproperty#
dipole: np.ndarrayparticlesproperty#
particles: np.ndarrayIndices of the particles inside this node.
quadproperty#
quad: Quadsecond_momentproperty#
second_moment: np.ndarraytotal_abs_chargeproperty#
total_abs_charge: floattotal_chargeproperty#
total_charge: floatFlatQuadtreeclass#
class FlatQuadtree(positions, charges, root_quad: Quad, *, leaf_capacity: int = 8, max_depth: int = 48, compiled: bool = True, kernel: str = 'coulomb', order: int = 12, symmetric: bool = False)Array quadtree for point charges in a plane or for line charges.
Same tree and opening rule as :class:ReferenceQuadtree: nodes holding
more than leaf_capacity particles split into four until
max_depth, cells containing the target are always opened, and the
opening test uses unsoftened distance. positions is (2, N)
(component-major) and charges (N,); both are copied, so rebuild
after moving or changing a source.
kernel="coulomb" (the default) is the 3D point-charge interaction,
with signed moments through quadrupole order. kernel="log" treats
each charge as an infinite line charge per unit length along z, the
interaction of 2D Cartesian PIC: potential -2 k_e q ln r and field
2 k_e q r / r**2. It uses complex multipole expansions of order
terms, so accuracy can be raised by raising order as well as by
lowering theta.
chargeattributeinstance attribute#
charge = _device(charges, compiled=compiled).reshape(-1).copy()kernelattributeinstance attribute#
kernel = kernelleaf_capacityattributeinstance attribute#
leaf_capacity = int(leaf_capacity)max_depthattributeinstance attribute#
max_depth = int(max_depth)moment_imattributeinstance attribute#
moment_im = self._xp.empty((count, self.order + 1))moment_reattributeinstance attribute#
moment_re = self._xp.empty((count, self.order + 1))node_countattributeinstance attribute#
node_count = int(count)node_pattributeinstance attribute#
node_p = self._xp.empty((count, 2))node_qattributeinstance attribute#
node_q = self._xp.empty(count)node_qabsattributeinstance attribute#
node_qabs = self._xp.empty(count)node_sattributeinstance attribute#
node_s = self._xp.empty((count, 3))orderattributeinstance attribute#
order = int(order)posattributeinstance attribute#
pos = _points(positions, 'positions', compiled=compiled).copy()symmetricattributeinstance attribute#
symmetric = bool(symmetric)collect_nodesmethod#
def collect_nodes() -> list[FlatNode]Views of every node that holds at least one particle.
evaluatemethod#
def evaluate(targets, *, exclude=None, theta=0.5, softening=0.0)Potential (M,) and field (2, M) at targets (2, M), without k_e.
exclude[i] is a source index skipped for target i (-1: none).
field_atmethod#
def field_at(positions, *, theta: float = 0.5, k_e: float = K_E, softening: float = 0.0, reference_radius: float = 1.0) -> tuple[np.ndarray, np.ndarray]Potential, shape (n,), and electric field, shape (2, n), at probes (2, n).
For line charges the potential is only defined up to a constant; it
is -2 k_e sum q ln(r / reference_radius), so the potential of the
net charge vanishes at reference_radius from it.
fmmmethod#
def fmm(theta: float = 0.5, softening: float = 0.0, potential: bool = True)Line-charge potential and field at every source, by the FMM.
Fast multipole method for kernel="log" (Greengard-Rokhlin, complex
form): a dual-tree traversal pairs well-separated cells, whose
multipole expansions are translated into each other’s local
expansions (M2L, both ways at once); local expansions are shifted down
the tree (L2L) and evaluated at the particles (L2P); adjacent leaves
interact exactly (P2P). Cost is O(N) rather than the O(N log N) of
:meth:evaluate with every particle as a target. Returns the raw
potential (N,) and field (2, N) in input order, without k_e,
excluding each particle’s own charge; softening applies to the exact
near field only.
potential=False skips the potential (returned as zeros), which saves
a logarithm per near-field pair when only forces are needed.
direct_fieldfunction#
def direct_field(targets, pos, charge, *, softening=0.0, exclude=None, compiled=True, kernel='coulomb') -> tuple[np.ndarray, np.ndarray]Exact potential (M,) and field (2, M) at targets (2, M), without k_e.
pos (2, N) and charge (N,) are the sources. exclude[i]
is the index of a source skipped for target i (-1 for none). kernel
is "coulomb" or "log" (line charges), as for :class:FlatQuadtree.