# tree_d_3d.flat_tree

*module*

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.

[View source](https://github.com/max-models/tree-d-3d/blob/devel/src/tree_d_3d/flat_tree.py#L1-L1)

## tree_d_3d.flat_tree.MAX_ORDER

*attribute* · *module attribute*

```python
MAX_ORDER = 64
```

[View source](https://github.com/max-models/tree-d-3d/blob/devel/src/tree_d_3d/flat_tree.py#L42-L42)

## tree_d_3d.flat_tree.FlatNode

*class*

```python
class FlatNode
```

Read-only view of one node of a :class:`FlatQuadtree`.

[View source](https://github.com/max-models/tree-d-3d/blob/devel/src/tree_d_3d/flat_tree.py#L87-L135)

### tree_d_3d.flat_tree.FlatNode.index

*attribute* · *instance attribute*

```python
index = index
```

[View source](https://github.com/max-models/tree-d-3d/blob/devel/src/tree_d_3d/flat_tree.py#L92-L92)

### tree_d_3d.flat_tree.FlatNode.tree

*attribute* · *instance attribute*

```python
tree = tree
```

[View source](https://github.com/max-models/tree-d-3d/blob/devel/src/tree_d_3d/flat_tree.py#L91-L91)

### tree_d_3d.flat_tree.FlatNode.children

*property*

```python
children: tuple[FlatNode | None, ...]
```

[View source](https://github.com/max-models/tree-d-3d/blob/devel/src/tree_d_3d/flat_tree.py#L131-L135)

### tree_d_3d.flat_tree.FlatNode.count

*property*

```python
count: int
```

[View source](https://github.com/max-models/tree-d-3d/blob/devel/src/tree_d_3d/flat_tree.py#L100-L101)

### tree_d_3d.flat_tree.FlatNode.dipole

*property*

```python
dipole: np.ndarray
```

[View source](https://github.com/max-models/tree-d-3d/blob/devel/src/tree_d_3d/flat_tree.py#L112-L113)

### tree_d_3d.flat_tree.FlatNode.particles

*property*

```python
particles: np.ndarray
```

Indices of the particles inside this node.

[View source](https://github.com/max-models/tree-d-3d/blob/devel/src/tree_d_3d/flat_tree.py#L121-L125)

### tree_d_3d.flat_tree.FlatNode.quad

*property*

```python
quad: Quad
```

[View source](https://github.com/max-models/tree-d-3d/blob/devel/src/tree_d_3d/flat_tree.py#L95-L97)

### tree_d_3d.flat_tree.FlatNode.second_moment

*property*

```python
second_moment: np.ndarray
```

[View source](https://github.com/max-models/tree-d-3d/blob/devel/src/tree_d_3d/flat_tree.py#L116-L118)

### tree_d_3d.flat_tree.FlatNode.total_abs_charge

*property*

```python
total_abs_charge: float
```

[View source](https://github.com/max-models/tree-d-3d/blob/devel/src/tree_d_3d/flat_tree.py#L108-L109)

### tree_d_3d.flat_tree.FlatNode.total_charge

*property*

```python
total_charge: float
```

[View source](https://github.com/max-models/tree-d-3d/blob/devel/src/tree_d_3d/flat_tree.py#L104-L105)

### tree_d_3d.flat_tree.FlatNode.is_external

*method*

```python
def is_external() -> bool
```

[View source](https://github.com/max-models/tree-d-3d/blob/devel/src/tree_d_3d/flat_tree.py#L127-L128)

## tree_d_3d.flat_tree.FlatQuadtree

*class*

```python
class FlatQuadtree
```

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``.

[View source](https://github.com/max-models/tree-d-3d/blob/devel/src/tree_d_3d/flat_tree.py#L158-L624)

### tree_d_3d.flat_tree.FlatQuadtree.charge

*attribute* · *instance attribute*

```python
charge = _device(charges, compiled=compiled).reshape(-1).copy()
```

[View source](https://github.com/max-models/tree-d-3d/blob/devel/src/tree_d_3d/flat_tree.py#L322-L322)

### tree_d_3d.flat_tree.FlatQuadtree.kernel

*attribute* · *instance attribute*

```python
kernel = kernel
```

[View source](https://github.com/max-models/tree-d-3d/blob/devel/src/tree_d_3d/flat_tree.py#L308-L308)

### tree_d_3d.flat_tree.FlatQuadtree.leaf_capacity

*attribute* · *instance attribute*

```python
leaf_capacity = int(leaf_capacity)
```

[View source](https://github.com/max-models/tree-d-3d/blob/devel/src/tree_d_3d/flat_tree.py#L333-L333)

### tree_d_3d.flat_tree.FlatQuadtree.max_depth

*attribute* · *instance attribute*

```python
max_depth = int(max_depth)
```

[View source](https://github.com/max-models/tree-d-3d/blob/devel/src/tree_d_3d/flat_tree.py#L334-L334)

### tree_d_3d.flat_tree.FlatQuadtree.moment_im

*attribute* · *instance attribute*

```python
moment_im = self._xp.empty((count, self.order + 1))
```

[View source](https://github.com/max-models/tree-d-3d/blob/devel/src/tree_d_3d/flat_tree.py#L351-L351)

### tree_d_3d.flat_tree.FlatQuadtree.moment_re

*attribute* · *instance attribute*

```python
moment_re = self._xp.empty((count, self.order + 1))
```

[View source](https://github.com/max-models/tree-d-3d/blob/devel/src/tree_d_3d/flat_tree.py#L350-L350)

### tree_d_3d.flat_tree.FlatQuadtree.node_count

*attribute* · *instance attribute*

```python
node_count = int(count)
```

[View source](https://github.com/max-models/tree-d-3d/blob/devel/src/tree_d_3d/flat_tree.py#L341-L341)

### tree_d_3d.flat_tree.FlatQuadtree.node_p

*attribute* · *instance attribute*

```python
node_p = self._xp.empty((count, 2))
```

[View source](https://github.com/max-models/tree-d-3d/blob/devel/src/tree_d_3d/flat_tree.py#L347-L347)

### tree_d_3d.flat_tree.FlatQuadtree.node_q

*attribute* · *instance attribute*

```python
node_q = self._xp.empty(count)
```

[View source](https://github.com/max-models/tree-d-3d/blob/devel/src/tree_d_3d/flat_tree.py#L345-L345)

### tree_d_3d.flat_tree.FlatQuadtree.node_qabs

*attribute* · *instance attribute*

```python
node_qabs = self._xp.empty(count)
```

[View source](https://github.com/max-models/tree-d-3d/blob/devel/src/tree_d_3d/flat_tree.py#L346-L346)

### tree_d_3d.flat_tree.FlatQuadtree.node_s

*attribute* · *instance attribute*

```python
node_s = self._xp.empty((count, 3))
```

[View source](https://github.com/max-models/tree-d-3d/blob/devel/src/tree_d_3d/flat_tree.py#L348-L348)

### tree_d_3d.flat_tree.FlatQuadtree.order

*attribute* · *instance attribute*

```python
order = int(order)
```

[View source](https://github.com/max-models/tree-d-3d/blob/devel/src/tree_d_3d/flat_tree.py#L309-L309)

### tree_d_3d.flat_tree.FlatQuadtree.pos

*attribute* · *instance attribute*

```python
pos = _points(positions, 'positions', compiled=compiled).copy()
```

[View source](https://github.com/max-models/tree-d-3d/blob/devel/src/tree_d_3d/flat_tree.py#L321-L321)

### tree_d_3d.flat_tree.FlatQuadtree.symmetric

*attribute* · *instance attribute*

```python
symmetric = bool(symmetric)
```

[View source](https://github.com/max-models/tree-d-3d/blob/devel/src/tree_d_3d/flat_tree.py#L311-L311)

### tree_d_3d.flat_tree.FlatQuadtree.root

*property*

```python
root: FlatNode
```

[View source](https://github.com/max-models/tree-d-3d/blob/devel/src/tree_d_3d/flat_tree.py#L391-L392)

### tree_d_3d.flat_tree.FlatQuadtree.collect_nodes

*method*

```python
def collect_nodes() -> list[FlatNode]
```

Views of every node that holds at least one particle.

[View source](https://github.com/max-models/tree-d-3d/blob/devel/src/tree_d_3d/flat_tree.py#L394-L397)

### tree_d_3d.flat_tree.FlatQuadtree.evaluate

*method*

```python
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).

[View source](https://github.com/max-models/tree-d-3d/blob/devel/src/tree_d_3d/flat_tree.py#L399-L461)

### tree_d_3d.flat_tree.FlatQuadtree.field_at

*method*

```python
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.

[View source](https://github.com/max-models/tree-d-3d/blob/devel/src/tree_d_3d/flat_tree.py#L596-L624)

### tree_d_3d.flat_tree.FlatQuadtree.fmm

*method*

```python
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.

[View source](https://github.com/max-models/tree-d-3d/blob/devel/src/tree_d_3d/flat_tree.py#L505-L594)

## tree_d_3d.flat_tree.direct_field

*function*

```python
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`.

[View source](https://github.com/max-models/tree-d-3d/blob/devel/src/tree_d_3d/flat_tree.py#L48-L84)
