Changelog#

Unreleased#

Added#

  • The desktop front end’s distribution, solprop-gui, built from gui/ in this repository and installed with pip install "solprop[gui]" at solprop’s own version. Only its skeleton is in place: the solprop-gui command opens a placeholder window. Its design is docs/dev/gui/design.md.

  • The first performance-history rows (benchmarks/history/), recorded at 0.1.0 on six CPU and three GPU hardware sets, with numba runs beside the native ones on three of them; docs/dev/benchmarking.md lists how each is run. The history takes any backend: each row names it, and rows compare only within one backend; a run the history recorded no longer makes the next run in that checkout dirty. The benchmark harness names an AMD GPU from its PCI IDs where ROCm reports only “AMD Radeon Graphics”.

  • One table of the route defaults, solprop.driver.defaults.ROUTE_DEFAULTS, with the functions that resolve each default (default_rtol, default_integrator, default_controller, default_scheme, resolve_noise); the run record’s new defaults entry (DefaultsResolution), the start-of-run summary and the settings’ display say which of the integrator, rtol and the collar scheme took the route’s default, and the summary names a Multistep carrier frame (#87).

  • Recipes (SEP 41): solprop.recipe.Recipe reads a TOML description of a gnlse or capillary run, with quantities in their units ("125 um", "1.2 bar"), checks it (check(), each issue at its parameter path), builds and runs it, saves it and exports it as the Python script it stands for (to_python()). solprop.recipe.parameters gives each keyword’s forms, units, display unit, default rule and manual section. The solprop command (also python -m solprop) runs, checks and exports recipes, and forwards solprop scan. Example recipes ship with the package (Recipe.examples()). Errors and warnings that concern a value carry its parameter; writing TOML takes the tomli-w dependency. solprop.grid.suggest proposes wavelength_limits and trange from the input pulses, as a starting point. A run built from a recipe records it in a top-level recipe group of its output, as written and as resolved (the recipe that rebuilds the run); Simulation.run(stop=...) takes a flag that stops the run, writes its state and returns, and solprop run stops cleanly on Ctrl-C. solprop resume and solprop.recipe.resume_file continue a run from its file alone, and solprop.output.resumability says whether a file can continue, from where and with which resume=, whether it can rebuild itself, and, on request, what the gate would compare. run_scan(stop=) takes the same flag: Local and Pool stop the point in flight, Pool through a message on the worker pipe, Batch between groups; a stopped point is not committed and a rerun computes it; Queue and Slurm refuse it. run_scan(progress=) accepts a solprop.log.ScanReporter (and a WatchingScanReporter for each point’s run, relayed from Pool workers). A recipe’s [scan] table gives Recipe.scan(), ready for run_scan. Simulation.estimate() reports a run’s memory (admitted before anything is allocated), output size and the cost of one right-hand-side evaluation; solprop.capabilities.output says what a results file can give: its basis, which quantities are physical fluxes, which planes keep fields, which operations its readers refuse and why. A reporter that implements observe_plane (solprop.log.PlaneObserver) is shown each saved plane as a lazy view, whose spectrum, field, power and spectral density come from the processing functions, fetched from a device only on request; a failing observer is detached and the run completes. See recipes and the command line.

  • Design solvers for any guide (SEP 41): solprop.tools.solve_parameter finds every value of a guide or fill parameter (a radius, a pressure, a core size, a wall thickness) that meets a target, ZDW, RDW or SolitonOrder, discarding sign changes through a wall resonance, and returns them as DesignRoots with the attainable range when there is none. tools.radius_zdw inverts zdw in the radius; tools.soliton_peak_power and tools.pulse_energy give the pulse for a soliton order. A recipe’s [pins] table sets a value from such a target (Recipe.resolve(), Recipe.pin_roots()), solved at each build and each scan point and recorded with the run. Recipe.modes() and Recipe.preview_input() preview the mode set and the launched pulse before a run. solprop.capabilities.route lists a route’s keywords, their values and the rules that couple them, each refusal naming its rule (SolpropError.rule), and solprop.capabilities.species what a gas admits: its ionisation models with their status, its Raman response, and its data ranges.

  • solprop.pulses.load_spectrum reads a measured spectrum from a text file (frequency or wavelength, power density, and an optional phase) into a DataPulse. A wrapped phase column is unwrapped only where the power is above a noise floor, a phase in the FFT sign convention of a Luna.jl file is read with phase_convention="fft", and a wrapped column sampled too coarsely to unwrap is warned on (SEP 41).

  • prop_capillary, prop_waveguide and their Simulation constructors take launch=, which puts each pulse into chosen modes with a share and an optional phase: launch={"HE1,1": 0.9, "HE1,2": {"share": 0.1, "phase": 1.57}}, or a list with one mapping per pulse of pulses=. Modes are named as channel= names them; the shares are energy fractions, and on a guide that declares a change of basis a coordinate split scaled to the pulse’s energy under the flux metric. The run records the launch as configuration["modal_launch"], which a resume compares; a run without one records nothing and resumes as before (SEP 41).

  • SpatialPulse takes displacement=(d_x, d_y) and tilt=(theta_x, theta_y), as coupling.Alignment defines them, applied at the launch plane on a Cartesian grid with the energy set after them; a tilt the grid’s band, window or time window cannot carry is refused, and so is either on a radial grid, a y component on a line grid, a PlanarPulse and a guide’s incident beam. A record without them continues as an unmoved beam (SEP 41).

  • prop_waveguide and Simulation.from_waveguide take interpolate_planes and step_planes, and prop_capillary, prop_waveguide and their constructors take polarisation_taper, as prop_gnlse does; step_planes defaults to None on every interface that takes it, which on prop_gnlse behaves as its 0 did. The guide lists the deliberate differences between the routes’ keywords (SEP 41).

  • Simulation.from_radial, from_free and from_planar take absorber and absorber_scheme, as prop_radial, prop_free and prop_planar and the modal from_* do.

  • solprop.sol exports wavelength_to_axis, which the plots’ documentation sends the reader to.

  • resume="force" continues a file across changed physics, in the same file, while the stored arrays’ layout agrees, the statistics fit and work is left: the Adams history restarts, the session’s execution record marks the file, and later sessions and readers respect the mark (SEP 42, ADR-063). RunMetadata.compare and solprop.output.compatibility hold the comparison; RunOutcome.resumes and RunOutcome.forced_positions read back how each session continued its file.

  • Cross-section drawings in the library (SEP 41): solprop.plotting.geometry.outline gives the outline of a capillary, antiresonant, step-index, graded-index, rectangular or planar guide, or of a radial, Cartesian, line or planar grid, as shapes in metres; draw(ax, guide, palette=...) paints it in Matplotlib, and thumbnail(route, representation, size) makes a small figure of a route for a front end’s picker. Colours come from an explicit Palette (LIGHT, DARK). The user guide’s cross-section figures draw their outlines from it.

  • Recipes on the free-space routes (SEP 41): radial, free and planar recipes write the grids, medium, responses, beam and settings as tables of registered types (RadialGrid, FreeGrid, LineGrid, PlanarGrid, SpatialMedium, KerrResponse with gamma3, EnvelopePlasmaResponse, PlasmaResponse, OptmatRate, SpatialPulse, PlanarPulse, SimulationSettings, PIController, default_spec, SpatialAbsorber), the absorber under settings; the Cartesian and planar tutorials and the planar compressor ship as examples; Recipe.preview_input() gives a free-space beam’s power, energy and fluence at the launch plane, and Recipe.modes() the planar route’s slab channels.

Changed#

  • solprop.recipe loads its names on first use (PEP 562), as solprop itself does, so solprop.recipe.quantity, paths, document and errors import without the driver, the backends, h5py or SciPy: about 40 ms rather than half a second, for a front end that parses quantities in a process that never runs a simulation.

  • A run asking for the zdw statistic is no longer stopped when its band holds no zero-dispersion point: the plane records nan with a warning. A direct stats.zdw call still raises, now NoZeroDispersionError, a ModeError.

  • Corrected the mixtures guide on spatial fluence and on who refuses a z table, three worked examples’ prose, how ReferenceState.temperature reads where a source declares none, and what the antiresonant guide claims for the dispersion width’s fit to the finite-element solutions.

  • A resume compares the stored and requested run records path by path instead of requiring equal configuration hashes (SEP 42, ADR-063). The layout and the physics must agree, numbers to 1e-11 per element, so a run resumes after an upgrade that adds a field with a declared neutral value, after an optmat release that changed no number it uses, or on another platform; a changed tolerance or integrator continues an unfinished run, with a warning and a record of it, and a finished file is returned only under the numerics every session ran. optmat models are compared by the digest of their numbers, so a changed optmat version, model name or status no longer refuses a resume. A refusal names what differs by class and part of the record, and says what to do instead. An Adams history continues across a changed call budget or safety factor, an Adams file continues under Runge-Kutta, and the interpolated-plane refusal is decided by the file, whatever interpolate_planes the resuming run asks for. source= in field_plane is checked by the same comparison.

  • A scan rerun reuses a finished point whose record differs only in rounding, a neutral value or provenance, and refuses one whose numerics or physics changed, naming what; the Queue and Slurm executors keep exact identity (SEP 42). The output schema is bumped only for a change an older reader would misread (ADR-063).

  • backend="auto" now warns, once per process, whenever it chooses numba_cpu: when solprop-native is not installed as well as on a processor below its wheel’s baseline. The warning says how to install the native backend; an explicit backend="numba_cpu" does not warn. The README and manual now recommend the native backend by default and describe numba_cpu as its slower fallback, with the 0.1.0 measurements. They no longer advise numba_cpu for scans of one-thread workers, which the history does not support, and they now say that only GPU builds refuse z-tabulated problems.

  • Planned work has moved to GitHub issues, labelled by type, priority and area: the roadmap now lists only SEPs (ADR-062). Issue templates and CONTRIBUTING.md say what an issue or a pull request should hold and what to check before opening one.

  • The README, CITATION.cff and the citing page name solprop’s Zenodo DOI, 10.5281/zenodo.23854248, which covers every release.

Changed (breaking)#

  • A free-space run’s transverse boundary is a SpatialAbsorber (SEP 40), given as SimulationSettings.spatial_absorber or the spatial_absorber keyword of prop_radial, prop_free, prop_planar and their Simulation.from_* builders. By default every radial, Cartesian, line and planar run carries a designed safety ramp on its outer 10 % of transverse wavenumbers, folded exactly into the operator, radial and planar included; spatial_absorber=False removes it. A real-space collar is designed on request, in the grid’s room outside its requested size, from a protected band (kappa, angle, or the launch) and its width, not from the run length, and is refused by name where it cannot meet its budget. TransverseCollar, transverse_collar_rate, SimulationSettings.transverse_absorber and Absorber.spatial are removed, and Absorber.collar_length no longer sets a real-space rate. Kernel schema 41.

  • Every spatial grid takes collar_fraction in place of window_factor, default 0, and puts the room it asks for outside the requested size on every geometry (SEP 40). RadialGrid’s radius is now the requested aperture, the Dirichlet boundary is at outer_radius = radius * (1 + collar_fraction), and n_r counts every sample; the transform, the nodes and the effective area use the outer radius. Fractions of 1 and more are allowed. FreeGrid(rx, nx, ry, ny) and LineGrid lose their inert 10 % margin, so their spacing at the same sample count changes; give collar_fraction=0.1 for the old grid. RadialGrid(R, n_r) keeps its wall and spacing.

  • A free-space run whose light reaches its transverse boundary now warns or stops, where it used to log and carry on (SEP 40). The boundary monitor reads the outer 5 % of the grid on every backend. It warns above 1e-6 of the launch energy and stops at 1e-4, naming a size and a sample count to try, and BoundaryCheck.transverse_budget sets those levels apart from the time window’s. The safety ramp’s removal is judged at the same levels. Its monitor record reports a transverse_status, with ramp_removed and spatial_removed. Reads are at most min(L/16, 4g/v) apart, a ceiling on the step, so a low-source run takes a few more steps. A launch with more than 1e-4 of its energy in the ramp’s band is refused.

  • Names and keywords with two meanings or two spellings are settled, with no aliases; docs/dev/conventions.md section 7 gives the rule for each:

    • prop_2d, time_1d and spectrogram take time_range, not trange, which stays the scalar window length on prop_*. DEFAULT_TRANGE is DEFAULT_TIME_RANGE.

    • stats.zdw and modes.base.zdw take wavelength_limits, not wavelength_range, as does wavelength_to_axis. wavelength_range is now only the attribute that says where a data model is valid.

    • bandpass and Passband take spec_range, not limits, as the plots and accessors do; Passband.limits is Passband.spec_range. A call passing the pair by position is unaffected.

    • band_coherence takes db_min, not floor_db.

    • plotting.energy and plotting.stats are plot_energy and plot_stats, the names they already had in solprop.sol.

    • backends.protocol.StepHistory is BackendHistory. Its z is each attempt’s end and output.schema.StepHistory’s is its start, so the two no longer share a name.

    • RUN_SUMMARY_ANNOTATION moves from driver.summary to output.schema, beside the other annotation names. The stored name is still "run_summary".

  • mode_average=True beside tdse= is refused by name when the simulation is built. It used to build, then fail at translation with a message that did not name it.

  • resume takes False, True, "interpolated" or "force", and any other value is a ValueError: a mistyped word used to read as True. An appending HDF5Output holds the file’s own record as its metadata after a resume, not the one offered, and compares the two instead of requiring equal hashes (SEP 42).

  • The output schema is 2: a DataPulse records its spectral_phase (below), and every field of a recorded dataclass is recorded, at its default too. This release refuses a file written by 0.1.0, and every configuration hash moves, so a 0.1.0 run cannot be resumed; read such a file with 0.1.0.

  • DataPulse.from_time is replaced by the class TimeDataPulse: write TimeDataPulse(t=t, field=field, energy=energy). It holds the time record and has no omega, spectrum or spectral_phase. The method kept the spectrum at the record’s own bin spacing and splined its magnitude and phase, which cannot follow the fringes of pulses far apart: pulses at 100 and 600 fs in a record of 800 fs, of relative amplitude −0.9, came out 85 % wrong with no warning. The class evaluates the record’s transform exactly at the grid’s frequencies, 3e-12 from the direct sum on that case, less its mean: a baseline is an offset of the instrument, not part of the field.

Fixed#

  • A gallery page re-run on its own keeps its MaterialRangeWarning: the docs build’s import of solprop.sol silenced it for every script after.

  • A scan derived from one cached simulation, the scans guide’s own recipe, now gives every point its own noise seed. with_input(noise="keep") and with_mixture keep the ability to draw unseeded noise again, and the draw replaces only the initial noise of the shared translation. Before, such points shared the base’s draw, depended on the worker under Pool, could not be rerun in a new process and failed under base_seed. Resuming a derived, unseeded run now takes the file’s seed, as a constructor’s does, instead of being refused.

  • One object returned by a scan builder for every point no longer collapses a noise ensemble under Batch, where every member ran on the last point’s noise while the file recorded each point’s own seed. The draw now goes on a copy, and the builder’s object is left as it is. A file written by such a scan holds the wrong noise for its recorded seeds and a rerun does not detect it: run those points again.

  • A scan file whose points’ run records declare another output schema, a 0.1.0 scan among them, is refused by name, in the words a run file is refused with, when it is read (ScanOutput, read_point) or resumed or collected (run_scan, a queue’s collector). Before, only the scan container’s own version was checked, and that stayed 1, so such a scan was read as if its layout were this release’s.

  • Under input noise, the boundary monitor subtracts at most one floor from the collar’s removal: the floor is put in once at the launch and cannot lose more. It subtracted the floor’s rate over the whole run, about 37 floors under the default collar, so a removal of a few 1e-3 read as zero, with no warning, and the split scheme’s subtraction grew with the number of steps (#126).

  • A resumed run’s monitor record continues from the latest execution that kept one: a second hard kill before the monitor started, or an execution with boundary_check=None, no longer restarts the totals, and the stretch run unwatched is listed under unwatched. The signed removal is kept and restored, so a stop and resume reads as the whole run; the quiet collar’s state is restored; and a resume on a held row no longer counts the resumed state in the next row’s guard maxima (#128).

  • BoundaryCheck(quiet_collar=True) no longer records the collar as left out on the Cartesian, line and planar routes, whose stages keep it, or caps their steps for nothing (#94).

  • A scan point keeps its closing boundary-monitor record, without the per-plane rows, under every executor (read_point(...)["boundary_monitor"]); a batched point no longer stores the launch’s zeros; and a BoundaryWarning raised as an error fails its own batched point alone, as under Local (#132).

  • The native backend’s HIP build compiles again under its default warnings-as-errors: ROCm’s Clang refused a thread-local variable that only an OpenMP build uses (0.1.0 needed -DSOLPROP_NATIVE_WARNINGS_AS_ERRORS=OFF).

  • Resuming a file that holds only its launch plane, from an input-noise run stopped before its first saved plane, no longer adds the noise draw to the stored field a second time, which had doubled the noise amplitude for the rest of the run on every backend. set_initial takes resume=True for a stored field, which moves the kernel schema to 40: rebuild solprop-native. It moves neither the output schema nor the resume key.

  • DataPulse.from_spectral_density interpolates the phase it is given. It used to wrap the phase into the complex spectrum and unwrap it again, which cannot follow a change of more than pi between samples, so a strongly chirped pulse was built wrongly with no warning: 20 000 fs² of group-delay dispersion on a 0.4 nm axis at 800 nm came out 13.5 % away from the converged spectrum, on a 1.6 nm axis 100 %. A DataPulse given a complex spectrum still unwraps it, and its docstring states that limit.

  • A measured field in time puts a pulse where it sat in the record (TimeDataPulse, above). DataPulse.from_time unwrapped the phase, and a pulse more than half the record’s length from t = 0 changed its phase by more than pi per bin, so it was moved by one record length with no warning: at 500 fs in a record from 0 to 800 fs it came out at −300 fs.

[0.1.0] - 2026-10-31#

The first public release of solprop and of its compiled backend, solprop-native, which is released at the same version. Releases are 0.x for now: a minor release may break the API, the output schema or the resume key, and lists every such break under “Changed (breaking)” (ADR-058).

Added#

  • Propagation equations. The unidirectional pulse propagation equation (UPPE) and the generalised nonlinear Schrödinger equation (GNLSE), for the real electric field or a complex envelope, in waveguides and in free space.

  • Waveguides.

    • Gas-filled hollow capillaries, with resolved modes (the default) or mode-averaged (mode_average=True on prop_capillary and prop_waveguide). Tapers, pressure gradients and gas flow are supported, as is a coherent wall that couples the modes.

    • Antiresonant hollow-core fibres (the Vincetti model).

    • Step-index, graded-index, rectangular and planar guides.

    • Modes tabulated from arrays or from external mode solvers.

    • A general modal route for any guide that supplies its own electric and magnetic mode fields, including guides that change along their length.

  • Free space. Radial, Cartesian and line grids with the exact (non-paraxial) diffraction operator; planar guides can be resolved across their width. Launches, shot noise, energies, fluences and intensities measure the medium’s axial Poynting flux at the plane, so a requested energy is the physical energy there (SEP 34). Far-field and aperture statistics.

  • Media and responses.

    • Gases, gas mixtures, glasses and metals from the optmat material library.

    • Kerr and third-harmonic nonlinearity, including a dispersive electronic response.

    • Photoionisation with atomic and molecular rate models, and plasma on field and envelope grids.

    • Rotational and vibrational Raman responses, scalar and vector.

    • Quantum shot noise, on by default (SEP 37): one photon per mode with a random phase, added to the input field (noise="input") and recorded with its seed. A resumed run draws the same realisation again, and a scan draws each point’s from a seed of its own. noise=None switches it off and noise="modified" keeps the realisation apart from the field. The Cartesian, line and hybrid planar grids, slab mode sets, the TDSE response and single precision carry none by default and record why. Spectral densities can subtract the one-photon floor (noise_floor="subtract").

    • An optional helium TDSE atomic response for research. Its native backend is not yet publicly available.

  • Integrators and their defaults.

    • Two adaptive integrators: a low-call exponential Adams method (Multistep()) and an embedded interaction-picture Runge–Kutta pair (RungeKutta()), with a low-storage Runge–Kutta variant and fixed schedules. The linear propagation is integrated exactly.

    • Each interface has its own default integrator and tolerances, chosen by measurement: RungeKutta() for prop_gnlse (rtol 1e-7), prop_waveguide (1e-5) and prop_free (1e-4); Multistep() for prop_capillary (1e-6), prop_radial and prop_planar (1e-5). The choosing-a-stepper page tabulates them and says when to choose another.

    • Multistep(carrier_frame="scalar"), an opt-in carrier frame for the Adams history that is the fastest method measured on dense, multimode, Raman-active runs such as a comb-to-continuum capillary (SEP 35).

    • Backends agree with each other to bounded, rounding-level differences.

  • Time-window boundaries (SEP 21). Every interface absorbs at the edges of the time window and of the spectrum. The temporal collar lies outside the requested window: the grid widens to trange / (1 - 2 collar_fraction) (collar_fraction 0.1), so the whole request is flat, and transform sizes default to fft_size="auto", the cheapest 2-3-5-smooth size up to the next power of two. absorber=False switches the soft collars off and keeps the same grid. A boundary monitor, on by default (boundary_check= on every prop_* function and Simulation.from_* constructor), warns (BoundaryWarning) and then stops the run (BoundaryError) when light above the shot-noise floor reaches the window’s edge, and records the energy the collar removed.

  • Coupling simulations (SEP 31). solprop.coupling hands a field from one simulation to the next. A FieldPlane holds a coherent field at one plane with its reference and provenance; couple maps it into another simulation’s InputPort under a declared CouplingModel and returns an energy report; transport carries a free-space plane through lenses, apertures, masks, mirrors, interfaces and spectral filters; Outcouple synthesises a guide’s exit field. An incident beam launches into a general modal guide with the share of its energy that the guide captures. Each step is admitted against an error budget and refuses what it cannot certify.

  • Backends.

    • numpy_ref, the NumPy reference, against which every other backend is tested within derived error bounds, and numba_cpu, compiled with Numba. The backends come in tiers: new features land on numpy_ref and native; numba_cpu is mature and takes a new feature only where its design says so; mlx is experimental.

    • The optional native backend, the solprop-native distribution: C++20 kernels on Kokkos for multi-core CPUs (OpenMP) and for NVIDIA and AMD GPUs (CUDA, HIP). pip install "solprop[native]" installs a precompiled CPU wheel on macOS on Apple silicon, Linux on x86-64 and aarch64, and Windows on x86-64. backend="auto" selects it when installed, except on a processor below the wheel’s CPU baseline, where it uses numba_cpu and warns once (SEP 29). On the native backend the exponential Adams march runs in compiled code, many steps per call (SEP 39).

    • One thread budget per process, which every pool of both CPU backends stays within: the smallest of the CPU affinity mask, the physical cores that hold it, the control group’s CPU quota and SOLPROP_THREADS (ADR-052).

    • Single precision (SEP 33): precision="single" holds the state, the transforms and the responses in binary32, with the controller in binary64. numpy_ref runs it as the reference, and the mlx backend (pip install "solprop[mlx]") on the Apple GPU. A survival record states which problems keep their accuracy.

  • Running and recording.

    • HDF5 output with periodic snapshots and provenance, and resuming an interrupted run, which continues the Adams history.

    • A summary at the start of every run of what it is about to compute (the code, the machine, the backend, the grid, the medium, the responses, the noise, the integrator, the absorber and the output), and at its end the wall time, CPU time and peak memory. Each execution records its machine and what it spent in the file.

    • Statistics along the propagation.

    • Parameter scans on process pools and Slurm clusters.

  • Analysis and tools.

    • Processing and plotting. Spectral ranges and band-pass windows are given in the units of any spectral axis (spec_range, Passband).

    • Ensemble coherence by spectral band, and replay of a run’s accepted steps (FixedSchedule.from_history), to compare two precisions or backends on one discretisation.

    • Soliton and dispersive-wave design tools.

    • Pulse compressors: Taylor phase, chirped mirrors, materials, gratings and prisms.

    • An interactive session through import solprop.sol as sol.

  • Performance tracking for developers (SEP 38): python -m benchmarks track times about a dozen representative workloads, with each interface’s defaults, at one thread and at the production width, and records them by hardware; python -m benchmarks ab compares two builds.

  • Documentation. An introduction with executable lessons, a user guide, a long-form primer on the physics and numerics, focused tutorials and worked examples, the API reference, and a developer’s guide with the design records (SEPs and ADRs).

Changed (breaking)#

Nothing was released before 0.1.0. For scripts and files from a development checkout:

  • Output files of development versions are not read. The output schema restarts at 1 (solprop.output.SCHEMA_VERSION); keep the development install that wrote such a file to read it. The kernel contract’s version is KERNEL_SCHEMA_VERSION, separate from it.

  • Defaults changed late in development, so a development script’s results change: shot noise is on; each interface takes its own integrator and tolerances; the temporal collar lies outside the requested window, which widens the grid (collar_placement="inside" rebuilds the former geometry); transform sizes are fft_size="auto"; and the boundary monitor is on.

  • Renamed or removed. prop_gnlse, prop_capillary, prop_waveguide and their Simulation.from_* constructors take wavelength and shape, not lambda0 and pulseshape, and responses_for takes wavelength, as the pulses and the design tools already did. The plots’ wavelength_range is spec_range, in the units of their axis, and solprop.plotting.style.spectral_limits is gone (wavelength_to_axis converts a wavelength range); bandpass takes limits, not wavelength_limits; poynting_spectrum is gone (beam_spectrum is the axial-flux spectrum); the same_basis function is gone (use couple); and solprop.driver no longer exports propagate_fixed_rk4, the tests’ fixed-step oracle. Multistep() refuses the PIController settings it does not read.

Known limitations#

  • The mlx backend is experimental. Its Metal kernels are tested by hand on a Metal device, not in continuous integration, and its coverage is not measured there. The single-precision survival record is the reference implementation’s (on mlx for two cases). On the default fft_size="auto" grids MLX’s GPU transform runs four to eight times slower than on powers of two, so a run can be slower than binary64 native: build single-precision grids with fft_size="pow2". The guide’s speed table predates the current grids and is marked so.

  • Free space has no accurate transverse absorber. By default a radial grid’s edge is a hard wall and a Cartesian or line grid wraps around; the optional real-space collar (an absorber given to a spatial run, Absorber.spatial) removes only part of the light that reaches it, at a large cost in steps, and the boundary monitor watches the time window only. Size the transverse window so that the light stays clear of its edge. SEP 40 (a draft) rebuilds these boundaries.

  • A run is resumed by the release that wrote it. A run’s configuration hash covers every recorded field, so a release that adds one changes every hash, and within 0.x the output schema may change too: finish a run with the release that started it.

  • GPU builds are made from source. The wheels are for CPUs; CUDA and HIP builds of solprop-native are built from the repository (building the native backend).

  • CPython 3.14 only. solprop-native, pyFFTW and h5py have no wheels for 3.15 yet.

  • macOS: import solprop before pyFFTW. pyFFTW’s macOS wheel starts its own OpenMP runtime, so the native backend refuses to load in a process that imported pyFFTW first; solprop itself imports them in the working order.