# tree_d_3d

*module*

Tree Dynamics in 3D: Barnes-Hut trees and fast multipole methods for charged particles.

The particles move in a plane for now; full 3D, on an octree, is planned.

* :class:`FlatQuadtree` and :func:`direct_field`: fields of point charges (the 3D
  Coulomb kernel) or line charges (``kernel="log"``, the 2D Poisson kernel) at
  arbitrary targets, by tree walk, FMM or direct sum, on NumPy or CuPy arrays.
* :class:`ElectrostaticSimulation`: time integration of :class:`ChargedBody`
  particles with an optional uniform magnetic field, absorbing walls and absorbing
  :mod:`simgeometry` shapes.
* :class:`ReferenceQuadtree`: a readable object tree computing the same as the flat tree.
* :class:`GroundedStripField`: line charges between two grounded plates, by
  conformal mapping and the FMM.
* :class:`BoundaryIntegralElectrodes`: conductors at prescribed potentials, by the
  single-layer boundary-integral (panel) method, with :func:`conductors_from_shape`
  panelling any 2D :mod:`simgeometry` shape.
* :mod:`tree_d_3d.gravity`: the classic gravitational Barnes-Hut N-body code.

The compiled kernels are in :mod:`tree_d_3d.kernels`.

The solvers carry scope-profiler regions, tagged ``"tree_d_3d"``. The tag is opt-in, so
they record nothing unless the application enables it, for example with
``scope_profiler.setup(enable_tags=["tree_d_3d"])``.

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

## tree_d_3d.K_E

*attribute* · *module attribute*

```python
K_E: float = 8987551792.3
```

Re-exported from: `tree_d_3d.constants`

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

## tree_d_3d.PROFILE_TAG

*attribute* · *module attribute*

```python
PROFILE_TAG = 'tree_d_3d'
```

Re-exported from: `tree_d_3d._profiling`

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

## tree_d_3d.AbsorbedParticles

*class*

```python
class AbsorbedParticles
```

Re-exported from: `tree_d_3d.simulation`

Particles removed at absorbing walls and objects, in removal order.

Positions and velocities are taken just after the drift that crossed the
wall or entered the object, so positions lie up to one step beyond the
boundary; ``wall`` names the wall or object; ``time`` is the end of
that step. ``position`` and ``velocity`` are ``(2, M)`` (component-major).
The bodies themselves are kept in :attr:`bodies`, detached from the
simulation.

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

### tree_d_3d.AbsorbedParticles.bodies

*attribute* · *instance attribute*

```python
bodies: list[ChargedBody] = []
```

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

### tree_d_3d.AbsorbedParticles.charge

*attribute* · *instance attribute*

```python
charge = np.zeros(0)
```

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

### tree_d_3d.AbsorbedParticles.mass

*attribute* · *instance attribute*

```python
mass = np.zeros(0)
```

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

### tree_d_3d.AbsorbedParticles.position

*attribute* · *instance attribute*

```python
position = np.zeros((2, 0))
```

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

### tree_d_3d.AbsorbedParticles.time

*attribute* · *instance attribute*

```python
time = np.zeros(0)
```

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

### tree_d_3d.AbsorbedParticles.velocity

*attribute* · *instance attribute*

```python
velocity = np.zeros((2, 0))
```

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

### tree_d_3d.AbsorbedParticles.wall

*attribute* · *instance attribute*

```python
wall = np.zeros(0, dtype=str)
```

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

### tree_d_3d.AbsorbedParticles.kinetic_energy

*property*

```python
kinetic_energy: float
```

Kinetic energy carried out through the walls.

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

### tree_d_3d.AbsorbedParticles.total_charge

*property*

```python
total_charge: float
```

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

### tree_d_3d.AbsorbedParticles.counts

*method*

```python
def counts() -> dict[str, int]
```

Number of particles absorbed at each wall and object.

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

### tree_d_3d.AbsorbedParticles.record

*method*

```python
def record(bodies, *, time, walls, positions, velocities, masses, charges)
```

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

## tree_d_3d.BoundaryIntegralElectrodes

*class*

```python
class BoundaryIntegralElectrodes
```

