solprop.translate.stage#

The host scalars of one stage, and the device form they are the other half of.

A stage of the interaction-picture step needs three z-dependent quantities that are arrays – the propagator’s exponent dPhi, the response prefactor and the noise’s unitary phase – and a handful that are scalars: the number density, the pre-ionisation, the mode set’s taper, the Raman decay rates and, since schema 13, the frozen section and the residual weights of a coupled flow, and since schema 14 the weights of a moving guide’s maps. Until this module the backends fetched all seven for themselves, each through the function that hides the representation (solprop.translate.linop.delta_phi(), solprop.translate.linop.unitary_phase(), solprop.translate.responses.prefactor_at()) or straight off a PiecewiseChebyshev. That works on a host and cannot work on a device: a Clenshaw pass over a Python object is not something a compiled step loop can call, and the z table it walks is an array of the state’s size times the degree – 1.55 GB at the comb-to-continuum size.

The seam’s evaluation form is therefore split in two:

  • terms, the fixed arrays, uploaded once per run (terms()). A constant operator is one term; an expansion is n_terms of them.

  • scalars, a small frozen record of host floats per stage (scalars()). The weights that multiply the terms, and every scalar path value the responses read.

A backend evaluates dPhi as one linear combination of the terms it holds over the bundle’s floats (combine()) and knows nothing else about the representation. The z table has no such split: solprop.translate.problem.KernelProblem.device_ready is the predicate, the host backends evaluate the table, the native backend’s CPU build evaluates it natively into term slots it then weighs by +-1 (SEP 29 section 6.6), and require_device_ready() is the refusal an accelerator build raises.

The split costs nothing in accuracy because it is the arithmetic that was already there: delta() is tensordot(weights(z2) - weights(z1), terms), and the constant path is D (z2 - z1), so a backend that goes through this module reproduces delta_phi() to the last bit.

Functions

anchor(problem, z0, *[, helpers])

Return the weights a step's stages all subtract, evaluated once.

combine(weights, fixed)

Return the weighted sum of the terms, the one arithmetic a backend does.

require_device_ready(problem, name)

Refuse a problem whose z quantities an accelerator would hold as a z table.

scalars(problem, z0, z1, *[, at, helpers])

Return the host scalars of one stage, from the step's anchor to the node.

scalars_at(problem, z0, positions, *[, at, ...])

Return the host scalars of several stages of one step, each table read once.

terms(problem)

Return the fixed arrays of a problem's two array-valued z quantities.

Classes

StageScalars(z0, z1, phi_delta, phi_at, ...)

Everything about one stage that is a host scalar.

StageTerms(phi, prefactor[, separable])

The fixed arrays a backend holds for the whole run.

StepAnchor(z, phi)

The weights of Phi at the start of a step, hoisted for all of its stages.

class solprop.translate.stage.StageScalars(z0, z1, phi_delta, phi_at, prefactor, density, taper_scale, raman_rates, kerr_density, plasma_densities, plasma_preionisation, raman_densities, flow_section=-1, flow_weights=None, transform_weights=None, dispersive_kerr_densities=())[source]#

Bases: object

Everything about one stage that is a host scalar.

One stage runs from the step’s anchor z0 to the node position z1; the weights for Phi are the difference between the two, because Phi is cumulative and the step needs its increment, while everything else is a value at z1.

Attributes:
z0float

The step’s anchor, m.

z1float

The stage’s position, m, z0 + c_i h.

phi_deltanumpy.ndarray or None

Weights of Phi(z1) - Phi(z0), float64, shape (n_phi_terms,); None when Phi is a z table.

phi_atnumpy.ndarray or None

Weights of Phi(z1) itself, float64, shape (n_phi_terms,), which the noise’s unitary phase needs; None on the same condition.

prefactornumpy.ndarray or None

Weights of the prefactor at z1, float64, shape (n_pre_terms,); None when the prefactor is a z table.

