Shot noise#

Weak fluctuations can grow into a substantial part of a propagated spectrum. Solprop provides a classical one-photon-per-mode seed with random spectral phase, useful for studying modulation instability, Raman-seeded broadening and shot-to-shot coherence. Select noise="input" or noise="modified" and provide seed= to reproduce a realisation.

Begin with the modulation-instability ensemble and comb-to-continuum shot. A seed ensemble estimates the variability of the chosen noise model. It does not automatically include experimental pulse-energy, duration, timing or phase noise; those need explicit input variations.

One photon per bin#

The rule is that each mode of the field — each frequency bin, each waveguide mode, each polarisation component — starts with the energy of one photon, \(\hbar\omega\). Written as a spectral energy density that is

\[ S(\omega_k)\,\delta\omega = \hbar\omega_k , \]

and since conventions, §4, fixes \(S(\omega)\) in terms of the stored DFT bins, the amplitude follows immediately:

\[ |\tilde E_k| = \sqrt{\frac{\hbar\omega_k}{\delta\omega}}\;\frac{\sqrt{2\pi}}{2\,\delta t} \quad\text{(field grid)}, \qquad |\tilde A_k| = \sqrt{\frac{\hbar\omega_k}{\delta\omega}}\;\frac{\sqrt{2\pi}}{\delta t} \quad\text{(envelope grid)} . \]

The factor of two between them is the analytic signal’s doubling of every interior bin of a one-sided spectrum, and it pairs exactly with the two prefactors of spectral_density(). The two are a matched set: change one and the photon number changes with it. photons_per_bin() reads the number back out of any stored spectrum and is how the pairing is tested.

The modulus is deterministic; only the phase is random. This is the standard one-photon-per-mode seeding of the supercontinuum literature. It assigns exactly one photon of energy to each active bin; it is not a full quantum-state model, whose modulus would be Rayleigh-distributed. Ensembles over seeds sample the random phase of that fixed spectral amplitude, not amplitude noise. It also differs from the truncated-Wigner vacuum of Drummond and Corney, J. Opt. Soc. Am. B 18, 139 (2001), Eq. (6.13), which assigns half a photon per mode with a Gaussian random amplitude, and from their Raman Langevin noise (Eq. 6.16), which solprop does not add. This semiclassical prescription and its distinction from technical pump noise are illustrated by Adamu et al., Scientific Reports (2020).

Note

Because the modulus is fixed, the photon number per bin is exactly one — to round-off, not to a statistical error. A test of the noise amplitude is a test of arithmetic, not a test of a distribution; what needs a statistical test is the phase, which is checked against a uniform distribution with a Kolmogorov–Smirnov statistic.

The active band, on both grids#

Noise goes only where the physics is: the active mask \(\omega_{\min}/2 < \omega < 1.1\,\omega_{\max}\) of conventions, §3 (see also the active band), on both grids. Outside it the stored spectrum is exactly zero. On a field grid this matters: without the mask, noise would sit in the zero-frequency bin and above the window edge. A photon at \(\omega = 0\) is not a photon, and energy placed above the apodised window edge is energy the spectral collar would spend the run absorbing.

The random phase, by contrast, is drawn over the whole spectral axis and the amplitude is masked afterwards. That way the random stream does not shift when the band moves, and a seed keeps meaning one realisation on a given grid size.

Two models#

input

modified

what it is

a realisation added to the initial field at z = 0

a realisation injected into the nonlinear operator at every step

propagation

dispersed, amplified and absorbed like any other field

advanced by the unitary part of the linear operator: dispersion yes, gain and loss no

strength

the physical picture, correct wherever seeding happens near the input

no artificial four-wave mixing from a propagated background

cost

nothing during the run

about eight times as many integrator steps

use it when

the process is seeded early and the fibre is short

the seeding is distributed, or the spontaneous gain is what you are measuring

Both come from the same draw. input_noise() and modified_noise() produce identical spectra for the same seed and differ only in the label they record, because the difference between the models is entirely in how the driver consumes the realisation.

Why the modified field must be advanced#