Re-exported from: `tree_d_3d.electrodes`

Conductors held at prescribed potentials, by the single-layer boundary integral.

``conductors`` is a sequence of :class:`Conductor`. Call :meth:`solve` once per
configuration of free charges (or with none, for a pure capacitance problem); then
:meth:`probe` (and :meth:`potential`, :meth:`field`) give the total field --
electrodes plus free charges -- at arbitrary points, and :meth:`force_on_sources`
the force the electrodes and the other free charges exert back on the free charges.

Panels within ``near_field`` of their own lengths of a target use the exact field of
a uniformly charged segment (:func:`segment_field`); farther ones are summed fast as
``far_points`` Gauss--Legendre point charges each, whose error falls as
``(L / r) ** (2 * far_points)``. ``near_field=0, far_points=1`` gives the plain
midpoint rule.

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

### tree_d_3d.BoundaryIntegralElectrodes.charge

*attribute* · *instance attribute*

```python
charge: np.ndarray | None = None
```

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

### tree_d_3d.BoundaryIntegralElectrodes.conductors

*attribute* · *instance attribute*

```python
conductors = list(conductors)
```

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

### tree_d_3d.BoundaryIntegralElectrodes.ends

*attribute* · *instance attribute*

```python
ends = np.concatenate(ends, axis=1)
```

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

### tree_d_3d.BoundaryIntegralElectrodes.far_points

*attribute* · *instance attribute*

```python
far_points = int(far_points)
```

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

### tree_d_3d.BoundaryIntegralElectrodes.group

*attribute* · *instance attribute*

```python
group = np.concatenate(group)
```

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

### tree_d_3d.BoundaryIntegralElectrodes.k_e

*attribute* · *instance attribute*

```python
k_e = float(k_e)
```

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

### tree_d_3d.BoundaryIntegralElectrodes.lengths

*attribute* · *instance attribute*

```python
lengths = np.concatenate(lengths)
```

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

### tree_d_3d.BoundaryIntegralElectrodes.midpoints

*attribute* · *instance attribute*

```python
midpoints = np.concatenate(midpoints, axis=1)
```

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

### tree_d_3d.BoundaryIntegralElectrodes.near_field

*attribute* · *instance attribute*

```python
near_field = float(near_field)
```

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

### tree_d_3d.BoundaryIntegralElectrodes.num_panels

*attribute* · *instance attribute*

```python
num_panels = self.midpoints.shape[1]
```

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

### tree_d_3d.BoundaryIntegralElectrodes.starts

*attribute* · *instance attribute*

```python
starts = np.concatenate(starts, axis=1)
```

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

### tree_d_3d.BoundaryIntegralElectrodes.target_potential

*attribute* · *instance attribute*

```python
target_potential = np.concatenate(target)
```

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

### tree_d_3d.BoundaryIntegralElectrodes.field

*method*

```python
def field(points) -> np.ndarray
```

Field (V/m, shape ``(2, M)``) at ``points`` ``(2, M)``; see :meth:`probe`.

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

### tree_d_3d.BoundaryIntegralElectrodes.force_on_sources

*method*

```python
def force_on_sources() -> tuple[np.ndarray, np.ndarray]
```

Potential and field (V/m, shape ``(2, N)``) at the free charges from everything else.

Excludes each source's own singular self-term, so multiplying the field by a
source's charge gives the physical force on it from the panels and the other
sources. Call :meth:`solve` first.

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

### tree_d_3d.BoundaryIntegralElectrodes.panel_field

*method*

```python
def panel_field(points) -> tuple[np.ndarray, np.ndarray]
```

Potential (V) and field (V/m) at ``points`` ``(2, M)`` of the panels alone.

The induced charge from the last :meth:`solve`, without the free charges passed
to it: for codes that add the free charges' field themselves (by a tree code,
say). Near panels enter exactly, as in :meth:`probe`.

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

### tree_d_3d.BoundaryIntegralElectrodes.potential

*method*

```python
def potential(points) -> np.ndarray
```

Potential (V) at ``points`` ``(2, M)``; see :meth:`probe`.

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

### tree_d_3d.BoundaryIntegralElectrodes.probe

