solprop.backends.numba_cpu.coupled_flow#

The coupled flow of a coherent wall or a split block, on the CPU backend.

The second execution path of the reference’s linear.coupled_flow – the constant-Schur section (the scalar wall, the TE/TM wall and the split block on the general route) and the varying "frozen_residual" flow with its explicit residual in the Runge-Kutta source. A "cf4" section is refused by name: the fourth-order commutator-free flow is not yet implemented on the compiled backends.

What is host-side and what is compiled. The Schur decomposition of every section’s frozen block is the reference’s own code, run on the host once at construction (solprop.backends.numpy_ref.backend._coupled_flow_maps), and so is the eigenbasis of each triangular factor (solprop.translate.coupled.schur_eigenbasis()), shared with the native backend. The actions U = Z exp(l T) Z^H and U^-1 = Z exp(-l T) Z^H of every stage length, and the certificate’s measured norm(U) norm(U^-1), are formed by compiled kernels into the held buffers whenever a step’s section or length changes, exactly when the reference’s own memo re-forms them: form_flow_actions() takes each bin by its eigenbasis, W exp(l Lambda) Y, or – where that basis is ill-conditioned, a nearly degenerate nonnormal pair – by the reference’s own scaling, sixteen-term series and squaring, and flow_conditioning() measures both spectral norms by a Jacobi iteration on the Gram matrix. The decision is the reference’s (_refuse_flow_conditioning), in its words. The matrices are no longer the reference’s to the last bit; their departure is bounded in tests/flow_parity.py (the formation bound) and carried through the step’s parity bound. An adaptive run, which changes its length every step, re-forms the actions every trial; forming them on the host spent 44 to 68 % of such a step there, this spends about one per cent.

What is compiled is every traversal of the state: the stage combination mixed by the forward action, the pad onto the transform grid, the explicit residual’s combination over the stage weights and its application, the return to the interaction variable by the inverse action with both accumulations, and the accept pass with the error norm’s partials. None of them allocates.

Parity is a rounding statement (SEP 25). The matrices are within the formation bound of the reference’s, and each pass’s sums – the stage combination over the previous stages, each action’s channel sum, the residual’s weighted sum over the terms – are this backend’s own order, held with the matrices to the parity bound of tests/flow_parity.py, never to the reference’s bits. The combination, the accumulation and the accept pass thread over bins (columns for the accept) on a state of at least FLOW_ELEMENTS elements (flow_kernels()).

Module Attributes

FLOW_LEDGER_ROWS

The coupled flow's ledger rows, named on every route and zero where it is absent.

FLOW_ELEMENTS

State size from which the flow's stage combination, accumulation and accept thread.

Functions

flow_accept(state, propagator, dz, solution, ...)

Take the accept pass through the whole step's flow, with the norm's partials.

flow_accumulate(drive, action, slots, mix, ...)

Carry a stage's source back by U_i^-1 and accumulate it into both sums.

flow_combine(state, stages, weights, ...)

Form one stage's lab-frame input with the forward flow action.

flow_conditioning(forward, inverse, gram, ...)

Measure norm(U)_2 norm(U^-1)_2 of the whole step's actions per bin.

flow_kernels(threads, n_columns, n_omega)

Choose the serial or the threaded combination, accumulation and accept pass.

flow_pad(lab, crop, fine, scale)

Scatter the lab-frame input onto a transform grid, zeroing the rest.

form_flow_actions(fractions, dz, left, ...)

Form the forward and inverse actions of one section at every stage fraction.

make_flow_finish(*, contiguous, absorber, ...)

Compile the stage's source before the inverse action: crop, frame, residual.

residual_action(weights, terms, frozen, out)

Form the explicit residual R_s(z_1) = sum_t w_t T_t - G_s of one stage.

Classes

FlowWorkspace(problem, fractions, *, ...)

The coupled flow's held factors, actions and buffers, allocated once.

