Metadata-Version: 2.5
Name: pynbi
Version: 0.1.0
Summary: A Monte Carlo particle simulator for Neutral Beam Injector beam-lines
Project-URL: Homepage, https://codeberg.org/Kruayd/PyNBI
Project-URL: Repository, https://codeberg.org/Kruayd/PyNBI
Project-URL: Issues, https://codeberg.org/Kruayd/PyNBI/issues
Author-email: Luca Cinnirella <luca.cinnirella@protonmail.com>
License-Expression: GPL-3.0-or-later
License-File: LICENSE
Keywords: atomic-physics,beam-physics,fusion,fusion-energy,monte-carlo,nbi,neutral-beam-injector,nuclear-physics,particle-simulation,physics,plasma-physics
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Scientific/Engineering :: Physics
Requires-Python: >=3.14
Requires-Dist: h5py>=3.16
Requires-Dist: numba>=0.67
Requires-Dist: numpy>=1.26
Requires-Dist: pycroxe>=0.4
Requires-Dist: pydantic>=2.13
Requires-Dist: pyyaml>=6.0
Requires-Dist: scipy>=1.18
Requires-Dist: tbb>=2023.1
Requires-Dist: tqdm>=4.70
Requires-Dist: trimesh>=5.0
Requires-Dist: xarray>=2024.1
Provides-Extra: fast-geometry
Requires-Dist: embreex>=4.4; extra == 'fast-geometry'
Provides-Extra: test
Requires-Dist: pytest>=9.1; extra == 'test'
Description-Content-Type: text/markdown

# Welcome to PyNBI

A Monte Carlo particle simulator for Neutral Beam Injector (NBI) beam-lines,
following chemical and energetic species from beam extraction through to the
NBI's own exit.

## Table of contents

