solprop.translate.modal#

The transform between modal amplitudes and the field at quadrature nodes.

A nonlinear response is a statement about the field at a point of the cross-section, in volts per metre; the propagated state is a set of modal amplitudes in the square root of watts. Two matrices carry the state between them — synthesis onto the nodes, projection back onto the modes with the quadrature weights folded in — and a scalar path along z rescales both when the waveguide tapers. Section 4 of docs/dev/kernel-contract.md is the class Transform, field for field.

Mode-averaged propagation explicitly selects an effective-area transform. It has one node, but a resolved quadrature with one node remains a modal transform. In the mode-averaged case the matrices are one by one and the conversion factor is 1 / sqrt(eps0 c A_eff / 2) (conventions.md section 4).

The oversampling pad scale lives here. The state is zero-padded onto the fine spectral grid before the inverse transform, and the factor n_to / n_t that keeps the two time-domain signals equal at coincident samples (conventions.md section 3) is folded into the synthesis rather than into a response coefficient. It has to be: a response coefficient absorbs a field scale only when the response is homogeneous in the field, which the Kerr term is and an ionisation rate emphatically is not. Folded into the synthesis, the field every response sees is the physical one in volts per metre. The crop scale on the way back is folded into the prefactor instead (solprop.translate.responses), where it meets no such condition.

Module Attributes

DEFAULT_NODE_BUDGET

Bytes of node-chunk buffers a problem may hold, before the chunk size is reduced.

DEFAULT_SPATIAL_NODE_BUDGET

Node-chunk bytes a free-space, line or planar problem may hold by default (SEP 28).

TransformKind

What a transform represents (kernel schema 26, SEP 19).

BUFFERS_PER_NODE

Node-chunk buffers the fused pass holds: E, P and the Raman drive.

Functions

general_modal_transform(modes, tables, *, z_max)

Build the transform of a general modal guide: the tables and placeholders.

mode_averaged_transform(mode, grid, *, z_max)

Build the one-node transform of a mode-averaged problem.

multimode_transform(modes, quadrature, grid, ...)

Build the fixed-quadrature transform of a multimode problem.

Classes

Transform(synthesis, projection, ...[, ...])

The synthesis and projection matrices and their scaling along z.

solprop.translate.modal.BUFFERS_PER_NODE = 3#

Node-chunk buffers the fused pass holds: E, P and the Raman drive.

solprop.translate.modal.DEFAULT_NODE_BUDGET = 67108864#

Bytes of node-chunk buffers a problem may hold, before the chunk size is reduced.

Architecture section 13 chooses the whole node set when it fits a per-problem budget and the largest chunk that fits otherwise. Sixty-four mebibytes reproduces the chunk of about sixteen nodes that section 8 tabulates for the comb-to-continuum target (32768 time samples, six modes, Raman) while leaving a small problem un-chunked. It is an estimate until the benchmark suite measures the crossover.

The free-space, line and planar grids default to DEFAULT_SPATIAL_NODE_BUDGET.

solprop.translate.modal.DEFAULT_SPATIAL_NODE_BUDGET = 1073741824#

Node-chunk bytes a free-space, line or planar problem may hold by default (SEP 28).

A spatial evaluation pays a fixed cost per node chunk: its launches, its temporal transforms’ thread wake-ups, and its gather and scatter. A field volume is gigabytes, so a larger group is cheap in memory. With the per-chunk copies and clears removed, one gibibyte was the fastest of 64, 256 and 1024 MiB for the 384- and 768-square Kerr beams on Zen 5 and on Apple silicon, 2.3 to 2.4 times faster than 64 MiB on the native backend’s march; on an H200, IP-DOPRI5 was 1.3 times faster at one gibibyte than at 64 MiB.

class solprop.translate.modal.Transform(synthesis, projection, projection_coarse, taper_scale, node_chunk, aeff, geometry=None, node_weights=None, modal_tables=None, _n_pol=1, *, kind)[source]#

Bases: object

The synthesis and projection matrices and their scaling along z.

Section 4 of the kernel contract. The matrices are built once, at the reference position, and taper_scale carries whatever z dependence a scale-invariant waveguide has: the normalised mode field goes as 1 / a at nodes placed at r = a xi and the weights as a**2, so multiplying the synthesis by a(0) / a(z) and dividing the projection by it is exact rather than an approximation (solprop.modes.quadrature.ModeMatrices.rescaled()).