The modified model is credited to Chen and Wise, who derived it for a gain medium with no dispersion; solprop has not traced it to a specific publication. Taken literally, it holds the noise field constant in \(z\). In a dispersive medium that is wrong: the noise source and the field it seeds drift out of phase. Measured against the exact Bogoliubov result for spontaneous modulation instability, a constant noise field is wrong by a factor of 1.4 to 6.6 per frequency, rising to 50–80 in conservative propagation, as reported in Luna.jl pull request 440 (solprop has not reproduced these figures).

solprop draws one realisation \(N_0(\omega)\) and uses

\[ N(z) = N_0(\omega)\,e^{\,i\,\mathrm{Im}\,\Phi(\omega, z)} , \]

with \(\Phi\) the same cumulative linear operator the stepper already has, so the noise disperses but neither grows nor decays: only the phase of the linear operator is applied (dispersion yes, gain and loss no), because the source stands for vacuum fluctuations that are present afresh at every \(z\) (see the design decisions). It costs one complex multiply-add per element per stage and no extra transform.

The price of the modified model is step count: an undecaying broadband source is injected at every \(z\), and a pure-Kerr run needs roughly eight times as many steps as the same run with input noise (the figure Luna.jl pull request 440 reports).

Reproducibility#

In a run, choose the model with the driver’s noise keyword (None, the default, "input" or "modified") and fix the realisation with seed; prop_gnlse, prop_capillary, prop_waveguide, prop_radial and prop_free all take both (the hybrid planar route, prop_planar, does not carry noise). The realisation and its seed are stored in the output. The noise functions can also be called directly, and the generator is always passed in, never created inside:

import numpy as np
from solprop.noise import input_noise

realisation = input_noise(grid, np.random.default_rng(20260905), n_modes=3, n_pol=2)
realisation.seed          # 20260905, recorded for the output file
realisation.spectrum.shape  # (3, 2, n_omega)

Each mode and each polarisation component gets an independent realisation, because they are separate field modes and each is entitled to its own photon. The entropy the generator was created with is recovered and stored on the NoiseRealisation, so a run can be replayed from its output file — including a run that was left unseeded, which records the operating-system entropy it drew. No draw ever uses NumPy’s global generator.

A mode axis and a polarisation axis are always present: every draw has the shape (n_modes, n_pol, n_omega), so a one-mode run and the first mode of a two-mode run with the same seed see the same realisation. For the radial, Cartesian and line grids, spatial_noise() draws one photon per admitted transverse column, polarisation and frequency bin in the same way. It takes the entrance plane’s SpatialFlux: each column and bin is scaled so that its energy, weighted by the real axial admittance of the entrance medium, is \(\hbar\omega\), the measure a launch is normalised in. Nonpropagating, inactive and zero-frequency bins are not power ports and carry no photon; the random stream is drawn as before, so a seed keeps its meaning (energy and flux in a medium). A planar grid carries no noise draw.

An ensemble is a sequence of seeds. Run it on a fixed step schedule (schedule=, see scans): the numerical error of a fixed schedule is then a common systematic across the members, so the spread between members is the physics and not the stepper. The supercontinuum examples run such ensembles and compute the relative intensity noise and the coherence from them (processing).

A declared change of basis#

One photon per channel per bin is a statement about an orthonormal true-power basis: only there does “one photon in this channel” mean an energy of \(\hbar\omega\). A general modal guide that declares a unitary change of its channel basis therefore draws once, in the reference basis, with the same seed and the same stream, and carries that one realisation into the run’s coordinates through the inverse transform, transformed_noise().

from solprop.noise import transformed_noise

carried = transformed_noise(realisation, basis.transform)  # d = U.H c

The total photon number in each bin is exactly what it was, because \(U\) is unitary; the per-channel numbers are redistributed and the moduli are no longer all equal. That is the physics, not a defect: a fixed-modulus random-phase draw is not invariant in distribution under unitary mixing, so drawing again in the declared basis would be a different stochastic model. A basis with unequal channel norms is neither orthonormal nor a unitary transform of one, and a draw in it is refused by name.

Where solprop’s choices differ from Luna.jl’s, the differences are listed in Noteworthy differences from Luna.