densityfloat

Number density at z1, m^-3, or dimensionless 1 for an absolute chi3.

taper_scalefloat

The mode set’s scalar rescaling at z1, dimensionless; 1.0 with no taper.

raman_ratestuple of float

The decay rate of each Raman term at z1, s^-1, in the order of undamped. Empty with no Raman response. Hashable, because it is what a backend memoises its transformed kernel spectrum on.

kerr_densityfloat

The Kerr leaf’s own scalar scale at z1. With one component it is that component’s number density in m^-3 (bitwise the density above) and the leaf’s coefficient carries eps0 * gamma3; with several it is the composite sum(rho_s * gamma3_s) in m^2/V^2 and the coefficient is eps0 alone, so the product is the same quantity in both cases and only the split differs (the single-gas bits are kept by construction).

plasma_densitiestuple of float

Channel number densities at z1 in m^-3, in compiled channel order.

plasma_preionisationtuple of float

Channel initial ionised fractions at z1, dimensionless in [0, 1].

raman_densitiestuple of float

Raman component amplitude densities at z1 in m^-3. Multiple components add these amplitudes to the transformed-kernel cache key beside raman_rates.

flow_sectionint

Which section of linear.coupled_flow the step’s anchor z0 lies in, so that every stage of one step names the same frozen block; -1 for a problem carrying no coupled flow. Schema 13.

flow_weightsnumpy.ndarray or None

Weights of the coupled flow’s (n_intervals, n_deg) coefficient block at z1, float64, shape (n_intervals * n_deg,), flattened in that order; None without a coupled flow. Zero outside the section the stage is evaluated on, which is the step’s own whenever z1 lies in it – a stage landing exactly on a section edge reads the closing side of that kink and not the opening one – and z1’s own otherwise, so no expansion is ever extrapolated. The generator and the connection share the axes and are combined over the same weights (solprop.translate.coupled.flow_weights()). Schema 13.

transform_weightsnumpy.ndarray or None

Weights of transform.modal_tables’ moving maps – the (n_intervals, n_deg) coefficient axes of synthesis and projection, flattened in that order – at z1, float64, shape (n_intervals * n_deg,): a backend evaluates S(z1) and Q(z1) as that combination, zero outside the section z1 lies in (solprop.translate.modal_path.transform_weights()). None for a problem whose maps do not move – no general modal tables, or maps of one coefficient – so such a problem’s bundle carries nothing new. The transform’s sections are its own and need not be the coupled flow’s. Schema 14.

dispersive_kerr_densitiestuple of float

Each channel of responses.dispersive_kerr its number density at z1, in m^-3, in the leaf’s channel order; empty without the leaf. Schema 24.

Examples

>>> import numpy as np
>>> from solprop.grid import EnvelopeGrid
>>> from solprop.nonlinear.gnlse import problem_from
>>> from solprop.pulses import SechPulse
>>> from solprop.stepper import PIController
>>> from solprop.translate.problem import assemble
>>> from solprop.translate.stage import scalars
>>> grid = EnvelopeGrid.from_limits(1.0, 835e-9, (500e-9, 2e-6), 4e-12)
>>> gnlse = problem_from([0.0, 0.0, -1.2e-26], omega_ref=grid.omega0, gamma=0.11,
...                      shock=False)
>>> start = SechPulse(wavelength=835e-9, width=1e-13, peak_power=1.0).build(grid)
>>> problem = assemble(grid, gnlse.mode, [gnlse.kerr], start,
...                    controller=PIController(rtol=1e-8, atol=1e-14),
...                    save_planes=[1.0], dz_max=0.1, dz_init=1e-3,
...                    shock=False, prefactor_index=1.0, density=1.0)
>>> bundle = scalars(problem, 0.25, 0.75)
>>> bundle.phi_delta.tolist(), bundle.prefactor.tolist()
([0.5], [1.0])
density: float#
dispersive_kerr_densities: tuple[float, ...] = ()#
flow_section: int = -1#
flow_weights: Float[ndarray, 'n_flow_terms'] | None = None#
kerr_density: float#
phi_at: Float[ndarray, 'n_phi_at'] | None#
phi_delta: Float[ndarray, 'n_phi_terms'] | None#
plasma_densities: tuple[float, ...]#
plasma_preionisation: tuple[float, ...]#
prefactor: Float[ndarray, 'n_pre_terms'] | None#
property preionisation: float[source]#

