solprop.translate.linop#

The cumulative linear operator in a form a kernel can evaluate.

D(omega, z) is diagonal in omega, so it commutes with itself along z and exp(Phi(z2) - Phi(z1)) is the exact linear flow over any step. For a physical modal index, Phi = integral of D - log(w(z)/w(0))/2 also conserves true power along a gradient. This module turns solprop.linear.dispersion_operator() into something a kernel can evaluate, plus the hard kinks steps must land on and the frame quantities as diagnostics. Section 3 of docs/dev/kernel-contract.md is the class LinearOperator, field for field, and the derivation is in docs/guide/numerics/linear-operator.md.

There are three representations, and delta_phi() and unitary_phase() take any of them, which is what lets the choice change without a backend noticing:

  • a z-constant operator is the exact D (z2 - z1), to the last bit;

  • a mode set whose whole z dependence runs through scalar paths it declares (solprop.modes.protocol.ParameterisedMode) takes an expansion in those parameters with scalar integral tables along z (solprop.translate.expansion): D is analytic in the density and the core radius, while it is rho(z) that carries the cusps and the kinks, so the expensive array-valued object is resolved where it is easy and the hard structure stays in tables of scalars;

  • anything else takes the z table, an adaptive piecewise Chebyshev polynomial in z with array-valued coefficients, integrated exactly in the Chebyshev basis. It is the general representation and the fallback for a mode set the expansion cannot serve.

Three things this module is careful about:

  • The z-dependent operator is integrated, not applied by a rectangle rule, which would be globally first order in the step and invisible to the error estimate. Either representation is exact instead, and verify_phi() proves it against scipy.integrate.quad_vec.

  • Both cover slightly beyond the fibre end (a five per cent margin), so the last step and the noise phase are defined there.

  • The tolerance is the measured round-off floor of D, taken in the acceptance test’s own norm so that the two are comparable; measured any other way the search is paid twice.

Module Attributes

DEFAULT_MARGIN

Fraction of z_max the table covers beyond the fibre end.

DEFAULT_VERIFY_POINTS

Position pairs build_phi() checks against quad_vec at debug level.

ROUNDOFF_SAFETY

Margin between the measured round-off floor of D and the tolerance asked for.

MAX_TOL

Loosest tolerance the budget fallback will relax to before giving up and logging.

PhiRepresentation

Which representation of Phi the contract carries.

RepresentationChoice

What a caller may ask build_phi() for.

Functions

build_phi(mode, grid, frame, *, z_max[, ...])

Build the cumulative linear operator of a mode set over a run.

constant_phi(operator, z_max, *[, n_pol, ...])

Return the exact table of an operator that does not vary along z.

declared_paths(modes)

Return the scalar paths along z the mode set declares, without duplicates.

delta_phi(table, z1, z2, *[, helpers])

Return the exponent of the exact linear flow between two positions.

endpoint_phi(linear, sample, value_at, ...)

Add a real endpoint difference f(z) - f(z_0) to a cumulative operator.

parameter_paths(modes, z0, z1)

Return the varying scalar paths the whole mode set agrees on, or None.

parameters_own_z(modes)

Return whether every mode's declared parameters are its whole z dependence.

phi_rate(table, z, *[, before, helpers])

Return the rate D(z) = dPhi/dz of the representation that defines the flow.

power_weight_at(problem, z)

Evaluate the modal power weight at one statistics or observation plane.

power_weight_factors(problem, positions)

Factor the modal power weight at many planes into terms and per-plane weights.

power_weight_row(linear, z)

Evaluate the modal power weight of an assembled linear group at one position.

profile_table(profile, z0, z1, *[, tol])

Tabulate a scalar path along z in the form the seam carries.

rate_weights(table, z, *[, before, helpers])

Return D(z)'s weights over the operator's fixed terms.

tabulate(func, z0, z1, *[, kinks, tol])

Tabulate a scalar function of z as a frozen Chebyshev table.

unitary_phase(table, z)

Return the phase that advances the shot-noise field to a position.

verify_phi(table, operator_at, points)

Check the table against an independent quadrature of the operator.

with_power_weight(linear, modes, grid, index)

Mirror the prefactor representations for statistics, outside the step loop.

Classes

LinearOperator(phi_representation, ...[, ...])

The cumulative linear operator Phi(omega, z) and its landing points.

solprop.translate.linop.DEFAULT_MARGIN = 0.05#

Fraction of z_max the table covers beyond the fibre end.

The last step may land exactly on z_max and the noise phase is asked for there, so evaluation at the end must not fall off the domain; five per cent costs one extra interval.

solprop.translate.linop.DEFAULT_VERIFY_POINTS = 8#

Position pairs build_phi() checks against quad_vec at debug level.

class solprop.translate.linop.LinearOperator(phi_representation, phi_breakpoints, phi_coefficients, phi_degrees, hard_kinks, phi_constant, phi_expansion=None, beta0=None, beta1=None, separable=None, power_weight_representation=None, power_weight=None, power_weight_expansion=None, power_weight_constant=None, coupled_flow=None, modal_metric=None)[source]#