*method*

```python
def probe(points) -> tuple[np.ndarray, np.ndarray]
```

Potential (V) and field (V/m, shape ``(2, M)``) at ``points`` ``(2, M)``.

Includes the solved panel charges and the free charges passed to :meth:`solve`;
``points`` are probes, not sources, so no self term applies. Call :meth:`solve`
first.

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

### tree_d_3d.BoundaryIntegralElectrodes.solve

*method*

```python
def solve(source_positions=None, source_charges=None) -> np.ndarray
```

Solve for the panel charges that make every conductor an equipotential.

``source_positions`` ``(2, N)`` and ``source_charges`` ``(N,)`` are free line
charges (omit both for a pure capacitance problem). Returns and caches the
panel charges, used by :meth:`probe` and :meth:`force_on_sources`.

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

### tree_d_3d.BoundaryIntegralElectrodes.tabulate

*method*

```python
def tabulate(lower, upper, shape) -> TabulatedField
```

:meth:`probe` on a regular grid, for fast bilinear interpolation afterwards.

``lower`` and ``upper`` are the corners ``(x, y)`` of the rectangle and
``shape`` ``(nx, ny)`` the number of grid lines along each axis (at least 2).
Like :meth:`probe` it includes the free charges passed to :meth:`solve`, so
tabulate a static configuration and scale it, for example by an RF voltage.

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

### tree_d_3d.BoundaryIntegralElectrodes.total_charge

*method*

```python
def total_charge(conductor_index: int | None = None)
```

Induced charge of one conductor, or of every conductor if ``index`` is ``None``.

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

## tree_d_3d.ChargedBody

*class*

```python
class ChargedBody
```

Re-exported from: `tree_d_3d.bodies`

A physical particle or weighted macroparticle in consistent units.

Input mass and charge are per physical particle. Weight multiplies both,
preserving q/m; stored mass and charge are the totals. A fixed source
stays immobile. SI inputs give SI outputs.

State lives in arrays. A simulation adopts its bodies, after which
``pos``, ``vel`` and ``force`` are views of columns of the simulation's
``(2, N)`` arrays, and reading or assigning any attribute reads or writes
them.

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

### tree_d_3d.ChargedBody.charge

*attribute* · *class attribute* · *instance attribute*

```python
charge = property(lambda self: float(self._charge[self._index]), lambda self, value: self._charge.__setitem__(self._index, value))
```

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

### tree_d_3d.ChargedBody.fixed

*attribute* · *class attribute* · *instance attribute*

```python
fixed = property(lambda self: bool(self._fixed[self._index]), lambda self, value: self._fixed.__setitem__(self._index, value))
```

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

### tree_d_3d.ChargedBody.force

*attribute* · *class attribute* · *instance attribute*

```python
force = property(lambda self: self._force[:, self._index], lambda self, value: self._force.__setitem__((slice(None), self._index), value))
```

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

### tree_d_3d.ChargedBody.mass

*attribute* · *class attribute* · *instance attribute*

```python
mass = property(lambda self: float(self._mass[self._index]), lambda self, value: self._mass.__setitem__(self._index, value))
```

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

### tree_d_3d.ChargedBody.pos

*attribute* · *class attribute* · *instance attribute*

```python
pos = property(lambda self: self._pos[:, self._index], lambda self, value: self._pos.__setitem__((slice(None), self._index), value))
```

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

### tree_d_3d.ChargedBody.vel

*attribute* · *class attribute* · *instance attribute*

```python
vel = property(lambda self: self._vel[:, self._index], lambda self, value: self._vel.__setitem__((slice(None), self._index), value))
```

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

### tree_d_3d.ChargedBody.weight

*attribute* · *instance attribute*

```python
weight = _finite('weight', weight, positive=True)
```

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

### tree_d_3d.ChargedBody.add_force

*method*

```python
def add_force(fx: float, fy: float) -> None
```

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

### tree_d_3d.ChargedBody.reset_force

*method*

```python
def reset_force() -> None
```

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

## tree_d_3d.Conductor

*class* · *dataclass*

```python
class Conductor
```

