solprop.backends.numba_cpu.dispersive#

SEP 6’s frequency-dependent electronic response on the fused node path.

The leaf responses.dispersive_kerr carries each species’ spectral multipliers on the fine grid; solprop.nonlinear.responses.dispersive_cubic() is the arithmetic, and solprop.backends.numpy_ref.passes.dispersive_kerr_polarisation() evaluates it at every node. This module evaluates the same operator with the multipliers moved off the nodes and onto the modal columns, where they cost the fewest transforms.

Why the columns. The legacy transverse transform – one mode-averaged node, a fixed multimode quadrature, the radial Hankel pair – synthesises and projects with real matrices that do not depend on frequency, and the temporal collar is the same at every node. A spectral multiplier and the collar therefore commute with both: P G[w (G E)^3] = G[w P (G E)^3] for a projection P, a filter G and the collar w. So

  1. the input multipliers act on the modal spectrum the stage combination has already formed, before its inverse transform (no forward transform of the node field), and one batched inverse transform gives every filtered modal field;

  2. the filtered fields are synthesised at the nodes of a chunk by the same real GEMM as the bare field, and the products are formed there, in the leaf’s sector;

  3. each product is projected onto the modal columns (and onto the embedded rule’s, for the quadrature alarm) by the same GEMM as the polarisation;

  4. the collar multiplies the projected products in time, one batched forward transform takes them to frequency, and their output multipliers and the channels’ eps0 gamma0 rho, summed per product, land them in the stage’s own polarisation spectrum, after the stage’s forward transform has made it of every other response.

The collar acts before the output multipliers, G[w (G E)^3] (John Travers, 2026-10-08), so the response has no inverse transform of its own: its output joins the stage in frequency.

When the response is alone – no constant Kerr term, Raman, plasma or envelope plasma beside it – the response column and the polarisation’s projection are skipped: the node field buffer holds the filtered field and its product in place, and the stage’s forward transform is skipped, the response writing the polarisation spectrum itself. A symmetric response of one species whose product has the polarisation’s dtype goes further: its product is projected into the stage’s own projected buffer, the stage’s forward transform is the product’s, and g finishes it in the stage’s spectrum – the constant cubic’s transforms exactly – and with no in-stage absorber reading the bare modal field it filters the stage’s spectrum in place, holding no field-sized buffer at all. Otherwise the bare field stays where Raman, plasma and Kerr read it, and the response holds its own buffers (DispersiveWorkspace.memory_ledger()).

Mixtures. The symmetric evaluator forms one product for a whole mixture, under the filter built from the mixture’s own Kerr diagonal, g_mix = [sum_s f_s g_s^4 / sum_s f_s]^(1/4) with f_s = eps0 gamma3_0 rho_s (solprop.nonlinear.responses.mixture_filter()), rebuilt from the channels’ densities whenever they change (StageWeights); a mixture costs what one gas costs, and takes the lone path when it is alone. The polynomial groups species with equal leg bounds, which share their derivative fields and add their coefficient tables density-weighted at every stage (DispersiveTerms): an exact rewriting of the per-species sum, whose transform count then grows with the number of distinct leg bounds rather than of species.

The embedded rule’s products are projected at every stage, as the polarisation’s are, and windowed, transformed and combined only when a step is accepted (DispersiveWorkspace.finish_coarse()), from the last stage’s buffers, as the envelope currents’ are.

Module Attributes

DISPERSIVE_LEDGER_ROWS

The response's rows of the memory ledger, named (at zero) on every problem.

Functions

add_spectra(spectra, weights, out)

Add the projected products' spectra under their stage multipliers.

apply_window(target, window)

Multiply the response's products by the temporal collar, in place.

combine_spectra(spectra, weights, out)

Sum the projected products' spectra under their stage multipliers.

dispersive_form(kind, sector)

Return the product form of a grid kind and a sector.

dispersive_terms_layout(leaf, kind, *, analytic)

Lay out a leaf's filtered fields, products and output terms.

filter_spectra(spectrum, multipliers, out)

Write each input multiplier times the modal spectrum, one block per field.

make_dispersive_pairs(form, n_pol, *, parallel)

Compile the polynomial's two derivative products at every node of a chunk.

make_dispersive_self(form, n_pol, *, parallel)

Compile the product (u, u, u) of one field at every node of a chunk.

mixture_rows(quartics, members, factors, ...)