Bases: object

The cumulative linear operator Phi(omega, z) and its landing points.

Section 3 of the kernel contract. Every array is a read-only, C-contiguous copy of the stated dtype. Each shape annotation carries its own axis name because __post_init__ is what enforces their agreement, and that check must stay reachable in production where no runtime type checker runs.

Attributes:
phi_representation{‘constant’, ‘expansion’, ‘table’}

Which representation was built. "constant" carries phi_constant and the exact single interval beside it; "expansion" carries phi_expansion and no phi_coefficients; "table" carries phi_coefficients alone. The evaluation functions take all three, so nothing downstream branches on this to evaluate; what does branch on it is whether the problem is in the device form (solprop.translate.problem.KernelProblem.device_ready).

phi_breakpointsnumpy.ndarray

Interval edges in z, m, shape (n_intervals + 1,), strictly increasing. For an expansion these are the edges of its scalar tables, so the local step cap and the schedule read the same field either way.

phi_coefficientsnumpy.ndarray or None

Chebyshev coefficients of Phi, complex128, shape (n_intervals, n_deg, n_batch_or_1, n_modes, n_pol, n_omega), present when phi_representation is "table" and also for "constant", whose one interval of degree 1 is exact; absent for "expansion". The batch axis is 1 when the members share a fibre. Units are dimensionless: Phi is an exponent.

phi_degreesnumpy.ndarray

Degree used on each interval, numpy.intp, shape (n_intervals,). Diagnostic: the zero padding makes it unnecessary for evaluation.

hard_kinksnumpy.ndarray

Positions in z, m, shape (n_kinks,), where D itself jumps or kinks. Steps land on them; the far more numerous split edges are not tracked here and nothing lands on those.

phi_constantnumpy.ndarray or None

D itself, complex128, shape (n_batch_or_1, n_modes, n_pol, n_omega), present exactly when phi_representation is "constant", so that delta_phi() returns D (z2 - z1) to the last bit rather than the difference of two Clenshaw sums.

phi_expansionsolprop.translate.expansion.ParameterExpansion or None

Phi as an expansion in the mode set’s scalar paths with scalar integral tables along z, present exactly when phi_representation is "expansion". Its breakpoints, degrees and hard kinks are the fields above, which __post_init__ checks.

beta0solprop.translate.chebyshev.PiecewiseChebyshev or None

The frame’s beta_0(z) in 1/m as a scalar table; diagnostic, already inside Phi. None when the operator was supplied directly rather than built from a mode and a frame.

beta1solprop.translate.chebyshev.PiecewiseChebyshev or None

The frame’s beta_1(z) in s/m as a scalar table; diagnostic, as above.

separablesolprop.translate.spatial.SeparableOperator or None

Constant spatial coefficients without full operator arrays, present exactly for representation "separable". Shared with the response prefactor.

power_weight_representation{‘constant’, ‘expansion’, ‘table’} or None

Modal power-weight representation; absent for spatial geometry.

power_weightsolprop.translate.chebyshev.PiecewiseChebyshev or None

Dimensionless real index table, value shape (n_modes, n_pol, n_omega). Present for table and constant representations.

power_weight_expansionsolprop.translate.expansion.ParameterExpansion or None

Dimensionless real index expansion with plain basis-product weights.

power_weight_constantnumpy.ndarray or None

Positive dimensionless weights, shape (n_modes, n_pol, n_omega), present exactly for the constant representation.

coupled_flowsolprop.translate.coupled.CoupledFlowTables or None

Kernel schema 12: the generator of a guide whose channels couple – a coherent wall, a taper’s connection, a split block, a moving guide; executed by all three backends, a CF4 section by numpy_ref alone. None on every route that declares no coupling.

modal_metricsolprop.translate.coupled.ModalMetricTables or None

Kernel schema 12: the explicit physical flux metric, a documented leaf here and not a nested record, so that the controller, the flow and the output layer share one W by identity. None for the legacy orthogonal routes, whose power weight is the row above.

Raises:
TranslationError

If the interval counts disagree, a breakpoint is not finite and increasing, a hard kink is not an interval edge, or the member present is not the one phi_representation names.

Examples

>>> import numpy as np
>>> from solprop.translate.linop import constant_phi, delta_phi
>>> table = constant_phi(np.full(4, -1j), 2.0)
>>> table.phi_representation, table.phi_degrees.tolist()
('constant', [1])
>>> bool(np.array_equal(delta_phi(table, 0.0, 0.5), np.full((1, 1, 1, 4), -0.5j)))
True
beta0: PiecewiseChebyshev | None = None#
beta1: PiecewiseChebyshev | None = None#
coupled_flow: CoupledFlowTables | None = None#
hard_kinks: Float[ndarray, 'n_kinks']#
modal_metric: ModalMetricTables | None = None#
property n_intervals: int[source]#

