Choosing a backend#

This page says which backends solprop has, which one a run uses, how closely they agree and how a run’s memory is budgeted. The physics of a run is decided before a backend sees it: solprop first translates a simulation into one numerical problem — the arrays and tables of its linear operator, its nonlinear responses and its transforms — and every backend executes that same problem. The backends differ in speed, in where they run and in the representations they support. Choose a backend that supports the full configuration; a faster backend does not select a different physical model. Use the native backend, which "auto" selects; mlx and numpy_ref are for the cases below.

backend

what it is

use it for

native

the native backend, the solprop-native distribution built from the repository’s native/ directory and a required dependency of solprop: OpenMP, FFTW and a vendor BLAS on a CPU, or CUDA/HIP on a GPU

the one to use: what "auto" selects

mlx

the Apple-silicon GPU through Apple’s MLX, in single precision only (single precision); the mlx extra, macOS on Apple silicon; experimental in this release

precision="single" capillary and fibre runs on a Mac, where it is the "auto" choice; it refuses precision="double"

numpy_ref

plain NumPy, the numerical reference: every other backend is tested against it. It is unthreaded, and roughly 6 to 40 times slower than threaded native

checking a result, and the features native does not run. Also problems a GPU build refuses or runs slowly, in a process that has only that build

Which backend a run uses#

backend="auto", the default of every entry point, and an explicit name select as follows. The right-hand columns say what happens on this machine; where native isn’t consulted they repeat the first:

request

native usable

processor below the floor, or its features unreadable

native fails to load

no native package

double precision, auto

native

refuses, naming the missing features or the failed check

the load’s own error, unchanged

refuses, saying how to install it

native by name

native

as above

as above

as above

TDSE response, auto

numpy_ref; native isn’t consulted

the same

the same

the same

single precision, auto

mlx with a Metal device, otherwise refuses; native isn’t consulted

the same

the same

the same

numpy_ref by name

runs; native isn’t consulted

the same

the same

the same

mlx by name

runs where MLX is installed; native isn’t consulted

the same

the same

the same

"auto" has no fallback. These cases stay distinct, so an import failure is never reported as an unsupported platform, and a problem the chosen backend refuses is refused, naming what was refused and what runs it, for you to choose explicitly. Which platforms and processors have a native build is on the installation page. The run records the backend it used, and the SOLPROP_REQUIRE_DEVICE setting makes a GPU job stop rather than run anywhere else (a job that must run on a device).

A GPU build of the native backend also refuses a problem whose linear operator or nonlinear prefactor varies along z in a way that could not be expanded in the medium’s parameters and had to be tabulated along z instead: the adiabatic opt-out taper_coupling=False of a multimode capillary taper whose expansion does not resolve (a kinked radius profile, for instance), or a gas mixture of three or more species on independent density paths. The refusal leads with what runs it: a CPU build of the native backend, in a second environment, or backend="numpy_ref".

Multistep() (the Adams integrator) and RungeKutta(), each the default of some interfaces (the defaults), are available across the binary64 backends for their supported configurations; single precision refuses Multistep() on every backend. Beside a coupled flow, Adams requires its exponential formulation; CF4 requires Runge–Kutta on the reference backend. Check the response and geometry chapters for further restrictions. The backend used is logged at INFO and recorded in the output.

from solprop.backends.registry import available, resolve_name

print("installed:", available())
print("auto resolves to:", resolve_name("auto"))

How close they are. Backend qualification compares short calculations with the NumPy reference using stated rounding and discretisation budgets. A different build, transform plan or device can change floating-point summation order, so cross-backend agreement need not be bitwise. Nor is a repeat on the same build: solprop promises numerical accuracy, not identical bits, across backends, builds, machines, thread counts, batches and repeated runs (SEP 25), and a rounding difference can change an adaptive run’s steps. Small differences can also grow in noise-sensitive propagation: compare the physical observable and numerical convergence, not only an array digest. Record the backend, build and thread settings with a result. See numerical qualification and its backend-equivalence ladder for the scope and measurements of the existing checks.

Speed and GPUs. How to install the native backend, build it for a GPU and run on a device is on The native backend; thread counts, OpenMP and BLAS settings, and what to use for scans are on Performance tuning.

Features that run on the reference backend#

numpy_ref is the numerical reference, and it is also the supported way to run these features, which native does not run yet. Each is a limit of native, and each stays supported on the supported platforms:

feature

reached by

tracked by

the TDSE response

auto or by name

CoherentWall(method="cf4"), and a coupled flow with a CF4 section, with integrator=RungeKutta()

by name

#304

single precision away from Apple silicon

by name

#342

The TDSE row’s auto is that of the entry points; SimulationSettings built by hand with tdse_execution names the backend. Running numpy_ref by name on an unsupported platform may keep working, without a support promise. The list is not a general waiver: a new feature still lands on both numpy_ref and native (SEP 0), and joins the list only as an accepted exception.

The removed numba_cpu backend#