Re-exported from: `tree_d_3d.electrodes`

A conductor's boundary: a polyline of vertices ``(2, n)`` held at one potential.

Consecutive vertices are panel endpoints; ``closed`` connects the last vertex back
to the first (a loop), giving ``n`` panels instead of ``n - 1``.

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

### tree_d_3d.Conductor.closed

*attribute* · *class attribute* · *instance attribute*

```python
closed: bool = True
```

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

### tree_d_3d.Conductor.potential

*attribute* · *instance attribute*

```python
potential: float
```

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

### tree_d_3d.Conductor.vertices

*attribute* · *instance attribute*

```python
vertices: np.ndarray
```

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

## tree_d_3d.ElectrostaticSimulation

*class*

```python
class ElectrostaticSimulation
```

Re-exported from: `tree_d_3d.simulation`

Isolated, nonrelativistic electrostatic N-body dynamics in a plane.

Bounds are the initial tree extent, not physical walls. Without
``absorbing_walls`` all sources are retained. Symmetric electric kicks around an exact uniform-B drift give a
second-order scheme, reducing to velocity Verlet when B=0. Positions and
velocities are synchronized. Resolve close encounters and oscillations
by reducing dt; softening changes the interaction below that length scale.

``method`` selects the field evaluation: ``"tree"`` (Barnes-Hut at
``theta``), ``"direct"`` (exact all-pairs sum), or ``"auto"``, which sums
directly for ``theta == 0`` or at most ``direct_threshold`` charged
particles, where that is exact and faster. Fields are cached and reused
while positions, charges and parameters are unchanged, so one step costs
one field evaluation.

``absorbing_walls`` maps any of ``x_min``, ``x_max``, ``y_min`` and
``y_max`` to a wall coordinate. A particle that ends a drift beyond a wall
is removed before the end-of-step field evaluation and stops acting as a
source; see :attr:`absorbed`. Walls only remove particles: the field
stays free-space, with no image charges or wall potential.

``absorbing_objects`` maps names to 2D :mod:`simgeometry` shapes (a
``Circle``, ``Polygon``, ``Aperture2D``, ...). A particle that ends a drift
inside one is removed the same way and recorded under its name; an
inverted shape absorbs everything outside it, such as a round chamber
wall. Only end points are tested, so keep ``|v| dt`` below the thickness of
the thinnest object. Like walls, objects do not change the field: for
electrodes, pair them with :class:`tree_d_3d.BoundaryIntegralElectrodes`.

``kernel="log"`` makes every particle an infinite line charge per unit
length along z, the interaction of 2D Cartesian PIC, instead of a point
charge in the plane. Its pair energy is ``-2 k_e q_i q_j ln(r / r_0)``
with ``r_0 = reference_radius``, which shifts the potential energy but
not the forces. Line charges add ``method="fmm"``, the O(N) fast
multipole method of :meth:`FlatQuadtree.fmm`, which ``"auto"`` selects
above ``direct_threshold``. ``order`` is the number of expansion terms
for line charges; ``leaf_capacity`` defaults to 32 for the FMM and 8
otherwise.

Finite-theta forces are not pairwise symmetric: check energy and momentum
convergence.

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

### tree_d_3d.ElectrostaticSimulation.B_z

*attribute* · *instance attribute*

```python
B_z = _finite('B_z', B_z)
```

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

### tree_d_3d.ElectrostaticSimulation.absorbed

*attribute* · *instance attribute*

```python
absorbed = AbsorbedParticles()
```

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

### tree_d_3d.ElectrostaticSimulation.bodies

*attribute* · *instance attribute*

```python
bodies = list(bodies)
```

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

### tree_d_3d.ElectrostaticSimulation.bounds

*attribute* · *instance attribute*

```python
bounds = bounds
```

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

### tree_d_3d.ElectrostaticSimulation.current_time

*attribute* · *instance attribute*

```python
current_time = 0.0
```

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

### tree_d_3d.ElectrostaticSimulation.direct_threshold

*attribute* · *instance attribute*

```python
direct_threshold = int(direct_threshold)
```

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

### tree_d_3d.ElectrostaticSimulation.dt