Return the number of Chebyshev intervals.

Returns:
int

The length of phi_degrees.

Examples

>>> import numpy as np
>>> from solprop.translate.linop import constant_phi
>>> constant_phi(np.full(4, -1j), 2.0).n_intervals
1
property nbytes: int[source]#

Return the memory the representation occupies.

Returns:
int

Bytes: the coefficient array for a z table, the expansion’s terms and scalar tables together for an expansion. This is the number docs/dev/kernel-contract.md section 3 quotes.

Examples

>>> import numpy as np
>>> from solprop.translate.linop import constant_phi
>>> constant_phi(np.full(4, -1j), 2.0).nbytes
128
phi_breakpoints: Float[ndarray, 'n_edges']#
phi_coefficients: Complex[ndarray, 'n_intervals n_deg n_batch n_modes n_pol n_omega'] | None#
phi_constant: Complex[ndarray, 'n_batch_c n_modes_c n_pol_c n_omega_c'] | None#
phi_degrees: Int[ndarray, 'n_degrees']#
phi_expansion: ParameterExpansion | None = None#
phi_representation: Literal['constant', 'expansion', 'table', 'separable']#
property polynomial: PiecewiseChebyshev[source]#

Return the z table as a piecewise Chebyshev polynomial.

A view over the stored arrays, not a copy, so evaluation costs one Clenshaw pass and no memory. Only a "table" representation has one; an expansion’s equivalent is phi_expansion.weights, whose value is n_terms scalars.

Returns:
solprop.translate.chebyshev.PiecewiseChebyshev

Phi over [0, z_max (1 + margin)].

Raises:
TranslationError

If the representation is an expansion.

Examples

>>> import numpy as np
>>> from solprop.translate.linop import constant_phi
>>> table = constant_phi(np.full(4, -1j), 2.0)
>>> table.polynomial.z_span
(0.0, 2.1)
power_weight: PiecewiseChebyshev | None = None#
power_weight_constant: Float[ndarray, 'n_modes_w n_pol_w n_omega_w'] | None = None#
power_weight_expansion: ParameterExpansion | None = None#
power_weight_representation: Literal['constant', 'expansion', 'table'] | None = None#
property rate: PiecewiseChebyshev[source]#

Return the derivative of the cumulative operator’s scalar or z table.

D = dPhi/dz from the representation that defines the flow, so that a residual (D(z) - B) u integrates to zero against the chord B exactly as the table does (SEP 16 section 4.2): an expansion’s scalar integral tables differentiate to its resolved integrands, a z table to its resolved D.

Returns:
solprop.translate.chebyshev.PiecewiseChebyshev

The derivative of phi_expansion.weights (value shape (n_terms,)) for an expansion, of polynomial otherwise.

Examples

>>> import numpy as np
>>> from solprop.translate.linop import constant_phi
>>> table = constant_phi(np.full(2, -1j), 2.0)
>>> np.round(table.rate(0.5), 12).ravel().tolist()
[-1j, -1j]
separable: SeparableOperator | None = None#
property value_shape: tuple[int, ...][source]#

Return the shape of Phi at one position.

Returns:
tuple of int

(n_batch_or_1, n_modes, n_pol, n_omega), whichever representation carries it.

Examples

>>> import numpy as np
>>> from solprop.translate.linop import constant_phi
>>> constant_phi(np.full(4, -1j), 2.0).value_shape
(1, 1, 1, 4)
property z_span: tuple[float, float][source]#

Return the range of z the table covers.

Returns:
tuple of float

(first breakpoint, last breakpoint) in m.

Examples

>>> import numpy as np
>>> from solprop.translate.linop import constant_phi
>>> constant_phi(np.full(4, -1j), 2.0).z_span
(0.0, 2.1)
solprop.translate.linop.MAX_TOL = 1e-07#

Loosest tolerance the budget fallback will relax to before giving up and logging.

solprop.translate.linop.PhiRepresentation#

Which representation of Phi the contract carries.

"constant" is an operator that does not vary along z, held as D itself; "expansion" is the Chebyshev expansion in the mode set’s scalar paths of solprop.translate.expansion, with scalar integral tables along z; "table" is the piecewise Chebyshev table of z itself, which is the general representation.

The first two are the device form: each evaluates Phi as a weighted sum of a fixed set of arrays over host scalars the translation layer computes per stage – one term with the distance as its weight for a constant, n_terms for an expansion – so a backend holds the arrays and never the representation (solprop.translate.stage). The z table is a Clenshaw pass over an array of the state’s size times the degree and is evaluated on the host by NumPy and Numba; solprop.translate.problem.KernelProblem.device_ready is the predicate, and a device-resident backend refuses what fails it.

alias of Literal[‘constant’, ‘expansion’, ‘table’, ‘separable’]

solprop.translate.linop.ROUNDOFF_SAFETY = 100.0#

Margin between the measured round-off floor of D and the tolerance asked for.