Return the initial fraction for the one-channel specialised path.

Returns:
float

The sole channel’s fraction, or zero when there is no sole channel.

raman_densities: tuple[float, ...]#
raman_rates: tuple[float, ...]#
taper_scale: float#
transform_weights: Float[ndarray, 'n_transform_terms'] | None = None#
z0: float#
z1: float#
class solprop.translate.stage.StageTerms(phi, prefactor, separable=None)[source]#

Bases: object

The fixed arrays a backend holds for the whole run.

Each shape annotation carries its own axis name because the two members are independent builds with their own term counts; nothing here ties them together.

Attributes:
phinumpy.ndarray or None

The terms of Phi, complex128, shape (n_phi_terms, n_batch_or_1, n_modes, n_pol, n_omega). One term for a constant operator, n_terms for an expansion, and None for the z table, which has no such form.

prefactornumpy.ndarray or None

The terms of the prefactor, complex128, shape (n_pre_terms, n_modes, n_pol, n_omega), on the same three cases.

separablesolprop.translate.spatial.SeparableOperator or None

Shared constant spatial coefficients. When present, phi and prefactor are absent; a backend evaluates the operator and prefactor per element.

Examples

>>> import numpy as np
>>> from solprop.translate.linop import constant_phi
>>> from solprop.translate.stage import StageTerms
>>> table = constant_phi(np.array([-1j, -2j]), 1.0)
>>> StageTerms(phi=table.phi_constant[np.newaxis], prefactor=None).phi.shape
(1, 1, 1, 1, 2)
property device_ready: bool[source]#

Return whether both quantities are a weighted sum of fixed arrays.

Returns:
bool

True when neither member is None.

phi: Complex[ndarray, 'n_phi_terms n_batch n_modes n_pol n_omega'] | None#
prefactor: Complex[ndarray, 'n_pre_terms n_modes_p n_pol_p n_omega_p'] | None#
separable: SeparableOperator | None = None#
class solprop.translate.stage.StepAnchor(z, phi)[source]#

Bases: object

The weights of Phi at the start of a step, hoisted for all of its stages.

Phi is cumulative and the stage needs its increment, so every stage of a step subtracts the weights at the same anchor. Evaluating them once rather than once per stage is worth a sixth of the whole per-step scalar cost on the RDW gradient, where a Clenshaw pass over the scalar tables is the largest part of what the host does.

Attributes:
zfloat

The step’s anchor, m.

phinumpy.ndarray or None

Weights of Phi(z), float64, shape (n_phi_terms,); None when Phi is a z table and has no such form.

Examples

>>> from solprop.translate.stage import StepAnchor
>>> StepAnchor(0.5, None).z
0.5
phi: Float[ndarray, 'n_phi_terms'] | None#
z: float#
solprop.translate.stage.anchor(problem, z0, *, helpers=Helpers(name='numba'))[source]#

Return the weights a step’s stages all subtract, evaluated once.

Parameters:
problemsolprop.translate.problem.KernelProblem

The validated contract.

z0float

The step’s anchor, m, inside the tabulated range.

helperssolprop._helpers.Helpers, optional

The implementation of the tables’ Clenshaw pass: the backend’s that runs the step (StepDriver.helpers, SEP 29 section 6.6), or the shared Numba one by default.

Returns:
StepAnchor

The weights of Phi(z0), or None for the z table.

Raises:
solprop.exceptions.TranslationError