*attribute* · *instance attribute*

```python
dt = _finite('dt', dt, positive=True)
```

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

### tree_d_3d.ElectrostaticSimulation.iteration

*attribute* · *instance attribute*

```python
iteration = 0
```

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

### tree_d_3d.ElectrostaticSimulation.k_e

*attribute* · *instance attribute*

```python
k_e = _finite('k_e', k_e, minimum=0)
```

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

### tree_d_3d.ElectrostaticSimulation.kernel

*attribute* · *instance attribute*

```python
kernel = kernel
```

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

### tree_d_3d.ElectrostaticSimulation.leaf_capacity

*attribute* · *instance attribute*

```python
leaf_capacity = leaf_capacity
```

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

### tree_d_3d.ElectrostaticSimulation.max_depth

*attribute* · *instance attribute*

```python
max_depth = max_depth
```

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

### tree_d_3d.ElectrostaticSimulation.method

*attribute* · *instance attribute*

```python
method = method
```

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

### tree_d_3d.ElectrostaticSimulation.order

*attribute* · *instance attribute*

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

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

### tree_d_3d.ElectrostaticSimulation.reference_radius

*attribute* · *instance attribute*

```python
reference_radius = _finite('reference_radius', reference_radius, positive=True)
```

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

### tree_d_3d.ElectrostaticSimulation.softening

*attribute* · *instance attribute*

```python
softening = _finite('softening', softening, minimum=0)
```

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

### tree_d_3d.ElectrostaticSimulation.symmetric

*attribute* · *instance attribute*

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

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

### tree_d_3d.ElectrostaticSimulation.theta

*attribute* · *instance attribute*

```python
theta = _finite('theta', theta, minimum=0)
```

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

### tree_d_3d.ElectrostaticSimulation.absorbing_objects

*property*

```python
absorbing_objects: dict[str, Shape]
```

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

### tree_d_3d.ElectrostaticSimulation.absorbing_walls

*property*

```python
absorbing_walls: dict[str, float]
```

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

### tree_d_3d.ElectrostaticSimulation.num_particles

*property*

```python
num_particles: int
```

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

### tree_d_3d.ElectrostaticSimulation.build_tree

*method*

```python
def build_tree() -> FlatQuadtree
```

Build a tree, enlarging its extent without dropping particles.

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

### tree_d_3d.ElectrostaticSimulation.compute_fields

*method*

```python
def compute_fields(include_fixed: bool = True) -> FlatQuadtree | None
```

Electric force and potential on every particle, reusing a cache.

Fixed particles never move, so stepping skips them as targets
(``include_fixed=False``) and their force and potential stay zero
until they are asked for; they always act as sources. A later call
with ``include_fixed=True`` fills them in from the same tree.
Returns the tree used, or None for direct summation.

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

### tree_d_3d.ElectrostaticSimulation.get_angular_momentum

*method*

```python
def get_angular_momentum() -> float
```

Mechanical angular momentum about the origin (z component).

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

### tree_d_3d.ElectrostaticSimulation.get_center_of_mass

*method*

```python
def get_center_of_mass() -> np.ndarray
```

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

### tree_d_3d.ElectrostaticSimulation.get_kinetic_energy

*method*

```python
def get_kinetic_energy() -> float
```

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

### tree_d_3d.ElectrostaticSimulation.get_momentum

*method*

```python
def get_momentum() -> np.ndarray
```

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

### tree_d_3d.ElectrostaticSimulation.get_potential_energy

*method*

```python
def get_potential_energy(exact: bool = False) -> float
```

Pair energy for the force kernel, excluding self-energy.

By default this is ``0.5 * sum(q_i * phi_i)`` from the field
evaluation used for the forces, at no extra cost: exact when the
fields are summed directly, and accurate to the tree error otherwise.
``exact=True`` sums all pairs, which costs O(N squared).

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

### tree_d_3d.ElectrostaticSimulation.get_total_charge

*method*

```python
def get_total_charge() -> float
```

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

### tree_d_3d.ElectrostaticSimulation.get_total_energy

*method*

```python
def get_total_energy(exact: bool = False) -> float
```

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