The frame subtracts beta_0 + beta_1 (omega - origin) from beta, and on a wide field grid that is a cancellation of about eight digits: beta reaches 8e7 1/m at the top of the active band while the residual is a few thousand, so D carries an absolute round-off of order 1e-7 whatever the frame quantities cost. A hundred is the smallest round decade that leaves margin above the floor; see docs/guide/numerics/linear-operator.md.

The factor was originally what stood between a floor measured against the global envelope and a tail measured per element, and it was not enough: with the spectral collar active the global envelope is the collar’s own rate, the floor is read two orders too small, and the interval budget is exhausted before the tolerance is relaxed and the whole search paid again. The floor is now measured in the acceptance test’s own norm, so the two are comparable and the factor is a margin rather than a correction. Measured on a capillary with its absorber (1.5 m, 90 nm to 4 um, n_omega 2049, four modes): the floor goes from 1.5e-13 to 2.3e-7 at 1 mm and the first build from 416 s to 1.1 s, at half the memory and an agreement with quad_vec of 5.5e-11 against 2.5e-12.

solprop.translate.linop.RepresentationChoice#

What a caller may ask build_phi() for.

"auto", the default, takes the expansion when the mode set allows it and falls back to the z table otherwise. The other two force the choice, which is what an equivalence test between the two representations needs; "expansion" raises rather than falling back.

alias of Literal[‘auto’, ‘expansion’, ‘table’]

solprop.translate.linop.build_phi(mode, grid, frame, *, z_max, n_pol=1, absorber_rate=None, loss=LossPolicy(max_alpha=None), margin=0.05, tol=1e-12, n_max=64, max_intervals=512, representation='auto', reuse=None, index=None, kinks=())[source]#

Build the cumulative linear operator of a mode set over a run.

Three representations, in the order they are tried.

A mode set that reports z_constant takes constant_phi(): one interval, degree 1, and a delta_phi() that is exact to the last bit.

A mode set whose whole z dependence runs through scalar paths it declares (solprop.modes.protocol.ParameterisedMode) takes the expansion in those parameters: D is resolved once over the range each path spans, and Phi is that expansion against scalar integral tables along z (solprop.translate.expansion). The paths’ breakpoints are mandatory edges of the scalar tables, so a stage boundary or a taper corner is never crossed and becomes a hard kink, exactly as before; what changes is that the cusps and the corners are resolved in tables of n_terms scalars rather than of the state.

Anything else takes the z table: the scalar paths are resolved first, because they are cheap and carry the structure that makes the operator hard, and the operator itself is then resolved on the union of their edges and integrated exactly in the Chebyshev basis. That is the general representation and the fallback for a mode whose parameters do not resolve. The approximations in Phi are the interpolation of D and of the cumulative logarithmic power weight, each resolved to its measured round-off floor (docs/guide/numerics/linear-operator.md).

Parameters:
modeMode or sequence of Mode

The mode, or the ordered set of a multimode problem. Only neff, dneff_domega, the declared core_radius and density paths and, for the expansion, at_parameters are used.

gridsolprop.grid.TimeGrid

The grid; supplies the frequency axis, the active band and the layout.

framesolprop.linear.Frame

The co-moving frame, usually from solprop.linear.Frame.comoving().

z_maxfloat

Length the run covers, m, positive and finite.

n_polint, optional

Polarisation components, 1 or 2. D is the same for both components of one mode, and is stored for each so that a birefringent mode needs no new layout.

absorber_ratenumpy.ndarray or None, optional

Spectral absorber rate in 1/m, shape (n_omega,), folded into D before the integration so the collar is exact as a rate.

losssolprop.linear.LossPolicy, optional

What to do with an attenuation too large to represent. Applied to D before Phi is integrated, never to an accumulated exponent afterwards.

marginfloat, optional

Fraction of z_max the table extends beyond the end. Default DEFAULT_MARGIN.

tolfloat, optional

Target bound on the uniform error of the table relative to the value scale. The request is raised to ROUNDOFF_SAFETY times the measured round-off floor of D when that is larger, because no interpolant can resolve a function below the noise of its own evaluation, and relaxed further, by decades up to MAX_TOL, if the interval budget is exhausted anyway (docs/dev/stepper.md section 4, step 4). Both are logged.

n_maxint, optional

Highest Chebyshev degree tried on an interval, or on a parameter, before the interval is bisected or the expansion gives up.

max_intervalsint, optional

Interval budget; exceeding it sets the fallback flag and logs a warning.

representation{‘auto’, ‘expansion’, ‘table’}, optional

Which representation to build. "auto", the default, prefers the expansion; "table" forces the z table, which is what an equivalence test between the two needs; "expansion" refuses to fall back. A z_constant mode set takes constant_phi() whatever this says, since that path is exact.

reuseParameterExpansion or None, optional

Coefficients of the identical operator function on the same grid, frame and fixed parameters. The caller must establish function identity. Covered paths receive new scalar weights; other paths rebuild normally, with a logged reason.