If the position lies outside the tabulated range.

solprop.translate.stage.combine(weights, fixed)[source]#

Return the weighted sum of the terms, the one arithmetic a backend does.

A single term is multiplied rather than contracted, so that a constant operator’s D (z2 - z1) stays exact to the last bit – the property numpy_ref’s P0 rung rests on – and is not routed through a BLAS call for one product. A single term weighted by exactly one is the stored array itself, as solprop.translate.responses.prefactor_at() returns it on the same fast path: the alternative is a full array multiply and an allocation per stage for a factor of one, which on the comb-to-continuum prefactor is 1.6 MB eight times a step. The array is read-only, as everything crossing the seam is.

Parameters:
weightsnumpy.ndarray

Host scalars, float64, shape (n_terms,).

fixednumpy.ndarray

The terms, shape (n_terms, *value_shape).

Returns:
numpy.ndarray

The combination, of the terms’ value shape.

Raises:
BackendError

If the counts disagree.

Examples

>>> import numpy as np
>>> from solprop.translate.stage import combine
>>> combine(np.array([0.5]), np.array([[2.0, 4.0]])).tolist()
[1.0, 2.0]
>>> fixed = np.array([[2.0, 4.0]])
>>> bool(np.shares_memory(combine(np.array([1.0]), fixed), fixed))
True
solprop.translate.stage.require_device_ready(problem, name)[source]#

Refuse a problem whose z quantities an accelerator would hold as a z table.

A backend whose step loop runs on an accelerator calls this at construction: the native backend’s device builds. The z table is a Chebyshev table of the state’s own size times its degree and intervals, which the device would have to hold resident; the translation layer falls back to it for a mode set that declares no scalar parameter paths, that disagrees about their values, or whose operator kinks in a parameter (docs/dev/kernel-contract.md section 3). Every CPU backend runs it: numpy_ref and numba_cpu evaluate it on the host, and the native backend’s CPU build evaluates it natively (SEP 29 section 6.6).

Parameters:
problemsolprop.translate.problem.KernelProblem

The validated contract.

namestr

The backend’s registry name, for the message.

Raises:
solprop.exceptions.BackendError

If either quantity is a z table, naming which, its size and the backends that run it.

Examples

>>> from solprop.translate.stage import require_device_ready
>>> require_device_ready.__name__
'require_device_ready'
solprop.translate.stage.scalars(problem, z0, z1, *, at=None, helpers=Helpers(name='numba'))[source]#

Return the host scalars of one stage, from the step’s anchor to the node.

Parameters:
problemsolprop.translate.problem.KernelProblem

The validated contract.

z0float

The step’s anchor, m, inside the tabulated range.

z1float

The stage’s position, m, inside the tabulated range.

atStepAnchor or None, optional

The anchor’s weights from anchor(), hoisted by a caller that takes every stage of one step. None evaluates them here, which is one Clenshaw pass per stage that the step did not need. Must be the anchor of z0.

helperssolprop._helpers.Helpers, optional

The implementation of the tables’ Clenshaw pass: the backend’s that runs the stage (StepDriver.helpers, SEP 29 section 6.6), or the shared Numba one by default.

Returns:
StageScalars

The weights and the scalar path values, all host floats.

Raises:
solprop.exceptions.TranslationError

If a position lies outside the tabulated range.

solprop.exceptions.BackendError

If at is the anchor of a different position.

solprop.translate.stage.scalars_at(problem, z0, positions, *, at=None, helpers=Helpers(name='numba'))[source]#

Return the host scalars of several stages of one step, each table read once.

A trial knows every position it will evaluate before its first stage – the step’s end and z0 + c_i h for each node – and scalars() reads every scalar table once per position, a Python call and a compiled Clenshaw pass apiece. Here each table is read once over all the positions (at_positions()), which keeps every operation of the per-position pass, so bundle i equals scalars(problem, z0, positions[i], at=at) field for field and bit for bit, with the same interval choice at a breakpoint and the same refusal outside the range. The increment of Phi is formed as there: z1 - z0 for a constant operator, the difference of the integrated weights for an expansion.