### tree_d_3d.ElectrostaticSimulation.step

*method*

```python
def step(dt: float | None = None) -> FlatQuadtree | None
```

Electric half-kick, exact cyclotron drift, electric half-kick.

Returns the tree used for the end-of-step forces, or None when the
fields were summed directly. Forces on fixed particles are not
evaluated here and read as zero; call :meth:`compute_fields` for them.

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

## tree_d_3d.FlatNode

*class*

```python
class FlatNode
```

Re-exported from: `tree_d_3d.flat_tree`

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

*class*

```python
class FlatQuadtree
```

Re-exported from: `tree_d_3d.flat_tree`

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

*class*

```python
class GroundedStripField
```

Re-exported from: `tree_d_3d.strip`

Field and potential of line charges between plates at x = 0 and x = L.

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

### tree_d_3d.GroundedStripField.cutoff

*attribute* · *instance attribute*

```python
cutoff = float(cutoff)
```

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

### tree_d_3d.GroundedStripField.k_e

*attribute* · *instance attribute*

```python
k_e = float(k_e)
```

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

### tree_d_3d.GroundedStripField.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/strip.py#L71-L71)

### tree_d_3d.GroundedStripField.method

*attribute* · *instance attribute*

```python
method = method
```

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

### tree_d_3d.GroundedStripField.order

*attribute* · *instance attribute*

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

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

### tree_d_3d.GroundedStripField.softening

*attribute* · *instance attribute*

```python
softening = float(softening)
```

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

### tree_d_3d.GroundedStripField.theta

*attribute* · *instance attribute*

```python
theta = float(theta)
```

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

### tree_d_3d.GroundedStripField.width

*attribute* · *instance attribute*

```python
width = float(width)
```

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

### tree_d_3d.GroundedStripField.field_at_charges

*method*

```python
def field_at_charges(positions, charges, *, potential: bool = True) -> tuple[np.ndarray, np.ndarray]
```

Potential (V) and field (V/m, shape (2, N)) acting on every charge.

``positions`` has shape ``(2, N)``; ``charges`` are line charges per unit
length (C/m). With
``potential=False`` only the field is computed (the potential is
returned as zeros), which is cheaper.

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

### tree_d_3d.GroundedStripField.potential

*method*

```python
def potential(positions, charges, points) -> np.ndarray
```

Potential (V) at arbitrary ``points`` strictly between the plates.

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

### tree_d_3d.GroundedStripField.probe

*method*

```python
def probe(positions, charges, points) -> tuple[np.ndarray, np.ndarray]
```

Potential (V) and field (V/m, shape (2, M)) at ``points`` ``(2, M)``.

The points must lie strictly between the plates; they are probes, not
charges, so no self term applies.

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

### tree_d_3d.GroundedStripField.to_plane

*method*

```python
def to_plane(positions) -> np.ndarray
```

Zeta = exp(i pi z / L), as a complex array; ``positions`` is ``(2, N)``.

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

## tree_d_3d.Quad

*class*

```python
class Quad
```

Re-exported from: `tree_d_3d.quad`

Represents a quadrant in the quadtree.

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

### tree_d_3d.Quad.half

*attribute* · *instance attribute*

```python
half: float = half_size
```

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

### tree_d_3d.Quad.xmid

*attribute* · *instance attribute*

```python
xmid: float = x_mid
```

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

### tree_d_3d.Quad.ymid

*attribute* · *instance attribute*

```python
ymid: float = y_mid
```

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

### tree_d_3d.Quad.contains

*method*

```python
def contains(x: float, y: float) -> bool
```

Check if point is within this quadrant.

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

### tree_d_3d.Quad.northeast

*method*

```python
def northeast() -> Quad
```

Create northeast child quadrant.

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

### tree_d_3d.Quad.northwest

*method*

```python
def northwest() -> Quad
```

Create northwest child quadrant.

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

### tree_d_3d.Quad.southeast

*method*

```python
def southeast() -> Quad
```

Create southeast child quadrant.

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

### tree_d_3d.Quad.southwest

*method*

```python
def southwest() -> Quad
```

Create southwest child quadrant.

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

## tree_d_3d.ReferenceNode