Attributes:
synthesisnumpy.ndarray

Mode fields at the nodes, float64, shape (n_nodes * n_pol, n_modes), in V/(m sqrt(W)), with the oversampling pad scale n_to / n_t folded in. The column index is i * n_pol + p.

projectionnumpy.ndarray

The same fields with the quadrature weights folded in, float64, shape (n_modes, n_nodes * n_pol).

projection_coarsenumpy.ndarray

The embedded coarse-rule projection, same shape, for the quadrature error estimate. It is an alarm, never a controller.

taper_scalesolprop.translate.chebyshev.PiecewiseChebyshev or None

The scalar rescaling a(0) / a(z) as a table along z, or None when the cross-section does not change.

node_chunkint

Nodes the response pass handles at a time, at least 1 and at most n_nodes.

aeffsolprop.translate.chebyshev.PiecewiseChebyshev

Effective area in m^2 as a table along z. It is the normalisation itself when kind == 'mode_averaged' and a diagnostic otherwise.

geometrysolprop.translate.spatial.SpatialGeometry or None

Spatial data for radial/free/slab propagation; None for modal amplitudes. A free transform carries empty matrices and uses the declared FFT shape; a slab transform carries the x half of the map, the slab rule’s (n_x, n_p) synthesis and its projections, and an FFT in y.

node_weightsnumpy.ndarray or None

Physical transverse quadrature weights in m² at z=0, shape (n_nodes,), for resolved modal populations. Divide by taper_scale² at later planes. Spatial transforms use geometry.spatial_weights; mode-averaged transforms use aeff.

modal_tablessolprop.translate.modal_tables.ModalTransformTables or None

Kernel schema 12: the full E/H reference of a general modal guide, with its own node rule, separate synthesis and projection maps and optional factors. None for every route that goes through the real static matrices above. When it is present the general route reads these tables instead, and the static matrices are declared placeholders: they must be the single zero column (1, n_modes) and its two (n_modes, 1) transposes, with node_chunk = 1, no taper and no node weights. They are mandatory fields of the contract that this route does not use, and a placeholder that is checked cannot be mistaken for a second, silently wrong transform. The route’s own chunking is the ledger of solprop.backends.numpy_ref.general_modal, not node_chunk.

kind{‘mode_averaged’, ‘modal’, ‘radial’, ‘free’, ‘slab’}

Kernel schema 26: what the transform represents, stored rather than inferred from the node count, so that a one-node quadrature is an ordinary modal transform. A spatial kind must equal its geometry’s kind; a mode-averaged transform has one node, one component and neither tables nor node weights.

Raises:
TranslationError

If the matrix shapes disagree, or node_chunk is out of range.

Examples

>>> from solprop.grid import EnvelopeGrid
>>> from solprop.nonlinear.gnlse import SpecifiedMode
>>> from solprop.translate.modal import mode_averaged_transform
>>> grid = EnvelopeGrid.from_limits(1.0, 1030e-9, (700e-9, 2e-6), 2e-12)
>>> mode = SpecifiedMode.from_taylor(
...     [0.0], omega_ref=grid.omega0, aeff=1e-10
... )
>>> transform = mode_averaged_transform(mode, grid, z_max=1.0)
>>> transform.n_nodes, transform.node_chunk, transform.taper_scale is None
(1, 1, True)
>>> transform.kind
'mode_averaged'
aeff: PiecewiseChebyshev#
geometry: SpatialGeometry | None = None#
kind: Literal['mode_averaged', 'modal', 'radial', 'free', 'slab']#
modal_tables: ModalTransformTables | None = None#
property mode_averaged: bool[source]#

Return whether this is the effective-area case.

Read from the stored kind, never inferred from the node count: a one-node quadrature (SEP 19’s field-scale rule at n_r=1) is an ordinary modal transform, and a general modal guide’s placeholder column is not a node.

Returns:
bool

True when kind == "mode_averaged".

Examples

>>> from solprop.grid import EnvelopeGrid
>>> from solprop.nonlinear.gnlse import SpecifiedMode
>>> from solprop.translate.modal import mode_averaged_transform
>>> grid = EnvelopeGrid.from_limits(1.0, 1030e-9, (700e-9, 2e-6), 2e-12)
>>> mode = SpecifiedMode.from_taylor([0.0], omega_ref=grid.omega0)
>>> mode_averaged_transform(mode, grid, z_max=1.0).mode_averaged
True
property n_modes: int[source]#

