Open questions#
These are open, unscheduled questions about solprop as it is, not planned work: a check not yet made, a choice deferred, or a known limitation accepted for now. Each entry stands alone: what the question is, where it lives in the code or the documentation, what is known, and why it is open.
This page is not the roadmap. Missing functionality and known defects that are to be fixed are listed on the roadmap; a major change starts as an enhancement proposal. An entry here moves to the roadmap if it becomes work to do, and is removed when it is settled.
Contributions are welcome: an independent check of any entry below — a derivation, a comparison with a reference, a measurement — settles it.
Spatial propagation#
The on-axis spectrum’s end bins on the native backend. The on_axis_spectrum
statistic is computed natively on the native backend
(GeneralWorkspace::spatial_statistics in native/src/general.cpp) and in
Python on the others (solprop/backends/spatial.py, through
spectral_density()). On a field grid the two agree at every
interior bin and differ at the zero-frequency and Nyquist bins: the native kernel does
not double those two in the analytic signal, so that \(\delta\omega\sum_k S_k\) is the
axial fluence exactly, while spectral_density applies the interior factor at every
bin and, as its docstring states, overcounts those two by a factor of four. Every grid
that solprop.grid builds apodises both bins to zero, so no run built through the
public interfaces sees the difference. Open: which definition the statistic should have,
so that the backends also agree on a hand-built grid.
Transverse components resolved at the entrance only. With a transverse quadrature and
components=None, assemble() decides which Cartesian
field components (x, y or both) the transform carries from each mode’s
principal_component(0.0), and refuses an explicitly requested component that is zero
for every mode by the same z = 0 reading (_components and _transform in
solprop/translate/problem.py). The mode protocol’s principal_component(z) takes a
position, so a mode whose polarisation changed along the guide would gain a component the
transform does not carry. Every mode class today ignores z, so no run is affected. Open:
whether the resolution should take the union over the run’s length, or the protocol
should declare the component independent of z.
Thread-count invariance of the transverse FFT pool. On numba_cpu the Cartesian
transverse FFTs run on an FFTW pool of their own, sized by transverse_fftw_threads= or
SOLPROP_TRANSVERSE_FFTW_THREADS and otherwise by the FFT size rule
(solprop/backends/numba_cpu/backend.py); the backend metadata records the size. An FFTW
plan, and so its rounding, can depend on the thread count, as the temporal pool’s does.
The tests pin this pool to one thread or check only that the setting is recorded; none
compares a Cartesian run at two transverse thread counts. Open: the rounding bound that
holds between two sizes of this pool, and a test of it (bitwise independence is not
required, SEP 25).
GPU capacity at the largest Cartesian grids. A Cartesian run’s memory is set by its
\(n_x n_y\) columns per time sample (free space, Memory). The
backend ledger counts the retained arrays exactly, and native also records the device’s
free-memory decrease while its FFT plans are built, but cuFFT and rocFFT plan workspace is
opaque and these measurements are not upper bounds. No full-size Cartesian grid (for
example 512 × 512 transverse samples on a few thousand time samples) has been run on a
data-centre GPU, so the largest grid a given device holds is not known. Open: a
measurement of plan usage and peak device memory at the largest intended grid on each
qualified device.
Translation and materials#
Interior extrema in the scalar table check. Before a run, _check_scalar_domain
(solprop/translate/problem.py) checks that each scalar Chebyshev table of a density or
a preionisation fraction stays in its domain, by evaluating every interval at its ends and
at the real roots of its derivative. A root is kept only when numpy.isreal holds
exactly, so a stationary point returned with a rounding-size imaginary part is dropped,
and an excursion out of the domain at that point would pass without a refusal. No false
refusal and no missed excursion was seen on the fitted tables checked. Open: whether to
admit roots whose imaginary part is below a tolerance, and which tolerance.
with_mixture and the operator’s representation.
Simulation.with_mixture
reuses the existing pressure expansion when the new densities fall within its parameter
ranges. Updated to a constant fill inside those ranges, the run keeps the
"expansion" representation of the linear operator, where a fresh build of the same
mixture gives "constant"; the two agree to the expansion’s tolerance, not bitwise.
Open: whether an update to a constant fill should always rebuild the constant operator,
trading the reuse for bitwise agreement with a fresh build.
floor= beside a constant or profile pressure.
Mixture.fill documents floor as the
pressure-gradient floor in bar. Beside a scalar pressure or a supplied ZProfile it is
accepted and ignored (_pressure_path in solprop/physdata/mixtures.py). Open: refuse
it by name there, or apply it to a supplied profile.
A reserved electrons-per-event weight. The compiled plasma channels
(PlasmaChannel in solprop/translate/responses.py, EnvelopePlasmaChannel in
solprop/translate/envelope_plasma.py) carry electrons_per_event, and the reference
backend’s field-plasma current multiplies by it, but numba_cpu and native do not carry
it. Validation pins it to 1, so the backends agree. Open: remove the field, or carry it
through the compiled backends with a test, for a model that releases more than one
electron per event.
Numerical methods#
Each of these has a measurable question behind it; none blocks the current solver. The section numbers refer to the solver design.
Error norms for observables far below the pump. The weak norm of the step controller
(stepper.md §3) scores total content, so a weak spectral band — a dispersive wave, a
harmonic — can be under-resolved while the norm is met. The weighted norm is a tool, not
an answer. Open: which weighting controls the error of such an observable, and at what
cost.
Reject-free efficiency at matched global error. reject_free takes more steps and
buys a smaller error (§3). Open: whether it wins at matched global error, and on which
problems.
Schedule transfer outside the measured ranges. A calibrated fixed schedule transfers across the scan cases measured so far (§8). Open: how far it transfers in pressure, energy and length beyond them, and whether a schedule calibrated on a coarser transverse grid transfers to the production grid.
Round-off growth with varying operators. Open: whether the round-off floor of a long replayed run grows linearly with the step count when the linear operator varies along z, and whether dithering the schedule would decorrelate it.
Save-plane density versus cost. Every save plane is a step landing (ADR-009). Open: the cost of dense plane sets, measured against dense output.
FSAL through a coupled flow. The first-same-as-last identity and its saving are known for a coupled flow (§2). Open: whether to enable it, and with which parity statement between backends.
Validation#
Systematic differences against Luna’s mixture witness.
tests/backends/test_luna_mixture_propagation.py propagates a single gas, the same gas
split into two populations, and a two-channel plasma case over 64 fixed steps from Luna’s
own inputs and compares the exit field with Luna.jl’s
(tests/data/luna_mixture_propagation.json). The relative L2 differences are 5.25e-9
(single and split) and 1.12e-8 (plasma), the same on every run. The test admits them
under a predeclared rounding budget, \(64\,n_{to}\,n_{\rm channels}\cdot 7\cdot{\rm
steps}\cdot\varepsilon\) (1.30e-8 and 2.61e-8), which is an allowance rather than a count
of operations, so the margin is 2.3–2.5. Open: what the systematic difference is —
summation order, an input conversion or a small model difference — so that the bound can
be derived rather than budgeted.
NaN in free-space reference runs under NumPy 2.3 and 2.4. With every other
dependency at its floor, NumPy 2.3.2, 2.3.5 and 2.4.0 make the numpy_ref free-space
runs (line, planar and radial) return NaN fields and fail about 200 tests, among them
tests/backends/test_line_free.py and tests/backends/test_free_nonlinear.py, with
overflow warnings on the way; NumPy 2.5.0 passes them, independently of the Numba
version. The NumPy floor is held at 2.5 (dependencies) until the
cause is found. Open: which operation’s behaviour changed between 2.4 and 2.5, and
whether the code relies on it by accident.