*class*

```python
class ReferenceNode
```

Re-exported from: `tree_d_3d.reference`

Bucket node with signed moments about its geometric centre.

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

### tree_d_3d.ReferenceNode.NE

*attribute* · *instance attribute*

```python
NE = None
```

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

### tree_d_3d.ReferenceNode.NW

*attribute* · *instance attribute*

```python
NW = None
```

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

### tree_d_3d.ReferenceNode.SE

*attribute* · *instance attribute*

```python
SE = None
```

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

### tree_d_3d.ReferenceNode.SW

*attribute* · *instance attribute*

```python
SW = None
```

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

### tree_d_3d.ReferenceNode.bodies

*attribute* · *instance attribute*

```python
bodies = []
```

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

### tree_d_3d.ReferenceNode.body

*attribute* · *instance attribute*

```python
body = None
```

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

### tree_d_3d.ReferenceNode.center_of_charge

*attribute* · *instance attribute*

```python
center_of_charge = np.zeros(2)
```

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

### tree_d_3d.ReferenceNode.count

*attribute* · *instance attribute*

```python
count = 0
```

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

### tree_d_3d.ReferenceNode.dipole

*attribute* · *instance attribute*

```python
dipole = np.zeros(2)
```

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

### tree_d_3d.ReferenceNode.quad

*attribute* · *instance attribute*

```python
quad = quad
```

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

### tree_d_3d.ReferenceNode.second_moment

*attribute* · *instance attribute*

```python
second_moment = np.zeros((2, 2))
```

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

### tree_d_3d.ReferenceNode.total_abs_charge

*attribute* · *instance attribute*

```python
total_abs_charge = 0.0
```

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

### tree_d_3d.ReferenceNode.total_charge

*attribute* · *instance attribute*

```python
total_charge = 0.0
```

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

### tree_d_3d.ReferenceNode.children

*property*

```python
children
```

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

### tree_d_3d.ReferenceNode.is_external

*method*

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

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

## tree_d_3d.ReferenceQuadtree

*class*

```python
class ReferenceQuadtree
```

Re-exported from: `tree_d_3d.reference`

Bounded-depth quadtree with signed moments through quadrupole order.

Coincident particles remain in leaf buckets. Theta=0 evaluates all pairs.
Cells containing the target are always opened. The opening criterion uses
unsoftened distance so softening cannot make a nearby cell look distant.

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

### tree_d_3d.ReferenceQuadtree.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/reference.py#L61-L61)

### tree_d_3d.ReferenceQuadtree.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/reference.py#L62-L62)

### tree_d_3d.ReferenceQuadtree.root

*attribute* · *instance attribute*

```python
root = ReferenceNode(root_quad)
```

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

### tree_d_3d.ReferenceQuadtree.collect_nodes

*method*

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

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

### tree_d_3d.ReferenceQuadtree.compute_lorentz_force_on

*method*

```python
def compute_lorentz_force_on(body: ChargedBody, theta: float = 0.5, k_e: float = K_E, softening: float = 0.0, B_z: float = 0.0) -> None
```

Store q(E + v cross B), with no self-force.

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

### tree_d_3d.ReferenceQuadtree.field_at

*method*

```python
def field_at(position: Sequence[float], *, theta: float = 0.5, k_e: float = K_E, softening: float = 0.0, exclude: ChargedBody | None = None) -> tuple[float, np.ndarray]
```

Return (potential, electric field), excluding one source by identity.

Potential and field use the same softened multipole expansion. Rebuild
the tree after moving or changing any source particle.

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

### tree_d_3d.ReferenceQuadtree.insert

*method*

```python
def insert(body: ChargedBody) -> None
```

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

## tree_d_3d.TabulatedField

*class* · *dataclass*

```python
class TabulatedField
```

Re-exported from: `tree_d_3d.electrodes`

Potential and field sampled on a regular grid, interpolated bilinearly.

``x`` ``(nx,)`` and ``y`` ``(ny,)`` are the grid lines, ``potential`` ``(nx, ny)``
and ``field`` ``(2, nx, ny)`` the samples, as :meth:`BoundaryIntegralElectrodes.tabulate`
makes them. Calling it with ``(2, M)`` points returns the interpolated potential
``(M,)`` and field ``(2, M)``; points outside the grid take the value at the
nearest edge (see :meth:`contains`).

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