solprop.backends.numba_cpu.coupled_flow.FLOW_ELEMENTS = 32768#

State size from which the flow’s stage combination, accumulation and accept thread.

Every bin of the combination and the accumulation is independent, and the accept pass sums each column on its own, so the parallel variants (a prange over bins, and over columns for the accept) agree with the serial ones bin by bin. Measured on six threads of an Apple-silicon laptop under load, microseconds serial -> threaded: 4 channels x 8192 bins, combination 1121 -> 192, accumulation 330 -> 99, accept 352 -> 123; 8 x 16384, 10025 -> 2671, 1611 -> 351, 2862 -> 831; 2 x 4096 the accumulation and the accept were slower threaded (18 -> 52, 43 -> 62).

solprop.backends.numba_cpu.coupled_flow.FLOW_LEDGER_ROWS = ('coupled_flow_factors', 'coupled_flow_residual_terms', 'coupled_flow_actions', 'coupled_flow_eigenbasis', 'coupled_flow_formation', 'coupled_flow_residual_action', 'coupled_flow_lab', 'coupled_flow_drive', 'coupled_flow_error', 'coupled_flow_slots')#

The coupled flow’s ledger rows, named on every route and zero where it is absent.

The first three are the reference’s rows with the reference’s sizes: the Schur factors and frozen blocks of every section, the residual’s terms, and one forward and inverse action per distinct stage fraction. The rest are this backend’s own: every section’s eigenbasis with its route and row-sum norm, the formation kernels’ scratch and the measured conditioning, the stage’s residual block, the lab-frame stage input the residual and the pad both read, the stage’s source before the inverse action, the accepted step’s mixed error and the two bin-to-block indices, the actions’ and the residual’s, which leaves out the bins the error norm does not score.

class solprop.backends.numba_cpu.coupled_flow.FlowWorkspace(problem, fractions, *, contiguous, absorber)[source]#

Bases: object

The coupled flow’s held factors, actions and buffers, allocated once.

Parameters:
problemsolprop.translate.problem.KernelProblem

A problem the backend has admitted, carrying linear.coupled_flow.

fractionsnumpy.ndarray

The step’s distinct stage fractions in increasing order, the backend’s own propagator slots; an action pair is held per fraction.

contiguousbool

Whether the crop index is the leading block of the transform grid.

absorberbool

Whether the in-stage absorber contributes a term.

form_residual(weights)[source]#

Form the stage’s explicit residual into block, when there is one.

A constant-Schur section has none and forms nothing, as in the reference, whose _residual returns None there rather than multiplying by zero.

Parameters:
weightsnumpy.ndarray or None

The stage bundle’s flow_weights, which every stage of a problem carrying a flow has.

memory_ledger()[source]#

Return the size of every buffer the flow holds, in bytes.

Returns:
dict

Every row of FLOW_LEDGER_ROWS.

prepare(section, dz)[source]#

Hold the forward and inverse actions of one step, re-formed on a change.

The reference’s memo (NumpyRefBackend._flow_actions): the generator is frozen across the step, so an action depends on the stage fraction only through the length c_i dz, and the pairs survive between steps for the one section and length last seen. A step that crosses into the next section re-forms them, which is the cache invalidation that makes a section edge a kink of the flow. The whole step’s pair is checked against the certificate’s declared conditioning, which bounds every shorter one. The pairs are formed by form_flow_actions() and measured by flow_conditioning() into the held buffers, so a new length allocates nothing either.

Parameters:
sectionint

The section the step’s anchor lies in, from the step bundle’s flow_section.

dzfloat

The trial step, m.

Raises:
BackendError

If a pair needs more squarings than admitted, or the whole step’s measured conditioning exceeds the declared bound, in the reference’s words under this backend’s name.

solprop.backends.numba_cpu.coupled_flow.flow_accept(state, propagator, dz, solution, error, action, slots, mask, advanced, mixed_error, partials)[source]#

