solprop.backends.numba_cpu.general_modal#

The general modal guide’s nonlinear source, compiled, on the CPU backend.

The second execution path of solprop.backends.numpy_ref.general_modal, with the same four steps of one stage – scale, synthesise, respond, project – and the same maps, derived by the reference’s own solprop.backends.numpy_ref.backend._general_maps so that the two backends cannot read a record differently. What is this backend’s own is the execution: a fourth FFT plan family (solprop.backends.numba_cpu.fft.GeneralPlans) bound to buffers allocated once, compiled kernels in place of einsum, and nothing allocated inside a stage.

Parity is a rounding statement (SEP 25): this backend’s source is within the bound of tests/general_parity.py, fixed from the contractions’ term counts, the transforms’ lengths and the maps’ conditioning, of the reference’s – not equal to it. The contractions – the synthesis over channels, the real cubic’s symmetric contraction (each node’s coefficients, then the ten monomials of a sample), the analytic form’s contraction over k and l and then over j, the delayed contraction over j, k, l and the projection over nodes and components – are written for locality through the per-element bodies of solprop.backends.bodies.define_bodies(), and their order is this backend’s own. Each is diagonal in the bin (or the sample), a batch of tiny matrix-vector products with a different matrix per bin, not a GEMM, so BLAS has nothing to take: the loops run the bin (or the sample) innermost over contiguous rows. FFTW’s transforms differ from pocketfft’s by their own rounding, which the same bound covers.

Normalisation. FFTW applies none. The inverse transform’s 1 / n_to is folded into the per-bin scale of step 1 with the pad scale and the analytic doubling, and the doubled axis’s 1 / (2 n_to) into the delayed contraction; both are powers of two on every grid this package builds, so the folding is exact.

Chunks. The node loop runs in the reference’s node chunks, which size the plans and the per-chunk buffers; the projection accumulates each chunk’s partial into the source. A partial last chunk executes its own plans on prefix views of the same buffers.

A moving guide. Its maps are n_intervals * n_deg Chebyshev coefficients of each map, the reference’s GeneralMaps.synthesis_terms and projection_terms (views of the record’s own arrays, never copied), and a stage evaluates them at its position from the bundle’s transform_weights (kernel schema 14). The combination is one compiled kernel, combine, run once per map into two stage buffers allocated once: every coefficient is flattened to one axis, the axis is cut into fixed blocks that the threads share (prange), and each block is zeroed and then accumulates w_t c_t term by term in the terms’ order, skipping a term of zero weight – every section but the stage’s own – so that a stage reads n_deg of the coefficients and not all of them (a BLAS product over every term, as the reference’s numpy.tensordot is, would read all of them). The reference’s sum and this one differ by a rounding of n_deg terms, bounded in the backend parity tests. A constant guide’s maps are the reference’s arrays read directly.

Module Attributes

GENERAL_LEDGER_ROWS

The ledger rows of numba_cpu's general modal route, named on every route.

MAP_BLOCK

Map coefficients one thread of the per-stage combination takes at a time.

Functions

make_general_kernels(*, analytic, delayed, ...)

Compile the general route's kernels for one form, delay and threading regime.

Classes

GeneralKernels(synthesise, respond, product, ...)

The compiled kernels of one general modal problem.

GeneralWorkspace(problem, *, flag, ...)

The general modal route's buffers, plans and kernels, allocated once.

solprop.backends.numba_cpu.general_modal.GENERAL_LEDGER_ROWS = ('general_synthesis', 'general_projection', 'general_stage_maps', 'general_tensor', 'general_electric', 'general_magnetic', 'general_backward_electric', 'general_backward_magnetic', 'general_reciprocal_gram', 'general_coordinate_dual', 'general_scale', 'general_lab', 'general_scaled_state', 'general_source', 'general_source_partial', 'general_output', 'general_node_spectrum', 'general_node_field', 'general_node_polarisation', 'general_node_source', 'general_node_drive', 'general_node_drive_spectrum', 'general_node_product', 'general_node_histories', 'general_node_delayed', 'general_delayed_tensor', 'general_delayed_spectrum', 'general_fft_plan_heap_growth')#

The ledger rows of numba_cpu’s general modal route, named on every route.

The reference’s names where the two backends hold the same buffer – on a moving guide general_synthesis and general_projection are every coefficient of the maps and general_stage_maps the two maps a stage evaluates into, zero otherwise – and the rest are this backend’s own persistent buffers – the per-bin scale, the combined stage input, the source, its per-chunk partial and the scaled output, the doubled axis’s product buffer – and the allocator’s growth over planning the fourth family. Zero on every other route.

class solprop.backends.numba_cpu.general_modal.GeneralKernels(synthesise, respond, product, contract, add_delayed, project, combine)[source]#

Bases: NamedTuple

The compiled kernels of one general modal problem.

Attributes:
synthesisenumba.core.dispatcher.Dispatcher

Step 2: E(q, c, w) = sum_m S a into the chunk’s fine spectrum.

