solprop.translate.masks#
Grid data, masks, absorber rates and the noise field, in the kernel’s layout.
Everything a backend needs to know about the frequency axis and the time window, read from the grid and the absorber rather than recomputed: the two band masks, the spectral and temporal windows, the oversampling crop index and its two scale factors, the temporal absorber rates, and the shot-noise realisation in the canonical state layout.
Section 2 of docs/dev/kernel-contract.md is the class Grids, field for
field; the temporal rates belong to section 5 and the noise field to section 6, so those
are functions here and are stored by solprop.translate.responses and
solprop.translate.problem.
Nothing in this module computes physics. Every value is taken from
solprop.grid.TimeGrid, solprop.boundaries.Absorber or
solprop.noise.NoiseRealisation, so a mask can never disagree with the grid that
produced it.
Module Attributes
How the temporal absorber is applied (see |
|
The default scheme, chosen on accuracy (see |
Functions
|
Collect a grid's axis, masks and oversampling data for the kernel contract. |
|
Put a shot-noise realisation into the canonical state layout. |
|
Return the coarse and fine temporal absorber rates the chosen scheme needs. |
Classes
|
The frequency axis, the masks and the oversampling data a kernel needs. |
- solprop.translate.masks.AbsorberScheme#
How the temporal absorber is applied (see
docs/dev/decisions.md)."split_step"multiplies the modal time-domain field byexp(-alpha(t) h / 2)once per accepted step;"nonlinear"adds-alpha(t) E(t) / 2inside every stage evaluation.Both are exact as a rate in the sense that the factors telescope, but they are not equally accurate, and a comparison on a soliton launched into its own temporal collar measured the difference: the split factor is a Lie splitting of a term that does not commute with the linear flow, so it caps the observed order of the whole scheme at one wherever the collar does any work, and the energy it removes drifts with the step count at the same order. The in-stage term keeps the scheme’s own order and its absorbed energy is step-count independent to twelve digits. Because the split factor degrades the observed order, the in-stage term is the default whatever it costs.
alias of
Literal[‘split_step’, ‘nonlinear’]
- solprop.translate.masks.DEFAULT_ABSORBER_SCHEME: Literal['split_step', 'nonlinear'] = 'nonlinear'#
The default scheme, chosen on accuracy (see
AbsorberScheme).
- class solprop.translate.masks.Grids(omega, omega0, dt, dt_fine, n_t, n_to, ffac, crop_index, pad_scale, crop_scale, inband_mask, active_mask, norm_mask, omega_window, t_window_fine, t_window_fine_derivative, carrier_2w0=None)[source]#
Bases:
objectThe frequency axis, the masks and the oversampling data a kernel needs.
Section 2 of the kernel contract. The arrays are read-only, C-contiguous copies, so a backend may hold them for the life of a run without defensive copying. Equality is by identity: comparing whole axes elementwise is never what a caller means.
The shape annotations give each array its own axis name, because
__post_init__is what enforces their agreement and that check must stay reachable in production, where no runtime type checker runs.- Attributes:
- omeganumpy.ndarray
Coarse angular frequency axis, rad/s, shape
(n_omega,). Non-negative and increasing on a field grid, in FFT order aboutomega0on an envelope grid.- omega0float
Reference angular frequency, rad/s.
- dtfloat
Coarse sample spacing, s.
- dt_finefloat
Fine sample spacing, s.
- n_tint
Coarse number of time samples.
- n_toint
Fine number of time samples.
- ffacfloat
Oversampling factor of the fine grid.
- crop_indexnumpy.ndarray
Position of each coarse bin in the fine spectrum,
numpy.intp, shape(n_omega,).- pad_scalefloat
Factor applied when padding the coarse spectrum out,
n_to / n_t.- crop_scalefloat
Factor applied when cropping back,
n_t/n_to.- inband_masknumpy.ndarray
Boolean, shape
(n_omega,): the bins inside the requested wavelength limits.- active_masknumpy.ndarray
Boolean, shape
(n_omega,):omega_min/2 < omega < 1.1 omega_max. The linear operator and the prefactor are zero outside it and the hard band mask removes the state there once per accepted step.- norm_masknumpy.ndarray
Boolean, shape
(n_omega,): the bins the error norm scores, the controller’sregion–active_maskby default, orinband_mask.- omega_windownumpy.ndarray
The taper applied to the nonlinear polarisation in the spectral collar, shape
(n_omega,): the grid’s collar profile in[0, 1]when the polarisation taper is on, and all ones when it is off, which is the default. The rate absorber on the field is inside the linear operator either way; this taper is an option, off by default, because it sets a floor of the size of the field’s content in the collar.- t_window_finenumpy.ndarray
Temporal collar profile in
[0, 1], shape(n_to,), applied inside the response pass.- t_window_fine_derivativenumpy.ndarray
Analytic time derivative of the fine collar, in s^-1, shape
(n_to,). The field plasma uses it to hold the current at the collar (SEP 27).- carrier_2w0numpy.ndarray or None
exp(2 i omega_0 t)on the fine time axis, complex128, shape(n_to,), present exactly when a third-harmonic Kerr term is compiled on an envelope grid andNoneotherwise. That term sits an offset2 omega_0from the carrier the envelope is written against, so the response pass needs the factor; it is built here fromsolprop.nonlinear.responses.thg_carrier()and the grid’s own fine time axis, because a backend that built it fromomega0,n_toanddt_finewould be carrying a piece of physics (docs/dev/architecture.mdsection 3).
- Raises:
- TranslationError
If an array length disagrees with
n_omegaorn_to, or a size or scale is not positive.
Examples
>>> from solprop.grid import EnvelopeGrid >>> from solprop.translate.masks import build_masks >>> grid = EnvelopeGrid.from_limits(1.0, 835e-9, (500e-9, 2e-6), 4e-12) >>> masks = build_masks(grid) >>> masks.omega.shape == (grid.n_omega,), masks.pad_scale (True, 1.0)
- active_mask: Bool[ndarray, 'n_active']#
- crop_index: Int[ndarray, 'n_crop']#
- inband_mask: Bool[ndarray, 'n_inband']#
- property n_omega: int[source]#
Return the number of coarse spectral bins.
- Returns:
- int
The length of
omega.
Examples
>>> from solprop.grid import EnvelopeGrid >>> from solprop.translate.masks import build_masks >>> grid = EnvelopeGrid.from_limits(1.0, 835e-9, (500e-9, 2e-6), 4e-12) >>> build_masks(grid).n_omega == grid.n_omega True
- norm_mask: Bool[ndarray, 'n_norm']#
- omega: Float[ndarray, 'n_omega']#
- omega_window: Float[ndarray, 'n_window']#
- t_window_fine: Float[ndarray, 'n_to_window']#
- t_window_fine_derivative: Float[ndarray, 'n_to_derivative']#
- solprop.translate.masks.build_masks(grid, *, polarisation_taper=False, harmonic_carrier=False, region='active')[source]#
Collect a grid’s axis, masks and oversampling data for the kernel contract.
Every value is read from grid; nothing is recomputed, so the seam and the grid can never disagree. The one choice made here is whether the spectral collar tapers the nonlinear polarisation: off by default, because the rate absorber already acts on the field and the taper costs a systematic error of the size of the field’s content in the collar (2.3e-6 on the recurrence of an N = 6 soliton against 2.7e-7 without it).
- Parameters:
- gridsolprop.grid.TimeGrid
The field or envelope grid the run propagates on.
- polarisation_taperbool, optional
Whether
omega_windowcarries the grid’s collar profile or all ones. DefaultFalse.- harmonic_carrierbool, optional
Whether to carry
exp(2 i omega_0 t)on the fine time axis, which an envelope grid’s third-harmonic Kerr term needs and nothing else does. DefaultFalse.solprop.translate.problem.assemble()passes the compiled Kerr term’s own answer, so the field is present exactly when the term is.- regionstr, optional
The bins the error norm scores, the controller’s
region:"active"(the default) or"band".
- Returns:
- Grids
Section 2 of the kernel contract, with read-only arrays.
Examples
>>> import numpy as np >>> from solprop.grid import FieldGrid >>> from solprop.translate.masks import build_masks >>> grid = FieldGrid.from_limits(1.0, 800e-9, (200e-9, 4e-6), 200e-15) >>> masks = build_masks(grid) >>> bool(np.array_equal(masks.active_mask, grid.active_mask)) True >>> masks.crop_index.flags.writeable False >>> bool(np.all(masks.omega_window == 1.0)) True >>> taper = build_masks(grid, polarisation_taper=True).omega_window >>> bool(np.array_equal(taper, grid.omega_window)) True >>> masks.carrier_2w0 is None True
- solprop.translate.masks.noise_spectrum(noise, shape)[source]#
Put a shot-noise realisation into the canonical state layout.
The realisation is drawn as
(n_modes, n_pol, n_omega)(solprop.noise); the kernel state carries a leading batch axis (conventions.mdsection 6). A realisation with one mode or one polarisation is broadcast over the requested shape, so a batch of members shares one draw and stays reproducible from its seed.- Parameters:
- noisesolprop.noise.NoiseRealisation
The realisation, in the internal FFT convention.
- shapetuple of int
Target
(n_batch, n_modes, n_pol, n_omega).
- Returns:
- numpy.ndarray
Read-only complex128 array of the requested shape.
- Raises:
- TranslationError
If the realisation cannot be broadcast onto shape.
Examples
>>> import numpy as np >>> from solprop.grid import EnvelopeGrid >>> from solprop.noise import modified_noise >>> from solprop.translate.masks import noise_spectrum >>> grid = EnvelopeGrid.from_limits(1.0, 835e-9, (500e-9, 2e-6), 4e-12) >>> draw = modified_noise(grid, np.random.default_rng(7)) >>> noise_spectrum(draw, (1, 1, 1, grid.n_omega)).shape (1, 1, 1, 4096)
- solprop.translate.masks.temporal_rates(absorber, *, scheme='nonlinear')[source]#
Return the coarse and fine temporal absorber rates the chosen scheme needs.
The temporal collar cannot be folded into the cumulative linear operator, because it is not diagonal in
omega. It crosses the seam as the bounded profile ofsolprop.boundaries.temporal_collar_rate()on both time axes, and scheme records which of the two exact applications of the rate the backend is to use: the per-accepted-step factorexp(-alpha h / 2)on the coarse axis, or the term-alpha E / 2inside the nonlinear evaluation on the fine axis. Both rates are carried whichever is chosen, so switching schemes is not a retranslation.A collar whose temporal rates are all zero, however it was built (a spectral-only collar from
absorber(grid, temporal=False), or a hand-built one), crosses the seam as no temporal absorber under either scheme (SEP 22): the kernels then compile no in-stage term and no split pass, recycle the first-same-as-last stage, and the scheme, still recorded, applies to nothing. Under the in-stage scheme the result is exactly the zero rate’s, which only added zeros; under the split it differs by the transverse transform pair the zero factor cost, at rounding.- Parameters:
- absorbersolprop.boundaries.Absorber or None
The collar rates from
solprop.boundaries.absorber().Nonemeans no temporal absorber.- scheme{‘split_step’, ‘nonlinear’}, optional
Which application of the rate the backend will use. Default
DEFAULT_ABSORBER_SCHEME,'nonlinear'. Annotated as a plain string rather thanAbsorberSchemefor the reason given insolprop.fourier._check_kind: this is the run-time guard, and the test session’sbeartypehook would otherwise reject a bad value first, leaving the guard unreachable.
- Returns:
- tuple
(coarse, fine): rates in 1/m of shapes(n_t,)and(n_to,), or(None, None)when absorber isNoneor its temporal rates are all zero.
- Raises:
- TranslationError
If scheme is not one of the two schemes.
Examples
>>> from solprop.boundaries import absorber >>> from solprop.grid import EnvelopeGrid >>> from solprop.translate.masks import temporal_rates >>> grid = EnvelopeGrid.from_limits(1.0, 835e-9, (500e-9, 2e-6), 4e-12) >>> coarse, fine = temporal_rates(absorber(grid)) >>> coarse.shape == (grid.n_t,), fine.shape == (grid.n_to,) (True, True) >>> temporal_rates(None) (None, None) >>> temporal_rates(absorber(grid, temporal=False)) (None, None)