Write a symmetric mixture's filter rows at a stage; return its coefficient.

weigh_spectrum(weights, spectrum)

Multiply the stage's spectrum, in place, by the one product's multiplier.

weigh_terms(rows, channels, multipliers, ...)

Sum the output terms' multipliers that share a product, at this stage.

Classes

DispersiveLayout(n_modes, n_pol_state, ...)

How the backend runs the fused node path, as the response needs it.

DispersiveMatrices(synthesis, projection, ...)

The backend's taper-scaled transform matrices, rescaled in place every stage.

DispersiveTerms(symmetric, groups, keys, ...)

The response's filtered fields, products and output terms, laid out once.

DispersiveWorkspace(problem, plans, nodes, ...)

Preallocate the response's buffers and plans, and run it on the fused path.

StageWeights(leaf, terms)

The terms' weights at one stage: eps0 gamma0 rho per channel and per group.

solprop.backends.numba_cpu.dispersive.DISPERSIVE_LEDGER_ROWS = ('dispersive_filtered_time', 'dispersive_node_fields', 'dispersive_products', 'dispersive_coarse_products', 'dispersive_product_spectra', 'dispersive_chunk_scratch', 'dispersive_synthesis', 'dispersive_multipliers', 'dispersive_fft_plan_heap_growth')#

The response’s rows of the memory ledger, named (at zero) on every problem.

class solprop.backends.numba_cpu.dispersive.DispersiveLayout(n_modes, n_pol_state, chunk_rows, chunks, alone, embedded, real_synthesis, bare_time_free, parallel)[source]#

Bases: NamedTuple

How the backend runs the fused node path, as the response needs it.

Attributes:
n_modesint

Modal rows of the transform.

n_pol_stateint

Polarisation components the state carries.

chunk_rowsint

Rows of the node buffers: chunk nodes times components per node.

chunksint

Node chunks per stage; more than one needs a scratch to accumulate into.

alonebool

Whether the dispersive response is the only one, so that the node field buffer, the projected buffer and the polarisation spectrum are the response’s.

embeddedbool

Whether the quadrature has an embedded rule, projected for the alarm.

real_synthesisbool

Whether the backend’s scaled synthesis is real; otherwise the response holds a real copy for its real GEMMs.

bare_time_freebool

Whether nothing reads the bare modal field once the nodes are synthesised – there is no in-stage absorber – so that a lone symmetric response of one species may filter the stage’s own spectrum in place.

parallelbool

Whether the node loops are prange.

alone: bool#

Alias for field number 4

bare_time_free: bool#

Alias for field number 7

chunk_rows: int#

Alias for field number 2

chunks: int#

Alias for field number 3

embedded: bool#

Alias for field number 5

n_modes: int#

Alias for field number 0

n_pol_state: int#

Alias for field number 1

parallel: bool#

Alias for field number 8

real_synthesis: bool#

Alias for field number 6

class solprop.backends.numba_cpu.dispersive.DispersiveMatrices(synthesis, projection, embedded)[source]#

Bases: NamedTuple

The backend’s taper-scaled transform matrices, rescaled in place every stage.

Attributes:
synthesisnumpy.ndarray

The scaled synthesis, (n_transform_rows, n_modes); real, or a complex cast of a real matrix that the response replaces with a real copy of its own.

projectionnumpy.ndarray

The scaled projection, real, (n_modes, n_transform_rows).

embeddednumpy.ndarray or None

The embedded rule’s scaled projection, or None.

embedded: ndarray | None#

Alias for field number 2

projection: ndarray#

Alias for field number 1

synthesis: ndarray#

Alias for field number 0

class solprop.backends.numba_cpu.dispersive.DispersiveTerms(symmetric, groups, keys, cubes, mixtures, input_multipliers, term_rows, term_channels, term_multipliers, pair_constants, quartics, input_scale, output_scale)[source]#

Bases: NamedTuple

The response’s filtered fields, products and output terms, laid out once.