indexfloat or None, optional

Power-weight override matching the nonlinear prefactor. None uses the mode’s real effective index; a positive finite float makes the cumulative normalisation term identically zero. The GNLSE route passes this through assemble(prefactor_index=1.0) and retains its bitwise Phi.

kinkssequence of float or numpy.ndarray, optional

Further positions in m where steps must land, beside the mode set’s own breakpoints: where a response’s path kinks although the modes do not (a pre-ionisation path). They become interval edges and hard kinks of every representation; none (the default) builds exactly what the modes alone declare.

Returns:
LinearOperator

Section 3 of the kernel contract, covering [0, z_max (1 + margin)].

Raises:
TranslationError

If z_max, margin, representation or the mode set is invalid, or representation="expansion" is asked for and cannot be built.

Examples

>>> from solprop.grid import EnvelopeGrid
>>> from solprop.linear import Frame
>>> from solprop.nonlinear.gnlse import SpecifiedMode
>>> from solprop.translate.linop import build_phi
>>> grid = EnvelopeGrid.from_limits(1.0, 1030e-9, (700e-9, 2e-6), 2e-12)
>>> mode = SpecifiedMode.from_taylor(
...     [0.0, 0.0, 2e-26], omega_ref=grid.omega0
... )
>>> table = build_phi(mode, grid, Frame.comoving(grid), z_max=1.0)
>>> table.phi_representation, table.n_intervals
('constant', 1)
solprop.translate.linop.constant_phi(operator, z_max, *, n_pol=1, margin=0.05, beta0=None, beta1=None, kinks=())[source]#

Return the exact table of an operator that does not vary along z.

Phi(z) = D z is a polynomial of degree one, so a single interval represents it exactly and no adaptive search is needed. The operator itself is kept in phi_constant so that delta_phi() returns D (z2 - z1) to the last bit rather than the difference of two Clenshaw sums — the linear flow of a constant operator is then bitwise reproducible, which is the property the reference backend’s P0 test rests on.

Something other than D may still kink along z – a response’s pre-ionisation path – and steps land only on the hard kinks of Phi. Such positions are passed as kinks: the one interval is then split there into exact degree-1 pieces that carry them as hard kinks, while phi_constant and therefore delta_phi() are unchanged.

Parameters:
operatornumpy.ndarray

D(omega) in 1/m, complex, shape (n_omega,) or (n_batch, n_modes, n_pol, n_omega).

z_maxfloat

Length the run covers, m, positive and finite.

n_polint, optional

Polarisation components to broadcast a one-dimensional operator over, 1 or 2.

marginfloat, optional

Fraction of z_max the table extends beyond the end. Default DEFAULT_MARGIN.

beta0, beta1solprop.translate.chebyshev.PiecewiseChebyshev or None, optional

The frame quantities as scalar tables, for the record. None when the operator was supplied without a mode and a frame.

kinkssequence of float or numpy.ndarray, optional

Positions in m where steps must land although D does not change there. Those strictly inside the table become interval edges and hard kinks; none (the default) gives the single interval.

Returns:
LinearOperator

Representation "constant": one interval of degree 1 per stretch between kinks, and D itself.

Raises:
TranslationError

If z_max or margin is invalid, or the operator has an unusable shape.

Examples

>>> import numpy as np
>>> from solprop.translate.linop import constant_phi
>>> table = constant_phi(np.array([0.0, -1j, -2j]), 1.0)
>>> table.phi_coefficients.shape, table.z_span
((1, 2, 1, 1, 1, 3), (0.0, 1.05))
>>> table.phi_representation
'constant'
solprop.translate.linop.declared_paths(modes)[source]#

Return the scalar paths along z the mode set declares, without duplicates.

The union build_phi() resolves before it samples the operator, and the one the prefactor table of solprop.translate.responses resolves for the same reason: the paths are cheap and carry the structure the expensive array-valued function inherits. Paths compare by identity: a bore profile handed to two modes counts once, while the density path each mode derives from its own pressure argument counts per mode – which costs nothing but a repeated, cheap resolution.

Parameters:
modessequence of Mode

The mode set.

Returns:
tuple of solprop.profiles.ZProfile

The non-constant core_radius and density paths, each once.

Examples

>>> from solprop.modes import MarcatiliMode
>>> from solprop.profiles import Linear
>>> from solprop.translate.linop import declared_paths
>>> bore = Linear(125e-6, 75e-6, 0.8)
>>> modes = [MarcatiliMode(bore, "He", 2.0, m=m) for m in (1, 2)]
>>> len(declared_paths(modes))
1
solprop.translate.linop.delta_phi(table, z1, z2, *, helpers=None)[source]#

Return the exponent of the exact linear flow between two positions.

Phi(z2) - Phi(z1), so that exp(delta_phi(table, z1, z2)) is the linear propagator over [z1, z2] whatever the distance — the property the interaction picture rests on. A constant operator is handled as D (z2 - z1) rather than as the difference of two Chebyshev sums, which is exact. An expansion subtracts its scalar weights first and combines once, rather than combining twice and subtracting; the two agree to rounding and the first is n_terms flops per state element instead of two Clenshaw passes over it.