Return the number of modes the transform carries.

Returns:
int

The width of synthesis.

Examples

>>> from solprop.grid import EnvelopeGrid
>>> from solprop.nonlinear.gnlse import SpecifiedMode
>>> from solprop.translate.modal import mode_averaged_transform
>>> grid = EnvelopeGrid.from_limits(1.0, 1030e-9, (700e-9, 2e-6), 2e-12)
>>> mode = SpecifiedMode.from_taylor([0.0], omega_ref=grid.omega0)
>>> mode_averaged_transform(mode, grid, z_max=1.0).n_modes
1
property n_nodes: int[source]#

Return the number of quadrature nodes, one for a mode-averaged run.

Returns:
int

synthesis.shape[0] // n_pol; the rows carry node and polarisation together, so this uses the stored chunk arithmetic rather than a guess.

Examples

>>> from solprop.grid import EnvelopeGrid
>>> from solprop.nonlinear.gnlse import SpecifiedMode
>>> from solprop.translate.modal import mode_averaged_transform
>>> grid = EnvelopeGrid.from_limits(1.0, 1030e-9, (700e-9, 2e-6), 2e-12)
>>> mode = SpecifiedMode.from_taylor([0.0], omega_ref=grid.omega0)
>>> mode_averaged_transform(mode, grid, z_max=1.0).n_nodes
1
property n_pol: int[source]#

Return the transverse components the transform resolves at each node.

Returns:
int

1 when the rows of synthesis are one component per node – a scalar run, or the mode-averaged case – and 2 when both Cartesian components are carried, which is the polarisation-resolved multimode set.

Examples

>>> from solprop.grid import EnvelopeGrid
>>> from solprop.nonlinear.gnlse import SpecifiedMode
>>> from solprop.translate.modal import mode_averaged_transform
>>> grid = EnvelopeGrid.from_limits(1.0, 1030e-9, (700e-9, 2e-6), 2e-12)
>>> mode = SpecifiedMode.from_taylor([0.0], omega_ref=grid.omega0)
>>> mode_averaged_transform(mode, grid, z_max=1.0).n_pol
1
node_chunk: int#
node_weights: Float[ndarray, 'n_weights'] | None = None#
projection: Float[ndarray, 'n_modes_p n_columns']#
projection_coarse: Float[ndarray, 'n_modes_c n_columns_c']#
synthesis: Float[ndarray, 'n_rows n_modes']#
taper_scale: PiecewiseChebyshev | None#
solprop.translate.modal.TransformKind#

What a transform represents (kernel schema 26, SEP 19).

"mode_averaged" is the effective-area route of mode_averaged_transform(), the only kind whose response prefactor folds in an area conversion. "modal" is every resolved modal transform – a quadrature of any node count, including one, and the general modal tables. The spatial kinds match their geometry.

alias of Literal[‘mode_averaged’, ‘modal’, ‘radial’, ‘free’, ‘slab’]

solprop.translate.modal.general_modal_transform(modes, tables, *, z_max, margin=0.05, tol=1e-12)[source]#

Build the transform of a general modal guide: the tables and placeholders.

The general modal route synthesises and projects through the frequency-dependent complex maps of tables, not through a real static matrix, so section 4’s three matrices are the checked zero placeholders Transform describes and every other field of the group is absent. The one real entry is aeff, which is a diagnostic here as it is for any multimode transform: the scalar surrogate area of the first channel, which is what the legacy mode-averaged interfaces mean by an area and not the nonlinear coefficient of a vector channel.

Parameters:
modessequence of Mode

The guide’s channels, in state order; only the first channel’s aeff is read.

tablessolprop.translate.modal_tables.ModalTransformTables

The general guide’s E/H reference and execution maps, already tabulated on the run’s own canonical grids.

z_maxfloat

Length the run covers, m, positive and finite.

marginfloat, optional

Fraction of z_max the tables extend beyond the end.

tolfloat, optional

Target bound on the uniform error of the area table.

Returns:
Transform

Section 4 of the kernel contract, carrying modal_tables.

Raises:
TranslationError

If z_max or margin is invalid, or no channel was given.

solprop.translate.modal.mode_averaged_transform(mode, grid, *, z_max, margin=0.05, tol=1e-12)[source]#

Build the one-node transform of a mode-averaged problem.