Take the accept pass through the whole step’s flow, with the norm’s partials.

The reference’s exp(dPhi) (U a + dz U s) and the norm of exp(dPhi) (dz U e): the action is applied to the state, the solution sum and the error sum separately, and the error norm’s |y|^2 is of the state before the action, as the reference’s error_norm takes it. The mixed error is kept, because a weighted controller’s second traversal reads it.

Parameters:
statenumpy.ndarray

The state at the start of the step.

propagatornumpy.ndarray

exp(dPhi) over the whole step.

dzfloat

The step, m.

solutionnumpy.ndarray

The weighted stage sum of the solution.

errornumpy.ndarray

The weighted stage sum of the embedded difference.

actionnumpy.ndarray

U over the whole step, on the active bins.

slotsnumpy.ndarray

Each bin’s row of action, or -1.

masknumpy.ndarray

The in-band mask the norm is taken over.

advancednumpy.ndarray

Written with the state at the end of the step.

mixed_errornumpy.ndarray

Written with U e.

partialsnumpy.ndarray

Written with the three sums per column, shape (3, n_columns).

solprop.backends.numba_cpu.coupled_flow.flow_accumulate(drive, action, slots, mix, weight, error_weight, stage, solution, error)[source]#

Carry a stage’s source back by U_i^-1 and accumulate it into both sums.

Parameters:
drivenumpy.ndarray

The source from the finish kernel.

actionnumpy.ndarray

U_i^-1 on the active bins.

slotsnumpy.ndarray

Each bin’s row of action, or -1.

mixbool

False at the zero fraction, where the action is the identity.

weightfloat

The tableau’s solution weight for this stage.

error_weightfloat

The tableau’s embedded-difference weight.

stagenumpy.ndarray

Written with the stage derivative.

solutionnumpy.ndarray

Accumulated into.

errornumpy.ndarray

Accumulated into.

solprop.backends.numba_cpu.coupled_flow.flow_combine(state, stages, weights, n_previous, dz, propagator, action, slots, mix, lab)[source]#

Form one stage’s lab-frame input with the forward flow action.

The reference’s exp(dPhi_i) (U_i a + dz U_i sum_j a_ij k_j): the action is applied to the state and to the stage combination separately, as the reference’s _channels does, and the combination is summed over the previous stages from zero in tableau order. A bin the block does not cover, and every bin at the zero fraction, is the diagonal route’s own expression.

Parameters:
statenumpy.ndarray

The state at the start of the step, complex, shape (n_columns, n_omega).

stagesnumpy.ndarray

The stage derivatives so far.

weightsnumpy.ndarray

The tableau row for this stage.

n_previousint

How many stages the row combines.

dzfloat

The step, m.

propagatornumpy.ndarray

exp(dPhi_i) for this stage.

actionnumpy.ndarray

U_i on the active bins, shape (n_active, n_columns, n_columns).

slotsnumpy.ndarray

Each bin’s row of action, or -1 off the block.

mixbool

False at the zero fraction, where the action is the identity.

labnumpy.ndarray

Written with the lab-frame stage input.

solprop.backends.numba_cpu.coupled_flow.flow_conditioning(forward, inverse, gram, measured)[source]#

Measure norm(U)_2 norm(U^-1)_2 of the whole step’s actions per bin.

The certificate’s measurement (_check_flow_conditioning), each spectral norm by _spectral_norm, within a few n u of the singular value the reference takes and never below it by more than the rotations’ rounding; the comparison with the declared row is the reference’s own.

Parameters:
forward, inversenumpy.ndarray

The whole step’s actions per active bin.

gramnumpy.ndarray

Scratch, one n x n block.

measurednumpy.ndarray

Written with the product per bin.

solprop.backends.numba_cpu.coupled_flow.flow_kernels(threads, n_columns, n_omega)[source]#

Choose the serial or the threaded combination, accumulation and accept pass.

Parameters:
threadsint