Parameters:
tableLinearOperator

The cumulative operator.

z1, z2float

Positions in m, both inside the table’s span.

helperssolprop._helpers.Helpers or None, optional

The evaluating backend’s Chebyshev helpers, for a z table read during a run; None, the default, takes the shared translation-time evaluation.

Returns:
numpy.ndarray

Dimensionless exponent, complex, shape (n_batch, n_modes, n_pol, n_omega).

Raises:
TranslationError

If a position lies outside the table.

Examples

>>> import numpy as np
>>> from solprop.translate.linop import constant_phi, delta_phi
>>> table = constant_phi(np.array([-1j, -2j]), 1.0)
>>> exponent = delta_phi(table, 0.25, 0.75).ravel()
>>> bool(np.array_equal(np.exp(exponent), np.exp(np.array([-1j, -2j]) * 0.5)))
True
solprop.translate.linop.endpoint_phi(linear, sample, value_at, paths, *, quantity)[source]#

Add a real endpoint difference f(z) - f(z_0) to a cumulative operator.

The amplitude terms of the flow that are exact endpoint differences – the modal power conversion and the radial adiabatic amplitude (SEP 34 section 4.4) – are resolved as the endpoint function itself, never as a differentiated table, and joined to Phi: a z table gains the resolved function less its entrance value, an expansion an endpoint block over its own parameter box, whose weights reweight_expansion() rebuilds for batch reuse. An expansion that already carries an endpoint block is returned unchanged.

Parameters:
linearLinearOperator

Cumulative operator, a z table or an expansion.

samplecallable

z -> f(z) in the canonical layout, dimensionless.

value_atcallable or None

f at one point of the expansion’s parameter box; needed for an expansion.

pathscollections.abc.Mapping or None

The expansion’s varying parameter paths; needed for an expansion.

quantitystr

Name of f for the log and the refusal.

Returns:
LinearOperator

Operator whose Phi(z) - Phi(z_0) includes f(z) - f(z_0).

Raises:
TranslationError

If an expansion’s endpoint quantity cannot be resolved on its parameter box.

solprop.translate.linop.parameter_paths(modes, z0, z1)[source]#

Return the varying scalar paths the whole mode set agrees on, or None.

The expansion of solprop.translate.expansion has one dimension per path, and every mode is frozen at the same point of that space, so the set is usable only if every mode declares the same paths and agrees about their values. That is the ordinary case – the modes of one capillary share a bore and a fill – but it is not automatic, because each mode derives its own density object from its own pressure argument. This checks it rather than assuming it, by value at probe positions, and refuses the set otherwise; the caller then builds the z table, which makes no such assumption.

Unlike declared_paths(), which deduplicates by identity and returns the paths that drive the adaptive split, this returns them by name, because a name is what solprop.modes.protocol.ParameterisedMode.at_parameters() takes.

Parameters:
modessequence of Mode

The mode set.

z0, z1float

Ends of the tabulated range in m, where the paths are compared.

Returns:
dict or None

The non-constant paths by name, in a stable order, or None when a mode does not declare its parameters, the modes declare different ones, they disagree about a path’s values, or every declared path is constant. Each case is logged at DEBUG with what it was.

Examples

>>> from solprop.modes import MarcatiliMode
>>> from solprop.profiles import Linear
>>> from solprop.translate.linop import parameter_paths
>>> bore = Linear(125e-6, 75e-6, 0.8)
>>> modes = [MarcatiliMode(bore, "He", 2.0, m=m) for m in (1, 2)]
>>> sorted(parameter_paths(modes, 0.0, 0.8))
['core_radius']
solprop.translate.linop.parameters_own_z(modes)[source]#

Return whether every mode’s declared parameters are its whole z dependence.

An optional trait, read by name and defaulting to True – the assumption solprop.modes.protocol.ParameterisedMode has always carried. A mode that declares scalar paths and something else that moves says so by reporting parameters_own_z = False, and the translation layer then tabulates the operator along z instead of expanding it in parameters that cannot reproduce it. The case is a solprop.nonlinear.gnlse.SpecifiedMode whose dispersion follows a path beside its declared area: the area is still its kinks, which is what declared_paths() reads it for, but freezing the area would leave the dispersion moving.

Parameters:
modessequence of Mode

The mode set.

Returns:
bool

True when no mode denies it.

Examples

>>> from solprop.modes import MarcatiliMode
>>> from solprop.translate.linop import parameters_own_z
>>> parameters_own_z([MarcatiliMode(125e-6, "He", 1.0)])
True
solprop.translate.linop.phi_rate(table, z, *, before=False, helpers=None)[source]#

Return the rate D(z) = dPhi/dz of the representation that defines the flow.