The coupled flow’s and the moving guide’s weights are a basis row per position rather than a table, and are formed one position at a time.

Parameters:
problemsolprop.translate.problem.KernelProblem

The validated contract.

z0float

The step’s anchor, m, inside the tabulated range.

positionssequence of float

The stages’ positions, m, inside the tabulated range, in the order the bundles are returned.

atStepAnchor or None, optional

The anchor’s weights from anchor(); must be the anchor of z0.

helperssolprop._helpers.Helpers, optional

The implementation of the tables’ Clenshaw pass: the backend’s that runs the stages (StepDriver.helpers, SEP 29 section 6.6), or the shared Numba one by default, for callers outside a backend.

Returns:
tuple of StageScalars

One bundle per position, in order.

Raises:
solprop.exceptions.TranslationError

If a position lies outside the tabulated range.

solprop.exceptions.BackendError

If at is the anchor of a different position.

Examples

>>> import numpy as np
>>> from solprop.grid import EnvelopeGrid
>>> from solprop.nonlinear.gnlse import problem_from
>>> from solprop.pulses import SechPulse
>>> from solprop.stepper import PIController
>>> from solprop.translate.problem import assemble
>>> from solprop.translate.stage import scalars_at
>>> grid = EnvelopeGrid.from_limits(1.0, 835e-9, (500e-9, 2e-6), 4e-12)
>>> gnlse = problem_from([0.0, 0.0, -1.2e-26], omega_ref=grid.omega0, gamma=0.11,
...                      shock=False)
>>> start = SechPulse(wavelength=835e-9, width=1e-13, peak_power=1.0).build(grid)
>>> problem = assemble(grid, gnlse.mode, [gnlse.kerr], start,
...                    controller=PIController(rtol=1e-8, atol=1e-14),
...                    save_planes=[1.0], dz_max=0.1, dz_init=1e-3,
...                    shock=False, prefactor_index=1.0, density=1.0)
>>> [bundle.phi_delta.tolist() for bundle in scalars_at(problem, 0.25, [0.5, 0.75])]
[[0.25], [0.5]]
solprop.translate.stage.terms(problem)[source]#

Return the fixed arrays of a problem’s two array-valued z quantities.

Built once per run and uploaded once by a device backend. The constant fast path is the one-term case rather than a case of its own, so a backend has one code path (docs/dev/kernel-contract.md sections 3 and 5).

Parameters:
problemsolprop.translate.problem.KernelProblem

The validated contract.

Returns:
StageTerms

The terms of Phi and of the prefactor, each None where the z table carries the quantity instead.

Examples

>>> import numpy as np
>>> from solprop.grid import EnvelopeGrid
>>> from solprop.nonlinear.gnlse import problem_from
>>> from solprop.pulses import SechPulse
>>> from solprop.stepper import PIController
>>> from solprop.translate.problem import assemble
>>> from solprop.translate.stage import terms
>>> grid = EnvelopeGrid.from_limits(1.0, 835e-9, (500e-9, 2e-6), 4e-12)
>>> gnlse = problem_from([0.0, 0.0, -1.2e-26], omega_ref=grid.omega0, gamma=0.11,
...                      shock=False)
>>> start = SechPulse(wavelength=835e-9, width=1e-13, peak_power=1.0).build(grid)
>>> problem = assemble(grid, gnlse.mode, [gnlse.kerr], start,
...                    controller=PIController(rtol=1e-8, atol=1e-14),
...                    save_planes=[1.0], dz_max=0.1, dz_init=1e-3,
...                    shock=False, prefactor_index=1.0, density=1.0)
>>> held = terms(problem)
>>> held.phi.shape[0], held.prefactor.shape[0], held.device_ready
(1, 1, True)