Threads the kernel pool has.

n_columnsint

Columns of the state.

n_omegaint

Bins per column.

Returns:
tuple

flow_combine(), flow_accumulate() and flow_accept(), or their parallel variants for a state of at least FLOW_ELEMENTS.

Examples

>>> from solprop.backends.numba_cpu import coupled_flow
>>> coupled_flow.flow_kernels(1, 4, 8192)[0] is coupled_flow.flow_combine
True
solprop.backends.numba_cpu.coupled_flow.flow_pad(lab, crop, fine, scale)[source]#

Scatter the lab-frame input onto a transform grid, zeroing the rest.

The pad of solprop.backends.numba_cpu.kernels.make_combine_and_pad() on an input the flow has already formed: the whole of fine is written, because a complex-to-real plan destroys its input while it executes.

Parameters:
labnumpy.ndarray

The lab-frame stage input.

cropnumpy.ndarray

Where each coarse bin sits on the grid, numpy.intp.

finenumpy.ndarray

Written with the padded spectrum.

scalefloat

The inverse transform’s 1 / n_to, or one for the general route’s coarse buffer.

solprop.backends.numba_cpu.coupled_flow.form_flow_actions(fractions, dz, left, right, eigenvalues, route, vectors, triangular, forward, inverse, exponents, work)[source]#

Form the forward and inverse actions of one section at every stage fraction.

On a bin whose eigenbasis is well conditioned (route), U(l) = W exp(l Lambda) Y and U(-l) = W exp(-l Lambda) Y: each entry sum_k (W_ik e_k) Y_kj from zero in increasing k, with e_k from one real exponential and one cosine and sine of l lambda_k, shared by the pair. Elsewhere the reference’s scaling and squaring (_squared_series_action). A zero fraction is the identity and is left untouched, as the backend never applies it.

Parameters:
fractionsnumpy.ndarray

The distinct stage fractions, dimensionless.

dzfloat

The step, m; the lengths are fraction * dz, as the reference forms them.

left, rightnumpy.ndarray

The section’s W and Y per active bin.

eigenvaluesnumpy.ndarray

The section’s lambda per active bin, 1/m.

routenumpy.ndarray

True where a bin takes its eigenbasis.

vectors, triangularnumpy.ndarray

The section’s Schur factors per active bin, for the other bins.

forward, inversenumpy.ndarray

Written with the actions, one slot per fraction.

exponentsnumpy.ndarray

Scratch for one bin’s exp(+-l lambda).

worknumpy.ndarray

Scratch for one bin’s scaling and squaring.

solprop.backends.numba_cpu.coupled_flow.make_flow_finish(*, contiguous, absorber, residual)[source]#

Compile the stage’s source before the inverse action: crop, frame, residual.

Parameters:
contiguousbool

Whether the crop index is the leading block of the transform grid.

absorberbool

Whether the in-stage temporal absorber contributes its term.

residualbool

Whether the flow carries an explicit residual (a "frozen_residual" flow); a constant-Schur section’s is identically zero and is never formed, as in the reference.

Returns:
numba.core.dispatcher.Dispatcher

The compiled kernel.

solprop.backends.numba_cpu.coupled_flow.residual_action(weights, terms, frozen, out)[source]#

Form the explicit residual R_s(z_1) = sum_t w_t T_t - G_s of one stage.

The reference’s combine(weights, terms) - frozen[section], summed over the terms from zero in their order. A term of zero weight adds an exact zero, since every term is finite (CoupledFlowTables refuses anything else), so it is skipped: the weights vanish outside the stage’s own section.

Parameters:
weightsnumpy.ndarray

The stage bundle’s flow_weights, float64, shape (n_terms,).

termsnumpy.ndarray

The held coefficients of G(z) - C(z) on the active bins, 1/m.

frozennumpy.ndarray

The step’s section’s frozen block, 1/m.

outnumpy.ndarray

Written with the residual, 1/m.