solprop once had a third double-precision backend, numba_cpu, and 0.1.0 is the last release with it. Naming it now raises BackendError, naming native and numpy_ref, so a script or recipe that selects it needs backend="native", or no backend= at all. Output, snapshots and a scan’s execution metadata that record numba_cpu still load, and keep saying so: the record is not rewritten.

The backend is an execution resource, so changing it does not change a run’s identity. A run can continue on native or numpy_ref, with its script’s or recipe’s backend changed deliberately, through the normal resume checks; the original execution record stays and the new one is added. Adams history crosses to the new backend, except a frozen-residual coupled flow’s: a host engine’s history continues on numpy_ref, and on native starts afresh and says why. Anything refused is recovered with solprop==0.1.0.

Its settings are gone too: SOLPROP_NUMBA_THREADS, SOLPROP_NUMBA_REGIME, SOLPROP_FFTW_THREADS, SOLPROP_TRANSVERSE_FFTW_THREADS, SOLPROP_PASS_TIMERS, NUMBA_DISABLE_JIT and NUMBA_NUM_THREADS no longer do anything.

Which backend to use#

Use the native backend. numpy_ref is unthreaded and roughly 6 to 40 times slower than threaded native, so it is for checking a result and for what only it runs; the performance history records the native backend’s own timings. The measurements behind the advice below are in the indicative results; time your own problem for ten steps before committing a long campaign to one backend.

  • Which native build. Build the native backend on the machine that runs it, or install a distribution build, the wheel (build profiles): the local build was the fastest backend on every coherent-wall and general modal case measured, at one, four and eighteen threads, and the distribution build ran within about 5 % of it. On Linux, prefer MKL (with oneAPI sourced) for TE/TM coherent walls, where it was 1.4–1.8 times faster than OpenBLAS; elsewhere the two tied.

  • Independent scans on a many-core CPU. Many one-thread workers beat one many-thread run (Performance tuning). Stay on the native backend for them. For a large campaign time one point of the scan with your real number of workers.

  • The general modal route (from_waveguide(..., provider=...)): the compiled backends measured, native and the since-retired numba_cpu, were 1.3 to 7.7 times faster than numpy_ref over ten steps at one thread, and a local native build gains a further factor at more threads. numpy_ref is slowest on the analytic cubic, by five to eight times. Budget memory (execution resources) before raising the channel count, the node count or the grid.

  • The coupled flow (a coherent wall, a varying wall, a split block): both CPU backends run the constant-Schur and frozen-residual sections; CoherentWall(method="cf4") runs on numpy_ref alone. A single small wall run gains nothing past a few threads. On a varying wall the flow’s actions are re-formed every adaptive step, in a few per cent of a step on the native backend.

  • On a GPU (a CUDA or HIP build of the native backend): pick it for large grids. Every general-route, coupled-flow and moving-guide case measured ran faster on either a data-centre NVIDIA GPU or an AMD Instinct GPU than on an 18-core CPU socket (about four and three times on a scaled graded-index guide); a small case is limited by kernel-launch latency (The native backend).

  • Batches. A fixed-schedule batch fuses the accepted updates of its members, while the response and transform work stays per member, so it does not amortise a device’s launch sequence; measure a batch’s gain for your own shape. On a CPU, a Pool of one-thread workers is the better scan pattern.

Memory and scans#

For memory, output storage and scan workers, continue to execution resources. Choose resolution for the physics before tuning thread counts; reducing modes or nodes to fit memory changes the model or its numerical accuracy.

The Apple-GPU backend#

pip install solprop[mlx] installs MLX on macOS on Apple silicon, and with it the mlx backend (SEP 33). Apple GPUs have no binary64 arithmetic, so it runs precision="single" only, under the contract and with the accuracy record of single precision; it is validated against the reference backend’s single-precision path. It runs the capillary and fibre routes (mode-averaged and multimode, field and envelope grids, Kerr, Raman, field-grid plasma, both absorber schemes, Runge–Kutta and the low-storage march, lockstep batches) and radial and Cartesian free space, including a line, with the safety ramp, and the envelope-grid plasma. It reads the transverse edge on the host, from its state widened to binary64 (transverse boundaries).

The backend is experimental in this release. Its Metal kernels run in the macOS default lane of continuous integration, which requires a usable Metal device, but coverage and runtime type checks remain absent there (the remaining CI gaps). Its speed has not been re-measured on the default grids, whose 2-3-5-smooth sizes MLX’s GPU transform runs four to eight times slower than powers of two: build single-precision grids with fft_size="pow2" (grid sizes on mlx).

MLX computes float32 matrix products in TF32 by default on Apple GPUs, which SEP 25’s arithmetic rules forbid. solprop sets MLX_ENABLE_TF32=0 when it imports the backend, but MLX reads the setting once, when it first initialises: in a process that used MLX before, set it in the shell (export MLX_ENABLE_TF32=0) or before any import mlx. The backend measures its matrix products when it is built and refuses to run if they are TF32.