- [What PyNBI can and cannot simulate](#what-pynbi-can-and-cannot-simulate)
- [Installation](#installation)
- [Running a simulation](#running-a-simulation)
  - [Quick start](#quick-start)
  - [Available commands](#available-commands)
  - [Getting help](#getting-help)
  - [`run` command-line options](#run-command-line-options)
- [Configuration](#configuration)
  - [Example configuration](#example-configuration)
  - [Configuration reference](#configuration-reference)
- [Contributing](#contributing)
- [License](#license)

## What PyNBI can and cannot simulate

PyNBI simulates the evolution of chemical and energetic species along the
beam-line, from the acceleration grid all the way to the NBI's exit. Within
that region, it tracks:

- **Particle trajectories**, including gyration in a background magnetic
  field built up from any combination of uniform, grid-sampled, analytic
  coil, user-defined function, superimposed, and shielded (masked) components.
- **Particle interaction with a background gas**, sampling collisions
  (charge exchange, dissociation, ionization, ...) against cross-section
  data retrieved from [CroXe](https://codeberg.org/Kruayd/CroXe), and
  updating each particle's species and statistical weight accordingly.
- **Power deposition** onto beam-line components, as particles strike the
  vessel wall.

PyNBI does **not** simulate the beam's plasma source itself (the physics of
how the beam is generated, upstream of the acceleration grid), and it does
**not** simulate beam-plasma interaction inside a reactor: the simulation
domain ends at the NBI's exit, before a hypothetical reactor vessel would
even begin.

## Installation

PyNBI is available on [PyPI](https://pypi.org/):

```bash
pip install pynbi
```

If you want to speed-up wall power deposition computations, you can install it
with the optional `fast-geometry` dependency, which relies on `embreex` for fast
ray tracing operations:

```bash
pip install pynbi[fast-geometry]
```

> [!IMPORTANT]
> PyNBI requires Python 3.14 or newer.

> [!IMPORTANT]
> Running a simulation requires a reachable [CroXe](https://codeberg.org/Kruayd/CroXe)
> database: every species' mass, charge, and collision cross-section data is
> retrieved from it at runtime through [PyCroXe](https://codeberg.org/Kruayd/PyCroXe),
> a direct dependency of PyNBI. Without a running CroXe instance, PyNBI has
> no source of physical data to simulate with. You can either:
>
> - **Install CroXe locally**, by following
>   [CroXe installation instructions](https://codeberg.org/Kruayd/CroXe#installation)
> - **Point PyNBI at an existing CroXe instance**.
>
> In any case, see [`run` command-line options](#run-command-line-options) below
> for how connection details are supplied.

> [!IMPORTANT]
> PyCroXe (and therefore PyNBI) depends on
> [MariaDB Connector/Python](https://mariadb.com/docs/connectors/connectors-quickstart-guides/connector-python-guide)
> version 2.0, currently distributed as a release candidate. `pip install
pynbi` installs it automatically as a self-contained binary wheel, hence no
> separate MariaDB Connector/C installation is needed.

## Running a simulation

### Quick start

```bash
pynbi run configs/example.yaml
```

This loads the given YAML configuration, connects to CroXe using its
defaults (`127.0.0.1`, read-only `croxe-guest` user, `CroXe` database),
builds the species table, runs the simulation with a live progress bar, and
writes an HDF5 output file containing particle trajectories, a
species/energy-resolved 3D flux grid, and wall power deposition.

### Available commands

At present, PyNBI provides a single command:

| Command | Description                                           |
| ------- | ----------------------------------------------------- |
| `run`   | Run a full simulation from a YAML configuration file. |

### Getting help

```bash
pynbi --help        # top-level help, lists available commands
pynbi run --help    # full list of options for the `run` command
```

### `run` command-line options

| Option                | Default       | Description                                                                                                                                                                       |
| --------------------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `config` (positional) | _required_    | Path to the YAML configuration file.                                                                                                                                              |
| `--croxe-url`         | _(unset)_     | Full SQLAlchemy connection URL for CroXe (e.g. `mariadb+mariadbconnector://user:pass@host/CroXe`). Takes priority over `--croxe-host`/`--croxe-user`/`--croxe-database` if given. |
| `--croxe-host`        | `127.0.0.1`   | CroXe database host. Append `:port` for a non-default port. Ignored if `--croxe-url` or the `CROXE_DB` environment variable is set.                                               |
| `--croxe-user`        | `croxe-guest` | CroXe database user. Ignored if `--croxe-url` or `CROXE_DB` is set.                                                                                                               |
| `--croxe-database`    | `CroXe`       | CroXe database name. Ignored if `--croxe-url` or `CROXE_DB` is set.                                                                                                               |
| `--no-check-files`    | off           | Skip verifying that files referenced by the config (vessel mesh, grid data, etc.) exist on disk before running. Mainly useful for testing; leave this off for a real run.         |
| `-h`, `--help`        | --            | Show help and exit.                                                                                                                                                               |

Connection parameters are resolved in exactly this order, matching
[PyCroXe's own `connect()`](https://pycroxe.readthedocs.io/en/stable/generated/generated/pycroxe.connect.html)
precedence: `--croxe-url` (if given), then the `CROXE_DB` environment variable
(if set), then `--croxe-host`/`--croxe-user`/`--croxe-database`.

Credentials are deliberately never accepted as a plain CLI flag (there is no
`--croxe-password`): a password on the command line ends up in shell history
and process listings.

## Configuration

A PyNBI run is fully described by one YAML file. Units are SI throughout,
except angles, which are always in degrees: length `[m]`, magnetic field
`[T]`, density `[m^-3]`, potential `[V]`, time `[s]`, angle `[deg]`.

### Example configuration

This is the example shipped in `configs/example.yaml`. It references two
files not included in the repository: `vessel.stl` (the NBI vessel mesh)
and `background_field.h5` (a grid-sampled background field). Both are
user-provided, see [`grid`](#magnetic-field) below for the array names
`background_field.h5` needs to contain. If you just want to try PyNBI
without a grid file on hand, replace the first `superposition` component
with a plain `{type: uniform, B: [0.0, 0.0, 0.02]}` and drop the `file`
line.

```yaml
# Units are SI compliant except for angles: length [m], B [T], density [m^-3],
# potential [V], time [s], angle [deg].

acceleration_potential: 50.0e3 # 50 keV beam
extracted_absolute_current: 5.0 # A -- grid-extracted current (single charge state)
target_gas: "H2" # background target gas for cross-sections

species:
  # Abundances may not sum to 1. If so, they are normalized on load.
  # Species properties are retrieved from CroXe at runtime.
  - { symbol: "H+", abundance: 0.80 }
  - { symbol: "H2+", abundance: 0.15 }
  - { symbol: "H3+", abundance: 0.05 }

beamlets:
  items:
    - {
        position: [0.0, 0.05, 0.0],
        direction: [5, -2, 0],
        divergence_deg: 0.5,
        aperture_radius: 0.005,
      }
    - {
        position: [0.0, -0.05, 0.0],
        direction: [5, 2, 0],
        divergence_deg: 0.5,
        aperture_radius: 0.005,
      }
    - {
        position: [0.0, 0.0, 0.05],
        direction: [5, 0, -2],
        divergence_deg: 0.5,
        aperture_radius: 0.005,
        weight: 0.8,
      }

fields:
  magnetic:
    # Magnetic shield zeroes the field inside the box.
    # Outside the shield, background grid + analytic coil are superimposed.
    type: masked
    mask:
      type: box
      half_extents: [0.25, 0.15, 0.15]
      transform:
        offset: [0.25, 0.0, 0.0]
    field:
      type: superposition
      components:
        - type: grid
          file: background_field.h5 # x, y, z, Bx, By, Bz in SI

        - type: rectangular_coil
          current: 1200.0
          coil_width: 0.40
          coil_height: 0.30
          n_turns: 1
          transform:
            offset: [1.60, 0.1, 0.0]
            rotation_axis: [1.0, 0.0, 0.0]
            rotation_angle_deg: 90.0

        # Second coil of the Helmholtz-like pair, mirrored along y
        - type: rectangular_coil
          current: 1200.0
          coil_width: 0.40
          coil_height: 0.30
          n_turns: 1
          transform:
            offset: [1.60, -0.1, 0.0]
            rotation_axis: [1.0, 0.0, 0.0]
            rotation_angle_deg: 90.0

  gas:
    type: uniform
    density: 5.0e18 # m^-3

geometry:
  file: vessel.stl

simulation:
  n_particles: 1_000_000
  gyro_period_fraction: 1e-3 # timestep = 1/1000 of smallest cyclotron period
  max_steps: 13000
  min_active_fraction: 1.0e-4 # stop when fewer than 100 particles remain
  seed: 42 # set to null for a random seed


# The output section below is commented out to show the defaults. See Output in
# the configuration reference for what each field does.
# output:
#   path: run.h5
#   trajectory_subsample: 10_000
#   rec_every: 10
#   flux_grid_cell_size: 0.05
#   save_face_energy: true
#   save_boundary_events: false
#   compression: gzip
```

### Configuration reference

#### Top level

| Field                        | Type                        | Required       | Description                                                                                                                                                                                                                                                                                                                                                              |
| ---------------------------- | --------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `acceleration_potential`     | float, > 0                  | yes            | Beam acceleration potential, in Volts.                                                                                                                                                                                                                                                                                                                                   |
| `extracted_absolute_current` | float, > 0                  | yes            | Absolute value of the beam's extracted current at the grid, in Amperes. Used to rescale every particle's statistical weight, so the total simulated current matches this value. Requires every initial species (see `species` below) to carry the same, non-zero sign of charge (a real grid extracts a single charge state, never a mix, and never a neutral directly). |
| `target_gas`                 | string                      | yes            | Symbol of the background target gas species (e.g. `"H2"`), passed to CroXe as the collision target when building the species table.                                                                                                                                                                                                                                      |
| `species`                    | list of [Species](#species) | yes, ≥ 1 entry | Starting species and their relative abundances.                                                                                                                                                                                                                                                                                                                          |
| `beamlets`                   | [Beamlets](#beamlets)       | yes            | Beamlet geometry, given inline or via CSV file.                                                                                                                                                                                                                                                                                                                          |
| `fields`                     | [Fields](#fields)           | yes            | Background magnetic field and gas density.                                                                                                                                                                                                                                                                                                                               |
| `geometry`                   | [Vessel](#vessel-geometry)  | yes            | The vessel mesh particles travel through.                                                                                                                                                                                                                                                                                                                                |
| `simulation`                 | [Simulation](#simulation)   | yes            | Particle count, timestep resolution, stop criteria, RNG seed.                                                                                                                                                                                                                                                                                                            |
| `output`                     | [Output](#output)           | no             | HDF5 output settings. Uses all defaults if omitted.                                                                                                                                                                                                                                                                                                                      |

#### Species

Each entry in the top-level `species` list:

| Field       | Type       | Required | Description                                                                                                                                     |
| ----------- | ---------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `symbol`    | string     | yes      | Species chemical symbol, as stored in CroXe.                                                                                                    |
| `abundance` | float, > 0 | yes      | Relative abundance. Abundances across all species need not sum to 1; if they don't, they're normalized on load, preserving the original ratios. |

#### Beamlets

`beamlets` requires **exactly one** of `items` or `file`:

| Field   | Type                        | Required              | Description                                                                                                                                                                    |
| ------- | --------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `items` | list of [Beamlet](#beamlet) | one of `items`/`file` | Beamlets given inline.                                                                                                                                                         |
| `file`  | string (`.csv`)             | one of `items`/`file` | Path to a CSV file with columns `pos_x`, `pos_y`, `pos_z`, `dir_x`, `dir_y`, `dir_z`, and optionally `div_deg`, `radius`, `weight`. Position and radius columns are in meters. |

##### Beamlet

Each entry in `beamlets.items`:

| Field             | Type                  | Required | Default | Description                                            |
| ----------------- | --------------------- | -------- | ------- | ------------------------------------------------------ |
| `position`        | `[x, y, z]`, meters   | yes      | --      | Beamlet center position.                               |
| `direction`       | `[x, y, z]`, non-null | yes      | --      | Beamlet direction axis (need not be normalized).       |
| `divergence_deg`  | float, ≥ 0            | no       | `0.0`   | Angular divergence at the σ√2 point, in degrees.       |
| `aperture_radius` | float, ≥ 0            | no       | `0.0`   | Emission disc radius, in meters.                       |
| `weight`          | float, > 0            | no       | `1.0`   | Relative intensity of this beamlet against the others. |

#### Fields

| Field      | Type                              | Required | Description                                           |
| ---------- | --------------------------------- | -------- | ----------------------------------------------------- |
| `magnetic` | [Magnetic field](#magnetic-field) | yes      | Background magnetic field, of any of the types below. |
| `gas`      | [Background gas](#background-gas) | yes      | Background gas density, of any of the types below.    |

##### Magnetic field

`fields.magnetic.type` selects one of six variants:

**`uniform`**

| Field       | Type                    | Required | Description                    |
| ----------- | ----------------------- | -------- | ------------------------------ |
| `B`         | `[Bx, By, Bz]`, Tesla   | yes      | Uniform field vector.          |
| `transform` | [Transform](#transform) | no       | Optional rigid-body transform. |

**`grid`** -- sampled from a file

| Field       | Type                    | Required | Description                                                       |
| ----------- | ----------------------- | -------- | ----------------------------------------------------------------- |
| `file`      | string (`.h5`/`.nc`)    | yes      | Must contain arrays `x`, `y`, `z`, `Bx`, `By`, `Bz`, in SI units. |
| `transform` | [Transform](#transform) | no       | Optional rigid-body transform.                                    |

**`rectangular_coil`** -- analytic field of a single rectangular loop, in its own local frame (origin at the coil center, z-axis normal to the coil plane, width along local x, height along local y)

| Field         | Type                    | Required | Default  | Description                                |
| ------------- | ----------------------- | -------- | -------- | ------------------------------------------ |
| `current`     | float                   | yes      | --       | Current flowing in the coil, in Amperes.   |
| `coil_width`  | float, > 0              | yes      | --       | Coil extent along local x-axis, in meters. |
| `coil_height` | float, > 0              | yes      | --       | Coil extent along local y-axis, in meters. |
| `n_turns`     | int, > 0                | no       | `1`      | Number of coil turns.                      |
| `transform`   | [Transform](#transform) | no       | identity | Optional rigid-body transform.             |

**`vector_function`** -- an arbitrary user-defined field

| Field       | Type                    | Required | Description                                                                                                                                             |
| ----------- | ----------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `module`    | string                  | yes      | Path to a `.py` file, or an importable module name, containing the function.                                                                            |
| `function`  | string                  | yes      | Function name within `module`. Must accept a `(n, 3)` array of positions (meters) as its first positional argument and return a `(n, 3)` array (Tesla). |
| `params`    | dict                    | no       | Extra keyword arguments forwarded to the function.                                                                                                      |
| `transform` | [Transform](#transform) | no       | Optional rigid-body transform.                                                                                                                          |

**`superposition`** -- sum of any number of other magnetic field nodes

| Field        | Type                                           | Required | Description                                                                                  |
| ------------ | ---------------------------------------------- | -------- | -------------------------------------------------------------------------------------------- |
| `components` | list of [Magnetic field](#magnetic-field), ≥ 1 | yes      | Fields to be superimposed, of any type, including further `superposition` or `masked` nodes. |

**`masked`** -- field zeroed inside a 3D shield geometry

| Field   | Type                              | Required | Description             |
| ------- | --------------------------------- | -------- | ----------------------- |
| `field` | [Magnetic field](#magnetic-field) | yes      | The field to be masked. |
| `mask`  | [Geometry](#geometry-shapes)      | yes      | The shielding geometry. |

##### Background gas

`fields.gas.type` selects one of three variants:

**`uniform`**

| Field     | Type       | Required | Description                  |
| --------- | ---------- | -------- | ---------------------------- |
| `density` | float, ≥ 0 | yes      | Uniform gas density, in m⁻³. |

**`grid`** -- sampled from a file

| Field       | Type                    | Required | Description                                                |
| ----------- | ----------------------- | -------- | ---------------------------------------------------------- |
| `file`      | string (`.h5`/`.nc`)    | yes      | Must contain arrays `x`, `y`, `z`, `density`, in SI units. |
| `transform` | [Transform](#transform) | no       | Optional rigid-body transform.                             |

**`scalar_function`** -- an arbitrary user-defined density

| Field       | Type                    | Required | Description                                                                                                                                         |
| ----------- | ----------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `module`    | string                  | yes      | Path to a `.py` file, or an importable module name, containing the function.                                                                        |
| `function`  | string                  | yes      | Function name within `module`. Must accept a `(n, 3)` array of positions (meters) as its first positional argument and return a `(n,)` array (m⁻³). |
| `params`    | dict                    | no       | Extra keyword arguments forwarded to the function.                                                                                                  |
| `transform` | [Transform](#transform) | no       | Optional rigid-body transform.                                                                                                                      |

#### Vessel geometry

| Field       | Type                    | Required | Description                                              |
| ----------- | ----------------------- | -------- | -------------------------------------------------------- |
| `file`      | string (`.stl`)         | yes      | Path to the vessel STL mesh. Vertex positions in meters. |
| `transform` | [Transform](#transform) | no       | Optional rigid-body transform.                           |

##### Geometry shapes

Used for magnetic field masks (`fields.magnetic.mask`, above), selected by `type`:

**`box`** -- centered at the origin, edges parallel to the local axes

| Field          | Type                         | Required | Description                    |
| -------------- | ---------------------------- | -------- | ------------------------------ |
| `half_extents` | `[x, y, z]`, meters, all > 0 | yes      | Half-length of each edge.      |
| `transform`    | [Transform](#transform)      | no       | Optional rigid-body transform. |

**`cylinder`** -- centered at the origin

| Field         | Type                    | Required | Default     | Description                                              |
| ------------- | ----------------------- | -------- | ----------- | -------------------------------------------------------- |
| `axis`        | `[x, y, z]`, non-null   | no       | `[0, 0, 1]` | Cylinder axis direction, in the local frame.             |
| `radius`      | float, > 0              | yes      | --          | Cylinder radius, in meters.                              |
| `half_length` | float, > 0              | yes      | --          | Half of the cylinder's length along its axis, in meters. |
| `transform`   | [Transform](#transform) | no       | identity    | Optional rigid-body transform.                           |

**`stl`** -- an arbitrary mesh shape

| Field       | Type                    | Required | Description                                       |
| ----------- | ----------------------- | -------- | ------------------------------------------------- |
| `file`      | string (`.stl`)         | yes      | Path to the STL mesh. Vertex positions in meters. |
| `transform` | [Transform](#transform) | no       | Optional rigid-body transform.                    |

#### Transform

A shared rigid-body transformation (translation + rotation), used throughout the sections above. Rotation may be given as **either** an axis and angle **or** Euler roll/pitch/yaw angles, never both.

| Field                | Type                          | Required | Default     | Description                                                                               |
| -------------------- | ----------------------------- | -------- | ----------- | ----------------------------------------------------------------------------------------- |
| `offset`             | `[x, y, z]`, meters           | no       | `[0, 0, 0]` | Translation vector.                                                                       |
| `rotation_axis`      | `[x, y, z]`, non-null         | no       | `null`      | Rotation axis (need not be normalized). Must be given together with `rotation_angle_deg`. |
| `rotation_angle_deg` | float                         | no       | `null`      | Right-handed rotation around `rotation_axis`, in degrees.                                 |
| `rotation_rpy_deg`   | `[roll, pitch, yaw]`, degrees | no       | `null`      | Euler angles. Mutually exclusive with `rotation_axis`/`rotation_angle_deg`.               |

#### Simulation

The simulation runs until whichever of `min_active_fraction`, `max_steps` (if set), or `max_time` (if set) is reached first.

| Field                         | Type                | Required | Default  | Description                                                                                                                                                                                                                                                                                                                                       |
| ----------------------------- | ------------------- | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `n_particles`                 | int, > 0            | yes      | --       | Number of particles to simulate.                                                                                                                                                                                                                                                                                                                  |
| `gyro_period_fraction`        | float, > 0          | no       | `1.0e-2` | Fraction of the smallest cyclotron period that bounds the timestep: `dt <= gyro_period_fraction * T_c_min`.                                                                                                                                                                                                                                       |
| `max_collision_probability`   | float, in (0, 1)    | no       | `0.05`   | Upper bound on per-step collision probability, also constraining the timestep. Values above `0.1` trigger a warning.                                                                                                                                                                                                                              |
| `allow_geometric_fallback_dt` | bool                | no       | `false`  | If neither the gyro-motion nor the collision-probability constraint yields a finite timestep (no magnetic field and no possible collision anywhere), a `ValueError` is raised by default, since this usually signals a configuration mistake. Set to `true` to opt into a fallback timestep derived from the vessel's own geometric size instead. |
| `max_steps`                   | int, > 0            | no       | `null`   | Stop after this many steps.                                                                                                                                                                                                                                                                                                                       |
| `max_time`                    | float, > 0, seconds | no       | `null`   | Stop after this much simulated time has elapsed.                                                                                                                                                                                                                                                                                                  |
| `min_active_fraction`         | float, in [0, 1]    | no       | `0.0`    | Stop once the active-particle fraction drops to or below this value. The default (`0.0`) means "run until every particle is inactive."                                                                                                                                                                                                            |
| `seed`                        | int, ≥ 0            | no       | `null`   | RNG seed. If unset, entropy is drawn from the OS (results won't be reproducible run-to-run).                                                                                                                                                                                                                                                      |

#### Output

Results are written to a single HDF5 file: a subsample of particle trajectories, a species/energy-resolved 3D flux grid, and, optionally, wall power deposition data.

| Field                  | Type                         | Required | Default    | Description                                                                                                                                                                                                                                                                                                                                 |
| ---------------------- | ---------------------------- | -------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `path`                 | string                       | no       | `"run.h5"` | Output file path.                                                                                                                                                                                                                                                                                                                           |
| `trajectory_subsample` | int, > 0                     | no*      | `10000`    | Exact number of particle trajectories to record. Mutually exclusive with `trajectory_fraction`.                                                                                                                                                                                                                                             |
| `trajectory_fraction`  | float, in (0, 1]             | no*      | `null`     | Fraction of particles' trajectories to record, instead of an exact count. Mutually exclusive with `trajectory_subsample`.                                                                                                                                                                                                                   |
| `rec_every`            | int, > 0                     | no       | `1`        | Record trajectories every this many steps. `1` records every step.                                                                                                                                                                                                                                                                          |
| `flux_grid_cell_size`  | float, > 0, meters           | no       | `0.05`     | Edge length of the isotropic cubic cells used for the 3D particle-flux grid. The grid's extent is derived automatically from the vessel's own bounding box.                                                                                                                                                                                 |
| `save_face_energy`     | bool                         | no       | `true`     | Include wall power-deposition data in the output.                                                                                                                                                                                                                                                                                           |
| `save_boundary_events` | bool                         | no       | `false`    | Include the raw per-particle boundary-crossing event log, in addition to the aggregated per-face power total. Off by default: it's the single most expensive part of the output to write, and is only useful for debugging or answering questions (e.g. species-resolved power at a given face) not already covered by the aggregated data. |
| `compression`          | `"gzip"`, `"lzf"`, or `null` | no       | `"gzip"`   | HDF5 compression algorithm. `lzf` is faster to write but compresses less; `null` disables compression entirely (fastest, largest files).                                                                                                                                                                                                    |

\* If neither `trajectory_subsample` nor `trajectory_fraction` is set, a warning is issued and `trajectory_subsample` defaults to `10000`.

## Contributing

Right now you can suggest changes through
[e-mail](mailto:luca.cinnirella@protonmail.com), but soon some more standard
ways of contributing will be available.

## License

PyNBI is free as in freedom and licensed under the [GPL v3](./LICENSE).