### tree_d_3d.TabulatedField.field

*attribute* · *instance attribute*

```python
field: np.ndarray
```

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

### tree_d_3d.TabulatedField.potential

*attribute* · *instance attribute*

```python
potential: np.ndarray
```

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

### tree_d_3d.TabulatedField.x

*attribute* · *instance attribute*

```python
x: np.ndarray
```

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

### tree_d_3d.TabulatedField.y

*attribute* · *instance attribute*

```python
y: np.ndarray
```

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

### tree_d_3d.TabulatedField.contains

*method*

```python
def contains(points) -> np.ndarray
```

Mask of the ``(2, M)`` points inside the tabulated rectangle.

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

## tree_d_3d.conductors_from_shape

*function*

```python
def conductors_from_shape(shape: OutlinedShape, potential: float, max_panel_length: float | Callable[[np.ndarray], np.ndarray]) -> list[Conductor]
```

Re-exported from: `tree_d_3d.electrodes`

Closed conductors on the boundary of a 2D :mod:`simgeometry` shape.

One :class:`Conductor` per boundary loop of ``shape.outlines(max_panel_length)``
(an ``Aperture2D`` gives two, one per plate), all held at ``potential``; an open
curve such as a ``Polyline`` (a plate of zero thickness) gives an open conductor. Panels are
at most ``max_panel_length`` long; it can also be a function of ``(2, m)`` points
returning ``(m,)`` lengths, to grade the panels (short where the field varies
fastest, such as an aperture tip). Keep panels shorter than the gaps between
surfaces, a plate's thickness included, so that the charge on one face does not
have to resolve the field of the facing one.

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

## tree_d_3d.direct_field

*function*

```python
def direct_field(targets, pos, charge, *, softening=0.0, exclude=None, compiled=True, kernel='coulomb') -> tuple[np.ndarray, np.ndarray]
```

Re-exported from: `tree_d_3d.flat_tree`

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)

## tree_d_3d.kernels_compiled

*function*

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

Re-exported from: `tree_d_3d.kernels`

Whether the kernels run compiled.

On the CuPy backend they always do (CUDA); otherwise this checks once per
process whether the Pyccel versions compile. Without them the tree kernels
run as Python loops, so ``method="auto"`` sums directly.

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

## tree_d_3d.segment_field

*function*

```python
def segment_field(points, start, end) -> tuple[np.ndarray, np.ndarray]
```

Re-exported from: `tree_d_3d.electrodes`

Potential ``(K,)`` and field ``(2, K)`` of uniformly charged straight segments.

Pairs column by column: ``points``, ``start`` and ``end`` are ``(2, K)``, and segment
``k`` from ``start[:, k]`` to ``end[:, k]`` carries unit total charge spread evenly
along it, in the log kernel of :func:`tree_d_3d.direct_field` (a point charge ``q``
has potential ``-2 q ln r``), without ``k_e``. With ``u`` and ``v`` the target's
coordinates along and across the segment of length ``L`` from its start,

``phi = -(2 / L) [u ln r_a - (u - L) ln r_b - L + |v| theta]``,
``E = (2 / L) [ln(r_a / r_b) t + sign(v) theta n]``,

where ``r_a`` and ``r_b`` are the distances to the two ends, ``theta`` the angle the
segment subtends at the target, and ``t`` and ``n`` the unit tangent and normal. On
the segment itself the normal field (a jump of the surface charge) is taken as zero;
at an end point the potential is finite but the field diverges logarithmically.

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

**Modules**

- `tree_d_3d.bodies`
- `tree_d_3d.constants`
- `tree_d_3d.electrodes`
- `tree_d_3d.flat_tree`
- `tree_d_3d.gravity`
- `tree_d_3d.kernels`
- `tree_d_3d.quad`
- `tree_d_3d.reference`
- `tree_d_3d.simulation`
- `tree_d_3d.smooth`
- `tree_d_3d.strip`