A constant or separable operator returns D itself. A table’s interval edges are continuous in Phi but, at a hard kink, not in D: a position on an edge belongs to the interval starting there unless before asks for the one ending there – the side a step that ends on the edge came from (SEP 16 section 4.2).

Parameters:
tableLinearOperator

The cumulative operator.

zfloat

Position in m, inside the table’s span.

beforebool, optional

Read an edge from the interval ending there.

helperssolprop._helpers.Helpers or None, optional

The stepping backend’s implementation of an expansion’s scalar rate rows (SEP 29 section 6.6); None takes the shared compiled pass. A z table’s rows, the size of the state, always take the shared pass.

Returns:
numpy.ndarray

D(z) in 1/m, shape (n_batch, n_modes, n_pol, n_omega).

Examples

>>> import numpy as np
>>> from solprop.translate.linop import constant_phi, phi_rate
>>> table = constant_phi(np.array([-1j, -2j]), 1.0)
>>> phi_rate(table, 0.3).ravel().imag.tolist()
[-1.0, -2.0]
solprop.translate.linop.power_weight_at(problem, z)[source]#

Evaluate the modal power weight at one statistics or observation plane.

Parameters:
problemsolprop.translate.problem.KernelProblem

Validated modal problem carrying the weight row.

zfloat

Position in m inside the translated range.

Returns:
numpy.ndarray

Positive dimensionless weights of shape (n_modes, n_pol, n_omega).

Raises:
TranslationError

If no modal row is present or the position is outside the translated range.

solprop.translate.linop.power_weight_factors(problem, positions)[source]#

Factor the modal power weight at many planes into terms and per-plane weights.

A saved run records the weight at every save plane so that an analysis can recover the internal amplitude without the modes. The dense stack is n_z state-sized rows; the factored form is what the problem already holds: one row for a constant fill, the expansion’s few terms along a declared parameter path, and the dense stack itself only for the z table. numpy.tensordot(basis[i], terms, axes=1) reproduces power_weight_at() at positions[i] bit for bit, because it is the arithmetic that function performs.

Parameters:
problemsolprop.translate.problem.KernelProblem

Validated modal problem carrying the weight row.

positionsnumpy.ndarray

Positions in m inside the translated range, shape (n_z,).

Returns:
termsnumpy.ndarray

Dimensionless rows of shape (n_terms, n_modes, n_pol, n_omega).

basisnumpy.ndarray

Dimensionless per-plane weights of the rows, shape (n_z, n_terms).

Raises:
TranslationError

If no modal row is present or a position is outside the translated range.

solprop.translate.linop.power_weight_row(linear, z)[source]#

Evaluate the modal power weight of an assembled linear group at one position.

power_weight_at() on a problem and the entrance conversion of the launch in solprop.translate.problem are the same quantity, so they share this one arithmetic path: a second, independent evaluation of the mode’s index would leave the launch and the output sites disagreeing at the last few bits and break the exactness of the entrance round trip.

Parameters:
linearLinearOperator

Assembled linear group carrying the weight row.

zfloat

Position in m inside the translated range.

Returns:
numpy.ndarray

Positive dimensionless weights of shape (n_modes, n_pol, n_omega).

Raises:
TranslationError

If no modal row is present or the position is outside the translated range.

solprop.translate.linop.profile_table(profile, z0, z1, *, tol=1e-12)[source]#

Tabulate a scalar path along z in the form the seam carries.

A ZProfile is a Python object with a __call__; a backend cannot evaluate one inside a compiled loop. The seam’s representation of a path is therefore a scalar-valued solprop.translate.chebyshev.PiecewiseChebyshev over the same z range as the cumulative operator — plain arrays, one Clenshaw pass, and the profile’s own breakpoints as mandatory edges so a stage boundary is never crossed. This is what “ZProfile table” means in docs/dev/kernel-contract.md.

Where the path is exactly zero at an interval’s edge – a declared vacuum, such as the end of a pressure profile that falls to 0 bar – the table is exactly zero there too. The fit alone misses that zero by a few ulp of the value scale, of either sign – its end samples are inset by one ulp and its coefficients are rounded – and a density below zero is refused by the problem’s domain check. The correction moves only those ends, and only while it changes the interval by at most tol, so a path that is negative anywhere stays negative in its table and is still refused. A path that leaves zero too steeply for its inset sample to be round-off (a square-root vacuum end) keeps the table the fit gave.

Parameters:
profilesolprop.profiles.ZProfile

The path: a density, a core radius, an effective area, a taper rescaling.

z0, z1float

Ends of the range in m, with z1 > z0. Outside its own span every profile in solprop.profiles holds its end value, so the margin beyond the fibre is well defined.

tolfloat, optional

Target bound on the uniform error relative to the value scale.

Returns:
solprop.translate.chebyshev.PiecewiseChebyshev

The path as a scalar table, with its interior breakpoints as hard kinks.

Examples