The channels are grouped as the leaf says (groups()). The symmetric evaluator forms one filtered field and one product per group: a group of one species takes its own g, fixed, and a mixture – a group of several, the channels that act at the same nodes – the filter of its own Kerr diagonal, which StageWeights rebuilds from the channels’ densities (mixture_rows(); SEP 6, section 6, adopted for mixtures on 2026-10-08). The polynomial’s channels are grouped by their leg weight: species whose leg bounds are equal have the same d, so they share D E and D^2 E and their four coefficient tables add, density-weighted, at every stage – sum_s N_s gamma0_s (1, A_s, B_s, B'_s) – an exact rewriting of the per-species sum (SEP 6, section 6). The first n_cubes products are E^3, one per key – a single one shared by every channel, but on the general modal route one per node region, whose nodes alone it is formed at; group g then owns products n_cubes + 2 g, T(E, E, D E), and n_cubes + 2 g + 1, c0_g T(E, E, D^2 E) + c1_g T(E, D E, D E) with c0_g = sum_s f_s 3 (b_s + b'_s) and c1_g = sum_s f_s 6 b_s over its channels, f_s = eps0 gamma0_s rho_s at the stage.

Attributes:
symmetricbool

Whether the evaluator is the symmetric one.

groupstuple of tuple of int

The channels of each group, in channel order.

keystuple of int

Each group’s key: its node region on the general modal route, zero otherwise.

cubestuple of int

Each group’s cube product, for the polynomial; empty for the symmetric evaluator.

mixturestuple of int

The symmetric groups of more than one channel, whose filter is rebuilt per stage; empty for the polynomial. Mixture m’s field and product are its group’s, and its coefficient is factor n_channels + 1 + m.

input_multipliersnumpy.ndarray

One row per filtered field: g per group, or d and d^2 per group, times Pi_+ on a field grid’s analytic family; a mixture’s row is its stage’s.

term_rowsnumpy.ndarray

Each output term’s product, int64.

term_channelsnumpy.ndarray

Each term’s factor, int64: its channel; n_channels, the unit factor, for a product that already carries its channels’ factors; n_channels + 1 + m for mixture m.

term_multipliersnumpy.ndarray

Each term’s output multiplier times the sector’s Pi_+ and factor, float64; a mixture’s row is its stage’s.

pair_constantsnumpy.ndarray