The single “node” is the mode itself: the field a response sees is the propagated amplitude times 1 / sqrt(eps0 c A_eff / 2) (solprop.nonlinear.normalisation.sqrtw_to_vm()), and the polarisation the response returns is brought back by the reciprocal, which is already folded into solprop.nonlinear.normalisation.mode_average_prefactor(). So the projection is the identity and the synthesis carries the conversion — times the oversampling pad scale, for the reason in the module docstring.

A varying effective area is carried by taper_scale rather than by rebuilding anything: with tau(z) = sqrt(A_eff(0) / A_eff(z)) the synthesis at z is tau(z) times the stored one and the projection is the stored one divided by it, which reproduces the conversion at every position exactly. For a scale-invariant mode that is the a(0) / a(z) of solprop.modes.quadrature.taper_scale(), so mode-averaged and multimode tapers use one mechanism.

Parameters:
modeMode

The mode; only aeff and its declared core_radius breakpoints are used.

gridsolprop.grid.TimeGrid

The grid; supplies the oversampling pad scale.

z_maxfloat

Length the run covers, m, positive and finite.

marginfloat, optional

Fraction of z_max the tables extend beyond the end, matching the cumulative operator’s.

tolfloat, optional

Target bound on the uniform error of the tables relative to their value scale.

Returns:
Transform

Section 4 of the kernel contract, with n_nodes == 1.

Raises:
TranslationError

If z_max or margin is invalid.

Examples

>>> from solprop.grid import EnvelopeGrid
>>> from solprop.nonlinear.gnlse import SpecifiedMode
>>> from solprop.nonlinear.normalisation import sqrtw_to_vm
>>> from solprop.translate.modal import mode_averaged_transform
>>> grid = EnvelopeGrid.from_limits(1.0, 1030e-9, (700e-9, 2e-6), 2e-12)
>>> mode = SpecifiedMode.from_taylor(
...     [0.0], omega_ref=grid.omega0, aeff=1e-10
... )
>>> transform = mode_averaged_transform(mode, grid, z_max=1.0)
>>> float(transform.synthesis[0, 0]) == sqrtw_to_vm(1e-10) * grid.pad_scale
True
solprop.translate.modal.multimode_transform(modes, quadrature, grid, *, z_max, components=('x', 'y'), node_budget=67108864, margin=0.05, tol=1e-12)[source]#

Build the fixed-quadrature transform of a multimode problem.

The matrices are solprop.modes.quadrature.mode_matrix()’s, built once at the reference position, with the oversampling pad scale folded into the synthesis; the projection is the dual basis as mode_matrix builds it, so the pair a backend applies inverts to pad_scale times the identity on an orthonormal set. When the set is scale invariant and its radius varies, taper_scale tabulates a(0) / a(z) so the matrices are rescaled rather than rebuilt; a set whose cross-section changes shape would need rebuilding and is refused here rather than approximated.

Parameters:
modessequence of Mode

The ordered mode set.

quadraturesolprop.modes.quadrature.TransverseQuadrature

The fixed rule, with the Kronrod extension when an error estimate is wanted.

gridsolprop.grid.TimeGrid

The grid; supplies the pad scale and the fine time length the chunk is sized from.

z_maxfloat

Length the run covers, m, positive and finite.

componentssequence of str, optional

Transverse components carried: ("x", "y") for a vector run, one of them for a scalar run.

node_budgetint, optional

Bytes the node-chunk buffers may occupy. Default DEFAULT_NODE_BUDGET.

marginfloat, optional

Fraction of z_max the tables extend beyond the end.

tolfloat, optional

Target bound on the uniform error of the tables relative to their value scale.

Returns:
Transform

Section 4 of the kernel contract.

Raises:
TranslationError

If z_max, margin or node_budget is invalid, or the set varies along z in a way the rescaling cannot express.

Examples

>>> from solprop.grid import EnvelopeGrid
>>> from solprop.modes import MarcatiliMode, transverse_quadrature
>>> from solprop.translate.modal import multimode_transform
>>> grid = EnvelopeGrid.from_limits(1.0, 1030e-9, (700e-9, 2e-6), 2e-12)
>>> modes = [MarcatiliMode(125e-6, "He", 1.0, m=m) for m in (1, 2)]
>>> rule = transverse_quadrature(modes, n_r=16, radial_rule="gauss_kronrod")
>>> transform = multimode_transform(modes, rule, grid, z_max=1.0,
...                                 components=("x",))
>>> transform.n_modes, transform.n_nodes
(2, 17)