>>> from solprop.profiles import Linear
>>> from solprop.translate.linop import profile_table
>>> table = profile_table(Linear(1.0, 3.0, 2.0), 0.0, 2.0)
>>> float(table(1.0))
2.0
solprop.translate.linop.rate_weights(table, z, *, before=False, helpers=None)[source]#

Return D(z)’s weights over the operator’s fixed terms.

The weights of phi_rate() against the terms a device-ready backend holds (translate.stage.StageTerms.phi): [1] for a constant operator, whose single term is D, and the derivative of an expansion’s scalar integral tables. A resident Adams engine forms the rate from them (SEP 16 phase 3b).

Parameters:
tableLinearOperator

A constant operator or an expansion.

zfloat

Position in m, inside the table’s span.

beforebool, optional

Read an edge from the interval ending there.

helperssolprop._helpers.Helpers or None, optional

The stepping backend’s implementation of the expansion’s rate rows (SEP 29 section 6.6); None takes the shared compiled pass.

Returns:
numpy.ndarray

Shape (n_phi_terms,).

Raises:
TranslationError

For a z table or a separable operator, which have no such terms.

Examples

>>> import numpy as np
>>> from solprop.translate.linop import constant_phi, rate_weights
>>> rate_weights(constant_phi(np.array([-1j, -2j]), 1.0), 0.3).tolist()
[1.0]
solprop.translate.linop.tabulate(func, z0, z1, *, kinks=(), tol=1e-12)[source]#

Tabulate a scalar function of z as a frozen Chebyshev table.

The seam’s representation of anything that varies along z. Declared kinks outside [z0, z1] are dropped rather than refused, because a path is asked for slightly beyond the fibre end (the margin of DEFAULT_MARGIN) where it holds its last value.

Parameters:
funccallable

z -> value over arrays of positions in m.

z0, z1float

Ends of the range in m, with z1 > z0.

kinksarray_like, optional

Positions that must be interval edges; those outside the range are dropped.

tolfloat, optional

Target bound on the uniform error relative to the value scale.

Returns:
solprop.translate.chebyshev.PiecewiseChebyshev

The table, with read-only, C-contiguous arrays.

Examples

>>> import numpy as np
>>> from solprop.translate.linop import tabulate
>>> table = tabulate(lambda z: 2.0 * z, 0.0, 1.0)
>>> float(table(0.5)), table.coefficients.flags.writeable
(1.0, False)
solprop.translate.linop.unitary_phase(table, z)[source]#

Return the phase that advances the shot-noise field to a position.

The modified noise realisation is drawn once and carried along the fibre by the unitary part of the linear flow: dispersion yes, loss and gain no. That part is Im Phi(omega, z), so the same table serves the field and the noise and the noise costs no extra transform. Every representation answers it the same way: an expansion’s weights are real and its integrals vanish at the start of the range, so its value at z is Phi(omega, z) itself.

Parameters:
tableLinearOperator

The cumulative operator.

zfloat

Position in m, inside the table’s span.

Returns:
numpy.ndarray

Phase in radians, shape (n_batch, n_modes, n_pol, n_omega).

Raises:
TranslationError

If z lies outside the table.

Examples

>>> import numpy as np
>>> from solprop.translate.linop import constant_phi, unitary_phase
>>> table = constant_phi(np.array([-1j - 0.5, -2j]), 1.0)
>>> np.round(unitary_phase(table, 0.5), 12).ravel().tolist()
[-0.5, -1.0]
solprop.translate.linop.verify_phi(table, operator_at, points)[source]#

Check the table against an independent quadrature of the operator.

Step 3 of the construction in docs/dev/stepper.md section 4: Phi(z2) - Phi(z1) is compared with scipy.integrate.quad_vec of D over the same interval, bin by bin. The quadrature is the oracle and is far more expensive than the table, so this is a spot check at a handful of pairs, not part of a run.

Parameters:
tableLinearOperator

The cumulative operator to check.

operator_atcallable

z -> D(z) in the same layout as the table’s value, in 1/m.

pointssequence of tuple of float

(z1, z2) pairs in m, inside the table’s span.

Returns:
float

The largest deviation over all pairs and bins, relative to the scale of the exponent at that pair. Zero when points is empty.

Examples

>>> import numpy as np
>>> from solprop.translate.linop import constant_phi, verify_phi
>>> operator = np.array([-1j, -2j])
>>> table = constant_phi(operator, 1.0)
>>> float(verify_phi(table, lambda z: operator, [(0.0, 1.0)])) < 1e-14
True
solprop.translate.linop.with_power_weight(linear, modes, grid, index)[source]#

Mirror the prefactor representations for statistics, outside the step loop.

Parameters:
linearLinearOperator

Cumulative dispersion operator and its representation.

modessequence of solprop.modes.protocol.Mode

Modes in canonical state order.

gridsolprop.grid.TimeGrid

Temporal grid defining frequency and Parseval weights.

indexfloat or None

Constant index override, or None for the physical effective index.

Returns:
LinearOperator

Operator with a constant, parameter-expansion or z-table weight row.