respondnumba.core.dispatcher.Dispatcher

Step 3: the instantaneous polarisation, and the pair drive when delayed.

productnumba.core.dispatcher.Dispatcher

One response and channel’s kernel spectrum times the drive spectrum.

contractnumba.core.dispatcher.Dispatcher

The delayed contraction of one response and channel, accumulated.

add_delayednumba.core.dispatcher.Dispatcher

P += eps0 * total over the chunk’s rows.

projectnumba.core.dispatcher.Dispatcher

Step 4: the chunk’s projection, accumulated into the source.

combinenumba.core.dispatcher.Dispatcher

A moving guide’s map at a stage: sum_t w_t c_t over flattened coefficients.

add_delayed: Any#

Alias for field number 4

combine: Any#

Alias for field number 6

contract: Any#

Alias for field number 3

product: Any#

Alias for field number 2

project: Any#

Alias for field number 5

respond: Any#

Alias for field number 1

synthesise: Any#

Alias for field number 0

class solprop.backends.numba_cpu.general_modal.GeneralWorkspace(problem, *, flag, fftw_threads, parallel)[source]#

Bases: object

The general modal route’s buffers, plans and kernels, allocated once.

Parameters:
problemsolprop.translate.problem.KernelProblem

A problem the backend has admitted, carrying transform.modal_tables.

flagstr

The FFTW planning flag’s key.

fftw_threadsint or None

Override for FFTW’s pool for the fourth family; None sizes it from the chunk’s transform by the backend’s own rule (solprop.backends.numba_cpu.fft.choose_pools()).

parallelbool

Whether the node loops are a prange.

memory_ledger()[source]#

Return the bytes the route holds, one row per named buffer.

Returns:
dict

Every row of GENERAL_LEDGER_ROWS: the maps and the record’s resident reference, counted as the reference backend counts them, and this backend’s persistent buffers and plan heap growth.

move(weights)[source]#

Evaluate a moving guide’s maps at a stage, from its transform_weights.

The reference’s GeneralMaps.at on this backend’s buffers: nothing for a constant guide, and for a moving one the two weighted sums of the coefficients by make_general_kernels()’ combine, allocating nothing.

Parameters:
weightsnumpy.ndarray or None

The stage bundle’s transform_weights, float64, one per coefficient; None for maps that do not move.

Raises:
BackendError

If moving maps are asked for with no weights, or constant ones with some, in the reference’s words.

source(transform_weights=None, dispersive_densities=())[source]#

Evaluate one stage’s modal source from lab into output.

The reference’s solprop.backends.numpy_ref.general_modal.general_source() on this backend’s buffers, allocating nothing. SEP 6’s dispersive channels filter each chunk’s synthesised spectrum and land their products’ spectra in its polarisation spectrum, before the projection (GeneralDispersive).

Parameters:
transform_weightsnumpy.ndarray or None, optional

The stage bundle’s weights of a moving guide’s maps (move()); None for maps that do not move.

dispersive_densitiestuple of float, optional

Each dispersive channel’s number density at the stage, m^-3: the stage bundle’s dispersive_kerr_densities.

Returns:
numpy.ndarray

output: the projected polarisation times -i omega, shape (n_modes, n_omega), in sqrt(W)/m before the crop scale.

solprop.backends.numba_cpu.general_modal.MAP_BLOCK = 4096#

Map coefficients one thread of the per-stage combination takes at a time.

64 KiB of complex128 in the output block and as much per term read, so that a block’s running sum stays in a core’s level-2 cache while its n_deg terms stream past; a map of the graded-index taper has about 300 of them. Each element’s sum runs over its terms whatever the blocking, so the value does not depend on it.

solprop.backends.numba_cpu.general_modal.make_general_kernels(*, analytic, delayed, parallel, contiguous=False)[source]#

Compile the general route’s kernels for one form, delay and threading regime.

Every flag is a closure constant, so a problem without a delayed response pays nothing for it and the serial and parallel variants come from one body (docs/dev/backends.md section 3.2). The node loops are independent per node and the projection’s reduction is per output bin, so the result does not depend on how many threads ran either.

With contiguous the coarse bins are the leading bins of the fine spectrum (crop is arange(n_omega), as on every field grid), and the synthesis and the projection index the fine spectrum directly instead of through crop: the loads are then contiguous and the bin loops vectorise. Measured on one thread of an Apple-silicon laptop on the benchmark case’s 576 nodes, 2 channels and 513 bins: the synthesis 1.8 -> 1.35 ms a stage and the projection 1.4 to 1.6 -> 1.0 ms.

Parameters:
analyticbool

Whether the cubic is the analytic-signal form, which drives the delayed response with Re(E_k conj(E_l)) / 2; otherwise the real field’s E_k E_l.

delayedbool

Whether the respond kernel writes the delayed response’s pair drive.

parallelbool

Whether the node loops, and the projection’s channel loop, are a prange.

contiguousbool, optional

Whether crop is arange(n_omega), which the caller has checked.

Returns:
GeneralKernels

The compiled kernels.