3 (b + b') and 6 b per channel, m^4, shape (n_channels, 2); zero for the symmetric evaluator.

quarticsnumpy.ndarray

g_s^4 per channel of a mixture, (n_channels, n_bins); (0, 0) without a mixture.

input_scalenumpy.ndarray

What a symmetric input multiplier carries besides g: Pi_+ on a field grid’s analytic family, one otherwise.

output_scalenumpy.ndarray

The sector’s Pi_+ and factor, which every output multiplier carries.

cubes: tuple[int, ...]#

Alias for field number 3

groups: tuple[tuple[int, ...], ...]#

Alias for field number 1

input_multipliers: ndarray#

Alias for field number 5

input_scale: ndarray#

Alias for field number 11

keys: tuple[int, ...]#

Alias for field number 2

mixtures: tuple[int, ...]#

Alias for field number 4

property n_cubes: int[source]#

Return the number of cube products.

Returns:
int

One per distinct key for the polynomial, none for the symmetric evaluator.

property n_fields: int[source]#

Return the number of filtered fields.

Returns:
int

One per group (symmetric) or two per group (polynomial).

property n_products: int[source]#

Return the number of products.

Returns:
int

One per group (symmetric), or n_cubes + 2 n_groups (polynomial).

output_scale: ndarray#

Alias for field number 12

pair_constants: ndarray#

Alias for field number 9

pair_products(group)[source]#

Return a polynomial group’s two derivative products.

Parameters:
groupint

The group.

Returns:
tuple of int

T(E, E, D E)’s product and the weighted pair’s.

quartics: ndarray#

Alias for field number 10

symmetric: bool#

Alias for field number 0

term_channels: ndarray#

Alias for field number 7

term_multipliers: ndarray#

Alias for field number 8

term_rows: ndarray#

Alias for field number 6

class solprop.backends.numba_cpu.dispersive.DispersiveWorkspace(problem, plans, nodes, scratch, matrices, layout, *, flag, fftw_threads)[source]#

Bases: object

Preallocate the response’s buffers and plans, and run it on the fused path.

The stage drives it in five calls: prepare() weighs the channels at the stage and filters the padded modal spectrum, begin() scales the synthesis, respond_chunk() forms and projects the products of each node chunk, finish() applies the collar to them and transforms them, and after the stage’s own forward transform – skipped when replaces_transform – combine() adds their weighted spectra to the polarisation spectrum. The embedded rule’s products are finished and combined when a step is accepted (finish_coarse(), combine_coarse()).

Parameters:
problemsolprop.translate.problem.KernelProblem

The problem, carrying responses.dispersive_kerr on a legacy transform.

planssolprop.backends.numba_cpu.fft.TransformPlans

The backend’s plans, whose projected buffers and spectra the response adds into and, when it is alone, uses as its own.

nodesnumpy.ndarray

The backend’s node field buffer, (chunk_rows, n_to): read as the bare field, and, when the response is alone and symmetric, used as its own.

scratchnumpy.ndarray

The backend’s chunk projection scratch, (n_columns, n_to), shared between sequential uses when its dtype is the products’.

matricesDispersiveMatrices

The backend’s scaled transform matrices, whose views the response holds.

layoutDispersiveLayout

How the backend runs the path.

flagstr

The FFTW planning flag, a key of solprop.backends.numba_cpu.fft.PLAN_FLAGS.

fftw_threadsint or None

The FFTW pool override; None sizes each batched plan by the backend’s rule.

begin(bundle, taper)[source]#

Weigh the channels at the stage and scale the real copy of the synthesis.

prepare() has weighed them already on the stage path, and the weights update only when the densities change.

Parameters:
bundlesolprop.translate.stage.StageScalars

The stage’s scalars, whose channel densities weight the terms and, for the polynomial, its groups’ derivative products.

taperfloat

The stage’s taper scale.

combine()[source]#

Land the products’ weighted spectra in the stage’s polarisation spectrum.

After the stage’s forward transform (or instead of it, when replaces_transform).

combine_coarse()[source]#

Land the embedded rule’s weighted spectra in its polarisation spectrum.

finish()[source]#

Apply the collar to the projected products and transform them.

Before the stage’s forward transform: a borrowed product is the stage’s projected buffer, which that transform takes.

finish_coarse(bundle)[source]#

Apply the collar to the embedded rule’s products and transform them.

Called when a step is accepted, before the stage’s transform of the embedded projection; the products are the last stage’s, as the projection is, and so are the scalars that weigh them. Once per evaluation: the products hold the collar until another stage projects them again.

Parameters:
bundlesolprop.translate.stage.StageScalars

The last stage’s scalars.

memory_ledger()[source]#

Return the bytes of every buffer the response owns, by name.

Buffers it borrows from the backend – the node field buffer, the chunk scratch, the projected buffers and their spectra – are the backend’s rows.

Returns:
dict

Every row of DISPERSIVE_LEDGER_ROWS.

prepare(spectrum, bundle)[source]#

Weigh the channels, filter the stage’s spectrum and transform the fields.

Called after the stage combination has padded the modal spectrum and before the stage’s own inverse transform, which destroys it. In place, the filtered spectrum is the stage’s own, and that transform gives the filtered field.

Parameters:
spectrumnumpy.ndarray

The padded modal spectrum (doubled positive bins on the analytic family).

bundlesolprop.translate.stage.StageScalars

The stage’s scalars, whose channel densities weigh the terms and build a symmetric mixture’s filter, which the input multipliers read.

property replaces_transform: bool[source]#

Return whether the response writes the stage’s polarisation spectrum itself.

Alone with products of its own, nothing else is projected, so the stage’s forward transform of its projected buffer would transform nothing; the response’s combine() writes the spectrum instead.

Returns:
bool

True when the stage skips its forward transform.

respond_chunk(index)[source]#

Synthesise the filtered fields, form the products and project them.

Parameters:
indexint

The node chunk, in the backend’s order; the bare field of a polynomial response is the backend’s node buffer, already synthesised for it.

class solprop.backends.numba_cpu.dispersive.StageWeights(leaf, terms)[source]#

Bases: object

The terms’ weights at one stage: eps0 gamma0 rho per channel and per group.

It holds the stage’s own copies of the input and output multipliers, whose rows a symmetric mixture rebuilds from the channels’ densities (mixture_rows()), and updates only when the densities change: a fill of constant composition builds its filter once, and every stage of a run of varying composition its own.

Parameters:
leafsolprop.translate.responses.DispersiveKerrTerms

The compiled leaf, for its static coefficients.

termsDispersiveTerms

The layout.

property nbytes: int[source]#

Return the bytes the weights hold.

Returns:
int

The factors, group weights, stage multipliers and the layout’s arrays.

update(densities)[source]#

Weigh the channels by their densities at a stage, allocating nothing.

A no-op when the densities are those of the last update: everything here is a function of them alone.

Parameters:
densitiestuple of float

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

solprop.backends.numba_cpu.dispersive.add_spectra(spectra, weights, out)[source]#

Add the projected products’ spectra under their stage multipliers.

out[c, k] += sum_r weights[r, k] spectra[r C + c, k]: the response’s share of the polarisation spectrum, added to what the stage’s forward transform made of every other response’s. The products’ sum is formed first, in product order, and added once, so the result does not depend on what out held.

Parameters:
spectranumpy.ndarray

The forward transforms of the windowed products, one block of n_columns rows per product, at least n_bins long.

weightsnumpy.ndarray

One multiplier per product (weigh_terms()).

outnumpy.ndarray

The stage’s polarisation spectrum, (n_columns, n_bins), added to.

solprop.backends.numba_cpu.dispersive.apply_window(target, window)[source]#

Multiply the response’s products by the temporal collar, in place.

The collar acts on each product before Pi_+ and the output multipliers: G[w (G E)^3] (SEP 6, Phase 2a, open question 1, decided 2026-10-08).

Parameters:
targetnumpy.ndarray

The products in time, one row per column of each product.

windownumpy.ndarray

The temporal collar grids.t_window_fine.

solprop.backends.numba_cpu.dispersive.combine_spectra(spectra, weights, out)[source]#

Sum the projected products’ spectra under their stage multipliers.

out[c, k] = sum_r weights[r, k] spectra[r C + c, k]. out may be the first block of spectra: the first product writes each bin from itself, and every later product reads a later block.

Parameters:
spectranumpy.ndarray

The forward transforms of the projected products, one block of n_columns rows per product, at least n_bins long.

weightsnumpy.ndarray

One multiplier per product (weigh_terms()).

outnumpy.ndarray

Written with the summed spectrum, (n_columns, n_bins).

solprop.backends.numba_cpu.dispersive.dispersive_form(kind, sector)[source]#

Return the product form of a grid kind and a sector.

Parameters:
kind{‘field’, ‘envelope’}

The spectral layout.

sector{‘full’, ‘conserving’}

Which quartets the leaf keeps.

Returns:
str

"real" (the full cubic of a real field), "conserving" (the number-conserving group of analytic signals or envelopes) or "harmonic" (an envelope’s full cubic: the conserving group and both harmonic groups).

Examples

>>> from solprop.backends.numba_cpu.dispersive import dispersive_form
>>> dispersive_form("field", "full"), dispersive_form("envelope", "full")
('real', 'harmonic')
solprop.backends.numba_cpu.dispersive.dispersive_terms_layout(leaf, kind, *, analytic, keys=None)[source]#

Lay out a leaf’s filtered fields, products and output terms.

Every multiplier carries the sector’s own factor: one on a field grid’s full cubic, Pi_+ / 8 for its conserving sector (the one-sided spectrum of the real part of a quarter of the analytic polarisation), Pi_+ / 4 on an envelope grid. No transform normalisation: the products’ spectra land in the stage’s own polarisation spectrum, which its forward transform made unnormalised too.

Parameters:
leafsolprop.translate.responses.DispersiveKerrTerms

The compiled leaf.

kind{‘field’, ‘envelope’}

The spectral layout.

analyticbool

Whether the products are a field grid’s analytic signals.

keystuple of int or None, optional

A key per channel that must also agree for two channels to share a group: the general modal route’s node region. None takes the leaf’s own regions, a single key where it has none.

Returns:
DispersiveTerms

The layout.

solprop.backends.numba_cpu.dispersive.filter_spectra(spectrum, multipliers, out)[source]#

Write each input multiplier times the modal spectrum, one block per field.

Row field * n_columns + column of out is multipliers[field] * spectrum[column] on the first n_weighted bins and zero beyond, which is where an analytic family’s full-length spectrum holds nothing.

Parameters:
spectrumnumpy.ndarray

The padded modal spectrum the stage’s inverse transform reads, complex.

multipliersnumpy.ndarray

The input multipliers on the leaf’s spectral layout, float64.

outnumpy.ndarray

Written whole, shape (n_fields * n_columns, n_bins): a complex-to-real plan destroys its input, so nothing written once can be relied on.

solprop.backends.numba_cpu.dispersive.make_dispersive_pairs(form, n_pol, *, parallel, shifted=False)[source]#

Compile the polynomial’s two derivative products at every node of a chunk.

With E the bare field, F = D E and H = D^2 E, it writes T(E, E, F) over F and c0 T(E, E, H) + c1 T(E, F, F) over H, in place: the grouped polynomial’s second and third terms, whose output multipliers are 3 a + 6 b d and one (SEP 6, section 4.5 and the Phase 0 report).

Parameters:
form{‘real’, ‘conserving’, ‘harmonic’}

The product form (dispersive_form()).

n_polint

Components at one node, 1 or 2.

parallelbool

Whether the node loop is a prange.

shiftedbool, optional

Whether the bare field’s zero-frequency constant is subtracted per row as it is read (make_dispersive_self()).

Returns:
numba.core.dispatcher.Dispatcher

The compiled kernel (field, first, second, c0, c1, carrier, offset).

solprop.backends.numba_cpu.dispersive.make_dispersive_self(form, n_pol, *, parallel, shifted=False)[source]#

Compile the product (u, u, u) of one field at every node of a chunk.

The symmetric evaluator’s only product, of the filtered field, and the polynomial’s E^3. It may run in place: each sample reads every component before it writes.

Parameters:
form{‘real’, ‘conserving’, ‘harmonic’}

The product form (dispersive_form()).

n_polint

Components at one node, 1 or 2.

parallelbool

Whether the node loop is a prange.

shiftedbool, optional

Whether a constant per row is subtracted from the source as it is read: the zero-frequency bin of a field grid’s bare analytic signal, which Pi_+ excludes and the backend’s analytic family keeps.

Returns:
numba.core.dispatcher.Dispatcher

The compiled kernel (source, target, carrier, offset).

solprop.backends.numba_cpu.dispersive.mixture_rows(quartics, members, factors, input_scale, output_scale, inputs, outputs)[source]#

Write a symmetric mixture’s filter rows at a stage; return its coefficient.

g_mix = [sum_s f_s K_s / sum_s f_s]^(1/4) over the group’s channels, with K_s = g_s^4 and f_s the channel’s eps0 gamma3_0 rho at the stage: the arithmetic of solprop.nonlinear.responses.mixture_filter() – the weighted species sum, the fourth root as two square roots – within its rounding bound (a sum over a few channels, SEP 25), not to its bits: the order of the sum is free and the bin loop may vectorise. At a vacuum (no positive total) the filter is one and the coefficient zero.

Parameters:
quarticsnumpy.ndarray

K_s = (g_s^2)^2 per channel on the leaf’s layout.

membersnumpy.ndarray

The group’s channels, int64.

factorsnumpy.ndarray

f_s per channel at the stage (at least the members’).

input_scalenumpy.ndarray

What the input multiplier carries besides g_mix: Pi_+ on a field grid’s analytic family, one otherwise.

output_scalenumpy.ndarray

The sector’s Pi_+ and factor, which the output multiplier carries.

inputs, outputsnumpy.ndarray

Written with g_mix input_scale and g_mix output_scale.

Returns:
float

sum_s f_s, the mixture’s static coefficient at the stage.

solprop.backends.numba_cpu.dispersive.weigh_spectrum(weights, spectrum)[source]#

Multiply the stage’s spectrum, in place, by the one product’s multiplier.

The lone symmetric response of one species: its windowed product is the stage’s projected polarisation, the stage’s forward transform has made its spectrum, and the output multiplier g with eps0 gamma3_0 rho finishes it there.

Parameters:
weightsnumpy.ndarray

The stage multipliers (weigh_terms()); row zero is read.

spectrumnumpy.ndarray

The stage’s polarisation spectrum, modified in place.

solprop.backends.numba_cpu.dispersive.weigh_terms(rows, channels, multipliers, factors, out)[source]#

Sum the output terms’ multipliers that share a product, at this stage.

out[r] = sum_{t: row_t = r} factors[channel_t] multipliers[t]: each term one product of one channel, its multiplier carrying the sector’s Pi_+ and factor, weighted by the channel’s eps0 gamma3_0 rho. Products shared by channels – the polynomial’s E^3 and a group’s T(E, E, D E) – get one multiplier.

Parameters:
rowsnumpy.ndarray

Each term’s product, int64.

channelsnumpy.ndarray

Each term’s channel, int64.

multipliersnumpy.ndarray

Each term’s output multiplier, float64.

factorsnumpy.ndarray

eps0 gamma3_0 rho per channel at this stage.

outnumpy.ndarray

Written with one multiplier per product.